feat: separate Task Sync and Roomote Control settings

- Add new taskSyncEnabled setting to control task content syncing
- Keep remoteControlEnabled for Roomote Control functionality
- Task Sync controls whether task content is sent to cloud
- Roomote Control controls whether cloud can send instructions back
- Roomote Control now depends on Task Sync being enabled
- Usage metrics (tokens, cost) always reported regardless of settings
- Update UI with two separate toggles and clear descriptions
- Add info text explaining usage metrics are always reported
This commit is contained in:
Roo Code 2025-09-03 19:00:25 +00:00
parent d7896924a9
commit aa37e379ca
9 changed files with 103 additions and 21 deletions

View file

@ -162,6 +162,7 @@ export type UserFeatures = z.infer<typeof userFeaturesSchema>
export const userSettingsConfigSchema = z.object({
extensionBridgeEnabled: z.boolean().optional(),
taskSyncEnabled: z.boolean().optional(),
})
export type UserSettingsConfig = z.infer<typeof userSettingsConfigSchema>

View file

@ -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",

View file

@ -607,7 +607,11 @@ export class Task extends EventEmitter<TaskEvents> 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<TaskEvents> 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({

View file

@ -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()

View file

@ -278,6 +278,7 @@ export type ExtensionState = Pick<
| "includeDiagnosticMessages"
| "maxDiagnosticMessages"
| "remoteControlEnabled"
| "taskSyncEnabled"
| "openRouterImageGenerationSelectedModel"
| "includeTaskHistoryInEnhance"
> & {

View file

@ -134,6 +134,7 @@ export interface WebviewMessage {
| "mcpEnabled"
| "enableMcpServerCreation"
| "remoteControlEnabled"
| "taskSyncEnabled"
| "searchCommits"
| "alwaysApproveResubmit"
| "requestDelaySeconds"

View file

@ -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 (
<div className="flex flex-col h-full">
<div className="flex justify-between items-center mb-6">
@ -121,24 +132,56 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
</div>
)}
{userInfo?.extensionBridgeEnabled && (
<div className="border-t border-vscode-widget-border pt-4 mt-4">
<div className="flex items-center gap-3 mb-2">
<ToggleSwitch
checked={remoteControlEnabled}
onChange={handleRemoteControlToggle}
size="medium"
aria-label={t("cloud:remoteControl")}
data-testid="remote-control-toggle"
/>
<span className="font-medium text-vscode-foreground">{t("cloud:remoteControl")}</span>
</div>
<div className="text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8">
{t("cloud:remoteControlDescription")}
</div>
<hr className="border-vscode-widget-border mb-4" />
{/* Task Sync Toggle - Always shown when authenticated */}
<div className="border-t border-vscode-widget-border pt-4 mt-4">
<div className="flex items-center gap-3 mb-2">
<ToggleSwitch
checked={taskSyncEnabled}
onChange={handleTaskSyncToggle}
size="medium"
aria-label={t("cloud:taskSync")}
data-testid="task-sync-toggle"
/>
<span className="font-medium text-vscode-foreground">{t("cloud:taskSync")}</span>
</div>
)}
<div className="text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8">
{t("cloud:taskSyncDescription")}
</div>
{/* Remote Control Toggle - Only shown when extensionBridgeEnabled is true */}
{userInfo?.extensionBridgeEnabled && (
<>
<div className="flex items-center gap-3 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-4 ml-8">
{t("cloud:remoteControlDescription")}
{!taskSyncEnabled && (
<div className="text-vscode-errorForeground mt-2">
{t("cloud:remoteControlRequiresTaskSync")}
</div>
)}
</div>
</>
)}
{/* Info text about usage metrics */}
<div className="text-vscode-descriptionForeground text-sm mt-4 mb-4 ml-8 italic">
{t("cloud:usageMetricsAlwaysReported")}
</div>
<hr className="border-vscode-widget-border mb-4" />
</div>
<div className="flex flex-col gap-2 mt-4">
<VSCodeButton appearance="secondary" onClick={handleVisitCloudWebsite} className="w-full">

View file

@ -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 })),

View file

@ -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"
}