Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/types/src/cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -316,6 +317,7 @@ export const EVALS_SETTINGS: RooCodeSettings = {
mcpEnabled: false,

remoteControlEnabled: false,
taskSyncEnabled: true, // Default to true for backward compatibility

mode: "code", // "architect",

Expand Down
12 changes: 10 additions & 2 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional? We're fetching the provider state twice in quick succession (lines 612 and 649). Could we cache the state at the beginning of the method to avoid potential race conditions where providerRef.deref() might return undefined between operations?

const taskSyncEnabled = state?.taskSyncEnabled ?? true // Default to true for backward compatibility
const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled() && taskSyncEnabled

if (shouldCaptureMessage) {
CloudService.instance.captureEvent({
Expand Down Expand Up @@ -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({
Expand Down
11 changes: 11 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error is logged but the local state is still updated even if the cloud update fails. Should we consider showing a user-facing error message or reverting the local state to maintain consistency between local and cloud settings?

await provider.postStateToWebview()
break
case "refreshAllMcpServers": {
const mcpHub = provider.getMcpHub()

Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ export type ExtensionState = Pick<
| "includeDiagnosticMessages"
| "maxDiagnosticMessages"
| "remoteControlEnabled"
| "taskSyncEnabled"
| "openRouterImageGenerationSelectedModel"
| "includeTaskHistoryInEnhance"
> & {
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export interface WebviewMessage {
| "mcpEnabled"
| "enableMcpServerCreation"
| "remoteControlEnabled"
| "taskSyncEnabled"
| "searchCommits"
| "alwaysApproveResubmit"
| "requestDelaySeconds"
Expand Down
79 changes: 61 additions & 18 deletions webview-ui/src/components/cloud/CloudView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When disabling Task Sync automatically disables Roomote Control, there's no visual feedback to inform the user that both settings were changed. Consider adding a brief toast notification or inline message to make this behavior more transparent.

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">
Expand Down Expand Up @@ -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">
Expand Down
11 changes: 11 additions & 0 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 })),
Expand Down
6 changes: 5 additions & 1 deletion webview-ui/src/i18n/locales/en/cloud.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Loading