feat: remove Roomote Control from extension (#11271)

* feat: remove Roomote Control from extension

Remove all Roomote Control (remote control) functionality:

- Remove BridgeOrchestrator and entire bridge directory from @roo-code/cloud
- Remove remoteControlEnabled, featureRoomoteControlEnabled from extension state
- Remove extensionBridgeEnabled from CloudUserInfo and user settings
- Remove roomoteControlEnabled from organization/user feature schemas
- Remove enableBridge from Task and ClineProvider
- Remove remote control toggle from CloudView UI
- Remove remoteControlEnabled message handler
- Remove extension bridge disconnect on logout/deactivate
- Update CloudTaskButton to show for all logged-in users
- Remove remote control translation strings from all locales
- Update all related tests

CLO-765

* fix: remove dead getOrganizationMetadata and unused socket.io-client dep

* Readmes

* Readmes

* Types

* fix: remove leftover Roomote Control references from locale READMEs and stale BridgeOrchestrator mock

* Removes cloudtaskbutton

* fix: remove orphaned qrcode packages and dead openInCloud translation keys

* pnpmlock

* Revert these

* Revert these

* Revert these

* Remove socket.io

---------

Co-authored-by: Roo Code <roomote@roocode.com>
Co-authored-by: Bruno Bergher <bruno@roocode.com>
Co-authored-by: cte <cestreich@gmail.com>
This commit is contained in:
roomote[bot] 2026-02-19 16:33:39 -08:00 committed by GitHub
parent 159bf2e9f1
commit d00f1a2bb8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
101 changed files with 108 additions and 3708 deletions

View file

@ -35,7 +35,7 @@
- [简体中文](locales/zh-CN/README.md)
- [繁體中文](locales/zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,18 +58,17 @@ Roo Code adapts to how you work:
- 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
- Roomote Control: Roomote Control lets you remotely control tasks running in your local VS Code instance.
Learn more: [Using Modes](https://docs.roocode.com/basic-usage/using-modes) • [Custom Modes](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
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> |
| | | |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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">

7
locales/ca/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code s'adapta a la teva manera de treballar, no a l'inrevés:
- Mode Pregunta: respostes ràpides, explicacions i documents
- Mode Depuració: rastrejar problemes, afegir registres, aïllar les causes arrel
- Modes personalitzats: crea modes especialitzats per al teu equip o flux de treball
- Roomote Control: Roomote Control et permet controlar a distància tasques que s'executen a la teva instància local de VS Code.
Més informació: [Ús de Modes](https://docs.roocode.com/basic-usage/using-modes) • [Modes personalitzats](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Més informació: [Ús de Modes](https://docs.roocode.com/basic-usage/using-modes) • [Modes personalitzats](https://docs.roocode.com/advanced-usage/custom-modes)
## Tutorials i vídeos de funcionalitats
@ -69,7 +68,7 @@ Més informació: [Ús de Modes](https://docs.roocode.com/basic-usage/using-mode
| | | |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Instal·lant 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>Configurant perfils</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>Indexació de la base de codi</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>Modes personalitzats</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>Punts de control</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>Gestió de Context</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>Modes personalitzats</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>Punts de control</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>Gestió de Context</b> |
</div>
<p align="center">

7
locales/de/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code passt sich an deine Arbeitsweise an, nicht umgekehrt:
- Fragen-Modus: schnelle Antworten, Erklärungen und Dokumentationen
- Debug-Modus: Probleme aufspüren, Protokolle hinzufügen, Ursachen isolieren
- Benutzerdefinierte Modi: erstelle spezialisierte Modi für dein Team oder deinen Workflow
- Roomote Control: Mit Roomote Control kannst du Aufgaben in deiner lokalen VS Code-Instanz aus der Ferne steuern.
Mehr erfahren: [Modi verwenden](https://docs.roocode.com/basic-usage/using-modes) • [Benutzerdefinierte Modi](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Mehr erfahren: [Modi verwenden](https://docs.roocode.com/basic-usage/using-modes) • [Benutzerdefinierte Modi](https://docs.roocode.com/advanced-usage/custom-modes)
## Tutorial- & Feature-Videos
@ -69,7 +68,7 @@ Mehr erfahren: [Modi verwenden](https://docs.roocode.com/basic-usage/using-modes
| | | |
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Roo Code installieren</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>Profile konfigurieren</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>Codebasis-Indizierung</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>Benutzerdefinierte Modi</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>Kontextverwaltung</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>Benutzerdefinierte Modi</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>Kontextverwaltung</b> |
</div>
<p align="center">

7
locales/es/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code se adapta a tu forma de trabajar, no al revés:
- Modo Pregunta: respuestas rápidas, explicaciones y documentos
- Modo Depuración: rastrear problemas, agregar registros, aislar causas raíz
- Modos Personalizados: crea modos especializados para tu equipo o flujo de trabajo
- Roomote Control: Roomote Control te permite controlar de forma remota tareas que se ejecutan en tu instancia local de VS Code.
Más info: [Usar Modos](https://docs.roocode.com/basic-usage/using-modes) • [Modos Personalizados](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Más info: [Usar Modos](https://docs.roocode.com/basic-usage/using-modes) • [Modos Personalizados](https://docs.roocode.com/advanced-usage/custom-modes)
## Tutoriales y vídeos de funcionalidades
@ -69,7 +68,7 @@ Más info: [Usar Modos](https://docs.roocode.com/basic-usage/using-modes) • [M
| | | |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Instalando 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>Configurando perfiles</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>Indexación de la base de código</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>Modos personalizados</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>Gestión de Contexto</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>Modos personalizados</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>Gestión de Contexto</b> |
</div>
<p align="center">

7
locales/fr/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code s'adapte à votre façon de travailler, pas l'inverse :
- Mode Demande : réponses rapides, explications et documents
- Mode Débogage : tracer les problèmes, ajouter des journaux, isoler les causes profondes
- Modes Personnalisés : créez des modes spécialisés pour votre équipe ou votre flux de travail
- Roomote Control : Roomote Control te permet de piloter à distance les tâches exécutées dans ton instance VS Code locale.
En savoir plus : [Utiliser les Modes](https://docs.roocode.com/basic-usage/using-modes) • [Modes personnalisés](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
En savoir plus : [Utiliser les Modes](https://docs.roocode.com/basic-usage/using-modes) • [Modes personnalisés](https://docs.roocode.com/advanced-usage/custom-modes)
## Tutoriels & Vidéos de fonctionnalités
@ -69,7 +68,7 @@ En savoir plus : [Utiliser les Modes](https://docs.roocode.com/basic-usage/using
| | | |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Installer 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>Configurer les profils</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>Indexation de la base de code</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>Modes personnalisés</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>Gestion du Contexte</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>Modes personnalisés</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>Gestion du Contexte</b> |
</div>
<p align="center">

7
locales/hi/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@
- पूछें मोड: त्वरित उत्तर, स्पष्टीकरण और डॉक्स
- डीबग मोड: समस्याओं का पता लगाएं, लॉग जोड़ें, मूल कारणों को अलग करें
- कस्टम मोड: अपनी टीम या वर्कफ़्लो के लिए विशेष मोड बनाएं
- Roomote Control: Roomote Control से तुम अपनी लोकल VS Code इंस्टेंस में चल रही टास्क को रिमोट से कंट्रोल कर सकते हो।
और जानो: [मोड्स का इस्तेमाल](https://docs.roocode.com/basic-usage/using-modes) • [कस्टम मोड्स](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
और जानो: [मोड्स का इस्तेमाल](https://docs.roocode.com/basic-usage/using-modes) • [कस्टम मोड्स](https://docs.roocode.com/advanced-usage/custom-modes)
## ट्यूटोरियल और फ़ीचर वीडियो
@ -69,7 +68,7 @@
| | | |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>रू कोड इंस्टॉल करना</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>प्रोफाइल कॉन्फ़िगर करना</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>कोडबेस इंडेक्सिंग</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>कस्टम मोड</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>चेकपॉइंट्स</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>संदर्भ प्रबंधन</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>कस्टम मोड</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>चेकपॉइंट्स</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>संदर्भ प्रबंधन</b> |
</div>
<p align="center">

7
locales/id/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code beradaptasi dengan cara Anda bekerja, bukan sebaliknya:
- Mode Tanya: jawaban cepat, penjelasan, dan dokumen
- Mode Debug: melacak masalah, menambahkan log, mengisolasi akar penyebab
- Mode Kustom: buat mode khusus untuk tim atau alur kerja Anda
- Roomote Control: Roomote Control memungkinkan kamu mengontrol dari jarak jauh tugas yang berjalan di VS Code lokalmu.
Pelajari lebih lanjut: [Menggunakan Mode](https://docs.roocode.com/basic-usage/using-modes) • [Mode Kustom](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Pelajari lebih lanjut: [Menggunakan Mode](https://docs.roocode.com/basic-usage/using-modes) • [Mode Kustom](https://docs.roocode.com/advanced-usage/custom-modes)
## Video Tutorial & Fitur
@ -69,7 +68,7 @@ Pelajari lebih lanjut: [Menggunakan Mode](https://docs.roocode.com/basic-usage/u
| | | |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Menginstal 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>Mengonfigurasi Profil</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>Pengindeksan Basis Kode</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>Mode Kustom</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>Pos Pemeriksaan</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>Manajemen Konteks</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>Mode Kustom</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>Pos Pemeriksaan</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>Manajemen Konteks</b> |
</div>
<p align="center">

7
locales/it/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code si adatta al tuo modo di lavorare, non il contrario:
- Modalità Chiedi: risposte rapide, spiegazioni e documenti
- Modalità Debug: traccia problemi, aggiungi log, isola le cause principali
- Modalità Personalizzate: crea modalità specializzate per il tuo team o flusso di lavoro
- Roomote Control: Roomote Control ti permette di controllare da remoto le attività in esecuzione sulla tua istanza locale di VS Code.
Scopri di più: [Usare le Modalità](https://docs.roocode.com/basic-usage/using-modes) • [Modalità personalizzate](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Scopri di più: [Usare le Modalità](https://docs.roocode.com/basic-usage/using-modes) • [Modalità personalizzate](https://docs.roocode.com/advanced-usage/custom-modes)
## Tutorial e video sulle funzionalità
@ -69,7 +68,7 @@ Scopri di più: [Usare le Modalità](https://docs.roocode.com/basic-usage/using-
| | | |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Installazione di 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>Configurazione dei profili</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>Indicizzazione della codebase</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>Modalità personalizzate</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>Checkpoint</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>Gestione del Contesto</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>Modalità personalizzate</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>Checkpoint</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>Gestione del Contesto</b> |
</div>
<p align="center">

7
locales/ja/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Codeは、あなたの働き方に合わせるように適応します。
- 質問モード:迅速な回答、説明、ドキュメント
- デバッグモード:問題の追跡、ログの追加、根本原因の特定
- カスタムモード:チームやワークフローに特化したモードの構築
- Roomote Control: Roomote Control はローカルの VS Code で実行中のタスクをリモート操作できます。
詳しくは: [モードの使い方](https://docs.roocode.com/basic-usage/using-modes) • [カスタムモード](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
詳しくは: [モードの使い方](https://docs.roocode.com/basic-usage/using-modes) • [カスタムモード](https://docs.roocode.com/advanced-usage/custom-modes)
## チュートリアルと機能のビデオ
@ -69,7 +68,7 @@ Roo Codeは、あなたの働き方に合わせるように適応します。
| | | |
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>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>プロファイルの設定</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>コードベースのインデックス作成</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>カスタムモード</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>チェックポイント</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>コンテキスト管理</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>カスタムモード</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>チェックポイント</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>コンテキスト管理</b> |
</div>
<p align="center">

7
locales/ko/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code는 당신의 작업 방식에 맞춰 적응합니다.
- 질문 모드: 빠른 답변, 설명 및 문서
- 디버그 모드: 문제 추적, 로그 추가, 근본 원인 격리
- 사용자 지정 모드: 팀이나 워크플로우를 위한 특수 모드 구축
- Roomote Control: Roomote Control은 로컬 VS Code 인스턴스에서 실행 중인 작업을 원격으로 제어할 수 있어.
자세히: [모드 사용](https://docs.roocode.com/basic-usage/using-modes) • [사용자 지정 모드](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
자세히: [모드 사용](https://docs.roocode.com/basic-usage/using-modes) • [사용자 지정 모드](https://docs.roocode.com/advanced-usage/custom-modes)
## 튜토리얼 및 기능 비디오
@ -69,7 +68,7 @@ Roo Code는 당신의 작업 방식에 맞춰 적응합니다.
| | | |
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>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>프로필 구성하기</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>코드베이스 인덱싱</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>사용자 지정 모드</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>체크포인트</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>컨텍스트 관리</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>사용자 지정 모드</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>체크포인트</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>컨텍스트 관리</b> |
</div>
<p align="center">

5
locales/nl/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code past zich aan jouw werkwijze aan, niet andersom:
- Vraag Modus: snelle antwoorden, uitleg en documenten
- Debug Modus: spoor problemen op, voeg logs toe, isoleer de oorzaak
- Aangepaste Modi: bouw gespecialiseerde modi voor je team of workflow
- Roomote Control: Roomote Control laat je taken op je lokale VS Code-instantie op afstand besturen.
Meer info: [Modi gebruiken](https://docs.roocode.com/basic-usage/using-modes) • [Aangepaste modi](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Meer info: [Modi gebruiken](https://docs.roocode.com/basic-usage/using-modes) • [Aangepaste modi](https://docs.roocode.com/advanced-usage/custom-modes)
## Tutorial & Feature Videos

7
locales/pl/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code dostosowuje się do Twojego sposobu pracy, a nie odwrotnie:
- Tryb Zapytaj: szybkie odpowiedzi, wyjaśnienia i dokumenty
- Tryb Debugowanie: śledzenie problemów, dodawanie logów, izolowanie przyczyn źródłowych
- Tryby niestandardowe: buduj specjalistyczne tryby dla swojego zespołu lub przepływu pracy
- Roomote Control: Roomote Control pozwala zdalnie sterować zadaniami uruchomionymi na twojej lokalnej instancji VS Code.
Więcej: [Korzystanie z trybów](https://docs.roocode.com/basic-usage/using-modes) • [Tryby niestandardowe](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Więcej: [Korzystanie z trybów](https://docs.roocode.com/basic-usage/using-modes) • [Tryby niestandardowe](https://docs.roocode.com/advanced-usage/custom-modes)
## Filmy instruktażowe i prezentujące funkcje
@ -69,7 +68,7 @@ Więcej: [Korzystanie z trybów](https://docs.roocode.com/basic-usage/using-mode
| | | |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Instalacja 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>Konfiguracja profili</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>Indeksowanie bazy kodu</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>Tryby niestandardowe</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>Punkty kontrolne</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>Zarządzanie Kontekstem</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>Tryby niestandardowe</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>Punkty kontrolne</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>Zarządzanie Kontekstem</b> |
</div>
<p align="center">

View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ O Roo Code se adapta à sua maneira de trabalhar, e não o contrário:
- Modo Pergunta: respostas rápidas, explicações e documentos
- Modo Depuração: rastreie problemas, adicione logs, isole as causas raiz
- Modos Personalizados: crie modos especializados para sua equipe ou fluxo de trabalho
- Roomote Control: O Roomote Control permite controlar remotamente tarefas em execução na sua instância local do VS Code.
Saiba mais: [Usar Modos](https://docs.roocode.com/basic-usage/using-modes) • [Modos personalizados](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Saiba mais: [Usar Modos](https://docs.roocode.com/basic-usage/using-modes) • [Modos personalizados](https://docs.roocode.com/advanced-usage/custom-modes)
## Vídeos de tutorial e recursos
@ -69,7 +68,7 @@ Saiba mais: [Usar Modos](https://docs.roocode.com/basic-usage/using-modes) • [
| | | |
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Instalando o 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>Configurando perfis</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>Indexação da base de código</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>Modos personalizados</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>Gerenciamento de Contexto</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>Modos personalizados</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>Gerenciamento de Contexto</b> |
</div>
<p align="center">

7
locales/ru/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code адаптируется к вашему стилю работы, а н
- Режим Вопрос: быстрые ответы, объяснения и документация
- Режим Отладка: отслеживание проблем, добавление логов, изоляция первопричин
- Пользовательские режимы: создавайте специализированные режимы для вашей команды или рабочего процесса
- Roomote Control: Roomote Control позволяет удаленно управлять задачами, запущенными в вашей локальной инстансе VS Code.
Подробнее: [Использование режимов](https://docs.roocode.com/basic-usage/using-modes) • [Пользовательские режимы](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Подробнее: [Использование режимов](https://docs.roocode.com/basic-usage/using-modes) • [Пользовательские режимы](https://docs.roocode.com/advanced-usage/custom-modes)
## Обучающие видео и видео о функциях
@ -69,7 +68,7 @@ Roo Code адаптируется к вашему стилю работы, а н
| | | |
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Установка 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>Настройка профилей</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>Индексация кодовой базы</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>Пользовательские режимы</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>Контрольные точки</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>Управление Контекстом</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>Пользовательские режимы</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>Контрольные точки</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>Управление Контекстом</b> |
</div>
<p align="center">

13
locales/tr/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,18 +58,17 @@ Roo Code, sizin çalışma şeklinize uyum sağlar, tam tersi değil:
- Sor Modu: hızlı cevaplar, açıklamalar ve belgeler
- Hata Ayıklama Modu: sorunları izleyin, günlükler ekleyin, kök nedenleri izole edin
- Özel Modlar: ekibiniz veya iş akışınız için özel modlar oluşturun
- Roomote Control: Roomote Control, yerel VS Code örneğinde çalışan işleri uzaktan kontrol etmeni sağlar.
Daha fazla: [Modları kullanma](https://docs.roocode.com/basic-usage/using-modes) • [Özel modlar](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Daha fazla: [Modları kullanma](https://docs.roocode.com/basic-usage/using-modes) • [Özel modlar](https://docs.roocode.com/advanced-usage/custom-modes)
## Eğitim ve Özellik Videoları
<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>Roo Code Kurulumu</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>Profilleri Yapılandırma</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>Kod Tabanı İndeksleme</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>Özel Modlar</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>Kontrol Noktaları</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>Bağlam Yönetimi</b> |
| | | |
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Roo Code Kurulumu</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>Profilleri Yapılandırma</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>Kod Tabanı İndeksleme</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>Özel Modlar</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>Kontrol Noktaları</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>Bağlam Yönetimi</b> |
</div>
<p align="center">

13
locales/vi/README.md generated
View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,18 +58,17 @@ Roo Code thích ứng với cách bạn làm việc, chứ không phải ngượ
- Chế độ Hỏi: câu trả lời nhanh, giải thích và tài liệu
- Chế độ Gỡ lỗi: theo dõi sự cố, thêm nhật ký, cô lập nguyên nhân gốc rễ
- Chế độ Tùy chỉnh: xây dựng các chế độ chuyên biệt cho nhóm hoặc quy trình làm việc của bạn
- Roomote Control: Roomote Control cho phép bạn điều khiển từ xa các tác vụ đang chạy trên VS Code cục bộ của bạn.
Xem thêm: [Sử dụng Chế độ](https://docs.roocode.com/basic-usage/using-modes) • [Chế độ tùy chỉnh](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
Xem thêm: [Sử dụng Chế độ](https://docs.roocode.com/basic-usage/using-modes) • [Chế độ tùy chỉnh](https://docs.roocode.com/advanced-usage/custom-modes)
## Video hướng dẫn & tính năng
<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>Cài đặt 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>Định cấu hình Hồ sơ</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>Lập chỉ mục cơ sở mã</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>Chế độ tùy chỉnh</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>Điểm kiểm tra</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>Quản lý Ngữ cảnh</b> |
| | | |
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>Cài đặt 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>Định cấu hình Hồ sơ</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>Lập chỉ mục cơ sở mã</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>Chế độ tùy chỉnh</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>Điểm kiểm tra</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>Quản lý Ngữ cảnh</b> |
</div>
<p align="center">

View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,17 +58,16 @@ Roo Code 适应您的工作方式,而不是相反:
- 提问模式:快速回答、解释和文档
- 调试模式:跟踪问题、添加日志、隔离根本原因
- 自定义模式:为您的团队或工作流程构建专门的模式
- Roomote ControlRoomote Control 允许你远程控制在本地 VS Code 实例中运行的任务。
了解更多:[使用模式](https://docs.roocode.com/basic-usage/using-modes) • [自定义模式](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
了解更多:[使用模式](https://docs.roocode.com/basic-usage/using-modes) • [自定义模式](https://docs.roocode.com/advanced-usage/custom-modes)
## 教程和功能视频
<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>安装 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>配置个人资料</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>代码库索引</b> |
| | | |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <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>安装 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>配置个人资料</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>代码库索引</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>自定义模式</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>检查点</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>上下文管理</b> |
</div>

View file

@ -35,7 +35,7 @@
- [简体中文](../zh-CN/README.md)
- [繁體中文](../zh-TW/README.md)
- ...
</details>
</details>
---
@ -58,9 +58,8 @@ Roo Code 會配合您的工作方式,而非要您配合它:
- 詢問模式:快速回答、解釋和文件
- 偵錯模式:追蹤問題、新增日誌、鎖定根本原因
- 自訂模式:為您的團隊或工作流程建置專門的模式
- Roomote ControlRoomote Control 讓您能遠端控制在本機 VS Code 執行個體中運行的工作。
更多資訊:[使用模式](https://docs.roocode.com/basic-usage/using-modes) • [自訂模式](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
更多資訊:[使用模式](https://docs.roocode.com/basic-usage/using-modes) • [自訂模式](https://docs.roocode.com/advanced-usage/custom-modes)
## 教學和功能影片

View file

@ -15,7 +15,6 @@
"ioredis": "^5.6.1",
"jwt-decode": "^4.0.0",
"p-wait-for": "^5.0.2",
"socket.io-client": "^4.8.1",
"zod": "^3.25.76"
},
"devDependencies": {

View file

@ -42,15 +42,12 @@ export class StaticSettingsService implements SettingsService {
}
/**
* Returns static user settings with roomoteControlEnabled and extensionBridgeEnabled as true
* Returns static user settings with task sync enabled
*/
public getUserSettings(): UserSettingsData | undefined {
return {
features: {
roomoteControlEnabled: true,
},
features: {},
settings: {
extensionBridgeEnabled: true,
taskSyncEnabled: true,
},
version: 1,
@ -58,14 +55,11 @@ export class StaticSettingsService implements SettingsService {
}
public getUserFeatures(): UserFeatures {
return {
roomoteControlEnabled: true,
}
return {}
}
public getUserSettingsConfig(): UserSettingsConfig {
return {
extensionBridgeEnabled: true,
taskSyncEnabled: true,
}
}

View file

@ -30,7 +30,6 @@ export class StaticTokenAuthService extends EventEmitter<AuthServiceEvents> impl
this.userInfo = {
id: payload?.r?.u || payload?.sub || undefined,
organizationId: payload?.r?.o || undefined,
extensionBridgeEnabled: true,
}
}

View file

@ -625,8 +625,6 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
)?.email_address
}
let extensionBridgeEnabled = true
// Fetch organization info if user is in organization context
try {
const storedOrgId = this.getStoredOrganizationId()
@ -641,8 +639,6 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
if (userMembership) {
this.setUserOrganizationInfo(userInfo, userMembership)
extensionBridgeEnabled = await this.isExtensionBridgeEnabledForOrganization(storedOrgId)
this.log("[auth] User in organization context:", {
id: userMembership.organization.id,
name: userMembership.organization.name,
@ -662,10 +658,6 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
if (primaryOrgMembership) {
this.setUserOrganizationInfo(userInfo, primaryOrgMembership)
extensionBridgeEnabled = await this.isExtensionBridgeEnabledForOrganization(
primaryOrgMembership.organization.id,
)
this.log("[auth] Legacy credentials: Found organization membership:", {
id: primaryOrgMembership.organization.id,
name: primaryOrgMembership.organization.name,
@ -680,9 +672,6 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
// Don't throw - organization info is optional
}
// Set the extension bridge enabled flag
userInfo.extensionBridgeEnabled = extensionBridgeEnabled
return userInfo
}
@ -729,36 +718,6 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
throw new Error(errorMessage)
}
private async getOrganizationMetadata(
organizationId: string,
): Promise<{ public_metadata?: Record<string, unknown> } | null> {
try {
const response = await fetch(`${getClerkBaseUrl()}/v1/organizations/${organizationId}`, {
headers: {
Authorization: `Bearer ${this.credentials!.clientToken}`,
"User-Agent": this.userAgent(),
},
signal: AbortSignal.timeout(10000),
})
if (!response.ok) {
this.log(`[auth] Failed to fetch organization metadata: ${response.status} ${response.statusText}`)
return null
}
const data = await response.json()
return data.response || data
} catch (error) {
this.log("[auth] Error fetching organization metadata:", error)
return null
}
}
private async isExtensionBridgeEnabledForOrganization(organizationId: string): Promise<boolean> {
const orgMetadata = await this.getOrganizationMetadata(organizationId)
return orgMetadata?.public_metadata?.extension_bridge_enabled === true
}
private async clerkLogout(credentials: AuthCredentials): Promise<void> {
const formData = new URLSearchParams()
formData.append("_is_native", "1")

View file

@ -105,12 +105,8 @@ describe("CloudSettingsService - Response Parsing", () => {
},
},
user: {
features: {
roomoteControlEnabled: true,
},
settings: {
extensionBridgeEnabled: true,
},
features: {},
settings: {},
version: 1,
},
}

View file

@ -89,7 +89,6 @@ describe("StaticTokenAuthService", () => {
const userInfo = serviceWithJWT.getUserInfo()
expect(userInfo?.id).toBe("user_2xmBhejNeDTwanM8CgIOnMgVxzC")
expect(userInfo?.organizationId).toBe("org_123abc")
expect(userInfo?.extensionBridgeEnabled).toBe(true)
})
it("should parse job token without orgId (null orgId case)", () => {
@ -98,7 +97,6 @@ describe("StaticTokenAuthService", () => {
const userInfo = serviceWithJWT.getUserInfo()
expect(userInfo?.id).toBe("user_2xmBhejNeDTwanM8CgIOnMgVxzC")
expect(userInfo?.organizationId).toBeUndefined()
expect(userInfo?.extensionBridgeEnabled).toBe(true)
})
it("should parse auth token and extract userId from r.u", () => {
@ -107,7 +105,6 @@ describe("StaticTokenAuthService", () => {
const userInfo = serviceWithAuthToken.getUserInfo()
expect(userInfo?.id).toBe("user_123")
expect(userInfo?.organizationId).toBeUndefined()
expect(userInfo?.extensionBridgeEnabled).toBe(true)
})
it("should handle legacy JWT format with sub field", () => {
@ -116,7 +113,6 @@ describe("StaticTokenAuthService", () => {
const userInfo = serviceWithLegacyJWT.getUserInfo()
expect(userInfo?.id).toBe("user_123")
expect(userInfo?.organizationId).toBeUndefined()
expect(userInfo?.extensionBridgeEnabled).toBe(true)
})
it("should handle invalid JWT gracefully", () => {
@ -125,7 +121,6 @@ describe("StaticTokenAuthService", () => {
const userInfo = serviceWithInvalidJWT.getUserInfo()
expect(userInfo?.id).toBeUndefined()
expect(userInfo?.organizationId).toBeUndefined()
expect(userInfo?.extensionBridgeEnabled).toBe(true)
expect(mockLog).toHaveBeenCalledWith("[auth] Failed to parse JWT:", expect.any(Error))
})
@ -183,9 +178,7 @@ describe("StaticTokenAuthService", () => {
authService.broadcast()
expect(spy).toHaveBeenCalledWith({
userInfo: expect.objectContaining({
extensionBridgeEnabled: true,
}),
userInfo: expect.objectContaining({}),
})
})
@ -199,7 +192,6 @@ describe("StaticTokenAuthService", () => {
expect(spy).toHaveBeenCalledWith({
userInfo: {
extensionBridgeEnabled: true,
id: "user_2xmBhejNeDTwanM8CgIOnMgVxzC",
organizationId: "org_123abc",
},
@ -220,10 +212,9 @@ describe("StaticTokenAuthService", () => {
})
describe("getUserInfo", () => {
it("should return object with extensionBridgeEnabled flag", () => {
it("should return user info object", () => {
const userInfo = authService.getUserInfo()
expect(userInfo).toHaveProperty("extensionBridgeEnabled")
expect(userInfo?.extensionBridgeEnabled).toBe(true)
expect(userInfo).toBeDefined()
})
})
@ -305,9 +296,7 @@ describe("StaticTokenAuthService", () => {
})
expect(userInfoSpy).toHaveBeenCalledWith({
userInfo: expect.objectContaining({
extensionBridgeEnabled: true,
}),
userInfo: expect.objectContaining({}),
})
})
})

View file

@ -636,7 +636,6 @@ describe("WebAuthService", () => {
name: "John Doe",
email: "john@example.com",
picture: "https://example.com/avatar.jpg",
extensionBridgeEnabled: true,
},
})
})
@ -801,7 +800,6 @@ describe("WebAuthService", () => {
name: "Jane Smith",
email: "jane@example.com",
picture: "https://example.com/jane.jpg",
extensionBridgeEnabled: true,
})
})
@ -869,7 +867,6 @@ describe("WebAuthService", () => {
name: "Jane Smith",
email: "jane@example.com",
picture: "https://example.com/jane.jpg",
extensionBridgeEnabled: false,
organizationId: "org_1",
organizationName: "Org 1",
organizationRole: "member",
@ -920,7 +917,6 @@ describe("WebAuthService", () => {
name: "John Doe",
email: undefined,
picture: undefined,
extensionBridgeEnabled: true,
})
})
})
@ -1045,7 +1041,6 @@ describe("WebAuthService", () => {
name: "Test User",
email: undefined,
picture: undefined,
extensionBridgeEnabled: true,
},
})
})

View file

@ -1,142 +0,0 @@
import type { Socket } from "socket.io-client"
import * as vscode from "vscode"
import type { StaticAppProperties, GitProperties } from "@roo-code/types"
export interface BaseChannelOptions {
instanceId: string
appProperties: StaticAppProperties
gitProperties?: GitProperties
isCloudAgent: boolean
}
/**
* Abstract base class for communication channels in the bridge system.
* Provides common functionality for bidirectional communication between
* the VSCode extension and web application.
*
* @template TCommand - Type of commands this channel can receive.
* @template TEvent - Type of events this channel can publish.
*/
export abstract class BaseChannel<TCommand = unknown, TEventName extends string = string, TEventData = unknown> {
protected socket: Socket | null = null
protected readonly instanceId: string
protected readonly appProperties: StaticAppProperties
protected readonly gitProperties?: GitProperties
protected readonly isCloudAgent: boolean
constructor(options: BaseChannelOptions) {
this.instanceId = options.instanceId
this.appProperties = options.appProperties
this.gitProperties = options.gitProperties
this.isCloudAgent = options.isCloudAgent
}
/**
* Called when socket connects.
*/
public async onConnect(socket: Socket): Promise<void> {
this.socket = socket
await this.handleConnect(socket)
}
/**
* Called when socket disconnects.
*/
public onDisconnect(): void {
this.socket = null
this.handleDisconnect()
}
/**
* Called when socket reconnects.
*/
public async onReconnect(socket: Socket): Promise<void> {
this.socket = socket
await this.handleReconnect(socket)
}
/**
* Cleanup resources.
*/
public async cleanup(socket: Socket | null): Promise<void> {
if (socket) {
await this.handleCleanup(socket)
}
this.socket = null
}
/**
* Emit a socket event with error handling.
*/
protected publish<Params extends object>(
eventName: TEventName,
data: TEventData,
callback?: (params: Params) => void,
): boolean {
if (!this.socket) {
console.error(`[${this.constructor.name}#emit] socket not available for ${eventName}`)
return false
}
try {
// console.log(`[${this.constructor.name}#emit] emit() -> ${eventName}`, data)
this.socket.emit(eventName, data, callback)
return true
} catch (error) {
console.error(
`[${this.constructor.name}#emit] emit() failed -> ${eventName}: ${
error instanceof Error ? error.message : String(error)
}`,
)
return false
}
}
/**
* Handle incoming commands - template method that ensures common functionality
* is executed before subclass-specific logic.
*
* This method should be called by subclasses to handle commands.
* It will execute common functionality and then delegate to the abstract
* handleCommandImplementation method.
*/
public async handleCommand(command: TCommand): Promise<void> {
// Common functionality: focus the sidebar.
await vscode.commands.executeCommand(`${this.appProperties.appName}.SidebarProvider.focus`)
// Delegate to subclass-specific implementation.
await this.handleCommandImplementation(command)
}
/**
* Handle command-specific logic - must be implemented by subclasses.
* This method is called after common functionality has been executed.
*/
protected abstract handleCommandImplementation(command: TCommand): Promise<void>
/**
* Handle connection-specific logic.
*/
protected abstract handleConnect(socket: Socket): Promise<void>
/**
* Handle disconnection-specific logic.
*/
protected handleDisconnect(): void {
// Default implementation - can be overridden.
}
/**
* Handle reconnection-specific logic.
*/
protected abstract handleReconnect(socket: Socket): Promise<void>
/**
* Handle cleanup-specific logic.
*/
protected abstract handleCleanup(socket: Socket): Promise<void>
}

View file

@ -1,355 +0,0 @@
import crypto from "crypto"
import os from "os"
import {
type TaskProviderLike,
type TaskLike,
type CloudUserInfo,
type ExtensionBridgeCommand,
type TaskBridgeCommand,
type StaticAppProperties,
type GitProperties,
ConnectionState,
ExtensionSocketEvents,
TaskSocketEvents,
} from "@roo-code/types"
import { SocketTransport } from "./SocketTransport.js"
import { ExtensionChannel } from "./ExtensionChannel.js"
import { TaskChannel } from "./TaskChannel.js"
export interface BridgeOrchestratorOptions {
userId: string
socketBridgeUrl: string
token: string
provider: TaskProviderLike
sessionId: string
isCloudAgent: boolean
}
/**
* Central orchestrator for the extension bridge system.
* Coordinates communication between the VSCode extension and web application
* through WebSocket connections and manages extension/task channels.
*/
export class BridgeOrchestrator {
private static instance: BridgeOrchestrator | null = null
private static pendingTask: TaskLike | null = null
// Core
private readonly userId: string
private readonly socketBridgeUrl: string
private readonly token: string
private readonly provider: TaskProviderLike
private readonly instanceId: string
private readonly appProperties: StaticAppProperties
private readonly gitProperties?: GitProperties
private readonly isCloudAgent?: boolean
// Components
private socketTransport: SocketTransport
private extensionChannel: ExtensionChannel
private taskChannel: TaskChannel
// Reconnection
private readonly MAX_RECONNECT_ATTEMPTS = Infinity
private readonly RECONNECT_DELAY = 1_000
private readonly RECONNECT_DELAY_MAX = 30_000
public static getInstance(): BridgeOrchestrator | null {
return BridgeOrchestrator.instance
}
public static isEnabled(user: CloudUserInfo | null, remoteControlEnabled: boolean): boolean {
// Always disabled if signed out.
if (!user) {
return false
}
// Disabled by the user's organization?
if (!user.extensionBridgeEnabled) {
return false
}
// Disabled by the user?
if (!remoteControlEnabled) {
return false
}
return true
}
public static async connectOrDisconnect(
userInfo: CloudUserInfo,
remoteControlEnabled: boolean,
options: BridgeOrchestratorOptions,
): Promise<void> {
if (BridgeOrchestrator.isEnabled(userInfo, remoteControlEnabled)) {
await BridgeOrchestrator.connect(options)
} else {
await BridgeOrchestrator.disconnect()
}
}
public static async connect(options: BridgeOrchestratorOptions) {
const instance = BridgeOrchestrator.instance
if (!instance) {
try {
console.log(`[BridgeOrchestrator#connectOrDisconnect] Connecting...`)
// Populate telemetry properties before registering the instance.
await options.provider.getTelemetryProperties()
BridgeOrchestrator.instance = new BridgeOrchestrator(options)
await BridgeOrchestrator.instance.connect()
} catch (error) {
console.error(
`[BridgeOrchestrator#connectOrDisconnect] connect() failed: ${error instanceof Error ? error.message : String(error)}`,
)
}
} else {
if (
instance.connectionState === ConnectionState.FAILED ||
instance.connectionState === ConnectionState.DISCONNECTED
) {
console.log(
`[BridgeOrchestrator#connectOrDisconnect] Re-connecting... (state: ${instance.connectionState})`,
)
instance.reconnect().catch((error) => {
console.error(
`[BridgeOrchestrator#connectOrDisconnect] reconnect() failed: ${error instanceof Error ? error.message : String(error)}`,
)
})
} else {
console.log(
`[BridgeOrchestrator#connectOrDisconnect] Already connected or connecting (state: ${instance.connectionState})`,
)
}
}
}
public static async disconnect() {
const instance = BridgeOrchestrator.instance
if (instance) {
try {
console.log(
`[BridgeOrchestrator#connectOrDisconnect] Disconnecting... (state: ${instance.connectionState})`,
)
await instance.disconnect()
} catch (error) {
console.error(
`[BridgeOrchestrator#connectOrDisconnect] disconnect() failed: ${error instanceof Error ? error.message : String(error)}`,
)
} finally {
BridgeOrchestrator.instance = null
}
} else {
console.log(`[BridgeOrchestrator#connectOrDisconnect] Already disconnected`)
}
}
/**
* @TODO: What if subtasks also get spawned? We'd probably want deferred
* subscriptions for those too.
*/
public static async subscribeToTask(task: TaskLike): Promise<void> {
const instance = BridgeOrchestrator.instance
if (instance && instance.socketTransport.isConnected()) {
console.log(`[BridgeOrchestrator#subscribeToTask] Subscribing to task ${task.taskId}`)
await instance.subscribeToTask(task)
} else {
console.log(`[BridgeOrchestrator#subscribeToTask] Deferring subscription for task ${task.taskId}`)
BridgeOrchestrator.pendingTask = task
}
}
private constructor(options: BridgeOrchestratorOptions) {
this.userId = options.userId
this.socketBridgeUrl = options.socketBridgeUrl
this.token = options.token
this.provider = options.provider
this.instanceId = options.sessionId || crypto.randomUUID()
this.appProperties = { ...options.provider.appProperties, hostname: os.hostname() }
this.gitProperties = options.provider.gitProperties
this.isCloudAgent = options.isCloudAgent
this.socketTransport = new SocketTransport({
url: this.socketBridgeUrl,
socketOptions: {
query: {
token: this.token,
clientType: "extension",
instanceId: this.instanceId,
},
transports: ["websocket", "polling"],
reconnection: true,
reconnectionAttempts: this.MAX_RECONNECT_ATTEMPTS,
reconnectionDelay: this.RECONNECT_DELAY,
reconnectionDelayMax: this.RECONNECT_DELAY_MAX,
},
onConnect: () => this.handleConnect(),
onDisconnect: () => this.handleDisconnect(),
onReconnect: () => this.handleReconnect(),
})
this.extensionChannel = new ExtensionChannel({
instanceId: this.instanceId,
appProperties: this.appProperties,
gitProperties: this.gitProperties,
userId: this.userId,
provider: this.provider,
isCloudAgent: this.isCloudAgent,
})
this.taskChannel = new TaskChannel({
instanceId: this.instanceId,
appProperties: this.appProperties,
gitProperties: this.gitProperties,
isCloudAgent: this.isCloudAgent,
})
}
private setupSocketListeners() {
const socket = this.socketTransport.getSocket()
if (!socket) {
console.error("[BridgeOrchestrator] Socket not available")
return
}
// Remove any existing listeners first to prevent duplicates.
socket.off(ExtensionSocketEvents.RELAYED_COMMAND)
socket.off(TaskSocketEvents.RELAYED_COMMAND)
socket.off("connected")
socket.on(ExtensionSocketEvents.RELAYED_COMMAND, (message: ExtensionBridgeCommand) => {
console.log(
`[BridgeOrchestrator] on(${ExtensionSocketEvents.RELAYED_COMMAND}) -> ${message.type} for ${message.instanceId}`,
)
this.extensionChannel?.handleCommand(message)
})
socket.on(TaskSocketEvents.RELAYED_COMMAND, (message: TaskBridgeCommand) => {
console.log(
`[BridgeOrchestrator] on(${TaskSocketEvents.RELAYED_COMMAND}) -> ${message.type} for ${message.taskId}`,
)
this.taskChannel.handleCommand(message)
})
}
private async handleConnect() {
const socket = this.socketTransport.getSocket()
if (!socket) {
console.error("[BridgeOrchestrator#handleConnect] Socket not available")
return
}
await this.extensionChannel.onConnect(socket)
await this.taskChannel.onConnect(socket)
if (BridgeOrchestrator.pendingTask) {
console.log(
`[BridgeOrchestrator#handleConnect] Subscribing to task ${BridgeOrchestrator.pendingTask.taskId}`,
)
try {
await this.subscribeToTask(BridgeOrchestrator.pendingTask)
BridgeOrchestrator.pendingTask = null
} catch (error) {
console.error(
`[BridgeOrchestrator#handleConnect] subscribeToTask() failed: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
}
private handleDisconnect() {
this.extensionChannel.onDisconnect()
this.taskChannel.onDisconnect()
}
private async handleReconnect() {
const socket = this.socketTransport.getSocket()
if (!socket) {
console.error("[BridgeOrchestrator] Socket not available after reconnect")
return
}
// Re-setup socket listeners to ensure they're properly configured
// after automatic reconnection (Socket.IO's built-in reconnection)
// The socket.off() calls in setupSocketListeners prevent duplicates
this.setupSocketListeners()
await this.extensionChannel.onReconnect(socket)
await this.taskChannel.onReconnect(socket)
}
// Task API
public async subscribeToTask(task: TaskLike): Promise<void> {
const socket = this.socketTransport.getSocket()
if (!socket || !this.socketTransport.isConnected()) {
console.warn("[BridgeOrchestrator] Cannot subscribe to task: not connected. Will retry when connected.")
this.taskChannel.addPendingTask(task)
if (
this.connectionState === ConnectionState.DISCONNECTED ||
this.connectionState === ConnectionState.FAILED
) {
await this.connect()
}
return
}
await this.taskChannel.subscribeToTask(task, socket)
}
public async unsubscribeFromTask(taskId: string): Promise<void> {
const socket = this.socketTransport.getSocket()
if (!socket) {
return
}
await this.taskChannel.unsubscribeFromTask(taskId, socket)
}
// Shared API
public get connectionState(): ConnectionState {
return this.socketTransport.getConnectionState()
}
private async connect(): Promise<void> {
await this.socketTransport.connect()
this.setupSocketListeners()
}
public async disconnect(): Promise<void> {
await this.extensionChannel.cleanup(this.socketTransport.getSocket())
await this.taskChannel.cleanup(this.socketTransport.getSocket())
await this.socketTransport.disconnect()
BridgeOrchestrator.instance = null
BridgeOrchestrator.pendingTask = null
}
public async reconnect(): Promise<void> {
await this.socketTransport.reconnect()
// After a manual reconnect, we have a new socket instance
// so we need to set up listeners again.
this.setupSocketListeners()
}
}

View file

@ -1,282 +0,0 @@
import type { Socket } from "socket.io-client"
import {
type TaskProviderLike,
type TaskProviderEvents,
type ExtensionInstance,
type ExtensionBridgeCommand,
type ExtensionBridgeEvent,
RooCodeEventName,
TaskStatus,
ExtensionBridgeCommandName,
ExtensionBridgeEventName,
ExtensionSocketEvents,
HEARTBEAT_INTERVAL_MS,
} from "@roo-code/types"
import { type BaseChannelOptions, BaseChannel } from "./BaseChannel.js"
interface ExtensionChannelOptions extends BaseChannelOptions {
userId: string
provider: TaskProviderLike
}
/**
* Manages the extension-level communication channel.
* Handles extension registration, heartbeat, and extension-specific commands.
*/
export class ExtensionChannel extends BaseChannel<
ExtensionBridgeCommand,
ExtensionSocketEvents,
ExtensionBridgeEvent | ExtensionInstance
> {
private userId: string
private provider: TaskProviderLike
private extensionInstance: ExtensionInstance
private heartbeatInterval: NodeJS.Timeout | null = null
private eventListeners: Map<RooCodeEventName, (...args: unknown[]) => void> = new Map()
constructor(options: ExtensionChannelOptions) {
super({
instanceId: options.instanceId,
appProperties: options.appProperties,
gitProperties: options.gitProperties,
isCloudAgent: options.isCloudAgent,
})
this.userId = options.userId
this.provider = options.provider
this.extensionInstance = {
instanceId: this.instanceId,
userId: this.userId,
workspacePath: this.provider.cwd,
appProperties: this.appProperties,
gitProperties: this.gitProperties,
lastHeartbeat: Date.now(),
task: { taskId: "", taskStatus: TaskStatus.None },
taskHistory: [],
isCloudAgent: this.isCloudAgent,
}
this.setupListeners()
}
protected async handleCommandImplementation(command: ExtensionBridgeCommand): Promise<void> {
if (command.instanceId !== this.instanceId) {
console.log(`[ExtensionChannel] command -> instance id mismatch | ${this.instanceId}`, {
messageInstanceId: command.instanceId,
})
return
}
switch (command.type) {
case ExtensionBridgeCommandName.StartTask: {
console.log(`[ExtensionChannel] command -> createTask() | ${command.instanceId}`, {
text: command.payload.text?.substring(0, 100) + "...",
hasImages: !!command.payload.images,
mode: command.payload.mode,
providerProfile: command.payload.providerProfile,
})
this.provider.createTask(
command.payload.text,
command.payload.images,
undefined, // parentTask
undefined, // options
{ mode: command.payload.mode, currentApiConfigName: command.payload.providerProfile },
)
break
}
case ExtensionBridgeCommandName.StopTask: {
const instance = await this.updateInstance()
if (instance.task.taskStatus === TaskStatus.Running) {
console.log(`[ExtensionChannel] command -> cancelTask() | ${command.instanceId}`)
this.provider.cancelTask()
this.provider.postStateToWebview()
} else if (instance.task.taskId) {
console.log(`[ExtensionChannel] command -> clearTask() | ${command.instanceId}`)
this.provider.clearTask()
this.provider.postStateToWebview()
}
break
}
case ExtensionBridgeCommandName.ResumeTask: {
console.log(`[ExtensionChannel] command -> resumeTask() | ${command.instanceId}`, {
taskId: command.payload.taskId,
})
this.provider.resumeTask(command.payload.taskId)
this.provider.postStateToWebview()
break
}
}
}
protected async handleConnect(socket: Socket): Promise<void> {
await this.registerInstance(socket)
this.startHeartbeat(socket)
}
protected async handleReconnect(socket: Socket): Promise<void> {
await this.registerInstance(socket)
this.startHeartbeat(socket)
}
protected override handleDisconnect(): void {
this.stopHeartbeat()
}
protected async handleCleanup(socket: Socket): Promise<void> {
this.stopHeartbeat()
this.cleanupListeners()
await this.unregisterInstance(socket)
}
private async registerInstance(_socket: Socket): Promise<void> {
const instance = await this.updateInstance()
await this.publish(ExtensionSocketEvents.REGISTER, instance)
}
private async unregisterInstance(_socket: Socket): Promise<void> {
const instance = await this.updateInstance()
await this.publish(ExtensionSocketEvents.UNREGISTER, instance)
}
private startHeartbeat(socket: Socket): void {
this.stopHeartbeat()
this.heartbeatInterval = setInterval(async () => {
const instance = await this.updateInstance()
try {
socket.emit(ExtensionSocketEvents.HEARTBEAT, instance)
// Heartbeat is too frequent to log
} catch (error) {
console.error(
`[ExtensionChannel] emit() failed -> ${ExtensionSocketEvents.HEARTBEAT}: ${
error instanceof Error ? error.message : String(error)
}`,
)
}
}, HEARTBEAT_INTERVAL_MS)
}
private stopHeartbeat(): void {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval)
this.heartbeatInterval = null
}
}
private setupListeners(): void {
const eventMapping = [
{ from: RooCodeEventName.TaskCreated, to: ExtensionBridgeEventName.TaskCreated },
{ from: RooCodeEventName.TaskStarted, to: ExtensionBridgeEventName.TaskStarted },
{ from: RooCodeEventName.TaskCompleted, to: ExtensionBridgeEventName.TaskCompleted },
{ from: RooCodeEventName.TaskAborted, to: ExtensionBridgeEventName.TaskAborted },
{ from: RooCodeEventName.TaskFocused, to: ExtensionBridgeEventName.TaskFocused },
{ from: RooCodeEventName.TaskUnfocused, to: ExtensionBridgeEventName.TaskUnfocused },
{ from: RooCodeEventName.TaskActive, to: ExtensionBridgeEventName.TaskActive },
{ from: RooCodeEventName.TaskInteractive, to: ExtensionBridgeEventName.TaskInteractive },
{ from: RooCodeEventName.TaskResumable, to: ExtensionBridgeEventName.TaskResumable },
{ from: RooCodeEventName.TaskIdle, to: ExtensionBridgeEventName.TaskIdle },
{ from: RooCodeEventName.TaskPaused, to: ExtensionBridgeEventName.TaskPaused },
{ from: RooCodeEventName.TaskUnpaused, to: ExtensionBridgeEventName.TaskUnpaused },
{ from: RooCodeEventName.TaskSpawned, to: ExtensionBridgeEventName.TaskSpawned },
{ from: RooCodeEventName.TaskDelegated, to: ExtensionBridgeEventName.TaskDelegated },
{ from: RooCodeEventName.TaskDelegationCompleted, to: ExtensionBridgeEventName.TaskDelegationCompleted },
{ from: RooCodeEventName.TaskDelegationResumed, to: ExtensionBridgeEventName.TaskDelegationResumed },
{ from: RooCodeEventName.TaskUserMessage, to: ExtensionBridgeEventName.TaskUserMessage },
{ from: RooCodeEventName.TaskTokenUsageUpdated, to: ExtensionBridgeEventName.TaskTokenUsageUpdated },
] as const
eventMapping.forEach(({ from, to }) => {
// Create and store the listener function for cleanup.
const listener = async (...args: unknown[]) => {
const baseEvent: {
type: ExtensionBridgeEventName
instance: ExtensionInstance
timestamp: number
} = {
type: to,
instance: await this.updateInstance(),
timestamp: Date.now(),
}
let eventToPublish: ExtensionBridgeEvent
// Add payload for delegation events while avoiding `any`
if (to === ExtensionBridgeEventName.TaskDelegationCompleted) {
const [parentTaskId, childTaskId, summary] = args as [string, string, string]
eventToPublish = {
...(baseEvent as unknown as ExtensionBridgeEvent),
payload: { parentTaskId, childTaskId, summary },
} as unknown as ExtensionBridgeEvent
} else if (to === ExtensionBridgeEventName.TaskDelegationResumed) {
const [parentTaskId, childTaskId] = args as [string, string]
eventToPublish = {
...(baseEvent as unknown as ExtensionBridgeEvent),
payload: { parentTaskId, childTaskId },
} as unknown as ExtensionBridgeEvent
} else {
eventToPublish = baseEvent as unknown as ExtensionBridgeEvent
}
this.publish(ExtensionSocketEvents.EVENT, eventToPublish)
}
this.eventListeners.set(from, listener)
this.provider.on(from, listener)
})
}
private cleanupListeners(): void {
this.eventListeners.forEach((listener, eventName) => {
// Cast is safe because we only store valid event names from eventMapping.
this.provider.off(eventName as keyof TaskProviderEvents, listener)
})
this.eventListeners.clear()
}
private async updateInstance(): Promise<ExtensionInstance> {
const task = this.provider?.getCurrentTask()
const taskHistory = this.provider?.getRecentTasks() ?? []
const mode = await this.provider?.getMode()
const modes = (await this.provider?.getModes()) ?? []
const providerProfile = await this.provider?.getProviderProfile()
const providerProfiles = (await this.provider?.getProviderProfiles()) ?? []
this.extensionInstance = {
...this.extensionInstance,
lastHeartbeat: Date.now(),
task: task
? {
taskId: task.taskId,
parentTaskId: task.parentTaskId,
childTaskId: task.childTaskId,
taskStatus: task.taskStatus,
taskAsk: task?.taskAsk,
queuedMessages: task.queuedMessages,
tokenUsage: task.tokenUsage,
...task.metadata,
}
: { taskId: "", taskStatus: TaskStatus.None },
taskAsk: task?.taskAsk,
taskHistory,
mode,
providerProfile,
modes,
providerProfiles,
}
return this.extensionInstance
}
}

View file

@ -1,281 +0,0 @@
import { io, type Socket, type SocketOptions, type ManagerOptions } from "socket.io-client"
import { ConnectionState, type RetryConfig } from "@roo-code/types"
export interface SocketTransportOptions {
url: string
socketOptions: Partial<ManagerOptions & SocketOptions>
onConnect?: () => void | Promise<void>
onDisconnect?: (reason: string) => void
onReconnect?: () => void | Promise<void>
logger?: {
log: (message: string, ...args: unknown[]) => void
error: (message: string, ...args: unknown[]) => void
warn: (message: string, ...args: unknown[]) => void
}
}
/**
* Manages the WebSocket transport layer for the bridge system.
* Handles connection lifecycle, retries, and reconnection logic.
*/
export class SocketTransport {
private socket: Socket | null = null
private connectionState: ConnectionState = ConnectionState.DISCONNECTED
private retryTimeout: NodeJS.Timeout | null = null
private isPreviouslyConnected: boolean = false
private readonly retryConfig: RetryConfig = {
maxInitialAttempts: Infinity,
initialDelay: 1_000,
maxDelay: 15_000,
backoffMultiplier: 2,
}
private readonly CONNECTION_TIMEOUT = 2_000
private readonly options: SocketTransportOptions
constructor(options: SocketTransportOptions, retryConfig?: Partial<RetryConfig>) {
this.options = options
if (retryConfig) {
this.retryConfig = { ...this.retryConfig, ...retryConfig }
}
}
// This is the initial connnect attempt. We need to implement our own
// infinite retry mechanism since Socket.io's automatic reconnection only
// kicks in after a successful initial connection.
public async connect(): Promise<void> {
if (this.connectionState === ConnectionState.CONNECTED) {
console.log(`[SocketTransport#connect] Already connected`)
return
}
if (this.connectionState === ConnectionState.CONNECTING || this.connectionState === ConnectionState.RETRYING) {
console.log(`[SocketTransport#connect] Already in progress`)
return
}
let attempt = 0
let delay = this.retryConfig.initialDelay
while (attempt < this.retryConfig.maxInitialAttempts) {
console.log(`[SocketTransport#connect] attempt = ${attempt + 1}, delay = ${delay}ms`)
this.connectionState = attempt === 0 ? ConnectionState.CONNECTING : ConnectionState.RETRYING
try {
await this._connect()
break
} catch (_error) {
attempt++
if (this.socket) {
this.socket.disconnect()
this.socket = null
}
const promise = new Promise((resolve) => {
this.retryTimeout = setTimeout(resolve, delay)
})
await promise
delay = Math.min(delay * this.retryConfig.backoffMultiplier, this.retryConfig.maxDelay)
}
}
if (this.retryTimeout) {
clearTimeout(this.retryTimeout)
this.retryTimeout = null
}
if (this.socket?.connected) {
console.log(`[SocketTransport#connect] connected - ${this.options.url}`)
} else {
// Since we have infinite retries this should never happen.
this.connectionState = ConnectionState.FAILED
console.error(`[SocketTransport#connect] Giving up`)
}
}
private async _connect(): Promise<void> {
return new Promise((resolve, reject) => {
this.socket = io(this.options.url, this.options.socketOptions)
let connectionTimeout: NodeJS.Timeout | null = setTimeout(() => {
console.error(`[SocketTransport#_connect] failed to connect after ${this.CONNECTION_TIMEOUT}ms`)
if (this.connectionState !== ConnectionState.CONNECTED) {
this.socket?.disconnect()
reject(new Error("Connection timeout"))
}
}, this.CONNECTION_TIMEOUT)
// https://socket.io/docs/v4/client-api/#event-connect
this.socket.on("connect", async () => {
console.log(
`[SocketTransport#_connect] on(connect): isPreviouslyConnected = ${this.isPreviouslyConnected}`,
)
if (connectionTimeout) {
clearTimeout(connectionTimeout)
connectionTimeout = null
}
this.connectionState = ConnectionState.CONNECTED
if (this.isPreviouslyConnected) {
if (this.options.onReconnect) {
await this.options.onReconnect()
}
} else {
if (this.options.onConnect) {
await this.options.onConnect()
}
}
this.isPreviouslyConnected = true
resolve()
})
// https://socket.io/docs/v4/client-api/#event-connect_error
this.socket.on("connect_error", (error) => {
if (connectionTimeout && this.connectionState !== ConnectionState.CONNECTED) {
console.error(`[SocketTransport] on(connect_error): ${error.message}`)
clearTimeout(connectionTimeout)
connectionTimeout = null
reject(error)
}
})
// https://socket.io/docs/v4/client-api/#event-disconnect
this.socket.on("disconnect", (reason, details) => {
console.log(
`[SocketTransport#_connect] on(disconnect) (reason: ${reason}, details: ${JSON.stringify(details)})`,
)
this.connectionState = ConnectionState.DISCONNECTED
if (this.options.onDisconnect) {
this.options.onDisconnect(reason)
}
// Don't attempt to reconnect if we're manually disconnecting.
const isManualDisconnect = reason === "io client disconnect"
if (!isManualDisconnect && this.isPreviouslyConnected) {
// After successful initial connection, rely entirely on
// Socket.IO's reconnection logic.
console.log("[SocketTransport#_connect] will attempt to reconnect")
} else {
console.log("[SocketTransport#_connect] will *NOT* attempt to reconnect")
}
})
// https://socket.io/docs/v4/client-api/#event-error
// Fired upon a connection error.
this.socket.io.on("error", (error) => {
// Connection error.
if (connectionTimeout && this.connectionState !== ConnectionState.CONNECTED) {
console.error(`[SocketTransport#_connect] on(error): ${error.message}`)
clearTimeout(connectionTimeout)
connectionTimeout = null
reject(error)
}
// Post-connection error.
if (this.connectionState === ConnectionState.CONNECTED) {
console.error(`[SocketTransport#_connect] on(error): ${error.message}`)
}
})
// https://socket.io/docs/v4/client-api/#event-reconnect
// Fired upon a successful reconnection.
this.socket.io.on("reconnect", (attempt) => {
console.log(`[SocketTransport#_connect] on(reconnect) - ${attempt}`)
this.connectionState = ConnectionState.CONNECTED
if (this.options.onReconnect) {
this.options.onReconnect()
}
})
// https://socket.io/docs/v4/client-api/#event-reconnect_attempt
// Fired upon an attempt to reconnect.
this.socket.io.on("reconnect_attempt", (attempt) => {
console.log(`[SocketTransport#_connect] on(reconnect_attempt) - ${attempt}`)
})
// https://socket.io/docs/v4/client-api/#event-reconnect_error
// Fired upon a reconnection attempt error.
this.socket.io.on("reconnect_error", (error) => {
console.error(`[SocketTransport#_connect] on(reconnect_error): ${error.message}`)
})
// https://socket.io/docs/v4/client-api/#event-reconnect_failed
// Fired when couldn't reconnect within `reconnectionAttempts`.
// Since we use infinite retries, this should never fire.
this.socket.io.on("reconnect_failed", () => {
console.error(`[SocketTransport#_connect] on(reconnect_failed) - giving up`)
this.connectionState = ConnectionState.FAILED
})
// This is a custom event fired by the server.
this.socket.on("auth_error", (error) => {
console.error(
`[SocketTransport#_connect] on(auth_error): ${error instanceof Error ? error.message : String(error)}`,
)
if (connectionTimeout && this.connectionState !== ConnectionState.CONNECTED) {
clearTimeout(connectionTimeout)
connectionTimeout = null
reject(new Error(error.message || "Authentication failed"))
}
})
})
}
public async disconnect(): Promise<void> {
console.log(`[SocketTransport#disconnect] Disconnecting...`)
if (this.retryTimeout) {
clearTimeout(this.retryTimeout)
this.retryTimeout = null
}
if (this.socket) {
this.socket.removeAllListeners()
this.socket.io.removeAllListeners()
this.socket.disconnect()
this.socket = null
}
this.connectionState = ConnectionState.DISCONNECTED
console.log(`[SocketTransport#disconnect] Disconnected`)
}
public getSocket(): Socket | null {
return this.socket
}
public getConnectionState(): ConnectionState {
return this.connectionState
}
public isConnected(): boolean {
return this.connectionState === ConnectionState.CONNECTED && this.socket?.connected === true
}
public async reconnect(): Promise<void> {
console.log(`[SocketTransport#reconnect] Manually reconnecting...`)
if (this.connectionState === ConnectionState.CONNECTED) {
console.log(`[SocketTransport#reconnect] Already connected`)
return
}
this.isPreviouslyConnected = false
await this.disconnect()
await this.connect()
}
}

View file

@ -1,241 +0,0 @@
import type { Socket } from "socket.io-client"
import {
type ClineMessage,
type TaskEvents,
type TaskLike,
type TaskBridgeCommand,
type TaskBridgeEvent,
type JoinResponse,
type LeaveResponse,
RooCodeEventName,
TaskBridgeEventName,
TaskBridgeCommandName,
TaskSocketEvents,
} from "@roo-code/types"
import { type BaseChannelOptions, BaseChannel } from "./BaseChannel.js"
type TaskEventListener = {
[K in keyof TaskEvents]: (...args: TaskEvents[K]) => void | Promise<void>
}[keyof TaskEvents]
type TaskEventMapping = {
from: keyof TaskEvents
to: TaskBridgeEventName
createPayload: (task: TaskLike, ...args: any[]) => any // eslint-disable-line @typescript-eslint/no-explicit-any
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface TaskChannelOptions extends BaseChannelOptions {}
/**
* Manages task-level communication channels.
* Handles task subscriptions, messaging, and task-specific commands.
*/
export class TaskChannel extends BaseChannel<
TaskBridgeCommand,
TaskSocketEvents,
TaskBridgeEvent | { taskId: string }
> {
private subscribedTasks: Map<string, TaskLike> = new Map()
private pendingTasks: Map<string, TaskLike> = new Map()
private taskListeners: Map<string, Map<TaskBridgeEventName, TaskEventListener>> = new Map()
private readonly eventMapping: readonly TaskEventMapping[] = [
{
from: RooCodeEventName.Message,
to: TaskBridgeEventName.Message,
createPayload: (task: TaskLike, data: { action: string; message: ClineMessage }) => ({
type: TaskBridgeEventName.Message,
taskId: task.taskId,
action: data.action,
message: data.message,
}),
},
{
from: RooCodeEventName.TaskModeSwitched,
to: TaskBridgeEventName.TaskModeSwitched,
createPayload: (task: TaskLike, mode: string) => ({
type: TaskBridgeEventName.TaskModeSwitched,
taskId: task.taskId,
mode,
}),
},
{
from: RooCodeEventName.TaskInteractive,
to: TaskBridgeEventName.TaskInteractive,
createPayload: (task: TaskLike, _taskId: string) => ({
type: TaskBridgeEventName.TaskInteractive,
taskId: task.taskId,
}),
},
] as const
constructor(options: TaskChannelOptions) {
super(options)
}
protected async handleCommandImplementation(command: TaskBridgeCommand): Promise<void> {
const task = this.subscribedTasks.get(command.taskId)
if (!task) {
console.error(`[TaskChannel] Unable to find task ${command.taskId}`)
return
}
switch (command.type) {
case TaskBridgeCommandName.Message:
console.log(
`[TaskChannel] ${TaskBridgeCommandName.Message} ${command.taskId} -> submitUserMessage()`,
command,
)
await task.submitUserMessage(
command.payload.text,
command.payload.images,
command.payload.mode,
command.payload.providerProfile,
)
break
case TaskBridgeCommandName.ApproveAsk:
console.log(
`[TaskChannel] ${TaskBridgeCommandName.ApproveAsk} ${command.taskId} -> approveAsk()`,
command,
)
task.approveAsk(command.payload)
break
case TaskBridgeCommandName.DenyAsk:
console.log(`[TaskChannel] ${TaskBridgeCommandName.DenyAsk} ${command.taskId} -> denyAsk()`, command)
task.denyAsk(command.payload)
break
}
}
protected async handleConnect(socket: Socket): Promise<void> {
// Rejoin all subscribed tasks.
for (const taskId of this.subscribedTasks.keys()) {
await this.publish(TaskSocketEvents.JOIN, { taskId })
}
// Subscribe to any pending tasks.
for (const task of this.pendingTasks.values()) {
await this.subscribeToTask(task, socket)
}
this.pendingTasks.clear()
}
protected async handleReconnect(_socket: Socket): Promise<void> {
// Rejoin all subscribed tasks.
for (const taskId of this.subscribedTasks.keys()) {
await this.publish(TaskSocketEvents.JOIN, { taskId })
}
}
protected async handleCleanup(socket: Socket): Promise<void> {
const unsubscribePromises = []
for (const taskId of this.subscribedTasks.keys()) {
unsubscribePromises.push(this.unsubscribeFromTask(taskId, socket))
}
await Promise.allSettled(unsubscribePromises)
this.subscribedTasks.clear()
this.taskListeners.clear()
this.pendingTasks.clear()
}
/**
* Add a task to the pending queue (will be subscribed when connected).
*/
public addPendingTask(task: TaskLike): void {
this.pendingTasks.set(task.taskId, task)
}
public async subscribeToTask(task: TaskLike, _socket: Socket): Promise<void> {
const taskId = task.taskId
await this.publish(TaskSocketEvents.JOIN, { taskId }, (response: JoinResponse) => {
if (response.success) {
console.log(`[TaskChannel#subscribeToTask] subscribed to ${taskId}`)
this.subscribedTasks.set(taskId, task)
this.setupTaskListeners(task)
} else {
console.error(`[TaskChannel#subscribeToTask] failed to subscribe to ${taskId}: ${response.error}`)
}
})
}
public async unsubscribeFromTask(taskId: string, _socket: Socket): Promise<void> {
const task = this.subscribedTasks.get(taskId)
if (!task) {
return
}
await this.publish(TaskSocketEvents.LEAVE, { taskId }, (response: LeaveResponse) => {
if (response.success) {
console.log(`[TaskChannel#unsubscribeFromTask] unsubscribed from ${taskId}`)
} else {
console.error(`[TaskChannel#unsubscribeFromTask] failed to unsubscribe from ${taskId}`)
}
// If we failed to unsubscribe then something is probably wrong and
// we should still discard this task from `subscribedTasks`.
this.removeTaskListeners(task)
this.subscribedTasks.delete(taskId)
})
}
private setupTaskListeners(task: TaskLike): void {
if (this.taskListeners.has(task.taskId)) {
console.warn(`[TaskChannel] Listeners already exist for task, removing old listeners for ${task.taskId}`)
this.removeTaskListeners(task)
}
const listeners = new Map<TaskBridgeEventName, TaskEventListener>()
this.eventMapping.forEach(({ from, to, createPayload }) => {
const listener = (...args: unknown[]) => {
const payload = createPayload(task, ...args)
this.publish(TaskSocketEvents.EVENT, payload)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
task.on(from, listener as any)
listeners.set(to, listener)
})
this.taskListeners.set(task.taskId, listeners)
}
private removeTaskListeners(task: TaskLike): void {
const listeners = this.taskListeners.get(task.taskId)
if (!listeners) {
return
}
this.eventMapping.forEach(({ from, to }) => {
const listener = listeners.get(to)
if (listener) {
try {
task.off(from, listener as any) // eslint-disable-line @typescript-eslint/no-explicit-any
} catch (error) {
console.error(
`[TaskChannel] task.off(${from}) failed for task ${task.taskId}: ${
error instanceof Error ? error.message : String(error)
}`,
)
}
}
})
this.taskListeners.delete(task.taskId)
}
}

View file

@ -1,402 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Socket } from "socket.io-client"
import {
type TaskProviderLike,
type TaskProviderEvents,
type StaticAppProperties,
RooCodeEventName,
ExtensionBridgeEventName,
ExtensionSocketEvents,
} from "@roo-code/types"
import { ExtensionChannel } from "../ExtensionChannel.js"
describe("ExtensionChannel", () => {
let mockSocket: Socket
let mockProvider: TaskProviderLike
let extensionChannel: ExtensionChannel
const instanceId = "test-instance-123"
const userId = "test-user-456"
const appProperties: StaticAppProperties = {
appName: "roo-code",
appVersion: "1.0.0",
vscodeVersion: "1.0.0",
platform: "darwin",
editorName: "Roo Code",
hostname: "test-host",
}
// Track registered event listeners
const eventListeners = new Map<keyof TaskProviderEvents, Set<(...args: unknown[]) => unknown>>()
beforeEach(() => {
// Reset the event listeners tracker
eventListeners.clear()
// Create mock socket
mockSocket = {
emit: vi.fn(),
on: vi.fn(),
off: vi.fn(),
disconnect: vi.fn(),
} as unknown as Socket
// Create mock provider with event listener tracking
mockProvider = {
cwd: "/test/workspace",
appProperties: {
version: "1.0.0",
extensionVersion: "1.0.0",
},
gitProperties: undefined,
getCurrentTask: vi.fn().mockReturnValue(undefined),
getCurrentTaskStack: vi.fn().mockReturnValue([]),
getRecentTasks: vi.fn().mockReturnValue([]),
createTask: vi.fn(),
cancelTask: vi.fn(),
clearTask: vi.fn(),
resumeTask: vi.fn(),
getState: vi.fn(),
postStateToWebview: vi.fn(),
postMessageToWebview: vi.fn(),
getTelemetryProperties: vi.fn(),
getMode: vi.fn().mockResolvedValue("code"),
getModes: vi.fn().mockResolvedValue([
{ slug: "code", name: "Code", description: "Code mode" },
{ slug: "architect", name: "Architect", description: "Architect mode" },
]),
getProviderProfile: vi.fn().mockResolvedValue("default"),
getProviderProfiles: vi.fn().mockResolvedValue([{ name: "default", description: "Default profile" }]),
on: vi.fn((event: keyof TaskProviderEvents, listener: (...args: unknown[]) => unknown) => {
if (!eventListeners.has(event)) {
eventListeners.set(event, new Set())
}
eventListeners.get(event)!.add(listener)
return mockProvider
}),
off: vi.fn((event: keyof TaskProviderEvents, listener: (...args: unknown[]) => unknown) => {
const listeners = eventListeners.get(event)
if (listeners) {
listeners.delete(listener)
if (listeners.size === 0) {
eventListeners.delete(event)
}
}
return mockProvider
}),
} as unknown as TaskProviderLike
// Create extension channel instance
extensionChannel = new ExtensionChannel({
instanceId,
appProperties,
userId,
provider: mockProvider,
isCloudAgent: false,
})
})
afterEach(() => {
vi.clearAllMocks()
})
describe("Event Listener Management", () => {
it("should register event listeners on initialization", () => {
// Verify that listeners were registered for all expected events
const expectedEvents: RooCodeEventName[] = [
RooCodeEventName.TaskCreated,
RooCodeEventName.TaskStarted,
RooCodeEventName.TaskCompleted,
RooCodeEventName.TaskAborted,
RooCodeEventName.TaskFocused,
RooCodeEventName.TaskUnfocused,
RooCodeEventName.TaskActive,
RooCodeEventName.TaskInteractive,
RooCodeEventName.TaskResumable,
RooCodeEventName.TaskIdle,
RooCodeEventName.TaskPaused,
RooCodeEventName.TaskUnpaused,
RooCodeEventName.TaskSpawned,
RooCodeEventName.TaskDelegated,
RooCodeEventName.TaskDelegationCompleted,
RooCodeEventName.TaskDelegationResumed,
RooCodeEventName.TaskUserMessage,
RooCodeEventName.TaskTokenUsageUpdated,
]
// Check that on() was called for each event
expect(mockProvider.on).toHaveBeenCalledTimes(expectedEvents.length)
// Verify each event was registered
expectedEvents.forEach((eventName) => {
expect(mockProvider.on).toHaveBeenCalledWith(eventName, expect.any(Function))
})
// Verify listeners are tracked in our Map
expect(eventListeners.size).toBe(expectedEvents.length)
})
it("should remove all event listeners during cleanup", async () => {
// Verify initial state - listeners are registered
const initialListenerCount = eventListeners.size
expect(initialListenerCount).toBeGreaterThan(0)
// Get the count of listeners for each event before cleanup
const listenerCountsBefore = new Map<keyof TaskProviderEvents, number>()
eventListeners.forEach((listeners, event) => {
listenerCountsBefore.set(event, listeners.size)
})
// Perform cleanup
await extensionChannel.cleanup(mockSocket)
// Verify that off() was called for each registered event
expect(mockProvider.off).toHaveBeenCalledTimes(initialListenerCount)
// Verify all listeners were removed from our tracking Map
expect(eventListeners.size).toBe(0)
// Verify that the same listener functions that were added were removed
const onCalls = (mockProvider.on as any).mock.calls
const offCalls = (mockProvider.off as any).mock.calls
// Each on() call should have a corresponding off() call with the same listener
onCalls.forEach(([eventName, listener]: [keyof TaskProviderEvents, any]) => {
const hasMatchingOff = offCalls.some(
([offEvent, offListener]: [keyof TaskProviderEvents, any]) =>
offEvent === eventName && offListener === listener,
)
expect(hasMatchingOff).toBe(true)
})
})
it("should not have duplicate listeners after multiple channel creations", () => {
// Create a second channel with the same provider
const secondChannel = new ExtensionChannel({
instanceId: "instance-2",
appProperties,
userId,
provider: mockProvider,
isCloudAgent: false,
})
// Each event should have exactly 2 listeners (one from each channel)
eventListeners.forEach((listeners) => {
expect(listeners.size).toBe(2)
})
// Clean up the first channel
extensionChannel.cleanup(mockSocket)
// Each event should now have exactly 1 listener (from the second channel)
eventListeners.forEach((listeners) => {
expect(listeners.size).toBe(1)
})
// Clean up the second channel
secondChannel.cleanup(mockSocket)
// All listeners should be removed
expect(eventListeners.size).toBe(0)
})
it("should handle cleanup even if called multiple times", async () => {
// First cleanup
await extensionChannel.cleanup(mockSocket)
const firstOffCallCount = (mockProvider.off as any).mock.calls.length
// Second cleanup (should be safe to call again)
await extensionChannel.cleanup(mockSocket)
const secondOffCallCount = (mockProvider.off as any).mock.calls.length
// The second cleanup shouldn't try to remove listeners again
// since the internal Map was cleared
expect(secondOffCallCount).toBe(firstOffCallCount)
})
it("should properly forward events to socket when listeners are triggered", async () => {
// Connect the socket to enable publishing
await extensionChannel.onConnect(mockSocket)
// Clear the mock calls from the connection (which emits a register event)
;(mockSocket.emit as any).mockClear()
// Get a listener that was registered for TaskStarted
const taskStartedListeners = eventListeners.get(RooCodeEventName.TaskStarted)
expect(taskStartedListeners).toBeDefined()
expect(taskStartedListeners!.size).toBe(1)
// Trigger the listener
const listener = Array.from(taskStartedListeners!)[0]
if (listener) {
await listener("test-task-id")
}
// Verify the event was published to the socket
expect(mockSocket.emit).toHaveBeenCalledWith(
ExtensionSocketEvents.EVENT,
expect.objectContaining({
type: ExtensionBridgeEventName.TaskStarted,
instance: expect.objectContaining({
instanceId,
userId,
}),
timestamp: expect.any(Number),
}),
undefined,
)
})
it("should forward delegation events to socket", async () => {
await extensionChannel.onConnect(mockSocket)
;(mockSocket.emit as any).mockClear()
const delegatedListeners = eventListeners.get(RooCodeEventName.TaskDelegated)
expect(delegatedListeners).toBeDefined()
expect(delegatedListeners!.size).toBe(1)
const listener = Array.from(delegatedListeners!)[0]
if (listener) {
await (listener as any)("parent-id", "child-id")
}
expect(mockSocket.emit).toHaveBeenCalledWith(
ExtensionSocketEvents.EVENT,
expect.objectContaining({
type: ExtensionBridgeEventName.TaskDelegated,
instance: expect.any(Object),
timestamp: expect.any(Number),
}),
undefined,
)
})
it("should forward TaskDelegationCompleted with correct payload", async () => {
await extensionChannel.onConnect(mockSocket)
;(mockSocket.emit as any).mockClear()
const completedListeners = eventListeners.get(RooCodeEventName.TaskDelegationCompleted)
expect(completedListeners).toBeDefined()
const listener = Array.from(completedListeners!)[0]
if (listener) {
await (listener as any)("parent-1", "child-1", "Summary text")
}
expect(mockSocket.emit).toHaveBeenCalledWith(
ExtensionSocketEvents.EVENT,
expect.objectContaining({
type: ExtensionBridgeEventName.TaskDelegationCompleted,
instance: expect.any(Object),
timestamp: expect.any(Number),
payload: expect.objectContaining({
parentTaskId: "parent-1",
childTaskId: "child-1",
summary: "Summary text",
}),
}),
undefined,
)
})
it("should forward TaskDelegationResumed with correct payload", async () => {
await extensionChannel.onConnect(mockSocket)
;(mockSocket.emit as any).mockClear()
const resumedListeners = eventListeners.get(RooCodeEventName.TaskDelegationResumed)
expect(resumedListeners).toBeDefined()
const listener = Array.from(resumedListeners!)[0]
if (listener) {
await (listener as any)("parent-2", "child-2")
}
expect(mockSocket.emit).toHaveBeenCalledWith(
ExtensionSocketEvents.EVENT,
expect.objectContaining({
type: ExtensionBridgeEventName.TaskDelegationResumed,
instance: expect.any(Object),
timestamp: expect.any(Number),
payload: expect.objectContaining({
parentTaskId: "parent-2",
childTaskId: "child-2",
}),
}),
undefined,
)
})
it("should propagate all three delegation events in order", async () => {
await extensionChannel.onConnect(mockSocket)
;(mockSocket.emit as any).mockClear()
// Trigger TaskDelegated
const delegatedListener = Array.from(eventListeners.get(RooCodeEventName.TaskDelegated)!)[0]
await (delegatedListener as any)("p1", "c1")
// Trigger TaskDelegationCompleted
const completedListener = Array.from(eventListeners.get(RooCodeEventName.TaskDelegationCompleted)!)[0]
await (completedListener as any)("p1", "c1", "result")
// Trigger TaskDelegationResumed
const resumedListener = Array.from(eventListeners.get(RooCodeEventName.TaskDelegationResumed)!)[0]
await (resumedListener as any)("p1", "c1")
// Verify all three events were emitted
const emittedEvents = (mockSocket.emit as any).mock.calls.map((call: any[]) => call[1]?.type)
expect(emittedEvents).toContain(ExtensionBridgeEventName.TaskDelegated)
expect(emittedEvents).toContain(ExtensionBridgeEventName.TaskDelegationCompleted)
expect(emittedEvents).toContain(ExtensionBridgeEventName.TaskDelegationResumed)
// Verify correct order: Delegated → Completed → Resumed
const delegatedIdx = emittedEvents.indexOf(ExtensionBridgeEventName.TaskDelegated)
const completedIdx = emittedEvents.indexOf(ExtensionBridgeEventName.TaskDelegationCompleted)
const resumedIdx = emittedEvents.indexOf(ExtensionBridgeEventName.TaskDelegationResumed)
expect(delegatedIdx).toBeLessThan(completedIdx)
expect(completedIdx).toBeLessThan(resumedIdx)
})
})
describe("Memory Leak Prevention", () => {
it("should not accumulate listeners over multiple connect/disconnect cycles", async () => {
// Simulate multiple connect/disconnect cycles
for (let i = 0; i < 5; i++) {
await extensionChannel.onConnect(mockSocket)
extensionChannel.onDisconnect()
}
// Listeners should still be the same count (not accumulated)
expect(eventListeners.size).toBe(18)
// Each event should have exactly 1 listener
eventListeners.forEach((listeners) => {
expect(listeners.size).toBe(1)
})
})
it("should properly clean up heartbeat interval", async () => {
// Spy on setInterval and clearInterval
const setIntervalSpy = vi.spyOn(global, "setInterval")
const clearIntervalSpy = vi.spyOn(global, "clearInterval")
// Connect to start heartbeat
await extensionChannel.onConnect(mockSocket)
expect(setIntervalSpy).toHaveBeenCalled()
// Get the interval ID
const intervalId = setIntervalSpy.mock.results[0]?.value
// Cleanup should stop the heartbeat
await extensionChannel.cleanup(mockSocket)
expect(clearIntervalSpy).toHaveBeenCalledWith(intervalId)
setIntervalSpy.mockRestore()
clearIntervalSpy.mockRestore()
})
})
})

View file

@ -1,407 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Socket } from "socket.io-client"
import {
type TaskLike,
type ClineMessage,
type StaticAppProperties,
RooCodeEventName,
TaskBridgeEventName,
TaskBridgeCommandName,
TaskSocketEvents,
TaskStatus,
} from "@roo-code/types"
import { TaskChannel } from "../TaskChannel.js"
describe("TaskChannel", () => {
let mockSocket: Socket
let taskChannel: TaskChannel
let mockTask: TaskLike
const instanceId = "test-instance-123"
const taskId = "test-task-456"
const appProperties: StaticAppProperties = {
appName: "roo-code",
appVersion: "1.0.0",
vscodeVersion: "1.0.0",
platform: "darwin",
editorName: "Roo Code",
hostname: "test-host",
}
beforeEach(() => {
// Create mock socket
mockSocket = {
emit: vi.fn(),
on: vi.fn(),
off: vi.fn(),
disconnect: vi.fn(),
} as unknown as Socket
// Create mock task with event emitter functionality
const listeners = new Map<string, Set<(...args: unknown[]) => unknown>>()
mockTask = {
taskId,
taskStatus: TaskStatus.Running,
taskAsk: undefined,
metadata: {},
on: vi.fn((event: string, listener: (...args: unknown[]) => unknown) => {
if (!listeners.has(event)) {
listeners.set(event, new Set())
}
listeners.get(event)!.add(listener)
return mockTask
}),
off: vi.fn((event: string, listener: (...args: unknown[]) => unknown) => {
const eventListeners = listeners.get(event)
if (eventListeners) {
eventListeners.delete(listener)
if (eventListeners.size === 0) {
listeners.delete(event)
}
}
return mockTask
}),
approveAsk: vi.fn(),
denyAsk: vi.fn(),
submitUserMessage: vi.fn(),
abortTask: vi.fn(),
// Helper to trigger events in tests
_triggerEvent: (event: string, ...args: any[]) => {
const eventListeners = listeners.get(event)
if (eventListeners) {
eventListeners.forEach((listener) => listener(...args))
}
},
_getListenerCount: (event: string) => {
return listeners.get(event)?.size || 0
},
} as unknown as TaskLike & {
_triggerEvent: (event: string, ...args: any[]) => void
_getListenerCount: (event: string) => number
}
// Create task channel instance
taskChannel = new TaskChannel({
instanceId,
appProperties,
isCloudAgent: false,
})
})
afterEach(() => {
vi.clearAllMocks()
})
describe("Event Mapping Refactoring", () => {
it("should use the unified event mapping approach", () => {
// Access the private eventMapping through type assertion
const channel = taskChannel as any
// Verify eventMapping exists and has the correct structure
expect(channel.eventMapping).toBeDefined()
expect(Array.isArray(channel.eventMapping)).toBe(true)
expect(channel.eventMapping.length).toBe(3)
// Verify each mapping has the required properties
channel.eventMapping.forEach((mapping: any) => {
expect(mapping).toHaveProperty("from")
expect(mapping).toHaveProperty("to")
expect(mapping).toHaveProperty("createPayload")
expect(typeof mapping.createPayload).toBe("function")
})
// Verify specific mappings
expect(channel.eventMapping[0].from).toBe(RooCodeEventName.Message)
expect(channel.eventMapping[0].to).toBe(TaskBridgeEventName.Message)
expect(channel.eventMapping[1].from).toBe(RooCodeEventName.TaskModeSwitched)
expect(channel.eventMapping[1].to).toBe(TaskBridgeEventName.TaskModeSwitched)
expect(channel.eventMapping[2].from).toBe(RooCodeEventName.TaskInteractive)
expect(channel.eventMapping[2].to).toBe(TaskBridgeEventName.TaskInteractive)
})
it("should setup listeners using the event mapping", async () => {
// Mock the publish method to simulate successful subscription
const channel = taskChannel as any
channel.publish = vi.fn((event: string, data: any, callback?: Function) => {
if (event === TaskSocketEvents.JOIN && callback) {
// Simulate successful join response
callback({ success: true })
}
return true
})
// Connect and subscribe to task
await taskChannel.onConnect(mockSocket)
await channel.subscribeToTask(mockTask, mockSocket)
// Wait for async operations
await new Promise((resolve) => setTimeout(resolve, 0))
// Verify listeners were registered for all mapped events
const task = mockTask as any
expect(task._getListenerCount(RooCodeEventName.Message)).toBe(1)
expect(task._getListenerCount(RooCodeEventName.TaskModeSwitched)).toBe(1)
expect(task._getListenerCount(RooCodeEventName.TaskInteractive)).toBe(1)
})
it("should correctly transform Message event payloads", async () => {
// Setup channel with task
const channel = taskChannel as any
let publishCalls: any[] = []
channel.publish = vi.fn((event: string, data: any, callback?: Function) => {
publishCalls.push({ event, data })
if (event === TaskSocketEvents.JOIN && callback) {
callback({ success: true })
}
return true
})
await taskChannel.onConnect(mockSocket)
await channel.subscribeToTask(mockTask, mockSocket)
await new Promise((resolve) => setTimeout(resolve, 0))
// Clear previous calls
publishCalls = []
// Trigger Message event
const messageData = {
action: "test-action",
message: { type: "say", text: "Hello" } as ClineMessage,
}
;(mockTask as any)._triggerEvent(RooCodeEventName.Message, messageData)
// Verify the event was published with correct payload
expect(publishCalls.length).toBe(1)
expect(publishCalls[0]).toEqual({
event: TaskSocketEvents.EVENT,
data: {
type: TaskBridgeEventName.Message,
taskId: taskId,
action: messageData.action,
message: messageData.message,
},
})
})
it("should correctly transform TaskModeSwitched event payloads", async () => {
// Setup channel with task
const channel = taskChannel as any
let publishCalls: any[] = []
channel.publish = vi.fn((event: string, data: any, callback?: Function) => {
publishCalls.push({ event, data })
if (event === TaskSocketEvents.JOIN && callback) {
callback({ success: true })
}
return true
})
await taskChannel.onConnect(mockSocket)
await channel.subscribeToTask(mockTask, mockSocket)
await new Promise((resolve) => setTimeout(resolve, 0))
// Clear previous calls
publishCalls = []
// Trigger TaskModeSwitched event
const mode = "architect"
;(mockTask as any)._triggerEvent(RooCodeEventName.TaskModeSwitched, mode)
// Verify the event was published with correct payload
expect(publishCalls.length).toBe(1)
expect(publishCalls[0]).toEqual({
event: TaskSocketEvents.EVENT,
data: {
type: TaskBridgeEventName.TaskModeSwitched,
taskId: taskId,
mode: mode,
},
})
})
it("should correctly transform TaskInteractive event payloads", async () => {
// Setup channel with task
const channel = taskChannel as any
let publishCalls: any[] = []
channel.publish = vi.fn((event: string, data: any, callback?: Function) => {
publishCalls.push({ event, data })
if (event === TaskSocketEvents.JOIN && callback) {
callback({ success: true })
}
return true
})
await taskChannel.onConnect(mockSocket)
await channel.subscribeToTask(mockTask, mockSocket)
await new Promise((resolve) => setTimeout(resolve, 0))
// Clear previous calls
publishCalls = []
// Trigger TaskInteractive event
;(mockTask as any)._triggerEvent(RooCodeEventName.TaskInteractive, taskId)
// Verify the event was published with correct payload
expect(publishCalls.length).toBe(1)
expect(publishCalls[0]).toEqual({
event: TaskSocketEvents.EVENT,
data: {
type: TaskBridgeEventName.TaskInteractive,
taskId: taskId,
},
})
})
it("should properly clean up listeners using event mapping", async () => {
// Setup channel with task
const channel = taskChannel as any
channel.publish = vi.fn((event: string, data: any, callback?: Function) => {
if (event === TaskSocketEvents.JOIN && callback) {
callback({ success: true })
}
if (event === TaskSocketEvents.LEAVE && callback) {
callback({ success: true })
}
return true
})
await taskChannel.onConnect(mockSocket)
await channel.subscribeToTask(mockTask, mockSocket)
await new Promise((resolve) => setTimeout(resolve, 0))
// Verify listeners are registered
const task = mockTask as any
expect(task._getListenerCount(RooCodeEventName.Message)).toBe(1)
expect(task._getListenerCount(RooCodeEventName.TaskModeSwitched)).toBe(1)
expect(task._getListenerCount(RooCodeEventName.TaskInteractive)).toBe(1)
// Clean up
await taskChannel.cleanup(mockSocket)
// Verify all listeners were removed
expect(task._getListenerCount(RooCodeEventName.Message)).toBe(0)
expect(task._getListenerCount(RooCodeEventName.TaskModeSwitched)).toBe(0)
expect(task._getListenerCount(RooCodeEventName.TaskInteractive)).toBe(0)
})
it("should handle duplicate listener prevention", async () => {
// Setup channel with task
await taskChannel.onConnect(mockSocket)
// Subscribe to the same task twice
const channel = taskChannel as any
channel.subscribedTasks.set(taskId, mockTask)
channel.setupTaskListeners(mockTask)
// Try to setup listeners again (should remove old ones first)
const warnSpy = vi.spyOn(console, "warn")
channel.setupTaskListeners(mockTask)
// Verify warning was logged
expect(warnSpy).toHaveBeenCalledWith(
`[TaskChannel] Listeners already exist for task, removing old listeners for ${taskId}`,
)
// Verify only one set of listeners exists
const task = mockTask as any
expect(task._getListenerCount(RooCodeEventName.Message)).toBe(1)
expect(task._getListenerCount(RooCodeEventName.TaskModeSwitched)).toBe(1)
expect(task._getListenerCount(RooCodeEventName.TaskInteractive)).toBe(1)
warnSpy.mockRestore()
})
})
describe("Command Handling", () => {
beforeEach(async () => {
// Setup channel with a subscribed task
await taskChannel.onConnect(mockSocket)
const channel = taskChannel as any
channel.subscribedTasks.set(taskId, mockTask)
})
it("should handle Message command", async () => {
const command = {
type: TaskBridgeCommandName.Message,
taskId,
timestamp: Date.now(),
payload: {
text: "Hello, world!",
images: ["image1.png"],
},
}
await taskChannel.handleCommand(command)
expect(mockTask.submitUserMessage).toHaveBeenCalledWith(
command.payload.text,
command.payload.images,
undefined,
undefined,
)
})
it("should handle ApproveAsk command", async () => {
const command = {
type: TaskBridgeCommandName.ApproveAsk,
taskId,
timestamp: Date.now(),
payload: {
text: "Approved",
},
}
await taskChannel.handleCommand(command)
expect(mockTask.approveAsk).toHaveBeenCalledWith(command.payload)
})
it("should handle DenyAsk command", async () => {
const command = {
type: TaskBridgeCommandName.DenyAsk,
taskId,
timestamp: Date.now(),
payload: {
text: "Denied",
},
}
await taskChannel.handleCommand(command)
expect(mockTask.denyAsk).toHaveBeenCalledWith(command.payload)
})
it("should log error for unknown task", async () => {
const errorSpy = vi.spyOn(console, "error")
const command = {
type: TaskBridgeCommandName.Message,
taskId: "unknown-task",
timestamp: Date.now(),
payload: {
text: "Hello",
},
}
await taskChannel.handleCommand(command)
expect(errorSpy).toHaveBeenCalledWith(`[TaskChannel] Unable to find task unknown-task`)
errorSpy.mockRestore()
})
})
})

View file

@ -1,6 +0,0 @@
export { type BridgeOrchestratorOptions, BridgeOrchestrator } from "./BridgeOrchestrator.js"
export { type SocketTransportOptions, SocketTransport } from "./SocketTransport.js"
export { BaseChannel } from "./BaseChannel.js"
export { ExtensionChannel } from "./ExtensionChannel.js"
export { TaskChannel } from "./TaskChannel.js"

View file

@ -2,7 +2,5 @@ export * from "./config.js"
export { CloudService } from "./CloudService.js"
export { BridgeOrchestrator } from "./bridge/index.js"
export { RetryQueue } from "./retry-queue/index.js"
export type { QueuedRequest, QueueStats, RetryQueueConfig, RetryQueueEvents } from "./retry-queue/index.js"

View file

@ -21,43 +21,9 @@ describe("organizationFeaturesSchema", () => {
expect(result.data).toEqual({})
})
it("should validate with roomoteControlEnabled as true", () => {
const input = { roomoteControlEnabled: true }
const result = organizationFeaturesSchema.safeParse(input)
expect(result.success).toBe(true)
expect(result.data).toEqual(input)
})
it("should validate with roomoteControlEnabled as false", () => {
const input = { roomoteControlEnabled: false }
const result = organizationFeaturesSchema.safeParse(input)
expect(result.success).toBe(true)
expect(result.data).toEqual(input)
})
it("should reject non-boolean roomoteControlEnabled", () => {
const input = { roomoteControlEnabled: "true" }
const result = organizationFeaturesSchema.safeParse(input)
expect(result.success).toBe(false)
})
it("should allow additional properties (for future extensibility)", () => {
const input = { roomoteControlEnabled: true, futureProperty: "test" }
const result = organizationFeaturesSchema.safeParse(input)
expect(result.success).toBe(true)
expect(result.data?.roomoteControlEnabled).toBe(true)
// Note: Additional properties are stripped by Zod, which is expected behavior
})
it("should have correct TypeScript type", () => {
// Type-only test to ensure TypeScript compilation
const features: OrganizationFeatures = {
roomoteControlEnabled: true,
}
expect(features.roomoteControlEnabled).toBe(true)
const emptyFeatures: OrganizationFeatures = {}
expect(emptyFeatures.roomoteControlEnabled).toBeUndefined()
expect(emptyFeatures).toEqual({})
})
})
@ -87,43 +53,7 @@ describe("organizationSettingsSchema with features", () => {
expect(result.data?.features).toEqual({})
})
it("should validate with features.roomoteControlEnabled as true", () => {
const input = {
...validBaseSettings,
features: {
roomoteControlEnabled: true,
},
}
const result = organizationSettingsSchema.safeParse(input)
expect(result.success).toBe(true)
expect(result.data?.features?.roomoteControlEnabled).toBe(true)
})
it("should validate with features.roomoteControlEnabled as false", () => {
const input = {
...validBaseSettings,
features: {
roomoteControlEnabled: false,
},
}
const result = organizationSettingsSchema.safeParse(input)
expect(result.success).toBe(true)
expect(result.data?.features?.roomoteControlEnabled).toBe(false)
})
it("should reject invalid features object", () => {
const input = {
...validBaseSettings,
features: {
roomoteControlEnabled: "invalid",
},
}
const result = organizationSettingsSchema.safeParse(input)
expect(result.success).toBe(false)
})
it("should have correct TypeScript type for features", () => {
// Type-only test to ensure TypeScript compilation
const settings: OrganizationSettings = {
version: 1,
defaultSettings: {},
@ -131,11 +61,9 @@ describe("organizationSettingsSchema with features", () => {
allowAll: true,
providers: {},
},
features: {
roomoteControlEnabled: true,
},
features: {},
}
expect(settings.features?.roomoteControlEnabled).toBe(true)
expect(settings.features).toEqual({})
const settingsWithoutFeatures: OrganizationSettings = {
version: 1,
@ -165,9 +93,7 @@ describe("organizationSettingsSchema with features", () => {
},
},
},
features: {
roomoteControlEnabled: true,
},
features: {},
hiddenMcps: ["test-mcp"],
hideMarketplaceMcps: true,
mcps: [],
@ -415,7 +341,6 @@ describe("organizationCloudSettingsSchema with llmEnhancedFeaturesEnabled", () =
describe("userSettingsConfigSchema with llmEnhancedFeaturesEnabled", () => {
it("should validate without llmEnhancedFeaturesEnabled property", () => {
const input = {
extensionBridgeEnabled: true,
taskSyncEnabled: true,
}
const result = userSettingsConfigSchema.safeParse(input)
@ -425,7 +350,6 @@ describe("userSettingsConfigSchema with llmEnhancedFeaturesEnabled", () => {
it("should validate with llmEnhancedFeaturesEnabled as true", () => {
const input = {
extensionBridgeEnabled: true,
taskSyncEnabled: true,
llmEnhancedFeaturesEnabled: true,
}
@ -436,7 +360,6 @@ describe("userSettingsConfigSchema with llmEnhancedFeaturesEnabled", () => {
it("should validate with llmEnhancedFeaturesEnabled as false", () => {
const input = {
extensionBridgeEnabled: true,
taskSyncEnabled: true,
llmEnhancedFeaturesEnabled: false,
}
@ -456,15 +379,12 @@ describe("userSettingsConfigSchema with llmEnhancedFeaturesEnabled", () => {
it("should have correct TypeScript type", () => {
// Type-only test to ensure TypeScript compilation
const settings: UserSettingsConfig = {
extensionBridgeEnabled: true,
taskSyncEnabled: true,
llmEnhancedFeaturesEnabled: true,
}
expect(settings.llmEnhancedFeaturesEnabled).toBe(true)
const settingsWithoutLlmFeatures: UserSettingsConfig = {
extensionBridgeEnabled: false,
}
const settingsWithoutLlmFeatures: UserSettingsConfig = {}
expect(settingsWithoutLlmFeatures.llmEnhancedFeaturesEnabled).toBeUndefined()
})

View file

@ -41,7 +41,6 @@ export interface CloudUserInfo {
organizationName?: string
organizationRole?: string
organizationImageUrl?: string
extensionBridgeEnabled?: boolean
}
/**
@ -143,9 +142,7 @@ export type OrganizationCloudSettings = z.infer<typeof organizationCloudSettings
* OrganizationFeatures
*/
export const organizationFeaturesSchema = z.object({
roomoteControlEnabled: z.boolean().optional(),
})
export const organizationFeaturesSchema = z.object({})
export type OrganizationFeatures = z.infer<typeof organizationFeaturesSchema>
@ -171,14 +168,11 @@ export type OrganizationSettings = z.infer<typeof organizationSettingsSchema>
* User Settings Schemas
*/
export const userFeaturesSchema = z.object({
roomoteControlEnabled: z.boolean().optional(),
})
export const userFeaturesSchema = z.object({})
export type UserFeatures = z.infer<typeof userFeaturesSchema>
export const userSettingsConfigSchema = z.object({
extensionBridgeEnabled: z.boolean().optional(),
taskSyncEnabled: z.boolean().optional(),
llmEnhancedFeaturesEnabled: z.boolean().optional(),
})
@ -430,206 +424,6 @@ export const extensionInstanceSchema = z.object({
export type ExtensionInstance = z.infer<typeof extensionInstanceSchema>
/**
* ExtensionBridgeEvent
*/
export enum ExtensionBridgeEventName {
TaskCreated = RooCodeEventName.TaskCreated,
TaskStarted = RooCodeEventName.TaskStarted,
TaskCompleted = RooCodeEventName.TaskCompleted,
TaskAborted = RooCodeEventName.TaskAborted,
TaskFocused = RooCodeEventName.TaskFocused,
TaskUnfocused = RooCodeEventName.TaskUnfocused,
TaskActive = RooCodeEventName.TaskActive,
TaskInteractive = RooCodeEventName.TaskInteractive,
TaskResumable = RooCodeEventName.TaskResumable,
TaskIdle = RooCodeEventName.TaskIdle,
TaskPaused = RooCodeEventName.TaskPaused,
TaskUnpaused = RooCodeEventName.TaskUnpaused,
TaskSpawned = RooCodeEventName.TaskSpawned,
TaskDelegated = RooCodeEventName.TaskDelegated,
TaskDelegationCompleted = RooCodeEventName.TaskDelegationCompleted,
TaskDelegationResumed = RooCodeEventName.TaskDelegationResumed,
TaskUserMessage = RooCodeEventName.TaskUserMessage,
TaskTokenUsageUpdated = RooCodeEventName.TaskTokenUsageUpdated,
ModeChanged = RooCodeEventName.ModeChanged,
ProviderProfileChanged = RooCodeEventName.ProviderProfileChanged,
InstanceRegistered = "instance_registered",
InstanceUnregistered = "instance_unregistered",
HeartbeatUpdated = "heartbeat_updated",
}
export const extensionBridgeEventSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal(ExtensionBridgeEventName.TaskCreated),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskStarted),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskCompleted),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskAborted),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskFocused),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskUnfocused),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskActive),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskInteractive),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskResumable),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskIdle),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskPaused),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskUnpaused),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskSpawned),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskDelegated),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskDelegationCompleted),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskDelegationResumed),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskUserMessage),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.TaskTokenUsageUpdated),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.ModeChanged),
instance: extensionInstanceSchema,
mode: z.string(),
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.ProviderProfileChanged),
instance: extensionInstanceSchema,
providerProfile: z.object({ name: z.string(), provider: z.string().optional() }),
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.InstanceRegistered),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.InstanceUnregistered),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeEventName.HeartbeatUpdated),
instance: extensionInstanceSchema,
timestamp: z.number(),
}),
])
export type ExtensionBridgeEvent = z.infer<typeof extensionBridgeEventSchema>
/**
* ExtensionBridgeCommand
*/
export enum ExtensionBridgeCommandName {
StartTask = "start_task",
StopTask = "stop_task",
ResumeTask = "resume_task",
}
export const extensionBridgeCommandSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal(ExtensionBridgeCommandName.StartTask),
instanceId: z.string(),
payload: z.object({
text: z.string(),
images: z.array(z.string()).optional(),
mode: z.string().optional(),
providerProfile: z.string().optional(),
}),
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeCommandName.StopTask),
instanceId: z.string(),
payload: z.object({ taskId: z.string() }),
timestamp: z.number(),
}),
z.object({
type: z.literal(ExtensionBridgeCommandName.ResumeTask),
instanceId: z.string(),
payload: z.object({ taskId: z.string() }),
timestamp: z.number(),
}),
])
export type ExtensionBridgeCommand = z.infer<typeof extensionBridgeCommandSchema>
/**
* TaskBridgeEvent
*/

View file

@ -366,9 +366,7 @@ export type ExtensionState = Pick<
apiModelId?: string
mcpServers?: McpServer[]
mdmCompliant?: boolean
remoteControlEnabled: boolean
taskSyncEnabled: boolean
featureRoomoteControlEnabled: boolean
openAiCodexIsAuthenticated?: boolean
debug?: boolean
@ -472,7 +470,6 @@ export interface WebviewMessage {
| "deleteMessageConfirm"
| "submitEditedMessage"
| "editMessageConfirm"
| "remoteControlEnabled"
| "taskSyncEnabled"
| "searchCommits"
| "setApiConfigPassword"

204
pnpm-lock.yaml generated
View file

@ -495,9 +495,6 @@ importers:
p-wait-for:
specifier: ^5.0.2
version: 5.0.2
socket.io-client:
specifier: ^4.8.1
version: 4.8.1
zod:
specifier: 3.25.76
version: 3.25.76
@ -971,9 +968,6 @@ importers:
simple-git:
specifier: ^3.27.0
version: 3.27.0
socket.io-client:
specifier: ^4.8.1
version: 4.8.1
sound-play:
specifier: ^1.1.0
version: 1.1.0
@ -1200,9 +1194,6 @@ importers:
'@tanstack/react-query':
specifier: ^5.68.0
version: 5.76.1(react@18.3.1)
'@types/qrcode':
specifier: ^1.5.5
version: 1.5.5
'@vscode/codicons':
specifier: ^0.0.36
version: 0.0.36
@ -1266,9 +1257,6 @@ importers:
pretty-bytes:
specifier: ^7.0.0
version: 7.0.0
qrcode:
specifier: ^1.5.4
version: 1.5.4
react:
specifier: ^18.3.1
version: 18.3.1
@ -4093,9 +4081,6 @@ packages:
resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==}
engines: {node: '>=18.0.0'}
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@ -4580,9 +4565,6 @@ packages:
'@types/ps-tree@1.1.6':
resolution: {integrity: sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==}
'@types/qrcode@1.5.5':
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
'@types/react-dom@18.3.7':
resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
peerDependencies:
@ -5275,10 +5257,6 @@ packages:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
@ -5429,9 +5407,6 @@ packages:
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
cliui@7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
@ -5865,15 +5840,6 @@ packages:
supports-color:
optional: true
debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
@ -5892,10 +5858,6 @@ packages:
supports-color:
optional: true
decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
decamelize@4.0.0:
resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
engines: {node: '>=10'}
@ -6024,9 +5986,6 @@ packages:
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
engines: {node: '>=0.3.1'}
dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dingbat-to-unicode@1.0.1:
resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==}
@ -6262,13 +6221,6 @@ packages:
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
engine.io-client@6.6.3:
resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==}
engine.io-parser@5.2.3:
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
engines: {node: '>=10.0.0'}
enhanced-resolve@5.18.1:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
engines: {node: '>=10.13.0'}
@ -8896,10 +8848,6 @@ packages:
pkg-types@2.2.0:
resolution: {integrity: sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==}
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
points-on-curve@0.2.0:
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
@ -9123,11 +9071,6 @@ packages:
resolution: {integrity: sha512-CnzhOgrZj8DvkDqI+Yx+9or33i3Y9uUYbKyYpP4C13jWwXx/keQ38RMTMmxuLCWQlxjZrOH0Foq7P2fGP7adDQ==}
engines: {node: '>=18'}
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
hasBin: true
qs@6.14.0:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'}
@ -9431,9 +9374,6 @@ packages:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
@ -9632,9 +9572,6 @@ packages:
resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
engines: {node: '>= 18'}
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@ -9750,14 +9687,6 @@ packages:
resolution: {integrity: sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==}
engines: {node: '>= 18'}
socket.io-client@4.8.1:
resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==}
engines: {node: '>=10.0.0'}
socket.io-parser@4.2.4:
resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
engines: {node: '>=10.0.0'}
socks-proxy-agent@8.0.5:
resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
engines: {node: '>= 14'}
@ -10869,9 +10798,6 @@ packages:
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
engines: {node: '>= 0.4'}
which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
which-pm-runs@1.1.0:
resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
engines: {node: '>=4'}
@ -10921,10 +10847,6 @@ packages:
workerpool@9.2.0:
resolution: {integrity: sha512-PKZqBOCo6CYkVOwAxWxQaSF2Fvb5Iv2fCeTP7buyWI2GiynWr46NcXSgK/idoV6e60dgCBfgYc+Un3HMvmqP8w==}
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@ -10940,18 +10862,6 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@8.17.1:
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
ws@8.18.2:
resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==}
engines: {node: '>=10.0.0'}
@ -10995,17 +10905,10 @@ packages:
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
xmlhttprequest-ssl@2.1.2:
resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
engines: {node: '>=0.4.0'}
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@ -11025,10 +10928,6 @@ packages:
engines: {node: '>= 14.6'}
hasBin: true
yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
yargs-parser@20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
@ -11041,10 +10940,6 @@ packages:
resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
engines: {node: '>=10'}
yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
yargs@16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}
@ -14310,8 +14205,6 @@ snapshots:
dependencies:
tslib: 2.8.1
'@socket.io/component-emitter@3.1.2': {}
'@standard-schema/spec@1.1.0': {}
'@standard-schema/utils@0.3.0': {}
@ -14798,10 +14691,6 @@ snapshots:
'@types/ps-tree@1.1.6': {}
'@types/qrcode@1.5.5':
dependencies:
'@types/node': 24.2.1
'@types/react-dom@18.3.7(@types/react@18.3.23)':
dependencies:
'@types/react': 18.3.23
@ -15655,8 +15544,6 @@ snapshots:
camelcase-css@2.0.1: {}
camelcase@5.3.1: {}
camelcase@6.3.0: {}
camelize@1.0.1: {}
@ -15818,12 +15705,6 @@ snapshots:
client-only@0.0.1: {}
cliui@6.0.0:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
cliui@7.0.4:
dependencies:
string-width: 4.2.3
@ -16274,10 +16155,6 @@ snapshots:
dependencies:
ms: 2.1.3
debug@4.3.7:
dependencies:
ms: 2.1.3
debug@4.4.1(supports-color@8.1.1):
dependencies:
ms: 2.1.3
@ -16288,8 +16165,6 @@ snapshots:
dependencies:
ms: 2.1.3
decamelize@1.2.0: {}
decamelize@4.0.0: {}
decimal.js-light@2.5.1: {}
@ -16387,8 +16262,6 @@ snapshots:
diff@5.2.0: {}
dijkstrajs@1.0.3: {}
dingbat-to-unicode@1.0.1: {}
dir-glob@3.0.1:
@ -16537,20 +16410,6 @@ snapshots:
dependencies:
once: 1.4.0
engine.io-client@6.6.3:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.7
engine.io-parser: 5.2.3
ws: 8.17.1
xmlhttprequest-ssl: 2.1.2
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
engine.io-parser@5.2.3: {}
enhanced-resolve@5.18.1:
dependencies:
graceful-fs: 4.2.11
@ -19773,8 +19632,6 @@ snapshots:
exsolve: 1.0.7
pathe: 2.0.3
pngjs@5.0.0: {}
points-on-curve@0.2.0: {}
points-on-path@0.2.1:
@ -20045,12 +19902,6 @@ snapshots:
- supports-color
- utf-8-validate
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
pngjs: 5.0.0
yargs: 15.4.1
qs@6.14.0:
dependencies:
side-channel: 1.1.0
@ -20459,8 +20310,6 @@ snapshots:
require-directory@2.1.1: {}
require-main-filename@2.0.0: {}
resize-observer-polyfill@1.5.1: {}
resolve-from@4.0.0: {}
@ -20708,8 +20557,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
set-blocking@2.0.0: {}
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@ -20873,24 +20720,6 @@ snapshots:
smol-toml@1.3.4: {}
socket.io-client@4.8.1:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.7
engine.io-client: 6.6.3
socket.io-parser: 4.2.4
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
socket.io-parser@4.2.4:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.7
transitivePeerDependencies:
- supports-color
socks-proxy-agent@8.0.5:
dependencies:
agent-base: 7.1.3
@ -22247,8 +22076,6 @@ snapshots:
is-weakmap: 2.0.2
is-weakset: 2.0.4
which-module@2.0.1: {}
which-pm-runs@1.1.0: {}
which-typed-array@1.1.19:
@ -22296,12 +22123,6 @@ snapshots:
workerpool@9.2.0: {}
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
@ -22322,8 +22143,6 @@ snapshots:
wrappy@1.0.2: {}
ws@8.17.1: {}
ws@8.18.2: {}
ws@8.18.3: {}
@ -22341,12 +22160,8 @@ snapshots:
xmlchars@2.2.0: {}
xmlhttprequest-ssl@2.1.2: {}
xtend@4.0.2: {}
y18n@4.0.3: {}
y18n@5.0.8: {}
yallist@3.1.1: {}
@ -22357,11 +22172,6 @@ snapshots:
yaml@2.8.0: {}
yargs-parser@18.1.3:
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
yargs-parser@20.2.9: {}
yargs-parser@21.1.1: {}
@ -22373,20 +22183,6 @@ snapshots:
flat: 5.0.2
is-plain-obj: 2.1.0
yargs@15.4.1:
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
yargs@16.2.0:
dependencies:
cliui: 7.0.4

View file

@ -51,8 +51,6 @@ vi.mock("fs", () => ({
existsSync: vi.fn().mockReturnValue(false),
}))
const mockBridgeOrchestratorDisconnect = vi.fn().mockResolvedValue(undefined)
const mockCloudServiceInstance = {
off: vi.fn(),
on: vi.fn(),
@ -71,9 +69,6 @@ vi.mock("@roo-code/cloud", () => ({
return mockCloudServiceInstance
},
},
BridgeOrchestrator: {
disconnect: mockBridgeOrchestratorDisconnect,
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
}))
@ -181,20 +176,14 @@ vi.mock("../i18n", () => ({
t: vi.fn((key) => key),
}))
// Mock ClineProvider - remoteControlEnabled must call BridgeOrchestrator.disconnect for the test
// Mock ClineProvider
vi.mock("../core/webview/ClineProvider", async () => {
const { BridgeOrchestrator } = await import("@roo-code/cloud")
const mockInstance = {
resolveWebviewView: vi.fn(),
postMessageToWebview: vi.fn(),
postStateToWebview: vi.fn(),
postStateToWebviewWithoutClineMessages: vi.fn(),
getState: vi.fn().mockResolvedValue({}),
remoteControlEnabled: vi.fn().mockImplementation(async (enabled: boolean) => {
if (!enabled) {
await BridgeOrchestrator.disconnect()
}
}),
initializeCloudProfileSyncWhenReady: vi.fn().mockResolvedValue(undefined),
providerSettingsManager: {},
contextProxy: { getGlobalState: vi.fn() },
@ -230,7 +219,6 @@ describe("extension.ts", () => {
beforeEach(() => {
vi.clearAllMocks()
mockBridgeOrchestratorDisconnect.mockClear()
mockContext = {
extensionPath: "/test/path",
@ -274,73 +262,6 @@ describe("extension.ts", () => {
expect(dotenvx.config).toHaveBeenCalledTimes(1)
})
test("authStateChangedHandler calls BridgeOrchestrator.disconnect when logged-out event fires", async () => {
const { CloudService, BridgeOrchestrator } = await import("@roo-code/cloud")
// Capture the auth state changed handler.
vi.mocked(CloudService.createInstance).mockImplementation(async (_context, _logger, handlers) => {
if (handlers?.["auth-state-changed"]) {
authStateChangedHandler = handlers["auth-state-changed"]
}
return {
off: vi.fn(),
on: vi.fn(),
telemetryClient: null,
hasActiveSession: vi.fn().mockReturnValue(false),
authService: null,
} as any
})
// Activate the extension.
const { activate } = await import("../extension")
await activate(mockContext)
// Verify handler was registered.
expect(authStateChangedHandler).toBeDefined()
// Trigger logout.
await authStateChangedHandler!({
state: "logged-out" as AuthState,
previousState: "logged-in" as AuthState,
})
// Verify BridgeOrchestrator.disconnect was called
expect(mockBridgeOrchestratorDisconnect).toHaveBeenCalled()
})
test("authStateChangedHandler does not call BridgeOrchestrator.disconnect for other states", async () => {
const { CloudService } = await import("@roo-code/cloud")
// Capture the auth state changed handler.
vi.mocked(CloudService.createInstance).mockImplementation(async (_context, _logger, handlers) => {
if (handlers?.["auth-state-changed"]) {
authStateChangedHandler = handlers["auth-state-changed"]
}
return {
off: vi.fn(),
on: vi.fn(),
telemetryClient: null,
hasActiveSession: vi.fn().mockReturnValue(false),
authService: null,
} as any
})
// Activate the extension.
const { activate } = await import("../extension")
await activate(mockContext)
// Trigger login.
await authStateChangedHandler!({
state: "logged-in" as AuthState,
previousState: "logged-out" as AuthState,
})
// Verify BridgeOrchestrator.disconnect was NOT called.
expect(mockBridgeOrchestratorDisconnect).not.toHaveBeenCalled()
})
describe("Roo model cache refresh on auth state change (ROO-202)", () => {
beforeEach(() => {
vi.resetModules()

View file

@ -13,7 +13,6 @@ vi.mock("../core/task/Task", () => {
public parentTask?: any
public apiConfiguration: any
public rootTask?: any
public enableBridge?: boolean
constructor(opts: any) {
this.taskId = opts.historyItem?.id ?? `task-${Math.random().toString(36).slice(2, 8)}`
this.parentTask = opts.parentTask
@ -49,7 +48,6 @@ describe("Single-open-task invariant", () => {
enableCheckpoints: true,
checkpointTimeout: 60,
cloudUserInfo: null,
remoteControlEnabled: false,
}),
removeClineFromStack,
addClineToStack,

View file

@ -55,7 +55,7 @@ import {
countEnabledMcpTools,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { CloudService, BridgeOrchestrator } from "@roo-code/cloud"
import { CloudService } from "@roo-code/cloud"
// api
import { ApiHandler, ApiHandlerCreateMessageMetadata, buildApiHandler } from "../../api"
@ -143,7 +143,6 @@ export interface TaskOptions extends CreateTaskOptions {
apiConfiguration: ProviderSettings
enableCheckpoints?: boolean
checkpointTimeout?: number
enableBridge?: boolean
consecutiveMistakeLimit?: number
task?: string
images?: string[]
@ -332,9 +331,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
checkpointService?: RepoPerTaskCheckpointService
checkpointServiceInitializing = false
// Task Bridge
enableBridge: boolean
// Message Queue Service
public readonly messageQueueService: MessageQueueService
private messageQueueStateChangedHandler: (() => void) | undefined
@ -426,7 +422,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
apiConfiguration,
enableCheckpoints = true,
checkpointTimeout = DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
enableBridge = false,
consecutiveMistakeLimit = DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
task,
images,
@ -497,7 +492,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
this.diffViewProvider = new DiffViewProvider(this.cwd, this)
this.enableCheckpoints = enableCheckpoints
this.checkpointTimeout = checkpointTimeout
this.enableBridge = enableBridge
this.parentTask = parentTask
this.taskNumber = taskNumber
@ -1938,16 +1932,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
private async startTask(task?: string, images?: string[]): Promise<void> {
try {
if (this.enableBridge) {
try {
await BridgeOrchestrator.subscribeToTask(this)
} catch (error) {
console.error(
`[Task#startTask] BridgeOrchestrator.subscribeToTask() failed: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
// `conversationHistory` (for API) and `clineMessages` (for webview)
// need to be in sync.
// If the extension process were killed, then on restart the
@ -2012,16 +1996,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
private async resumeTaskFromHistory() {
try {
if (this.enableBridge) {
try {
await BridgeOrchestrator.subscribeToTask(this)
} catch (error) {
console.error(
`[Task#resumeTaskFromHistory] BridgeOrchestrator.subscribeToTask() failed: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
const modifiedClineMessages = await this.getSavedClineMessages()
// Remove any resume messages that may have been added before.
@ -2352,16 +2326,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
console.error("Error removing event listeners:", error)
}
if (this.enableBridge) {
BridgeOrchestrator.getInstance()
?.unsubscribeFromTask(this.taskId)
.catch((error) =>
console.error(
`[Task#dispose] BridgeOrchestrator#unsubscribeFromTask() failed: ${error instanceof Error ? error.message : String(error)}`,
),
)
}
// Release any terminals associated with this task.
try {
// Release any terminals associated with this task.

View file

@ -75,14 +75,10 @@ vi.mock("@roo-code/telemetry", () => ({
},
}))
// Mock @roo-code/cloud to prevent socket.io-client initialization issues
vi.mock("@roo-code/cloud", () => ({
CloudService: {
isEnabled: () => false,
},
BridgeOrchestrator: {
subscribeToTask: vi.fn(),
},
}))
// Mock delay to prevent actual delays

View file

@ -75,14 +75,10 @@ vi.mock("@roo-code/telemetry", () => ({
},
}))
// Mock @roo-code/cloud to prevent socket.io-client initialization issues
vi.mock("@roo-code/cloud", () => ({
CloudService: {
isEnabled: () => false,
},
BridgeOrchestrator: {
subscribeToTask: vi.fn(),
},
}))
// Mock delay to prevent actual delays

View file

@ -676,14 +676,12 @@ describe("useMcpToolTool", () => {
mockProviderRef.deref.mockReturnValue({
getMcpHub: () => ({
callTool: vi.fn().mockResolvedValue(mockToolResult),
getAllServers: vi
.fn()
.mockReturnValue([
{
name: "figma-server",
tools: [{ name: "get_screenshot", description: "Get screenshot" }],
},
]),
getAllServers: vi.fn().mockReturnValue([
{
name: "figma-server",
tools: [{ name: "get_screenshot", description: "Get screenshot" }],
},
]),
}),
postMessageToWebview: vi.fn(),
})
@ -790,14 +788,12 @@ describe("useMcpToolTool", () => {
mockProviderRef.deref.mockReturnValue({
getMcpHub: () => ({
callTool: vi.fn().mockResolvedValue(mockToolResult),
getAllServers: vi
.fn()
.mockReturnValue([
{
name: "figma-server",
tools: [{ name: "get_screenshot", description: "Get screenshot" }],
},
]),
getAllServers: vi.fn().mockReturnValue([
{
name: "figma-server",
tools: [{ name: "get_screenshot", description: "Get screenshot" }],
},
]),
}),
postMessageToWebview: vi.fn(),
})
@ -852,14 +848,12 @@ describe("useMcpToolTool", () => {
mockProviderRef.deref.mockReturnValue({
getMcpHub: () => ({
callTool: vi.fn().mockResolvedValue(mockToolResult),
getAllServers: vi
.fn()
.mockReturnValue([
{
name: "figma-server",
tools: [{ name: "get_screenshots", description: "Get screenshots" }],
},
]),
getAllServers: vi.fn().mockReturnValue([
{
name: "figma-server",
tools: [{ name: "get_screenshots", description: "Get screenshots" }],
},
]),
}),
postMessageToWebview: vi.fn(),
})

View file

@ -49,7 +49,7 @@ import {
} from "@roo-code/types"
import { aggregateTaskCostsRecursive, type AggregatedCosts } from "./aggregateTaskCosts"
import { TelemetryService } from "@roo-code/telemetry"
import { CloudService, BridgeOrchestrator, getRooCodeApiUrl } from "@roo-code/cloud"
import { CloudService, getRooCodeApiUrl } from "@roo-code/cloud"
import { Package } from "../../shared/package"
import { findLast } from "../../shared/array"
@ -1078,7 +1078,6 @@ export class ClineProvider
workspacePath: historyItem.workspace,
onCreated: this.taskCreationCallback,
startTask: options?.startTask ?? true,
enableBridge: BridgeOrchestrator.isEnabled(cloudUserInfo, taskSyncEnabled),
// Preserve the status from the history item to avoid overwriting it when the task saves messages
initialStatus: historyItem.status,
})
@ -2194,11 +2193,9 @@ export class ClineProvider
includeCurrentCost,
maxGitStatusFiles,
taskSyncEnabled,
remoteControlEnabled,
imageGenerationProvider,
openRouterImageApiKey,
openRouterImageGenerationSelectedModel,
featureRoomoteControlEnabled,
lockApiConfigAcrossModes,
} = await this.getState()
@ -2343,11 +2340,9 @@ export class ClineProvider
includeCurrentCost: includeCurrentCost ?? true,
maxGitStatusFiles: maxGitStatusFiles ?? 0,
taskSyncEnabled,
remoteControlEnabled,
imageGenerationProvider,
openRouterImageApiKey,
openRouterImageGenerationSelectedModel,
featureRoomoteControlEnabled,
openAiCodexIsAuthenticated: await (async () => {
try {
const { openAiCodexOAuthManager } = await import("../../integrations/openai-codex/oauth")
@ -2564,32 +2559,9 @@ export class ClineProvider
includeCurrentCost: stateValues.includeCurrentCost ?? true,
maxGitStatusFiles: stateValues.maxGitStatusFiles ?? 0,
taskSyncEnabled,
remoteControlEnabled: (() => {
try {
const cloudSettings = CloudService.instance.getUserSettings()
return cloudSettings?.settings?.extensionBridgeEnabled ?? false
} catch (error) {
console.error(
`[getState] failed to get remote control setting from cloud: ${error instanceof Error ? error.message : String(error)}`,
)
return false
}
})(),
imageGenerationProvider: stateValues.imageGenerationProvider,
openRouterImageApiKey: stateValues.openRouterImageApiKey,
openRouterImageGenerationSelectedModel: stateValues.openRouterImageGenerationSelectedModel,
featureRoomoteControlEnabled: (() => {
try {
const userSettings = CloudService.instance.getUserSettings()
const hasOrganization = cloudUserInfo?.organizationId != null
return hasOrganization || (userSettings?.features?.roomoteControlEnabled ?? false)
} catch (error) {
console.error(
`[getState] failed to get featureRoomoteControlEnabled: ${error instanceof Error ? error.message : String(error)}`,
)
return false
}
})(),
}
}
@ -2786,64 +2758,6 @@ export class ClineProvider
return true
}
public async remoteControlEnabled(enabled: boolean) {
if (!enabled) {
await BridgeOrchestrator.disconnect()
return
}
const userInfo = CloudService.instance.getUserInfo()
if (!userInfo) {
this.log("[ClineProvider#remoteControlEnabled] Failed to get user info, disconnecting")
await BridgeOrchestrator.disconnect()
return
}
const config = await CloudService.instance.cloudAPI?.bridgeConfig().catch(() => undefined)
if (!config) {
this.log("[ClineProvider#remoteControlEnabled] Failed to get bridge config")
return
}
await BridgeOrchestrator.connectOrDisconnect(userInfo, enabled, {
...config,
provider: this,
sessionId: vscode.env.sessionId,
isCloudAgent: CloudService.instance.isCloudAgent,
})
const bridge = BridgeOrchestrator.getInstance()
if (bridge) {
const currentTask = this.getCurrentTask()
if (currentTask && !currentTask.enableBridge) {
try {
currentTask.enableBridge = true
await BridgeOrchestrator.subscribeToTask(currentTask)
} catch (error) {
const message = `[ClineProvider#remoteControlEnabled] BridgeOrchestrator.subscribeToTask() failed: ${error instanceof Error ? error.message : String(error)}`
this.log(message)
console.error(message)
}
}
} else {
for (const task of this.clineStack) {
if (task.enableBridge) {
try {
await BridgeOrchestrator.getInstance()?.unsubscribeFromTask(task.taskId)
} catch (error) {
const message = `[ClineProvider#remoteControlEnabled] BridgeOrchestrator#unsubscribeFromTask() failed: ${error instanceof Error ? error.message : String(error)}`
this.log(message)
console.error(message)
}
}
}
}
}
/**
* Gets the CodeIndexManager for the current active workspace
* @returns CodeIndexManager instance for the current workspace or the default one
@ -2999,15 +2913,8 @@ export class ClineProvider
}
}
const {
apiConfiguration,
organizationAllowList,
enableCheckpoints,
checkpointTimeout,
experiments,
cloudUserInfo,
remoteControlEnabled,
} = await this.getState()
const { apiConfiguration, organizationAllowList, enableCheckpoints, checkpointTimeout, experiments } =
await this.getState()
// Single-open-task invariant: always enforce for user-initiated top-level tasks
if (!parentTask) {
@ -3035,7 +2942,6 @@ export class ClineProvider
parentTask,
taskNumber: this.clineStack.length + 1,
onCreated: this.taskCreationCallback,
enableBridge: BridgeOrchestrator.isEnabled(cloudUserInfo, remoteControlEnabled),
initialTodos: options.initialTodos,
...options,
})

View file

@ -129,9 +129,6 @@ vi.mock("@roo-code/cloud", () => ({
}
},
},
BridgeOrchestrator: {
isEnabled: vi.fn().mockReturnValue(false),
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
}))

View file

@ -78,9 +78,6 @@ vi.mock("@roo-code/cloud", () => ({
isAuthenticated: vi.fn().mockReturnValue(false),
},
},
BridgeOrchestrator: {
isEnabled: vi.fn().mockReturnValue(false),
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://api.roo-code.com"),
}))

View file

@ -110,9 +110,6 @@ vi.mock("@roo-code/cloud", () => ({
}
},
},
BridgeOrchestrator: {
isEnabled: vi.fn().mockReturnValue(false),
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
}))

View file

@ -301,9 +301,6 @@ vi.mock("@roo-code/cloud", () => ({
}
},
},
BridgeOrchestrator: {
isEnabled: vi.fn().mockReturnValue(false),
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
}))
@ -554,9 +551,7 @@ describe("ClineProvider", () => {
diagnosticsEnabled: true,
openRouterImageApiKey: undefined,
openRouterImageGenerationSelectedModel: undefined,
remoteControlEnabled: false,
taskSyncEnabled: false,
featureRoomoteControlEnabled: false,
checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
}

View file

@ -112,9 +112,6 @@ vi.mock("@roo-code/cloud", () => ({
}
},
},
BridgeOrchestrator: {
isEnabled: vi.fn().mockReturnValue(false),
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
}))

View file

@ -114,9 +114,6 @@ vi.mock("@roo-code/cloud", () => ({
}
},
},
BridgeOrchestrator: {
isEnabled: vi.fn().mockReturnValue(false),
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
}))

View file

@ -229,9 +229,6 @@ vi.mock("@roo-code/cloud", () => ({
}
},
},
BridgeOrchestrator: {
isEnabled: vi.fn().mockReturnValue(false),
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
}))

View file

@ -1352,25 +1352,10 @@ export const webviewMessageHandler = async (
}
break
}
case "remoteControlEnabled":
try {
await CloudService.instance.updateUserSettings({ extensionBridgeEnabled: message.bool ?? false })
} catch (error) {
provider.log(
`CloudService#updateUserSettings failed: ${error instanceof Error ? error.message : String(error)}`,
)
}
break
case "taskSyncEnabled":
const enabled = message.bool ?? false
const updatedSettings: Partial<UserSettingsConfig> = { taskSyncEnabled: enabled }
// If disabling task sync, also disable remote control.
if (!enabled) {
updatedSettings.extensionBridgeEnabled = false
}
try {
await CloudService.instance.updateUserSettings(updatedSettings)
} catch (error) {

View file

@ -18,7 +18,7 @@ if (fs.existsSync(envPath)) {
}
import type { CloudUserInfo, AuthState } from "@roo-code/types"
import { CloudService, BridgeOrchestrator } from "@roo-code/cloud"
import { CloudService } from "@roo-code/cloud"
import { TelemetryService, PostHogTelemetryClient } from "@roo-code/telemetry"
import { customToolRegistry } from "@roo-code/core"
@ -200,16 +200,6 @@ export async function activate(context: vscode.ExtensionContext) {
authStateChangedHandler = async (data: { state: AuthState; previousState: AuthState }) => {
postStateListener()
if (data.state === "logged-out") {
try {
await provider.remoteControlEnabled(false)
} catch (error) {
cloudLogger(
`[authStateChangedHandler] remoteControlEnabled(false) failed: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
// Handle Roo models cache based on auth state (ROO-202)
const handleRooModelsCache = async () => {
try {
@ -265,36 +255,11 @@ export async function activate(context: vscode.ExtensionContext) {
}
settingsUpdatedHandler = async () => {
const userInfo = CloudService.instance.getUserInfo()
if (userInfo && CloudService.instance.cloudAPI) {
try {
provider.remoteControlEnabled(CloudService.instance.isTaskSyncEnabled())
} catch (error) {
cloudLogger(
`[settingsUpdatedHandler] remoteControlEnabled failed: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
postStateListener()
}
userInfoHandler = async ({ userInfo }: { userInfo: CloudUserInfo }) => {
postStateListener()
if (!CloudService.instance.cloudAPI) {
cloudLogger("[userInfoHandler] CloudAPI is not initialized")
return
}
try {
provider.remoteControlEnabled(CloudService.instance.isTaskSyncEnabled())
} catch (error) {
cloudLogger(
`[userInfoHandler] remoteControlEnabled failed: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
cloudService = await CloudService.createInstance(context, cloudLogger, {
@ -481,12 +446,6 @@ export async function deactivate() {
}
}
const bridge = BridgeOrchestrator.getInstance()
if (bridge) {
await bridge.disconnect()
}
await McpServerManager.cleanup(extensionContext)
TelemetryService.instance.shutdown()
TerminalRegistry.cleanup()

View file

@ -521,7 +521,6 @@
"serialize-error": "^12.0.0",
"shell-quote": "^1.8.2",
"simple-git": "^3.27.0",
"socket.io-client": "^4.8.1",
"sound-play": "^1.1.0",
"stream-json": "^1.8.0",
"string-similarity": "^4.0.4",

View file

@ -942,9 +942,7 @@ describe("worktree path comparison", () => {
// Second init with stubbed worktree returning a trailing newline
const service2 = new RepoPerTaskCheckpointService("trim-test-2", shadowDir, workspaceDir, () => {})
vitest
.spyOn(service2 as any, "getShadowGitConfigWorktree")
.mockResolvedValue(workspaceDir + "\n")
vitest.spyOn(service2 as any, "getShadowGitConfigWorktree").mockResolvedValue(workspaceDir + "\n")
await service2.initShadowGit()
} finally {
@ -983,9 +981,7 @@ describe("worktree path comparison", () => {
// Second init should throw because core.worktree is missing
const service2 = new RepoPerTaskCheckpointService("missing-test-2", shadowDir, workspaceDir, () => {})
await expect(service2.initShadowGit()).rejects.toThrowError(
/core\.worktree to be set/,
)
await expect(service2.initShadowGit()).rejects.toThrowError(/core\.worktree to be set/)
} finally {
vitest.restoreAllMocks()
await fs.rm(shadowDir, { recursive: true, force: true })

View file

@ -33,7 +33,6 @@
"@roo-code/types": "workspace:^",
"@tailwindcss/vite": "^4.0.0",
"@tanstack/react-query": "^5.68.0",
"@types/qrcode": "^1.5.5",
"@vscode/codicons": "^0.0.36",
"@vscode/webview-ui-toolkit": "^1.4.0",
"axios": "^1.12.0",
@ -55,7 +54,6 @@
"mermaid": "^11.4.1",
"posthog-js": "^1.227.2",
"pretty-bytes": "^7.0.0",
"qrcode": "^1.5.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-compiler-runtime": "^1.0.0",

View file

@ -1,126 +0,0 @@
import { useState, useEffect, useCallback } from "react"
import { useTranslation } from "react-i18next"
import { Copy, Check, CloudUploadIcon } from "lucide-react"
import QRCode from "qrcode"
import type { HistoryItem } from "@roo-code/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useCopyToClipboard } from "@/utils/clipboard"
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input } from "@/components/ui"
import { vscode } from "@/utils/vscode"
import { LucideIconButton } from "./LucideIconButton"
interface CloudTaskButtonProps {
item?: HistoryItem
disabled?: boolean
}
export const CloudTaskButton = ({ item, disabled = false }: CloudTaskButtonProps) => {
const [dialogOpen, setDialogOpen] = useState(false)
const { t } = useTranslation()
const { cloudUserInfo, cloudApiUrl } = useExtensionState()
const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard()
const [canvasElement, setCanvasElement] = useState<HTMLCanvasElement | null>(null)
// Generate the cloud URL for the task
const cloudTaskUrl = item?.id ? `${cloudApiUrl}/task/${item.id}` : ""
const generateQRCode = useCallback(
(canvas: HTMLCanvasElement, context: string) => {
if (!cloudTaskUrl) {
// This will run again later when ready
return
}
QRCode.toCanvas(
canvas,
cloudTaskUrl,
{
width: 140,
margin: 0,
color: {
dark: "#000000",
light: "#FFFFFF",
},
},
(error: Error | null | undefined) => {
if (error) {
console.error(`Error generating QR code (${context}):`, error)
}
},
)
},
[cloudTaskUrl],
)
// Callback ref to capture canvas element when it mounts
const canvasRef = useCallback(
(node: HTMLCanvasElement | null) => {
if (node) {
setCanvasElement(node)
// Try to generate QR code immediately when canvas is available
if (dialogOpen) {
generateQRCode(node, "on mount")
}
} else {
setCanvasElement(null)
}
},
[dialogOpen, generateQRCode],
)
// Also generate QR code when dialog opens after canvas is available
useEffect(() => {
if (dialogOpen && canvasElement) {
generateQRCode(canvasElement, "in useEffect")
}
}, [dialogOpen, canvasElement, generateQRCode])
if (!cloudUserInfo?.extensionBridgeEnabled || !item?.id) {
return null
}
return (
<>
<LucideIconButton
icon={CloudUploadIcon}
disabled={disabled}
data-testid="cloud-task-button"
title={t("chat:task.openInCloud")}
onClick={() => setDialogOpen(true)}></LucideIconButton>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className="max-w-100">
<DialogHeader>
<DialogTitle>{t("chat:task.openInCloud")}</DialogTitle>
</DialogHeader>
<div className="flex flex-col space-y-4">
<p className="text-center md:text-left max-w-80">{t("chat:task.openInCloudIntro")}</p>
<div className="flex justify-center md:justify-start">
<div
className="w-[170px] h-[170px] bg-white rounded-lg border-border cursor-pointer hover:opacity-70 transition-opacity"
onClick={() => vscode.postMessage({ type: "openExternal", url: cloudTaskUrl })}
title={t("chat:task.openInCloud")}>
<canvas ref={canvasRef} className="m-[15px]" />
</div>
</div>
<div className="flex items-center space-x-2">
<Input value={cloudTaskUrl} disabled className="flex-1 font-mono text-sm" readOnly />
<Button
variant="outline"
size="icon"
onClick={(e) => copyWithFeedback(cloudTaskUrl, e)}
className="h-9 w-9">
{showCopyFeedback ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</>
)
}

View file

@ -9,7 +9,6 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
import { ShareButton } from "./ShareButton"
import { CloudTaskButton } from "./CloudTaskButton"
import { CopyIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon } from "lucide-react"
import { LucideIconButton } from "./LucideIconButton"
@ -64,7 +63,6 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
</>
)}
<ShareButton item={item} disabled={false} />
<CloudTaskButton item={item} disabled={buttonsDisabled} />
{debug && item?.id && (
<>
<LucideIconButton

View file

@ -1,234 +0,0 @@
import { useTranslation } from "react-i18next"
import { render, screen, fireEvent, waitFor } from "@/utils/test-utils"
import { CloudTaskButton } from "../CloudTaskButton"
// Mock the qrcode library
vi.mock("qrcode", () => ({
default: {
toCanvas: vi.fn((_canvas, _text, _options, callback) => {
// Simulate successful QR code generation
if (callback) {
callback(null)
}
}),
},
}))
// Mock react-i18next
vi.mock("react-i18next")
// Mock the cloud config
vi.mock("@roo-code/cloud/src/config", () => ({
getRooCodeApiUrl: vi.fn(() => "https://app.roocode.com"),
}))
// Mock the extension state context
vi.mock("@/context/ExtensionStateContext", () => ({
ExtensionStateContextProvider: ({ children }: { children: React.ReactNode }) => children,
useExtensionState: vi.fn(),
}))
// Mock clipboard utility
vi.mock("@/utils/clipboard", () => ({
useCopyToClipboard: () => ({
copyWithFeedback: vi.fn(),
showCopyFeedback: false,
}),
}))
const mockUseTranslation = vi.mocked(useTranslation)
const { useExtensionState } = await import("@/context/ExtensionStateContext")
const mockUseExtensionState = vi.mocked(useExtensionState)
describe("CloudTaskButton", () => {
const mockT = vi.fn((key: string) => key)
const mockItem = {
id: "test-task-id",
number: 1,
ts: Date.now(),
task: "Test Task",
tokensIn: 100,
tokensOut: 50,
totalCost: 0.01,
}
beforeEach(() => {
vi.clearAllMocks()
mockUseTranslation.mockReturnValue({
t: mockT,
i18n: {} as any,
ready: true,
} as any)
// Default extension state with bridge enabled
mockUseExtensionState.mockReturnValue({
cloudUserInfo: {
id: "test-user",
email: "test@example.com",
extensionBridgeEnabled: true,
},
cloudApiUrl: "https://app.roocode.com",
} as any)
})
test("renders cloud task button when extension bridge is enabled", () => {
render(<CloudTaskButton item={mockItem} />)
const button = screen.getByTestId("cloud-task-button")
expect(button).toBeInTheDocument()
expect(button).toHaveAttribute("aria-label", "chat:task.openInCloud")
})
test("does not render when extension bridge is disabled", () => {
mockUseExtensionState.mockReturnValue({
cloudUserInfo: {
id: "test-user",
email: "test@example.com",
extensionBridgeEnabled: false,
},
cloudApiUrl: "https://app.roocode.com",
} as any)
render(<CloudTaskButton item={mockItem} />)
expect(screen.queryByTestId("cloud-task-button")).not.toBeInTheDocument()
})
test("does not render when cloudUserInfo is null", () => {
mockUseExtensionState.mockReturnValue({
cloudUserInfo: null,
cloudApiUrl: "https://app.roocode.com",
} as any)
render(<CloudTaskButton item={mockItem} />)
expect(screen.queryByTestId("cloud-task-button")).not.toBeInTheDocument()
})
test("does not render when item has no id", () => {
const itemWithoutId = { ...mockItem, id: undefined }
render(<CloudTaskButton item={itemWithoutId as any} />)
expect(screen.queryByTestId("cloud-task-button")).not.toBeInTheDocument()
})
test("opens dialog when button is clicked", async () => {
render(<CloudTaskButton item={mockItem} />)
const button = screen.getByTestId("cloud-task-button")
fireEvent.click(button)
await waitFor(() => {
expect(screen.getByText("chat:task.openInCloud")).toBeInTheDocument()
})
})
test("displays correct cloud URL in dialog", async () => {
render(<CloudTaskButton item={mockItem} />)
const button = screen.getByTestId("cloud-task-button")
fireEvent.click(button)
await waitFor(() => {
const input = screen.getByDisplayValue("https://app.roocode.com/task/test-task-id")
expect(input).toBeInTheDocument()
expect(input).toBeDisabled()
})
})
test("displays intro text in dialog", async () => {
render(<CloudTaskButton item={mockItem} />)
const button = screen.getByTestId("cloud-task-button")
fireEvent.click(button)
await waitFor(() => {
expect(screen.getByText("chat:task.openInCloudIntro")).toBeInTheDocument()
})
})
// Note: QR code generation is tested implicitly through the canvas rendering test below
test("QR code canvas is rendered", async () => {
render(<CloudTaskButton item={mockItem} />)
const button = screen.getByTestId("cloud-task-button")
fireEvent.click(button)
await waitFor(() => {
// Canvas element doesn't have a specific aria label, find it directly
const canvas = document.querySelector("canvas")
expect(canvas).toBeInTheDocument()
expect(canvas?.tagName).toBe("CANVAS")
})
})
// Note: Error handling for QR code generation is non-critical as per PR feedback
test("button is disabled when disabled prop is true", () => {
render(<CloudTaskButton item={mockItem} disabled={true} />)
const button = screen.getByTestId("cloud-task-button")
expect(button).toBeDisabled()
})
test("button is enabled when disabled prop is false", () => {
render(<CloudTaskButton item={mockItem} disabled={false} />)
const button = screen.getByTestId("cloud-task-button")
expect(button).not.toBeDisabled()
})
test("dialog can be closed", async () => {
render(<CloudTaskButton item={mockItem} />)
// Open dialog
const button = screen.getByTestId("cloud-task-button")
fireEvent.click(button)
await waitFor(() => {
expect(screen.getByText("chat:task.openInCloud")).toBeInTheDocument()
})
// Close dialog by clicking the X button (assuming it exists in Dialog component)
const closeButton = screen.getByRole("button", { name: /close/i })
fireEvent.click(closeButton)
await waitFor(() => {
expect(screen.queryByText("chat:task.openInCloud")).not.toBeInTheDocument()
})
})
test("copy button exists in dialog", async () => {
render(<CloudTaskButton item={mockItem} />)
const button = screen.getByTestId("cloud-task-button")
fireEvent.click(button)
await waitFor(() => {
// Look for the copy button (it should have a Copy icon)
const copyButtons = screen.getAllByRole("button")
const copyButton = copyButtons.find(
(btn) => btn.querySelector('[class*="lucide"]') || btn.textContent?.includes("Copy"),
)
expect(copyButton).toBeInTheDocument()
})
})
test("uses correct URL from getRooCodeApiUrl", async () => {
// Mock getRooCodeApiUrl to return a custom URL
vi.doMock("@roo-code/cloud/src/config", () => ({
getRooCodeApiUrl: vi.fn(() => "https://custom.roocode.com"),
}))
// Clear module cache and re-import to get the mocked version
vi.resetModules()
// Since we can't easily test the dynamic import, let's skip this specific test
// The functionality is already covered by the main component using getRooCodeApiUrl
expect(true).toBe(true)
})
})

View file

@ -9,7 +9,7 @@ import { vscode } from "@src/utils/vscode"
import { telemetryClient } from "@src/utils/TelemetryClient"
import { ToggleSwitch } from "@/components/ui/toggle-switch"
import { renderCloudBenefitsContent } from "./CloudUpsellDialog"
import { ArrowRight, CircleAlert, Info, Lock, TriangleAlert } from "lucide-react"
import { ArrowRight, Info, Lock, TriangleAlert } from "lucide-react"
import { cn } from "@/lib/utils"
import { Tab, TabContent } from "../common/Tab"
import { Button } from "@/components/ui/button"
@ -28,13 +28,7 @@ type CloudViewProps = {
export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, organizations = [] }: CloudViewProps) => {
const { t } = useAppTranslation()
const {
remoteControlEnabled,
setRemoteControlEnabled,
taskSyncEnabled,
setTaskSyncEnabled,
featureRoomoteControlEnabled,
} = useExtensionState()
const { taskSyncEnabled, setTaskSyncEnabled } = useExtensionState()
const wasAuthenticatedRef = useRef(false)
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
const manualUrlInputRef = useRef<HTMLInputElement | null>(null)
@ -144,12 +138,6 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, organization
}
}
const handleRemoteControlToggle = () => {
const newValue = !remoteControlEnabled
setRemoteControlEnabled(newValue)
vscode.postMessage({ type: "remoteControlEnabled", bool: newValue })
}
const handleTaskSyncToggle = () => {
const newValue = !taskSyncEnabled
setTaskSyncEnabled(newValue)
@ -219,34 +207,6 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, organization
<div className="text-vscode-descriptionForeground text-sm mt-1 ml-8">
{t("cloud:taskSyncDescription")}
</div>
{/* Remote Control Toggle - Only shown when both extensionBridgeEnabled and featureRoomoteControlEnabled are true */}
{userInfo?.extensionBridgeEnabled && featureRoomoteControlEnabled && (
<>
<div className="flex items-center gap-3 mt-4 mb-2">
<ToggleSwitch
checked={remoteControlEnabled}
onChange={handleRemoteControlToggle}
size="medium"
aria-label={t("cloud:remoteControl")}
data-testid="remote-control-toggle"
disabled={!taskSyncEnabled}
/>
<span className="font-medium text-vscode-foreground">
{t("cloud:remoteControl")}
</span>
</div>
<div className="text-vscode-descriptionForeground text-sm mt-1 mb-2 ml-8">
{t("cloud:remoteControlDescription")}
{!taskSyncEnabled && (
<div className="text-vscode-editorWarning-foreground mt-2">
<CircleAlert className="inline size-3 mr-1 mb-0.5 text-vscode-editorWarning-foreground" />
{t("cloud:remoteControlRequiresTaskSync")}
</div>
)}
</div>
</>
)}
</div>
<div className="text-vscode-descriptionForeground text-sm mt-4 mb-8 pl-4">

View file

@ -23,10 +23,6 @@ vi.mock("@src/i18n/TranslationContext", () => ({
"cloud:taskSync": "Task sync",
"cloud:taskSyncDescription": "Sync your tasks for viewing and sharing on Roo Code Cloud",
"cloud:taskSyncManagedByOrganization": "Task sync is managed by your organization",
"cloud:remoteControl": "Roomote Control",
"cloud:remoteControlDescription":
"Enable following and interacting with tasks in this workspace with Roo Code Cloud",
"cloud:remoteControlRequiresTaskSync": "Task sync must be enabled to use Roomote Control",
"cloud:usageMetricsAlwaysReported": "Model usage info is always reported when logged in",
"cloud:profilePicture": "Profile picture",
"cloud:cloudUrlPillLabel": "Roo Code Cloud URL: ",
@ -52,12 +48,8 @@ vi.mock("@src/utils/TelemetryClient", () => ({
// Mock the extension state context
const mockExtensionState = {
remoteControlEnabled: false,
setRemoteControlEnabled: vi.fn(),
taskSyncEnabled: true,
setTaskSyncEnabled: vi.fn(),
featureRoomoteControlEnabled: true, // Default to true for tests
setFeatureRoomoteControlEnabled: vi.fn(),
}
vi.mock("@src/context/ExtensionStateContext", () => ({
@ -116,82 +108,6 @@ describe("CloudView", () => {
expect(screen.getByText("test@example.com")).toBeInTheDocument()
})
it("should display remote control toggle when user has extension bridge enabled and roomote control enabled", () => {
const mockUserInfo = {
name: "Test User",
email: "test@example.com",
extensionBridgeEnabled: true,
}
render(<CloudView userInfo={mockUserInfo} isAuthenticated={true} cloudApiUrl="https://app.roocode.com" />)
// Check that the remote control toggle is displayed
expect(screen.getByTestId("remote-control-toggle")).toBeInTheDocument()
expect(screen.getByText("Roomote Control")).toBeInTheDocument()
expect(
screen.getByText("Enable following and interacting with tasks in this workspace with Roo Code Cloud"),
).toBeInTheDocument()
})
it("should not display remote control toggle when user does not have extension bridge enabled", () => {
const mockUserInfo = {
name: "Test User",
email: "test@example.com",
extensionBridgeEnabled: false,
}
render(<CloudView userInfo={mockUserInfo} isAuthenticated={true} cloudApiUrl="https://app.roocode.com" />)
// Check that the remote control toggle is NOT displayed
expect(screen.queryByTestId("remote-control-toggle")).not.toBeInTheDocument()
expect(screen.queryByText("Roomote Control")).not.toBeInTheDocument()
})
it("should not display remote control toggle when roomote control is disabled", () => {
// Temporarily override the mock for this specific test
const originalFeatureRoomoteControlEnabled = mockExtensionState.featureRoomoteControlEnabled
mockExtensionState.featureRoomoteControlEnabled = false
const mockUserInfo = {
name: "Test User",
email: "test@example.com",
extensionBridgeEnabled: true, // Bridge enabled but roomote control disabled
}
render(<CloudView userInfo={mockUserInfo} isAuthenticated={true} cloudApiUrl="https://app.roocode.com" />)
// Check that the remote control toggle is NOT displayed
expect(screen.queryByTestId("remote-control-toggle")).not.toBeInTheDocument()
expect(screen.queryByText("Roomote Control")).not.toBeInTheDocument()
// Restore the original value
mockExtensionState.featureRoomoteControlEnabled = originalFeatureRoomoteControlEnabled
})
it("should display remote control toggle for organization users (simulating backend logic)", () => {
// This test simulates what the ClineProvider would do:
// Organization users are treated as having featureRoomoteControlEnabled true
const originalFeatureRoomoteControlEnabled = mockExtensionState.featureRoomoteControlEnabled
mockExtensionState.featureRoomoteControlEnabled = true // Simulating ClineProvider logic for org users
const mockUserInfo = {
name: "Test User",
email: "test@example.com",
organizationId: "org-123", // User is in an organization
extensionBridgeEnabled: true,
}
render(<CloudView userInfo={mockUserInfo} isAuthenticated={true} cloudApiUrl="https://app.roocode.com" />)
// Check that the remote control toggle IS displayed for organization users
// (The ClineProvider would set featureRoomoteControlEnabled to true for org users)
expect(screen.getByTestId("remote-control-toggle")).toBeInTheDocument()
expect(screen.getByText("Roomote Control")).toBeInTheDocument()
// Restore the original value
mockExtensionState.featureRoomoteControlEnabled = originalFeatureRoomoteControlEnabled
})
it("should not display cloud URL pill when pointing to production", () => {
const mockUserInfo = {
name: "Test User",

View file

@ -108,7 +108,7 @@ export function MarketplaceView({ stateManager, onDone, targetTab }: Marketplace
onClick={() => onDone?.()}
aria-label={t("settings:back")}>
<ArrowLeft />
<span className="sr-only">{t("settings:back")}</span>
<span className="sr-only">{t("settings:back")}</span>
</Button>
<h3 className="font-bold m-0">{t("marketplace:title")}</h3>
</div>

View file

@ -94,12 +94,8 @@ export interface ExtensionStateContextType extends ExtensionState {
setTerminalOutputPreviewSize: (value: "small" | "medium" | "large") => void
mcpEnabled: boolean
setMcpEnabled: (value: boolean) => void
remoteControlEnabled: boolean
setRemoteControlEnabled: (value: boolean) => void
taskSyncEnabled: boolean
setTaskSyncEnabled: (value: boolean) => void
featureRoomoteControlEnabled: boolean
setFeatureRoomoteControlEnabled: (value: boolean) => void
setCurrentApiConfigName: (value: string) => void
setListApiConfigMeta: (value: ProviderSettingsEntry[]) => void
mode: Mode
@ -212,9 +208,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
writeDelayMs: 1000,
terminalShellIntegrationTimeout: 4000,
mcpEnabled: true,
remoteControlEnabled: false,
taskSyncEnabled: false,
featureRoomoteControlEnabled: false,
currentApiConfigName: "default",
listApiConfigMeta: [],
mode: defaultModeSlug,
@ -514,9 +508,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
profileThresholds: state.profileThresholds ?? {},
alwaysAllowFollowupQuestions,
followupAutoApproveTimeoutMs,
remoteControlEnabled: state.remoteControlEnabled ?? false,
taskSyncEnabled: state.taskSyncEnabled,
featureRoomoteControlEnabled: state.featureRoomoteControlEnabled ?? false,
setExperimentEnabled: (id, enabled) =>
setState((prevState) => ({ ...prevState, experiments: { ...prevState.experiments, [id]: enabled } })),
setApiConfiguration,
@ -554,10 +546,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setState((prevState) => ({ ...prevState, terminalShellIntegrationDisabled: value })),
setTerminalZdotdir: (value) => setState((prevState) => ({ ...prevState, terminalZdotdir: value })),
setMcpEnabled: (value) => setState((prevState) => ({ ...prevState, mcpEnabled: value })),
setRemoteControlEnabled: (value) => setState((prevState) => ({ ...prevState, remoteControlEnabled: value })),
setTaskSyncEnabled: (value) => setState((prevState) => ({ ...prevState, taskSyncEnabled: value }) as any),
setFeatureRoomoteControlEnabled: (value) =>
setState((prevState) => ({ ...prevState, featureRoomoteControlEnabled: value })),
setCurrentApiConfigName: (value) => setState((prevState) => ({ ...prevState, currentApiConfigName: value })),
setListApiConfigMeta,
setMode: (value: Mode) => setState((prevState) => ({ ...prevState, mode: value })),

View file

@ -214,9 +214,7 @@ describe("mergeExtensionState", () => {
hasOpenedModeSelector: false, // Add the new required property
maxImageFileSize: 5,
maxTotalImageSize: 20,
remoteControlEnabled: false,
taskSyncEnabled: false,
featureRoomoteControlEnabled: false,
checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, // Add the checkpoint timeout property
maxReadFileLine: -1,
}
@ -285,9 +283,7 @@ describe("mergeExtensionState", () => {
hasOpenedModeSelector: false,
maxImageFileSize: 5,
maxTotalImageSize: 20,
remoteControlEnabled: false,
taskSyncEnabled: false,
featureRoomoteControlEnabled: false,
checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
maxReadFileLine: -1,
}

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Compartició deshabilitada per l'organització",
"shareSuccessOrganization": "Enllaç d'organització copiat al porta-retalls",
"shareSuccessPublic": "Enllaç públic copiat al porta-retalls",
"openInCloud": "Obrir tasca a Roo Code Cloud",
"openInCloudIntro": "Continua monitoritzant o interactuant amb Roo des de qualsevol lloc. Escaneja, fes clic o copia per obrir.",
"openApiHistory": "Obrir historial d'API",
"openUiHistory": "Obrir historial d'UI",
"backToParentTask": "Tasca principal"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Visita Roo Code Cloud",
"taskSync": "Sincronització de tasques",
"taskSyncDescription": "Sincronitza les teves tasques per veure-les i compartir-les a Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Permet controlar tasques des de Roo Code Cloud",
"remoteControlRequiresTaskSync": "La sincronització de tasques ha d'estar habilitada per utilitzar Roomote Control",
"taskSyncManagedByOrganization": "La sincronització de tasques la gestiona la teva organització",
"usageMetricsAlwaysReported": "La informació d'ús del model sempre es reporta quan s'ha iniciat sessió",
"cloudUrlPillLabel": "URL de Roo Code Cloud",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Freigabe von der Organisation deaktiviert",
"shareSuccessOrganization": "Organisationslink in die Zwischenablage kopiert",
"shareSuccessPublic": "Öffentlicher Link in die Zwischenablage kopiert",
"openInCloud": "Aufgabe in Roo Code Cloud öffnen",
"openInCloudIntro": "Überwache oder interagiere mit Roo von überall aus. Scanne, klicke oder kopiere zum Öffnen.",
"openApiHistory": "API-Verlauf öffnen",
"openUiHistory": "UI-Verlauf öffnen",
"backToParentTask": "Übergeordnete Aufgabe"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Roo Code Cloud besuchen",
"taskSync": "Aufgabensynchronisierung",
"taskSyncDescription": "Synchronisiere deine Aufgaben zum Anzeigen und Teilen in Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Ermöglicht die Steuerung von Aufgaben über Roo Code Cloud",
"remoteControlRequiresTaskSync": "Die Aufgabensynchronisierung muss aktiviert sein, um Roomote Control zu verwenden",
"taskSyncManagedByOrganization": "Die Aufgabensynchronisierung wird von deiner Organisation verwaltet",
"usageMetricsAlwaysReported": "Modellnutzungsinformationen werden bei Anmeldung immer gemeldet",
"authWaiting": "Warte auf Abschluss der Authentifizierung...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Sharing disabled by organization",
"shareSuccessOrganization": "Organization link copied to clipboard",
"shareSuccessPublic": "Public link copied to clipboard",
"openInCloud": "Open task in Roo Code Cloud",
"openInCloudIntro": "Keep monitoring or interacting with Roo from anywhere. Scan, click or copy to open.",
"openApiHistory": "Open API History",
"openUiHistory": "Open UI History",
"backToParentTask": "Parent task"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Visit Roo Code Cloud",
"taskSync": "Task sync",
"taskSyncDescription": "Sync your tasks for viewing and sharing on Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Allow controlling tasks from Roo Code Cloud",
"remoteControlRequiresTaskSync": "Task sync must be enabled to use Roomote Control",
"taskSyncManagedByOrganization": "Task sync is managed by your organization",
"usageMetricsAlwaysReported": "Model usage info is always reported when logged in",
"cloudUrlPillLabel": "Roo Code Cloud URL",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Compartir deshabilitado por la organización",
"shareSuccessOrganization": "Enlace de organización copiado al portapapeles",
"shareSuccessPublic": "Enlace público copiado al portapapeles",
"openInCloud": "Abrir tarea en Roo Code Cloud",
"openInCloudIntro": "Continúa monitoreando o interactuando con Roo desde cualquier lugar. Escanea, haz clic o copia para abrir.",
"openApiHistory": "Abrir historial de API",
"openUiHistory": "Abrir historial de UI",
"backToParentTask": "Tarea principal"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Visitar Roo Code Cloud",
"taskSync": "Sincronización de tareas",
"taskSyncDescription": "Sincroniza tus tareas para verlas y compartirlas en Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Permite controlar tareas desde Roo Code Cloud",
"remoteControlRequiresTaskSync": "La sincronización de tareas debe estar habilitada para usar Roomote Control",
"taskSyncManagedByOrganization": "La sincronización de tareas es gestionada por tu organización",
"usageMetricsAlwaysReported": "La información de uso del modelo siempre se reporta cuando se ha iniciado sesión",
"authWaiting": "Esperando que se complete la autenticación...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Partage désactivé par l'organisation",
"shareSuccessOrganization": "Lien d'organisation copié dans le presse-papiers",
"shareSuccessPublic": "Lien public copié dans le presse-papiers",
"openInCloud": "Ouvrir la tâche dans Roo Code Cloud",
"openInCloudIntro": "Continue à surveiller ou interagir avec Roo depuis n'importe où. Scanne, clique ou copie pour ouvrir.",
"openApiHistory": "Ouvrir l'historique de l'API",
"openUiHistory": "Ouvrir l'historique de l'UI",
"backToParentTask": "Tâche parente"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Visiter Roo Code Cloud",
"taskSync": "Synchronisation des tâches",
"taskSyncDescription": "Synchronisez vos tâches pour les visualiser et les partager sur Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Permet de contrôler les tâches depuis Roo Code Cloud",
"remoteControlRequiresTaskSync": "La synchronisation des tâches doit être activée pour utiliser Roomote Control",
"taskSyncManagedByOrganization": "La synchronisation des tâches est gérée par votre organisation",
"usageMetricsAlwaysReported": "Les informations d'utilisation du modèle sont toujours signalées lors de la connexion",
"authWaiting": "En attente de la fin de l'authentification...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "संगठन द्वारा साझाकरण अक्षम किया गया",
"shareSuccessOrganization": "संगठन लिंक क्लिपबोर्ड में कॉपी किया गया",
"shareSuccessPublic": "सार्वजनिक लिंक क्लिपबोर्ड में कॉपी किया गया",
"openInCloud": "Roo Code Cloud में कार्य खोलें",
"openInCloudIntro": "कहीं से भी Roo की निगरानी या इंटरैक्ट करना जारी रखें। खोलने के लिए स्कैन करें, क्लिक करें या कॉपी करें।",
"openApiHistory": "API इतिहास खोलें",
"openUiHistory": "UI इतिहास खोलें",
"backToParentTask": "मूल कार्य"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Roo Code Cloud पर जाएं",
"taskSync": "कार्य सिंक",
"taskSyncDescription": "Roo Code Cloud पर देखने और साझा करने के लिए अपने कार्यों को सिंक करें",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Roo Code Cloud से कार्यों को नियंत्रित करने की अनुमति दें",
"remoteControlRequiresTaskSync": "Roomote Control का उपयोग करने के लिए कार्य सिंक सक्षम होना चाहिए",
"taskSyncManagedByOrganization": "कार्य सिंक आपके संगठन द्वारा प्रबंधित किया जाता है",
"usageMetricsAlwaysReported": "लॉग इन होने पर मॉडल उपयोग जानकारी हमेशा रिपोर्ट की जाती है",
"authWaiting": "प्रमाणीकरण पूरा होने की प्रतीक्षा कर रहे हैं...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Berbagi dinonaktifkan oleh organisasi",
"shareSuccessOrganization": "Tautan organisasi disalin ke clipboard",
"shareSuccessPublic": "Tautan publik disalin ke clipboard",
"openInCloud": "Buka tugas di Roo Code Cloud",
"openInCloudIntro": "Terus pantau atau berinteraksi dengan Roo dari mana saja. Pindai, klik atau salin untuk membuka.",
"openApiHistory": "Buka Riwayat API",
"openUiHistory": "Buka Riwayat UI",
"backToParentTask": "Tugas Induk"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Kunjungi Roo Code Cloud",
"taskSync": "Sinkronisasi tugas",
"taskSyncDescription": "Sinkronkan tugas Anda untuk melihat dan berbagi di Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Izinkan mengontrol tugas dari Roo Code Cloud",
"remoteControlRequiresTaskSync": "Sinkronisasi tugas harus diaktifkan untuk menggunakan Roomote Control",
"taskSyncManagedByOrganization": "Sinkronisasi tugas dikelola oleh organisasi Anda",
"usageMetricsAlwaysReported": "Informasi penggunaan model selalu dilaporkan saat masuk",
"authWaiting": "Menunggu autentikasi selesai...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Condivisione disabilitata dall'organizzazione",
"shareSuccessOrganization": "Link organizzazione copiato negli appunti",
"shareSuccessPublic": "Link pubblico copiato negli appunti",
"openInCloud": "Apri attività in Roo Code Cloud",
"openInCloudIntro": "Continua a monitorare o interagire con Roo da qualsiasi luogo. Scansiona, clicca o copia per aprire.",
"openApiHistory": "Apri cronologia API",
"openUiHistory": "Apri cronologia UI",
"backToParentTask": "Attività principale"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Visita Roo Code Cloud",
"taskSync": "Sincronizzazione attività",
"taskSyncDescription": "Sincronizza le tue attività per visualizzarle e condividerle su Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Consenti il controllo delle attività da Roo Code Cloud",
"remoteControlRequiresTaskSync": "La sincronizzazione delle attività deve essere abilitata per utilizzare Roomote Control",
"taskSyncManagedByOrganization": "La sincronizzazione delle attività è gestita dalla tua organizzazione",
"usageMetricsAlwaysReported": "Le informazioni sull'utilizzo del modello vengono sempre segnalate quando si è connessi",
"authWaiting": "In attesa del completamento dell'autenticazione...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "組織により共有が無効化されています",
"shareSuccessOrganization": "組織リンクをクリップボードにコピーしました",
"shareSuccessPublic": "公開リンクをクリップボードにコピーしました",
"openInCloud": "Roo Code Cloudでタスクを開く",
"openInCloudIntro": "どこからでもRooの監視や操作を続けられます。スキャン、クリック、またはコピーして開いてください。",
"openApiHistory": "API履歴を開く",
"openUiHistory": "UI履歴を開く",
"backToParentTask": "親タスク"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Roo Code Cloudを訪問",
"taskSync": "タスク同期",
"taskSyncDescription": "Roo Code Cloudでタスクを表示・共有するために同期",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Roo Code Cloudからタスクを制御できるようにする",
"remoteControlRequiresTaskSync": "Roomote Controlを使用するにはタスク同期を有効にする必要があります",
"taskSyncManagedByOrganization": "タスク同期は組織によって管理されます",
"usageMetricsAlwaysReported": "ログイン時にはモデル使用情報が常に報告されます",
"authWaiting": "認証完了をお待ちください...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "조직에서 공유가 비활성화됨",
"shareSuccessOrganization": "조직 링크가 클립보드에 복사되었습니다",
"shareSuccessPublic": "공개 링크가 클립보드에 복사되었습니다",
"openInCloud": "Roo Code Cloud에서 작업 열기",
"openInCloudIntro": "어디서나 Roo를 계속 모니터링하거나 상호작용할 수 있습니다. 스캔, 클릭 또는 복사하여 열기.",
"openApiHistory": "API 기록 열기",
"openUiHistory": "UI 기록 열기",
"backToParentTask": "상위 작업"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Roo Code Cloud 방문",
"taskSync": "작업 동기화",
"taskSyncDescription": "Roo Code Cloud에서 보고 공유할 수 있도록 작업을 동기화",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Roo Code Cloud에서 작업을 제어할 수 있도록 허용",
"remoteControlRequiresTaskSync": "Roomote Control을 사용하려면 작업 동기화가 활성화되어야 합니다",
"taskSyncManagedByOrganization": "작업 동기화는 조직에서 관리합니다",
"usageMetricsAlwaysReported": "로그인 시 모델 사용 정보가 항상 보고됩니다",
"authWaiting": "인증 완료를 기다리는 중...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Delen uitgeschakeld door organisatie",
"shareSuccessOrganization": "Organisatielink gekopieerd naar klembord",
"shareSuccessPublic": "Openbare link gekopieerd naar klembord",
"openInCloud": "Taak openen in Roo Code Cloud",
"openInCloudIntro": "Blijf Roo vanaf elke locatie monitoren of ermee interacteren. Scan, klik of kopieer om te openen.",
"openApiHistory": "API-geschiedenis openen",
"openUiHistory": "UI-geschiedenis openen",
"backToParentTask": "Bovenliggende taak"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Bezoek Roo Code Cloud",
"taskSync": "Taaksynchronisatie",
"taskSyncDescription": "Synchroniseer je taken om ze te bekijken en delen op Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Sta toe taken te besturen vanuit Roo Code Cloud",
"remoteControlRequiresTaskSync": "Taaksynchronisatie moet ingeschakeld zijn om Roomote Control te gebruiken",
"taskSyncManagedByOrganization": "Taaksynchronisatie wordt beheerd door uw organisatie",
"usageMetricsAlwaysReported": "Modelgebruiksinformatie wordt altijd gerapporteerd wanneer ingelogd",
"authWaiting": "Wachten tot authenticatie voltooid is...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Udostępnianie wyłączone przez organizację",
"shareSuccessOrganization": "Link organizacji skopiowany do schowka",
"shareSuccessPublic": "Link publiczny skopiowany do schowka",
"openInCloud": "Otwórz zadanie w Roo Code Cloud",
"openInCloudIntro": "Kontynuuj monitorowanie lub interakcję z Roo z dowolnego miejsca. Zeskanuj, kliknij lub skopiuj, aby otworzyć.",
"openApiHistory": "Otwórz historię API",
"openUiHistory": "Otwórz historię UI",
"backToParentTask": "Zadanie nadrzędne"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Odwiedź Roo Code Cloud",
"taskSync": "Synchronizacja zadań",
"taskSyncDescription": "Synchronizuj swoje zadania, aby przeglądać i udostępniać je w Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Pozwól kontrolować zadania z Roo Code Cloud",
"remoteControlRequiresTaskSync": "Synchronizacja zadań musi być włączona, aby używać Roomote Control",
"taskSyncManagedByOrganization": "Synchronizacja zadań jest zarządzana przez Twoją organizację",
"usageMetricsAlwaysReported": "Informacje o użyciu modelu są zawsze raportowane po zalogowaniu",
"authWaiting": "Oczekiwanie na zakończenie uwierzytelniania...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Compartilhamento desabilitado pela organização",
"shareSuccessOrganization": "Link da organização copiado para a área de transferência",
"shareSuccessPublic": "Link público copiado para a área de transferência",
"openInCloud": "Abrir tarefa no Roo Code Cloud",
"openInCloudIntro": "Continue monitorando ou interagindo com Roo de qualquer lugar. Escaneie, clique ou copie para abrir.",
"openApiHistory": "Abrir histórico da API",
"openUiHistory": "Abrir histórico da UI",
"backToParentTask": "Tarefa pai"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Visitar Roo Code Cloud",
"taskSync": "Sincronização de tarefas",
"taskSyncDescription": "Sincronize suas tarefas para visualizar e compartilhar no Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Permite controlar tarefas a partir do Roo Code Cloud",
"remoteControlRequiresTaskSync": "A sincronização de tarefas deve estar habilitada para usar o Roomote Control",
"taskSyncManagedByOrganization": "A sincronização de tarefas é gerenciada pela sua organização",
"usageMetricsAlwaysReported": "As informações de uso do modelo são sempre reportadas quando conectado",
"authWaiting": "Aguardando conclusão da autenticação...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Обмен отключен организацией",
"shareSuccessOrganization": "Ссылка организации скопирована в буфер обмена",
"shareSuccessPublic": "Публичная ссылка скопирована в буфер обмена",
"openInCloud": "Открыть задачу в Roo Code Cloud",
"openInCloudIntro": "Продолжай отслеживать или взаимодействовать с Roo откуда угодно. Отсканируй, нажми или скопируй для открытия.",
"openApiHistory": "Открыть историю API",
"openUiHistory": "Открыть историю UI",
"backToParentTask": "Родительская задача"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Посетить Roo Code Cloud",
"taskSync": "Синхронизация задач",
"taskSyncDescription": "Синхронизируйте свои задачи для просмотра и обмена в Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Разрешить управление задачами из Roo Code Cloud",
"remoteControlRequiresTaskSync": "Для использования Roomote Control должна быть включена синхронизация задач",
"taskSyncManagedByOrganization": "Синхронизация задач управляется вашей организацией",
"usageMetricsAlwaysReported": "Информация об использовании модели всегда сообщается при входе в систему",
"authWaiting": "Ожидание завершения аутентификации...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Paylaşım kuruluş tarafından devre dışı bırakıldı",
"shareSuccessOrganization": "Organizasyon bağlantısı panoya kopyalandı",
"shareSuccessPublic": "Genel bağlantı panoya kopyalandı",
"openInCloud": "Görevi Roo Code Cloud'da aç",
"openInCloudIntro": "Roo'yu her yerden izlemeye veya etkileşime devam et. Açmak için tara, tıkla veya kopyala.",
"openApiHistory": "API Geçmişini Aç",
"openUiHistory": "UI Geçmişini Aç",
"backToParentTask": "Üst görev"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Roo Code Cloud'u ziyaret et",
"taskSync": "Görev senkronizasyonu",
"taskSyncDescription": "Görevlerinizi Roo Code Cloud'da görüntülemek ve paylaşmak için senkronize edin",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Roo Code Cloud'dan görevleri kontrol etmeye izin ver",
"remoteControlRequiresTaskSync": "Roomote Control'ü kullanmak için görev senkronizasyonu etkinleştirilmelidir",
"taskSyncManagedByOrganization": "Görev senkronizasyonu kuruluşunuz tarafından yönetilir",
"usageMetricsAlwaysReported": "Oturum açıldığında model kullanım bilgileri her zaman raporlanır",
"authWaiting": "Kimlik doğrulama tamamlanması bekleniyor...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "Chia sẻ bị tổ chức vô hiệu hóa",
"shareSuccessOrganization": "Liên kết tổ chức đã được sao chép vào clipboard",
"shareSuccessPublic": "Liên kết công khai đã được sao chép vào clipboard",
"openInCloud": "Mở tác vụ trong Roo Code Cloud",
"openInCloudIntro": "Tiếp tục theo dõi hoặc tương tác với Roo từ bất cứ đâu. Quét, nhấp hoặc sao chép để mở.",
"openApiHistory": "Mở lịch sử API",
"openUiHistory": "Mở lịch sử UI",
"backToParentTask": "Nhiệm vụ cha"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "Truy cập Roo Code Cloud",
"taskSync": "Đồng bộ tác vụ",
"taskSyncDescription": "Đồng bộ tác vụ của bạn để xem và chia sẻ trên Roo Code Cloud",
"remoteControl": "Roomote Control",
"remoteControlDescription": "Cho phép điều khiển tác vụ từ Roo Code Cloud",
"remoteControlRequiresTaskSync": "Đồng bộ tác vụ phải được bật để sử dụng Roomote Control",
"taskSyncManagedByOrganization": "Việc đồng bộ hóa công việc được quản lý bởi tổ chức của bạn",
"usageMetricsAlwaysReported": "Thông tin sử dụng mô hình luôn được báo cáo khi đăng nhập",
"authWaiting": "Đang chờ hoàn tất xác thực...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "组织已禁用分享功能",
"shareSuccessOrganization": "组织链接已复制到剪贴板",
"shareSuccessPublic": "公开链接已复制到剪贴板",
"openInCloud": "在 Roo Code Cloud 中打开任务",
"openInCloudIntro": "从任何地方继续监控或与 Roo 交互。扫描、点击或复制以打开。",
"openApiHistory": "打开 API 历史",
"openUiHistory": "打开 UI 历史",
"backToParentTask": "父任务"

View file

@ -15,9 +15,6 @@
"visitCloudWebsite": "访问 Roo Code Cloud",
"taskSync": "任务同步",
"taskSyncDescription": "同步您的任务以在 Roo Code Cloud 上查看和共享",
"remoteControl": "Roomote Control",
"remoteControlDescription": "允许从 Roo Code Cloud 控制任务",
"remoteControlRequiresTaskSync": "必须启用任务同步才能使用 Roomote Control",
"taskSyncManagedByOrganization": "任务同步由您的组织管理",
"usageMetricsAlwaysReported": "登录时始终报告模型使用信息",
"authWaiting": "等待身份验证完成...",

View file

@ -25,8 +25,6 @@
"sharingDisabledByOrganization": "組織已停用分享功能",
"shareSuccessOrganization": "組織連結已複製到剪貼簿",
"shareSuccessPublic": "公開連結已複製到剪貼簿",
"openInCloud": "在 Roo Code Cloud 中開啟工作",
"openInCloudIntro": "從任何地方繼續監控或與 Roo 互動。掃描、點選或複製即可開啟。",
"openApiHistory": "開啟 API 歷史紀錄",
"openUiHistory": "開啟 UI 歷史紀錄",
"backToParentTask": "上層工作"

Some files were not shown because too many files have changed in this diff Show more