diff --git a/packages/types/src/cloud.ts b/packages/types/src/cloud.ts index ed5bb4e187..9413196ccc 100644 --- a/packages/types/src/cloud.ts +++ b/packages/types/src/cloud.ts @@ -162,6 +162,7 @@ export type UserFeatures = z.infer export const userSettingsConfigSchema = z.object({ extensionBridgeEnabled: z.boolean().optional(), + taskSyncEnabled: z.boolean().optional(), }) export type UserSettingsConfig = z.infer diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 81c6ae6dfe..de360a9f1f 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -139,6 +139,7 @@ export const globalSettingsSchema = z.object({ enableMcpServerCreation: z.boolean().optional(), remoteControlEnabled: z.boolean().optional(), + taskSyncEnabled: z.boolean().optional(), mode: z.string().optional(), modeApiConfigs: z.record(z.string(), z.string()).optional(), @@ -316,6 +317,7 @@ export const EVALS_SETTINGS: RooCodeSettings = { mcpEnabled: false, remoteControlEnabled: false, + taskSyncEnabled: true, // Default to true for backward compatibility mode: "code", // "architect", diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 9259bae761..5daadd5e25 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -607,7 +607,11 @@ export class Task extends EventEmitter implements TaskLike { this.emit(RooCodeEventName.Message, { action: "created", message }) await this.saveClineMessages() - const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled() + // Check if we should capture the message + // Only capture if: not partial, cloud is enabled, and taskSyncEnabled is true + const state = await this.providerRef.deref()?.getState() + const taskSyncEnabled = state?.taskSyncEnabled ?? true // Default to true for backward compatibility + const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled() && taskSyncEnabled if (shouldCaptureMessage) { CloudService.instance.captureEvent({ @@ -640,7 +644,11 @@ export class Task extends EventEmitter implements TaskLike { await provider?.postMessageToWebview({ type: "messageUpdated", clineMessage: message }) this.emit(RooCodeEventName.Message, { action: "updated", message }) - const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled() + // Check if we should capture the message + // Only capture if: not partial, cloud is enabled, and taskSyncEnabled is true + const state = await this.providerRef.deref()?.getState() + const taskSyncEnabled = state?.taskSyncEnabled ?? true // Default to true for backward compatibility + const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled() && taskSyncEnabled if (shouldCaptureMessage) { CloudService.instance.captureEvent({ diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index b9a30708dd..517b6d65cf 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -965,6 +965,17 @@ export const webviewMessageHandler = async ( await provider.remoteControlEnabled(message.bool ?? false) await provider.postStateToWebview() break + case "taskSyncEnabled": + try { + await CloudService.instance.updateUserSettings({ + taskSyncEnabled: message.bool ?? true, + }) + } catch (error) { + provider.log(`Failed to update cloud settings for task sync: ${error}`) + } + await updateGlobalState("taskSyncEnabled", message.bool ?? true) + await provider.postStateToWebview() + break case "refreshAllMcpServers": { const mcpHub = provider.getMcpHub() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index d4caf2f674..649f4c92db 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -278,6 +278,7 @@ export type ExtensionState = Pick< | "includeDiagnosticMessages" | "maxDiagnosticMessages" | "remoteControlEnabled" + | "taskSyncEnabled" | "openRouterImageGenerationSelectedModel" | "includeTaskHistoryInEnhance" > & { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 1202f48a21..68fda3dc2c 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -134,6 +134,7 @@ export interface WebviewMessage { | "mcpEnabled" | "enableMcpServerCreation" | "remoteControlEnabled" + | "taskSyncEnabled" | "searchCommits" | "alwaysApproveResubmit" | "requestDelaySeconds" diff --git a/webview-ui/src/components/cloud/CloudView.tsx b/webview-ui/src/components/cloud/CloudView.tsx index 63733ef7d2..242084d4b6 100644 --- a/webview-ui/src/components/cloud/CloudView.tsx +++ b/webview-ui/src/components/cloud/CloudView.tsx @@ -23,7 +23,7 @@ type CloudViewProps = { export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: CloudViewProps) => { const { t } = useAppTranslation() - const { remoteControlEnabled, setRemoteControlEnabled } = useExtensionState() + const { remoteControlEnabled, setRemoteControlEnabled, taskSyncEnabled, setTaskSyncEnabled } = useExtensionState() const wasAuthenticatedRef = useRef(false) const rooLogoUri = (window as any).IMAGES_BASE_URI + "/roo-logo.svg" @@ -75,6 +75,17 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl vscode.postMessage({ type: "remoteControlEnabled", bool: newValue }) } + const handleTaskSyncToggle = () => { + const newValue = !taskSyncEnabled + setTaskSyncEnabled(newValue) + vscode.postMessage({ type: "taskSyncEnabled", bool: newValue }) + // If disabling task sync, also disable remote control + if (!newValue && remoteControlEnabled) { + setRemoteControlEnabled(false) + vscode.postMessage({ type: "remoteControlEnabled", bool: false }) + } + } + return (
@@ -121,24 +132,56 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
)} - {userInfo?.extensionBridgeEnabled && ( -
-
- - {t("cloud:remoteControl")} -
-
- {t("cloud:remoteControlDescription")} -
-
+ {/* Task Sync Toggle - Always shown when authenticated */} +
+
+ + {t("cloud:taskSync")}
- )} +
+ {t("cloud:taskSyncDescription")} +
+ + {/* Remote Control Toggle - Only shown when extensionBridgeEnabled is true */} + {userInfo?.extensionBridgeEnabled && ( + <> +
+ + + {t("cloud:remoteControl")} + +
+
+ {t("cloud:remoteControlDescription")} + {!taskSyncEnabled && ( +
+ {t("cloud:remoteControlRequiresTaskSync")} +
+ )} +
+ + )} + + {/* Info text about usage metrics */} +
+ {t("cloud:usageMetricsAlwaysReported")} +
+ +
+
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 2f4af84f58..ea5448bd14 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -101,6 +101,8 @@ export interface ExtensionStateContextType extends ExtensionState { setEnableMcpServerCreation: (value: boolean) => void remoteControlEnabled: boolean setRemoteControlEnabled: (value: boolean) => void + taskSyncEnabled: boolean + setTaskSyncEnabled: (value: boolean) => void alwaysApproveResubmit?: boolean setAlwaysApproveResubmit: (value: boolean) => void requestDelaySeconds: number @@ -299,6 +301,13 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setState((prevState) => mergeExtensionState(prevState, newState)) setShowWelcome(!checkExistKey(newState.apiConfiguration)) setDidHydrateState(true) + // Update taskSyncEnabled if present in state message + if ((newState as any).taskSyncEnabled !== undefined) { + setState( + (prevState) => + ({ ...prevState, taskSyncEnabled: (newState as any).taskSyncEnabled }) as any, + ) + } // Update alwaysAllowFollowupQuestions if present in state message if ((newState as any).alwaysAllowFollowupQuestions !== undefined) { setAlwaysAllowFollowupQuestions((newState as any).alwaysAllowFollowupQuestions) @@ -417,6 +426,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode alwaysAllowFollowupQuestions, followupAutoApproveTimeoutMs, remoteControlEnabled: state.remoteControlEnabled ?? false, + taskSyncEnabled: (state as any).taskSyncEnabled ?? true, setExperimentEnabled: (id, enabled) => setState((prevState) => ({ ...prevState, experiments: { ...prevState.experiments, [id]: enabled } })), setApiConfiguration, @@ -464,6 +474,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setEnableMcpServerCreation: (value) => setState((prevState) => ({ ...prevState, enableMcpServerCreation: value })), setRemoteControlEnabled: (value) => setState((prevState) => ({ ...prevState, remoteControlEnabled: value })), + setTaskSyncEnabled: (value) => setState((prevState) => ({ ...prevState, taskSyncEnabled: value }) as any), 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/en/cloud.json b/webview-ui/src/i18n/locales/en/cloud.json index 88948c9153..0b629ed873 100644 --- a/webview-ui/src/i18n/locales/en/cloud.json +++ b/webview-ui/src/i18n/locales/en/cloud.json @@ -11,7 +11,11 @@ "cloudBenefitHistory": "Access your task history", "cloudBenefitMetrics": "Get a holistic view of your token consumption", "visitCloudWebsite": "Visit Roo Code Cloud", + "taskSync": "Task sync", + "taskSyncDescription": "Sync your tasks for viewing and sharing on Roo Code Cloud", "remoteControl": "Roomote Control", - "remoteControlDescription": "Enable following and interacting with tasks in this workspace with Roo Code Cloud", + "remoteControlDescription": "Allow controlling tasks from Roo Code Cloud", + "remoteControlRequiresTaskSync": "Task sync must be enabled to use Roomote Control", + "usageMetricsAlwaysReported": "Model usage info is always reported when logged in", "cloudUrlPillLabel": "Roo Code Cloud URL" }