From 1d714c8ce4d925b7cc7c100702e215ee3cda1a48 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Mon, 4 Aug 2025 13:58:14 -0700 Subject: [PATCH] Extension bridge (#6677) Co-authored-by: Matt Rubens --- pnpm-lock.yaml | 28 +++-- src/core/task/Task.ts | 29 ++++- src/core/webview/ClineProvider.ts | 115 +++++++++++++++++- src/core/webview/webviewMessageHandler.ts | 5 + src/extension.ts | 64 +++++----- src/package.json | 2 +- src/shared/ExtensionMessage.ts | 1 + src/shared/WebviewMessage.ts | 1 + src/utils/remoteControl.ts | 11 ++ .../src/components/account/AccountView.tsx | 71 ++++++++--- .../account/__tests__/AccountView.spec.tsx | 87 +++++++++++-- webview-ui/src/components/modes/ModesView.tsx | 4 +- .../src/context/ExtensionStateContext.tsx | 5 + webview-ui/src/i18n/locales/ca/account.json | 3 + webview-ui/src/i18n/locales/de/account.json | 3 + webview-ui/src/i18n/locales/en/account.json | 14 ++- webview-ui/src/i18n/locales/en/settings.json | 2 +- webview-ui/src/i18n/locales/es/account.json | 3 + webview-ui/src/i18n/locales/fr/account.json | 3 + webview-ui/src/i18n/locales/hi/account.json | 3 + webview-ui/src/i18n/locales/id/account.json | 3 + webview-ui/src/i18n/locales/it/account.json | 3 + webview-ui/src/i18n/locales/ja/account.json | 3 + webview-ui/src/i18n/locales/ko/account.json | 3 + webview-ui/src/i18n/locales/nl/account.json | 3 + webview-ui/src/i18n/locales/pl/account.json | 3 + .../src/i18n/locales/pt-BR/account.json | 3 + webview-ui/src/i18n/locales/ru/account.json | 3 + webview-ui/src/i18n/locales/tr/account.json | 3 + webview-ui/src/i18n/locales/vi/account.json | 3 + .../src/i18n/locales/zh-CN/account.json | 3 + .../src/i18n/locales/zh-TW/account.json | 3 + 32 files changed, 404 insertions(+), 86 deletions(-) create mode 100644 src/utils/remoteControl.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2847df1a1..0d952b6aeb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -563,8 +563,8 @@ importers: specifier: ^1.14.0 version: 1.14.0(typescript@5.8.3) '@roo-code/cloud': - specifier: ^0.4.0 - version: 0.4.0 + specifier: ^0.5.0 + version: 0.5.0 '@roo-code/ipc': specifier: workspace:^ version: link:../packages/ipc @@ -3065,11 +3065,11 @@ packages: cpu: [x64] os: [win32] - '@roo-code/cloud@0.4.0': - resolution: {integrity: sha512-1a27RG2YjQFfsU5UlfbQnpj/K/6gYBcysp2FXaX9+VaaTh5ZzReQeHJ9uREnyE059zoFpVuNywwNxGadzyotWw==} + '@roo-code/cloud@0.5.0': + resolution: {integrity: sha512-4u6Ce2Rmr5a9nxhjGUMRRWUWhZc63EmF/UJ/+Az5/1JARMOp0kHN5Pwqz2QAgfD137+TFSBKQORpiN0GXrdt2w==} - '@roo-code/types@1.42.0': - resolution: {integrity: sha512-AITVSV6WFd17jE8lQXFy7PkHam8M+mMkT7o9ipGZZ3cV7SbrnmL/Hg/HjkA9lkdJYbcC5dEK94py8KVBQn8Umw==} + '@roo-code/types@1.44.0': + resolution: {integrity: sha512-3xbW4pYaCgWuHF5qOsiXpIcd281dlFTe1zboUGgcUUsB414Hu3pQI86PdgJxVGtZgxtaca0eHTQ2Sqjqq8nPlA==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -6267,8 +6267,8 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - ioredis@5.7.0: - resolution: {integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==} + ioredis@5.6.1: + resolution: {integrity: sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==} engines: {node: '>=12.22.0'} ip-address@9.0.5: @@ -12191,16 +12191,18 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.40.2': optional: true - '@roo-code/cloud@0.4.0': + '@roo-code/cloud@0.5.0': dependencies: - '@roo-code/types': 1.42.0 - ioredis: 5.7.0 + '@roo-code/types': 1.44.0 + ioredis: 5.6.1 p-wait-for: 5.0.2 zod: 3.25.76 transitivePeerDependencies: - supports-color - '@roo-code/types@1.42.0': {} + '@roo-code/types@1.44.0': + dependencies: + zod: 3.25.76 '@sec-ant/readable-stream@0.4.1': {} @@ -15963,7 +15965,7 @@ snapshots: internmap@2.0.3: {} - ioredis@5.7.0: + ioredis@5.6.1: dependencies: '@ioredis/commands': 1.3.0 cluster-key-slot: 1.1.2 diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 3cb6abe7f7..e0c332d16f 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -32,7 +32,7 @@ import { isBlockingAsk, } from "@roo-code/types" import { TelemetryService } from "@roo-code/telemetry" -import { CloudService } from "@roo-code/cloud" +import { CloudService, TaskBridgeService } from "@roo-code/cloud" // api import { ApiHandler, ApiHandlerCreateMessageMetadata, buildApiHandler } from "../../api" @@ -118,6 +118,7 @@ export type TaskOptions = { parentTask?: Task taskNumber?: number onCreated?: (task: Task) => void + enableTaskBridge?: boolean } export class Task extends EventEmitter implements TaskLike { @@ -237,6 +238,9 @@ export class Task extends EventEmitter implements TaskLike { checkpointService?: RepoPerTaskCheckpointService checkpointServiceInitializing = false + // Task Bridge + taskBridgeService?: TaskBridgeService + // Streaming isWaitingForFirstChunk = false isStreaming = false @@ -268,6 +272,7 @@ export class Task extends EventEmitter implements TaskLike { parentTask, taskNumber = -1, onCreated, + enableTaskBridge = false, }: TaskOptions) { super() @@ -345,6 +350,11 @@ export class Task extends EventEmitter implements TaskLike { this.toolRepetitionDetector = new ToolRepetitionDetector(this.consecutiveMistakeLimit) + // Initialize TaskBridgeService only if enabled + if (enableTaskBridge) { + this.taskBridgeService = TaskBridgeService.getInstance() + } + onCreated?.(this) if (startTask) { @@ -931,6 +941,11 @@ export class Task extends EventEmitter implements TaskLike { // Start / Abort / Resume private async startTask(task?: string, images?: string[]): Promise { + if (this.taskBridgeService) { + await this.taskBridgeService.initialize() + await this.taskBridgeService.subscribeToTask(this) + } + // `conversationHistory` (for API) and `clineMessages` (for webview) // need to be in sync. // If the extension process were killed, then on restart the @@ -982,6 +997,11 @@ export class Task extends EventEmitter implements TaskLike { } private async resumeTaskFromHistory() { + if (this.taskBridgeService) { + await this.taskBridgeService.initialize() + await this.taskBridgeService.subscribeToTask(this) + } + const modifiedClineMessages = await this.getSavedClineMessages() // Remove any resume messages that may have been added before @@ -1227,6 +1247,13 @@ export class Task extends EventEmitter implements TaskLike { this.pauseInterval = undefined } + // Unsubscribe from TaskBridge service. + if (this.taskBridgeService) { + this.taskBridgeService + .unsubscribeFromTask(this.taskId) + .catch((error) => console.error("Error unsubscribing from task bridge:", error)) + } + // Release any terminals associated with this task. try { // Release any terminals associated with this task. diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index ed8f8a27d1..384de58be7 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -17,7 +17,6 @@ import { type ProviderSettings, type RooCodeSettings, type ProviderSettingsEntry, - type ProviderSettingsWithId, type TelemetryProperties, type TelemetryPropertiesProvider, type CodeActionId, @@ -66,6 +65,7 @@ import { fileExistsAtPath } from "../../utils/fs" import { setTtsEnabled, setTtsSpeed } from "../../utils/tts" import { getWorkspaceGitInfo } from "../../utils/git" import { getWorkspacePath } from "../../utils/path" +import { isRemoteControlEnabled } from "../../utils/remoteControl" import { setPanel } from "../../activate/registerCommands" @@ -111,6 +111,8 @@ export class ClineProvider protected mcpHub?: McpHub // Change from private to protected private marketplaceManager: MarketplaceManager private mdmService?: MdmService + private taskCreationCallback: (task: Task) => void + private taskEventListeners: WeakMap void>> = new WeakMap() public isViewLaunched = false public settingsImportedAt?: number @@ -162,6 +164,40 @@ export class ClineProvider this.marketplaceManager = new MarketplaceManager(this.context, this.customModesManager) + this.taskCreationCallback = (instance: Task) => { + this.emit(RooCodeEventName.TaskCreated, instance) + + // Create named listener functions so we can remove them later. + const onTaskStarted = () => this.emit(RooCodeEventName.TaskStarted, instance.taskId) + const onTaskCompleted = (taskId: string, tokenUsage: any, toolUsage: any) => + this.emit(RooCodeEventName.TaskCompleted, taskId, tokenUsage, toolUsage) + const onTaskAborted = () => this.emit(RooCodeEventName.TaskAborted, instance.taskId) + const onTaskFocused = () => this.emit(RooCodeEventName.TaskFocused, instance.taskId) + const onTaskUnfocused = () => this.emit(RooCodeEventName.TaskUnfocused, instance.taskId) + const onTaskActive = (taskId: string) => this.emit(RooCodeEventName.TaskActive, taskId) + const onTaskIdle = (taskId: string) => this.emit(RooCodeEventName.TaskIdle, taskId) + + // Attach the listeners. + instance.on(RooCodeEventName.TaskStarted, onTaskStarted) + instance.on(RooCodeEventName.TaskCompleted, onTaskCompleted) + instance.on(RooCodeEventName.TaskAborted, onTaskAborted) + instance.on(RooCodeEventName.TaskFocused, onTaskFocused) + instance.on(RooCodeEventName.TaskUnfocused, onTaskUnfocused) + instance.on(RooCodeEventName.TaskActive, onTaskActive) + instance.on(RooCodeEventName.TaskIdle, onTaskIdle) + + // Store the cleanup functions for later removal. + this.taskEventListeners.set(instance, [ + () => instance.off(RooCodeEventName.TaskStarted, onTaskStarted), + () => instance.off(RooCodeEventName.TaskCompleted, onTaskCompleted), + () => instance.off(RooCodeEventName.TaskAborted, onTaskAborted), + () => instance.off(RooCodeEventName.TaskFocused, onTaskFocused), + () => instance.off(RooCodeEventName.TaskUnfocused, onTaskUnfocused), + () => instance.off(RooCodeEventName.TaskActive, onTaskActive), + () => instance.off(RooCodeEventName.TaskIdle, onTaskIdle), + ]) + } + // Initialize Roo Code Cloud profile sync. this.initializeCloudProfileSync().catch((error) => { this.log(`Failed to initialize cloud profile sync: ${error}`) @@ -297,6 +333,14 @@ export class ClineProvider task.emit(RooCodeEventName.TaskUnfocused) + // Remove event listeners before clearing the reference. + const cleanupFunctions = this.taskEventListeners.get(task) + + if (cleanupFunctions) { + cleanupFunctions.forEach((cleanup) => cleanup()) + this.taskEventListeners.delete(task) + } + // Make sure no reference kept, once promises end it will be // garbage collected. task = undefined @@ -654,12 +698,17 @@ export class ClineProvider enableCheckpoints, fuzzyMatchThreshold, experiments, + cloudUserInfo, + remoteControlEnabled, } = await this.getState() if (!ProfileValidator.isProfileAllowed(apiConfiguration, organizationAllowList)) { throw new OrganizationAllowListViolationError(t("common:errors.violated_organization_allowlist")) } + // Determine if TaskBridge should be enabled + const enableTaskBridge = isRemoteControlEnabled(cloudUserInfo, remoteControlEnabled) + const task = new Task({ provider: this, apiConfiguration, @@ -673,7 +722,8 @@ export class ClineProvider rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined, parentTask, taskNumber: this.clineStack.length + 1, - onCreated: (instance) => this.emit(RooCodeEventName.TaskCreated, instance), + onCreated: this.taskCreationCallback, + enableTaskBridge, ...options, }) @@ -738,8 +788,13 @@ export class ClineProvider enableCheckpoints, fuzzyMatchThreshold, experiments, + cloudUserInfo, + remoteControlEnabled, } = await this.getState() + // Determine if TaskBridge should be enabled + const enableTaskBridge = isRemoteControlEnabled(cloudUserInfo, remoteControlEnabled) + const task = new Task({ provider: this, apiConfiguration, @@ -752,7 +807,8 @@ export class ClineProvider rootTask: historyItem.rootTask, parentTask: historyItem.parentTask, taskNumber: historyItem.number, - onCreated: (instance) => this.emit(RooCodeEventName.TaskCreated, instance), + onCreated: this.taskCreationCallback, + enableTaskBridge, }) await this.addClineToStack(task) @@ -1631,6 +1687,7 @@ export class ClineProvider includeDiagnosticMessages, maxDiagnosticMessages, includeTaskHistoryInEnhance, + remoteControlEnabled, } = await this.getState() const telemetryKey = process.env.POSTHOG_API_KEY @@ -1758,6 +1815,7 @@ export class ClineProvider includeDiagnosticMessages: includeDiagnosticMessages ?? true, maxDiagnosticMessages: maxDiagnosticMessages ?? 50, includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? false, + remoteControlEnabled: remoteControlEnabled ?? false, } } @@ -1945,6 +2003,8 @@ export class ClineProvider maxDiagnosticMessages: stateValues.maxDiagnosticMessages ?? 50, // Add includeTaskHistoryInEnhance setting includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? false, + // Add remoteControlEnabled setting + remoteControlEnabled: stateValues.remoteControlEnabled ?? false, } } @@ -2057,6 +2117,55 @@ export class ClineProvider return true } + /** + * Handle remote control enabled/disabled state changes + * Manages ExtensionBridgeService and TaskBridgeService lifecycle + */ + public async handleRemoteControlToggle(enabled: boolean): Promise { + const { + CloudService: CloudServiceImport, + ExtensionBridgeService, + TaskBridgeService, + } = await import("@roo-code/cloud") + const userInfo = CloudServiceImport.instance.getUserInfo() + + // Handle ExtensionBridgeService using static method + await ExtensionBridgeService.handleRemoteControlState(userInfo, enabled, this, (message: string) => + this.log(message), + ) + + if (isRemoteControlEnabled(userInfo, enabled)) { + // Set up TaskBridgeService for the currently active task if one exists + const currentTask = this.getCurrentCline() + if (currentTask && !currentTask.taskBridgeService) { + try { + currentTask.taskBridgeService = TaskBridgeService.getInstance() + await currentTask.taskBridgeService.subscribeToTask(currentTask) + this.log(`[TaskBridgeService] Subscribed current task ${currentTask.taskId} to TaskBridge`) + } catch (error) { + const message = `[TaskBridgeService#subscribeToTask] ${error instanceof Error ? error.message : String(error)}` + this.log(message) + console.error(message) + } + } + } else { + // Disconnect TaskBridgeService for all tasks in the stack + for (const task of this.clineStack) { + if (task.taskBridgeService) { + try { + await task.taskBridgeService.unsubscribeFromTask(task.taskId) + task.taskBridgeService = undefined + this.log(`[TaskBridgeService] Unsubscribed task ${task.taskId} from TaskBridge`) + } catch (error) { + const message = `[TaskBridgeService#unsubscribeFromTask] for task ${task.taskId}: ${error instanceof Error ? error.message : String(error)}` + this.log(message) + console.error(message) + } + } + } + } + } + /** * Returns properties to be included in every telemetry event * This method is called by the telemetry service to get context information diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index fdb7e90425..743e3b0c13 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -906,6 +906,11 @@ export const webviewMessageHandler = async ( await updateGlobalState("enableMcpServerCreation", message.bool ?? true) await provider.postStateToWebview() break + case "remoteControlEnabled": + await updateGlobalState("remoteControlEnabled", message.bool ?? false) + await provider.handleRemoteControlToggle(message.bool ?? false) + await provider.postStateToWebview() + break case "refreshAllMcpServers": { const mcpHub = provider.getMcpHub() if (mcpHub) { diff --git a/src/extension.ts b/src/extension.ts index beb69b30b5..ea6ab4e1b4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,7 +12,7 @@ try { console.warn("Failed to load environment variables:", e) } -import { CloudService } from "@roo-code/cloud" +import { CloudService, ExtensionBridgeService } from "@roo-code/cloud" import { TelemetryService, PostHogTelemetryClient } from "@roo-code/telemetry" import "./utils/path" // Necessary to have access to String.prototype.toPosix. @@ -29,6 +29,7 @@ import { CodeIndexManager } from "./services/code-index/manager" import { MdmService } from "./services/mdm/MdmService" import { migrateSettings } from "./utils/migrateSettings" import { autoImportSettings } from "./utils/autoImportSettings" +import { isRemoteControlEnabled } from "./utils/remoteControl" import { API } from "./extension/api" import { @@ -71,37 +72,13 @@ export async function activate(context: vscode.ExtensionContext) { console.warn("Failed to register PostHogTelemetryClient:", error) } - // Create logger for cloud services + // Create logger for cloud services. const cloudLogger = createDualLogger(createOutputChannelLogger(outputChannel)) - // Initialize Roo Code Cloud service. - const cloudService = await CloudService.createInstance(context, cloudLogger) - - try { - if (cloudService.telemetryClient) { - TelemetryService.instance.register(cloudService.telemetryClient) - } - } catch (error) { - outputChannel.appendLine( - `[CloudService] Failed to register TelemetryClient: ${error instanceof Error ? error.message : String(error)}`, - ) - } - - const postStateListener = () => { - ClineProvider.getVisibleInstance()?.postStateToWebview() - } - - cloudService.on("auth-state-changed", postStateListener) - cloudService.on("user-info", postStateListener) - cloudService.on("settings-updated", postStateListener) - - // Add to subscriptions for proper cleanup on deactivate - context.subscriptions.push(cloudService) - // Initialize MDM service const mdmService = await MdmService.createInstance(cloudLogger) - // Initialize i18n for internationalization support + // Initialize i18n for internationalization support. initializeI18n(context.globalState.get("language") ?? formatLanguage(vscode.env.language)) // Initialize terminal shell execution handlers. @@ -126,6 +103,29 @@ export async function activate(context: vscode.ExtensionContext) { ) } + // Initialize Roo Code Cloud service. + const cloudService = await CloudService.createInstance(context, cloudLogger) + + const postStateListener = () => ClineProvider.getVisibleInstance()?.postStateToWebview() + + cloudService.on("auth-state-changed", postStateListener) + cloudService.on("settings-updated", postStateListener) + + cloudService.on("user-info", ({ userInfo }) => { + postStateListener() + + // Check if remote control is enabled in user settings + const remoteControlEnabled = contextProxy.getValue("remoteControlEnabled") + + // Handle ExtensionBridgeService state using static method + ExtensionBridgeService.handleRemoteControlState(userInfo, remoteControlEnabled, provider, (message: string) => + outputChannel.appendLine(message), + ) + }) + + // Add to subscriptions for proper cleanup on deactivate. + context.subscriptions.push(cloudService) + const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy, codeIndexManager, mdmService) TelemetryService.instance.setProvider(provider) @@ -139,7 +139,7 @@ export async function activate(context: vscode.ExtensionContext) { }), ) - // Auto-import configuration if specified in settings + // Auto-import configuration if specified in settings. try { await autoImportSettings(outputChannel, { providerSettingsManager: provider.providerSettingsManager, @@ -232,6 +232,14 @@ export async function activate(context: vscode.ExtensionContext) { // This method is called when your extension is deactivated. export async function deactivate() { outputChannel.appendLine(`${Package.name} extension deactivated`) + + // Cleanup Extension Bridge service. + const extensionBridgeService = ExtensionBridgeService.getInstance() + + if (extensionBridgeService) { + await extensionBridgeService.disconnect() + } + await McpServerManager.cleanup(extensionContext) TelemetryService.instance.shutdown() TerminalRegistry.cleanup() diff --git a/src/package.json b/src/package.json index aa2110dfd5..d35f6f34dd 100644 --- a/src/package.json +++ b/src/package.json @@ -420,7 +420,7 @@ "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.9.0", "@qdrant/js-client-rest": "^1.14.0", - "@roo-code/cloud": "^0.4.0", + "@roo-code/cloud": "^0.5.0", "@roo-code/ipc": "workspace:^", "@roo-code/telemetry": "workspace:^", "@roo-code/types": "workspace:^", diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 930edeac73..2313d7d177 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -270,6 +270,7 @@ export type ExtensionState = Pick< | "profileThresholds" | "includeDiagnosticMessages" | "maxDiagnosticMessages" + | "remoteControlEnabled" > & { version: string clineMessages: ClineMessage[] diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index cb8759d851..2d94896bf5 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -130,6 +130,7 @@ export interface WebviewMessage { | "terminalCompressProgressBar" | "mcpEnabled" | "enableMcpServerCreation" + | "remoteControlEnabled" | "searchCommits" | "alwaysApproveResubmit" | "requestDelaySeconds" diff --git a/src/utils/remoteControl.ts b/src/utils/remoteControl.ts new file mode 100644 index 0000000000..f003b522d1 --- /dev/null +++ b/src/utils/remoteControl.ts @@ -0,0 +1,11 @@ +import type { CloudUserInfo } from "@roo-code/types" + +/** + * Determines if remote control features should be enabled + * @param cloudUserInfo - User information from cloud service + * @param remoteControlEnabled - User's remote control setting + * @returns true if remote control should be enabled + */ +export function isRemoteControlEnabled(cloudUserInfo?: CloudUserInfo | null, remoteControlEnabled?: boolean): boolean { + return !!(cloudUserInfo?.id && cloudUserInfo.extensionBridgeEnabled && remoteControlEnabled) +} diff --git a/webview-ui/src/components/account/AccountView.tsx b/webview-ui/src/components/account/AccountView.tsx index e3d1a293a7..e36818cc3a 100644 --- a/webview-ui/src/components/account/AccountView.tsx +++ b/webview-ui/src/components/account/AccountView.tsx @@ -5,8 +5,12 @@ import type { CloudUserInfo } from "@roo-code/types" import { TelemetryEventName } from "@roo-code/types" import { useAppTranslation } from "@src/i18n/TranslationContext" +import { useExtensionState } from "@src/context/ExtensionStateContext" import { vscode } from "@src/utils/vscode" import { telemetryClient } from "@src/utils/TelemetryClient" +import { ToggleSwitch } from "@/components/ui/toggle-switch" + +import { History, PiggyBank, Router, SquareArrowOutUpRightIcon } from "lucide-react" type AccountViewProps = { userInfo: CloudUserInfo | null @@ -17,6 +21,7 @@ type AccountViewProps = { export const AccountView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: AccountViewProps) => { const { t } = useAppTranslation() + const { remoteControlEnabled, setRemoteControlEnabled } = useExtensionState() const wasAuthenticatedRef = useRef(false) const rooLogoUri = (window as any).IMAGES_BASE_URI + "/roo-logo.svg" @@ -51,11 +56,17 @@ export const AccountView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: vscode.postMessage({ type: "openExternal", url: cloudUrl }) } + const handleRemoteControlToggle = () => { + const newValue = !remoteControlEnabled + setRemoteControlEnabled(newValue) + vscode.postMessage({ type: "remoteControlEnabled", bool: newValue }) + } + return ( -
+

{t("account:title")}

- + {t("settings:common.done")}
@@ -77,13 +88,13 @@ export const AccountView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: )}
{userInfo.name && ( -

{userInfo.name}

+

{userInfo.name}

)} {userInfo?.email && ( -

{userInfo?.email}

+

{userInfo?.email}

)} {userInfo?.organizationName && ( -
+
{userInfo.organizationImageUrl && ( )} + + {/* Remote Control Toggle - only show if user has extension bridge enabled */} + {userInfo?.extensionBridgeEnabled && ( +
+
+ + {t("account:remoteControl")} +
+
+ {t("account:remoteControlDescription")} +
+
+
+ )} +
{t("account:visitCloudWebsite")} @@ -125,30 +157,31 @@ export const AccountView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }:
-

+

{t("account:cloudBenefitsTitle")}

-

- {t("account:cloudBenefitsSubtitle")} -

-
    -
  • - - {t("account:cloudBenefitHistory")} +
      +
    • + + {t("account:cloudBenefitWalkaway")}
    • -
    • - +
    • + {t("account:cloudBenefitSharing")}
    • -
    • - +
    • + + {t("account:cloudBenefitHistory")} +
    • +
    • + {t("account:cloudBenefitMetrics")}
-
- +
+ {t("account:connect")}
diff --git a/webview-ui/src/components/account/__tests__/AccountView.spec.tsx b/webview-ui/src/components/account/__tests__/AccountView.spec.tsx index d6fd3013e6..2af759d615 100644 --- a/webview-ui/src/components/account/__tests__/AccountView.spec.tsx +++ b/webview-ui/src/components/account/__tests__/AccountView.spec.tsx @@ -11,11 +11,17 @@ vi.mock("@src/i18n/TranslationContext", () => ({ "settings:common.done": "Done", "account:signIn": "Connect to Roo Code Cloud", "account:cloudBenefitsTitle": "Connect to Roo Code Cloud", - "account:cloudBenefitsSubtitle": "Sync your prompts and telemetry to enable:", - "account:cloudBenefitHistory": "Online task history", - "account:cloudBenefitSharing": "Sharing and collaboration features", - "account:cloudBenefitMetrics": "Task, token, and cost-based usage metrics", + "account:cloudBenefitWalkaway": "Follow and control tasks from anywhere with Roomote Control", + "account:cloudBenefitSharing": "Share tasks with others", + "account:cloudBenefitHistory": "Access your task history", + "account:cloudBenefitMetrics": "Get a holistic view of your token consumption", "account:logOut": "Log out", + "account:connect": "Connect Now", + "account:visitCloudWebsite": "Visit Roo Code Cloud", + "account:remoteControl": "Roomote Control", + "account:remoteControlDescription": + "Enable following and interacting with tasks in this workspace with Roo Code Cloud", + "account:profilePicture": "Profile picture", } return translations[key] || key }, @@ -36,6 +42,14 @@ vi.mock("@src/utils/TelemetryClient", () => ({ }, })) +// Mock the extension state context +vi.mock("@src/context/ExtensionStateContext", () => ({ + useExtensionState: () => ({ + remoteControlEnabled: false, + setRemoteControlEnabled: vi.fn(), + }), +})) + // Mock window global for images Object.defineProperty(window, "IMAGES_BASE_URI", { value: "/images", @@ -55,13 +69,13 @@ describe("AccountView", () => { // Check that the benefits section is displayed expect(screen.getByRole("heading", { name: "Connect to Roo Code Cloud" })).toBeInTheDocument() - expect(screen.getByText("Sync your prompts and telemetry to enable:")).toBeInTheDocument() - expect(screen.getByText("Online task history")).toBeInTheDocument() - expect(screen.getByText("Sharing and collaboration features")).toBeInTheDocument() - expect(screen.getByText("Task, token, and cost-based usage metrics")).toBeInTheDocument() + expect(screen.getByText("Follow and control tasks from anywhere with Roomote Control")).toBeInTheDocument() + expect(screen.getByText("Share tasks with others")).toBeInTheDocument() + expect(screen.getByText("Access your task history")).toBeInTheDocument() + expect(screen.getByText("Get a holistic view of your token consumption")).toBeInTheDocument() // Check that the connect button is also present - expect(screen.getByText("account:connect")).toBeInTheDocument() + expect(screen.getByText("Connect Now")).toBeInTheDocument() }) it("should not display benefits when user is authenticated", () => { @@ -80,13 +94,60 @@ describe("AccountView", () => { ) // Check that the benefits section is NOT displayed - expect(screen.queryByText("Sync your prompts and telemetry to enable:")).not.toBeInTheDocument() - expect(screen.queryByText("Online task history")).not.toBeInTheDocument() - expect(screen.queryByText("Sharing and collaboration features")).not.toBeInTheDocument() - expect(screen.queryByText("Task, token, and cost-based usage metrics")).not.toBeInTheDocument() + expect( + screen.queryByText("Follow and control tasks from anywhere with Roomote Control"), + ).not.toBeInTheDocument() + expect(screen.queryByText("Share tasks with others")).not.toBeInTheDocument() + expect(screen.queryByText("Access your task history")).not.toBeInTheDocument() + expect(screen.queryByText("Get a holistic view of your token consumption")).not.toBeInTheDocument() // Check that user info is displayed instead expect(screen.getByText("Test User")).toBeInTheDocument() expect(screen.getByText("test@example.com")).toBeInTheDocument() }) + + it("should display remote control toggle when user has extension bridge enabled", () => { + const mockUserInfo = { + name: "Test User", + email: "test@example.com", + extensionBridgeEnabled: true, + } + + render( + {}} + />, + ) + + // 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( + {}} + />, + ) + + // Check that the remote control toggle is NOT displayed + expect(screen.queryByTestId("remote-control-toggle")).not.toBeInTheDocument() + expect(screen.queryByText("Roomote Control")).not.toBeInTheDocument() + }) }) diff --git a/webview-ui/src/components/modes/ModesView.tsx b/webview-ui/src/components/modes/ModesView.tsx index 21c531937f..d470f7a658 100644 --- a/webview-ui/src/components/modes/ModesView.tsx +++ b/webview-ui/src/components/modes/ModesView.tsx @@ -617,7 +617,9 @@ const ModesView = ({ onDone }: ModesViewProps) => { aria-expanded={open} className="justify-between w-full" data-testid="mode-select-trigger"> -
{getCurrentMode()?.name || t("prompts:modes.selectMode")}
+
+ {getCurrentMode()?.name || t("prompts:modes.selectMode")} +
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index da7ab63358..12f13bdf55 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -97,6 +97,8 @@ export interface ExtensionStateContextType extends ExtensionState { setMcpEnabled: (value: boolean) => void enableMcpServerCreation: boolean setEnableMcpServerCreation: (value: boolean) => void + remoteControlEnabled: boolean + setRemoteControlEnabled: (value: boolean) => void alwaysApproveResubmit?: boolean setAlwaysApproveResubmit: (value: boolean) => void requestDelaySeconds: number @@ -195,6 +197,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode terminalShellIntegrationTimeout: 4000, mcpEnabled: true, enableMcpServerCreation: false, + remoteControlEnabled: false, alwaysApproveResubmit: false, requestDelaySeconds: 5, currentApiConfigName: "default", @@ -408,6 +411,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode profileThresholds: state.profileThresholds ?? {}, alwaysAllowFollowupQuestions, followupAutoApproveTimeoutMs, + remoteControlEnabled: state.remoteControlEnabled ?? false, setExperimentEnabled: (id, enabled) => setState((prevState) => ({ ...prevState, experiments: { ...prevState.experiments, [id]: enabled } })), setApiConfiguration, @@ -454,6 +458,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setMcpEnabled: (value) => setState((prevState) => ({ ...prevState, mcpEnabled: value })), setEnableMcpServerCreation: (value) => setState((prevState) => ({ ...prevState, enableMcpServerCreation: value })), + setRemoteControlEnabled: (value) => setState((prevState) => ({ ...prevState, remoteControlEnabled: value })), setAlwaysApproveResubmit: (value) => setState((prevState) => ({ ...prevState, alwaysApproveResubmit: value })), setRequestDelaySeconds: (value) => setState((prevState) => ({ ...prevState, requestDelaySeconds: value })), setCurrentApiConfigName: (value) => setState((prevState) => ({ ...prevState, currentApiConfigName: value })), diff --git a/webview-ui/src/i18n/locales/ca/account.json b/webview-ui/src/i18n/locales/ca/account.json index a94a978b87..2804cc8dfa 100644 --- a/webview-ui/src/i18n/locales/ca/account.json +++ b/webview-ui/src/i18n/locales/ca/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Historial de tasques en línia", "cloudBenefitSharing": "Funcions de compartició i col·laboració", "cloudBenefitMetrics": "Mètriques d'ús basades en tasques, tokens i costos", + "cloudBenefitWalkaway": "Segueix i controla tasques des de qualsevol lloc amb Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Permet seguir i interactuar amb tasques en aquest espai de treball amb Roo Code Cloud", "visitCloudWebsite": "Visita Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/de/account.json b/webview-ui/src/i18n/locales/de/account.json index bd4d71eada..6edaf58fff 100644 --- a/webview-ui/src/i18n/locales/de/account.json +++ b/webview-ui/src/i18n/locales/de/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Online-Aufgabenverlauf", "cloudBenefitSharing": "Freigabe- und Kollaborationsfunktionen", "cloudBenefitMetrics": "Aufgaben-, Token- und kostenbasierte Nutzungsmetriken", + "cloudBenefitWalkaway": "Verfolge und steuere Aufgaben von überall mit Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Ermöglicht das Verfolgen und Interagieren mit Aufgaben in diesem Arbeitsbereich mit Roo Code Cloud", "visitCloudWebsite": "Roo Code Cloud besuchen" } diff --git a/webview-ui/src/i18n/locales/en/account.json b/webview-ui/src/i18n/locales/en/account.json index f900abb297..a73acef432 100644 --- a/webview-ui/src/i18n/locales/en/account.json +++ b/webview-ui/src/i18n/locales/en/account.json @@ -4,11 +4,13 @@ "logOut": "Log out", "testApiAuthentication": "Test API Authentication", "signIn": "Connect to Roo Code Cloud", - "connect": "Connect", + "connect": "Connect Now", "cloudBenefitsTitle": "Connect to Roo Code Cloud", - "cloudBenefitsSubtitle": "Sync your prompts and telemetry to enable:", - "cloudBenefitHistory": "Online task history", - "cloudBenefitSharing": "Sharing and collaboration features", - "cloudBenefitMetrics": "Task, token, and cost-based usage metrics", - "visitCloudWebsite": "Visit Roo Code Cloud" + "cloudBenefitWalkaway": "Follow and control tasks from anywhere with Roomote Control", + "cloudBenefitSharing": "Share tasks with others", + "cloudBenefitHistory": "Access your task history", + "cloudBenefitMetrics": "Get a holistic view of your token consumption", + "visitCloudWebsite": "Visit Roo Code Cloud", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Enable following and interacting with tasks in this workspace with Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index a48213110a..b20482d1b2 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -686,7 +686,7 @@ "name": "Enable concurrent file edits", "description": "When enabled, Roo can edit multiple files in a single request. When disabled, Roo must edit files one at a time. Disabling this can help when working with less capable models or when you want more control over file modifications." }, -"PREVENT_FOCUS_DISRUPTION": { + "PREVENT_FOCUS_DISRUPTION": { "name": "Background editing", "description": "Prevent editor focus disruption when enabled. File edits happen in the background without opening diff views or stealing focus. You can continue working uninterrupted while Roo makes changes. Files can be opened without focus to capture diagnostics or kept closed entirely." }, diff --git a/webview-ui/src/i18n/locales/es/account.json b/webview-ui/src/i18n/locales/es/account.json index 2bda10e82f..c8398ae25a 100644 --- a/webview-ui/src/i18n/locales/es/account.json +++ b/webview-ui/src/i18n/locales/es/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Historial de tareas en línea", "cloudBenefitSharing": "Funciones de compartir y colaboración", "cloudBenefitMetrics": "Métricas de uso basadas en tareas, tokens y costos", + "cloudBenefitWalkaway": "Sigue y controla tareas desde cualquier lugar con Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Permite seguir e interactuar con tareas en este espacio de trabajo con Roo Code Cloud", "visitCloudWebsite": "Visitar Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/fr/account.json b/webview-ui/src/i18n/locales/fr/account.json index 1af4483c5c..e50d11af15 100644 --- a/webview-ui/src/i18n/locales/fr/account.json +++ b/webview-ui/src/i18n/locales/fr/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Historique des tâches en ligne", "cloudBenefitSharing": "Fonctionnalités de partage et collaboration", "cloudBenefitMetrics": "Métriques d'utilisation basées sur les tâches, tokens et coûts", + "cloudBenefitWalkaway": "Suivez et contrôlez les tâches depuis n'importe où avec Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Permet de suivre et d'interagir avec les tâches dans cet espace de travail avec Roo Code Cloud", "visitCloudWebsite": "Visiter Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/hi/account.json b/webview-ui/src/i18n/locales/hi/account.json index be6ea00d88..485bc00633 100644 --- a/webview-ui/src/i18n/locales/hi/account.json +++ b/webview-ui/src/i18n/locales/hi/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "ऑनलाइन कार्य इतिहास", "cloudBenefitSharing": "साझाकरण और सहयोग सुविधाएं", "cloudBenefitMetrics": "कार्य, token और लागत आधारित उपयोग मेट्रिक्स", + "cloudBenefitWalkaway": "Roomote Control के साथ कहीं से भी कार्यों को फॉलो और नियंत्रित करें", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Roo Code Cloud के साथ इस वर्कस्पेस में कार्यों को फॉलो और इंटरैक्ट करने की सुविधा दें", "visitCloudWebsite": "Roo Code Cloud पर जाएं" } diff --git a/webview-ui/src/i18n/locales/id/account.json b/webview-ui/src/i18n/locales/id/account.json index 57f3fec0df..a3b6f4b97e 100644 --- a/webview-ui/src/i18n/locales/id/account.json +++ b/webview-ui/src/i18n/locales/id/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Riwayat tugas online", "cloudBenefitSharing": "Fitur berbagi dan kolaborasi", "cloudBenefitMetrics": "Metrik penggunaan berdasarkan tugas, token, dan biaya", + "cloudBenefitWalkaway": "Ikuti dan kontrol tugas dari mana saja dengan Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Memungkinkan mengikuti dan berinteraksi dengan tugas di workspace ini dengan Roo Code Cloud", "visitCloudWebsite": "Kunjungi Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/it/account.json b/webview-ui/src/i18n/locales/it/account.json index fda13f563c..7ffb569407 100644 --- a/webview-ui/src/i18n/locales/it/account.json +++ b/webview-ui/src/i18n/locales/it/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Cronologia attività online", "cloudBenefitSharing": "Funzionalità di condivisione e collaborazione", "cloudBenefitMetrics": "Metriche di utilizzo basate su attività, token e costi", + "cloudBenefitWalkaway": "Segui e controlla le attività da qualsiasi luogo con Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Abilita il monitoraggio e l'interazione con le attività in questo workspace con Roo Code Cloud", "visitCloudWebsite": "Visita Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/ja/account.json b/webview-ui/src/i18n/locales/ja/account.json index b41eaf7895..331d613f9b 100644 --- a/webview-ui/src/i18n/locales/ja/account.json +++ b/webview-ui/src/i18n/locales/ja/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "オンラインタスク履歴", "cloudBenefitSharing": "共有とコラボレーション機能", "cloudBenefitMetrics": "タスク、Token、コストベースの使用メトリクス", + "cloudBenefitWalkaway": "Roomote Controlでどこからでもタスクをフォローし制御", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Roo Code Cloudでこのワークスペースのタスクをフォローし操作することを有効にする", "visitCloudWebsite": "Roo Code Cloudを訪問" } diff --git a/webview-ui/src/i18n/locales/ko/account.json b/webview-ui/src/i18n/locales/ko/account.json index 6ad06d43fa..98b09b6e3d 100644 --- a/webview-ui/src/i18n/locales/ko/account.json +++ b/webview-ui/src/i18n/locales/ko/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "온라인 작업 기록", "cloudBenefitSharing": "공유 및 협업 기능", "cloudBenefitMetrics": "작업, 토큰, 비용 기반 사용 메트릭", + "cloudBenefitWalkaway": "Roomote Control로 어디서나 작업을 팔로우하고 제어하세요", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Roo Code Cloud로 이 워크스페이스의 작업을 팔로우하고 상호작용할 수 있게 합니다", "visitCloudWebsite": "Roo Code Cloud 방문" } diff --git a/webview-ui/src/i18n/locales/nl/account.json b/webview-ui/src/i18n/locales/nl/account.json index 15ceb1865b..94d08b4409 100644 --- a/webview-ui/src/i18n/locales/nl/account.json +++ b/webview-ui/src/i18n/locales/nl/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Online taakgeschiedenis", "cloudBenefitSharing": "Deel- en samenwerkingsfuncties", "cloudBenefitMetrics": "Taak-, token- en kostengebaseerde gebruiksstatistieken", + "cloudBenefitWalkaway": "Volg en beheer taken van overal met Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Schakel het volgen en interacteren met taken in deze workspace in met Roo Code Cloud", "visitCloudWebsite": "Bezoek Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/pl/account.json b/webview-ui/src/i18n/locales/pl/account.json index fdb0e4d894..b25f29b1bb 100644 --- a/webview-ui/src/i18n/locales/pl/account.json +++ b/webview-ui/src/i18n/locales/pl/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Historia zadań online", "cloudBenefitSharing": "Funkcje udostępniania i współpracy", "cloudBenefitMetrics": "Metryki użycia oparte na zadaniach, tokenach i kosztach", + "cloudBenefitWalkaway": "Śledź i kontroluj zadania z dowolnego miejsca za pomocą Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Umożliwia śledzenie i interakcję z zadaniami w tym obszarze roboczym za pomocą Roo Code Cloud", "visitCloudWebsite": "Odwiedź Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/pt-BR/account.json b/webview-ui/src/i18n/locales/pt-BR/account.json index 5492ca7520..5b4f457b99 100644 --- a/webview-ui/src/i18n/locales/pt-BR/account.json +++ b/webview-ui/src/i18n/locales/pt-BR/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Histórico de tarefas online", "cloudBenefitSharing": "Recursos de compartilhamento e colaboração", "cloudBenefitMetrics": "Métricas de uso baseadas em tarefas, tokens e custos", + "cloudBenefitWalkaway": "Acompanhe e controle tarefas de qualquer lugar com Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Permite acompanhar e interagir com tarefas neste workspace com Roo Code Cloud", "visitCloudWebsite": "Visitar Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/ru/account.json b/webview-ui/src/i18n/locales/ru/account.json index 1c8dcf5289..4f4a2de167 100644 --- a/webview-ui/src/i18n/locales/ru/account.json +++ b/webview-ui/src/i18n/locales/ru/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Онлайн-история задач", "cloudBenefitSharing": "Функции обмена и совместной работы", "cloudBenefitMetrics": "Метрики использования на основе задач, токенов и затрат", + "cloudBenefitWalkaway": "Отслеживайте и управляйте задачами откуда угодно с Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Позволяет отслеживать и взаимодействовать с задачами в этом рабочем пространстве с Roo Code Cloud", "visitCloudWebsite": "Посетить Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/tr/account.json b/webview-ui/src/i18n/locales/tr/account.json index a344ce940f..03131e3fb5 100644 --- a/webview-ui/src/i18n/locales/tr/account.json +++ b/webview-ui/src/i18n/locales/tr/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Çevrimiçi görev geçmişi", "cloudBenefitSharing": "Paylaşım ve işbirliği özellikleri", "cloudBenefitMetrics": "Görev, token ve maliyet tabanlı kullanım metrikleri", + "cloudBenefitWalkaway": "Roomote Control ile görevleri her yerden takip et ve kontrol et", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Bu çalışma alanındaki görevleri Roo Code Cloud ile takip etme ve etkileşim kurma imkanı sağlar", "visitCloudWebsite": "Roo Code Cloud'u ziyaret et" } diff --git a/webview-ui/src/i18n/locales/vi/account.json b/webview-ui/src/i18n/locales/vi/account.json index 0e826b75ad..3224160ba3 100644 --- a/webview-ui/src/i18n/locales/vi/account.json +++ b/webview-ui/src/i18n/locales/vi/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "Lịch sử tác vụ trực tuyến", "cloudBenefitSharing": "Tính năng chia sẻ và cộng tác", "cloudBenefitMetrics": "Số liệu sử dụng dựa trên tác vụ, token và chi phí", + "cloudBenefitWalkaway": "Theo dõi và điều khiển tác vụ từ bất kỳ đâu với Roomote Control", + "remoteControl": "Roomote Control", + "remoteControlDescription": "Cho phép theo dõi và tương tác với các tác vụ trong workspace này với Roo Code Cloud", "visitCloudWebsite": "Truy cập Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/zh-CN/account.json b/webview-ui/src/i18n/locales/zh-CN/account.json index 65a4c1d221..9e097472a0 100644 --- a/webview-ui/src/i18n/locales/zh-CN/account.json +++ b/webview-ui/src/i18n/locales/zh-CN/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "在线任务历史", "cloudBenefitSharing": "共享和协作功能", "cloudBenefitMetrics": "基于任务、Token 和成本的使用指标", + "cloudBenefitWalkaway": "使用 Roomote Control 随时随地跟踪和控制任务", + "remoteControl": "Roomote Control", + "remoteControlDescription": "允许通过 Roo Code Cloud 跟踪和操作此工作区中的任务", "visitCloudWebsite": "访问 Roo Code Cloud" } diff --git a/webview-ui/src/i18n/locales/zh-TW/account.json b/webview-ui/src/i18n/locales/zh-TW/account.json index dca8d3231c..edd25dcf18 100644 --- a/webview-ui/src/i18n/locales/zh-TW/account.json +++ b/webview-ui/src/i18n/locales/zh-TW/account.json @@ -10,5 +10,8 @@ "cloudBenefitHistory": "線上工作歷史", "cloudBenefitSharing": "分享和協作功能", "cloudBenefitMetrics": "基於工作、Token 和成本的使用指標", + "cloudBenefitWalkaway": "使用 Roomote Control 隨時隨地追蹤和控制工作", + "remoteControl": "Roomote Control", + "remoteControlDescription": "允許透過 Roo Code Cloud 追蹤和操作此工作區中的工作", "visitCloudWebsite": "造訪 Roo Code Cloud" }