mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
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:
parent
159bf2e9f1
commit
d00f1a2bb8
101 changed files with 108 additions and 3708 deletions
13
README.md
13
README.md
|
|
@ -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
7
locales/ca/README.md
generated
|
|
@ -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
7
locales/de/README.md
generated
|
|
@ -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
7
locales/es/README.md
generated
|
|
@ -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
7
locales/fr/README.md
generated
|
|
@ -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
7
locales/hi/README.md
generated
|
|
@ -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
7
locales/id/README.md
generated
|
|
@ -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
7
locales/it/README.md
generated
|
|
@ -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
7
locales/ja/README.md
generated
|
|
@ -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
7
locales/ko/README.md
generated
|
|
@ -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
5
locales/nl/README.md
generated
|
|
@ -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
7
locales/pl/README.md
generated
|
|
@ -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">
|
||||
|
|
|
|||
7
locales/pt-BR/README.md
generated
7
locales/pt-BR/README.md
generated
|
|
@ -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
7
locales/ru/README.md
generated
|
|
@ -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
13
locales/tr/README.md
generated
|
|
@ -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
13
locales/vi/README.md
generated
|
|
@ -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">
|
||||
|
|
|
|||
11
locales/zh-CN/README.md
generated
11
locales/zh-CN/README.md
generated
|
|
@ -35,7 +35,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -58,17 +58,16 @@ 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)
|
||||
|
||||
## 教程和功能视频
|
||||
|
||||
<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>
|
||||
|
|
|
|||
5
locales/zh-TW/README.md
generated
5
locales/zh-TW/README.md
generated
|
|
@ -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)
|
||||
|
||||
## 教學和功能影片
|
||||
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -105,12 +105,8 @@ describe("CloudSettingsService - Response Parsing", () => {
|
|||
},
|
||||
},
|
||||
user: {
|
||||
features: {
|
||||
roomoteControlEnabled: true,
|
||||
},
|
||||
settings: {
|
||||
extensionBridgeEnabled: true,
|
||||
},
|
||||
features: {},
|
||||
settings: {},
|
||||
version: 1,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({}),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
204
pnpm-lock.yaml
generated
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -129,9 +129,6 @@ vi.mock("@roo-code/cloud", () => ({
|
|||
}
|
||||
},
|
||||
},
|
||||
BridgeOrchestrator: {
|
||||
isEnabled: vi.fn().mockReturnValue(false),
|
||||
},
|
||||
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -110,9 +110,6 @@ vi.mock("@roo-code/cloud", () => ({
|
|||
}
|
||||
},
|
||||
},
|
||||
BridgeOrchestrator: {
|
||||
isEnabled: vi.fn().mockReturnValue(false),
|
||||
},
|
||||
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,9 +112,6 @@ vi.mock("@roo-code/cloud", () => ({
|
|||
}
|
||||
},
|
||||
},
|
||||
BridgeOrchestrator: {
|
||||
isEnabled: vi.fn().mockReturnValue(false),
|
||||
},
|
||||
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -114,9 +114,6 @@ vi.mock("@roo-code/cloud", () => ({
|
|||
}
|
||||
},
|
||||
},
|
||||
BridgeOrchestrator: {
|
||||
isEnabled: vi.fn().mockReturnValue(false),
|
||||
},
|
||||
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -229,9 +229,6 @@ vi.mock("@roo-code/cloud", () => ({
|
|||
}
|
||||
},
|
||||
},
|
||||
BridgeOrchestrator: {
|
||||
isEnabled: vi.fn().mockReturnValue(false),
|
||||
},
|
||||
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 })),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ca/chat.json
generated
2
webview-ui/src/i18n/locales/ca/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ca/cloud.json
generated
3
webview-ui/src/i18n/locales/ca/cloud.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/de/chat.json
generated
2
webview-ui/src/i18n/locales/de/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/de/cloud.json
generated
3
webview-ui/src/i18n/locales/de/cloud.json
generated
|
|
@ -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...",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/es/chat.json
generated
2
webview-ui/src/i18n/locales/es/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/es/cloud.json
generated
3
webview-ui/src/i18n/locales/es/cloud.json
generated
|
|
@ -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...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/fr/chat.json
generated
2
webview-ui/src/i18n/locales/fr/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/fr/cloud.json
generated
3
webview-ui/src/i18n/locales/fr/cloud.json
generated
|
|
@ -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...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/hi/chat.json
generated
2
webview-ui/src/i18n/locales/hi/chat.json
generated
|
|
@ -25,8 +25,6 @@
|
|||
"sharingDisabledByOrganization": "संगठन द्वारा साझाकरण अक्षम किया गया",
|
||||
"shareSuccessOrganization": "संगठन लिंक क्लिपबोर्ड में कॉपी किया गया",
|
||||
"shareSuccessPublic": "सार्वजनिक लिंक क्लिपबोर्ड में कॉपी किया गया",
|
||||
"openInCloud": "Roo Code Cloud में कार्य खोलें",
|
||||
"openInCloudIntro": "कहीं से भी Roo की निगरानी या इंटरैक्ट करना जारी रखें। खोलने के लिए स्कैन करें, क्लिक करें या कॉपी करें।",
|
||||
"openApiHistory": "API इतिहास खोलें",
|
||||
"openUiHistory": "UI इतिहास खोलें",
|
||||
"backToParentTask": "मूल कार्य"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/hi/cloud.json
generated
3
webview-ui/src/i18n/locales/hi/cloud.json
generated
|
|
@ -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": "प्रमाणीकरण पूरा होने की प्रतीक्षा कर रहे हैं...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/id/chat.json
generated
2
webview-ui/src/i18n/locales/id/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/id/cloud.json
generated
3
webview-ui/src/i18n/locales/id/cloud.json
generated
|
|
@ -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...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/it/chat.json
generated
2
webview-ui/src/i18n/locales/it/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/it/cloud.json
generated
3
webview-ui/src/i18n/locales/it/cloud.json
generated
|
|
@ -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...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ja/chat.json
generated
2
webview-ui/src/i18n/locales/ja/chat.json
generated
|
|
@ -25,8 +25,6 @@
|
|||
"sharingDisabledByOrganization": "組織により共有が無効化されています",
|
||||
"shareSuccessOrganization": "組織リンクをクリップボードにコピーしました",
|
||||
"shareSuccessPublic": "公開リンクをクリップボードにコピーしました",
|
||||
"openInCloud": "Roo Code Cloudでタスクを開く",
|
||||
"openInCloudIntro": "どこからでもRooの監視や操作を続けられます。スキャン、クリック、またはコピーして開いてください。",
|
||||
"openApiHistory": "API履歴を開く",
|
||||
"openUiHistory": "UI履歴を開く",
|
||||
"backToParentTask": "親タスク"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ja/cloud.json
generated
3
webview-ui/src/i18n/locales/ja/cloud.json
generated
|
|
@ -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": "認証完了をお待ちください...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ko/chat.json
generated
2
webview-ui/src/i18n/locales/ko/chat.json
generated
|
|
@ -25,8 +25,6 @@
|
|||
"sharingDisabledByOrganization": "조직에서 공유가 비활성화됨",
|
||||
"shareSuccessOrganization": "조직 링크가 클립보드에 복사되었습니다",
|
||||
"shareSuccessPublic": "공개 링크가 클립보드에 복사되었습니다",
|
||||
"openInCloud": "Roo Code Cloud에서 작업 열기",
|
||||
"openInCloudIntro": "어디서나 Roo를 계속 모니터링하거나 상호작용할 수 있습니다. 스캔, 클릭 또는 복사하여 열기.",
|
||||
"openApiHistory": "API 기록 열기",
|
||||
"openUiHistory": "UI 기록 열기",
|
||||
"backToParentTask": "상위 작업"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ko/cloud.json
generated
3
webview-ui/src/i18n/locales/ko/cloud.json
generated
|
|
@ -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": "인증 완료를 기다리는 중...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/nl/chat.json
generated
2
webview-ui/src/i18n/locales/nl/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/nl/cloud.json
generated
3
webview-ui/src/i18n/locales/nl/cloud.json
generated
|
|
@ -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...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/pl/chat.json
generated
2
webview-ui/src/i18n/locales/pl/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/pl/cloud.json
generated
3
webview-ui/src/i18n/locales/pl/cloud.json
generated
|
|
@ -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...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
2
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/pt-BR/cloud.json
generated
3
webview-ui/src/i18n/locales/pt-BR/cloud.json
generated
|
|
@ -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...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ru/chat.json
generated
2
webview-ui/src/i18n/locales/ru/chat.json
generated
|
|
@ -25,8 +25,6 @@
|
|||
"sharingDisabledByOrganization": "Обмен отключен организацией",
|
||||
"shareSuccessOrganization": "Ссылка организации скопирована в буфер обмена",
|
||||
"shareSuccessPublic": "Публичная ссылка скопирована в буфер обмена",
|
||||
"openInCloud": "Открыть задачу в Roo Code Cloud",
|
||||
"openInCloudIntro": "Продолжай отслеживать или взаимодействовать с Roo откуда угодно. Отсканируй, нажми или скопируй для открытия.",
|
||||
"openApiHistory": "Открыть историю API",
|
||||
"openUiHistory": "Открыть историю UI",
|
||||
"backToParentTask": "Родительская задача"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ru/cloud.json
generated
3
webview-ui/src/i18n/locales/ru/cloud.json
generated
|
|
@ -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": "Ожидание завершения аутентификации...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/tr/chat.json
generated
2
webview-ui/src/i18n/locales/tr/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/tr/cloud.json
generated
3
webview-ui/src/i18n/locales/tr/cloud.json
generated
|
|
@ -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...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/vi/chat.json
generated
2
webview-ui/src/i18n/locales/vi/chat.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/vi/cloud.json
generated
3
webview-ui/src/i18n/locales/vi/cloud.json
generated
|
|
@ -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...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
2
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
|
|
@ -25,8 +25,6 @@
|
|||
"sharingDisabledByOrganization": "组织已禁用分享功能",
|
||||
"shareSuccessOrganization": "组织链接已复制到剪贴板",
|
||||
"shareSuccessPublic": "公开链接已复制到剪贴板",
|
||||
"openInCloud": "在 Roo Code Cloud 中打开任务",
|
||||
"openInCloudIntro": "从任何地方继续监控或与 Roo 交互。扫描、点击或复制以打开。",
|
||||
"openApiHistory": "打开 API 历史",
|
||||
"openUiHistory": "打开 UI 历史",
|
||||
"backToParentTask": "父任务"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/zh-CN/cloud.json
generated
3
webview-ui/src/i18n/locales/zh-CN/cloud.json
generated
|
|
@ -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": "等待身份验证完成...",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
2
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
|
|
@ -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
Loading…
Add table
Reference in a new issue