Sfoglia il codice sorgente

Cloud account switcher (#8223)

* Cloud account switcher

* Bare metal evals fixes (#8224)

Co-authored-by: Roo Code <[email protected]>

* Rounded icons in chat account switcher

* Visual tweaks to CloudView

* Remove hardcoded timeout

* Safer check

* PR feedback

* Fix test

* Check for org mismatches in handleCredentialsChange

* Cloud: use the existing auth event flow to handle org switching

* Cleanup: broadcast() might be confusingly named.

---------

Co-authored-by: Chris Estreich <[email protected]>
Co-authored-by: Roo Code <[email protected]>
Co-authored-by: Bruno Bergher <[email protected]>
Co-authored-by: John Richmond <[email protected]>
Matt Rubens 3 mesi fa
parent
commit
0682629ac8
33 ha cambiato i file con 528 aggiunte e 44 eliminazioni
  1. 16 0
      packages/cloud/src/CloudService.ts
  2. 8 0
      packages/cloud/src/StaticTokenAuthService.ts
  3. 45 2
      packages/cloud/src/WebAuthService.ts
  4. 4 0
      packages/types/src/cloud.ts
  5. 12 0
      src/core/webview/ClineProvider.ts
  6. 32 0
      src/core/webview/webviewMessageHandler.ts
  7. 4 0
      src/shared/ExtensionMessage.ts
  8. 2 0
      src/shared/WebviewMessage.ts
  9. 2 0
      webview-ui/src/App.tsx
  10. 13 1
      webview-ui/src/components/chat/ChatTextArea.tsx
  11. 149 0
      webview-ui/src/components/cloud/CloudAccountSwitcher.tsx
  12. 46 39
      webview-ui/src/components/cloud/CloudView.tsx
  13. 146 0
      webview-ui/src/components/cloud/OrganizationSwitcher.tsx
  14. 9 2
      webview-ui/src/components/cloud/__tests__/CloudView.spec.tsx
  15. 4 0
      webview-ui/src/context/ExtensionStateContext.tsx
  16. 2 0
      webview-ui/src/i18n/locales/ca/cloud.json
  17. 2 0
      webview-ui/src/i18n/locales/de/cloud.json
  18. 2 0
      webview-ui/src/i18n/locales/en/cloud.json
  19. 2 0
      webview-ui/src/i18n/locales/es/cloud.json
  20. 2 0
      webview-ui/src/i18n/locales/fr/cloud.json
  21. 2 0
      webview-ui/src/i18n/locales/hi/cloud.json
  22. 2 0
      webview-ui/src/i18n/locales/id/cloud.json
  23. 2 0
      webview-ui/src/i18n/locales/it/cloud.json
  24. 2 0
      webview-ui/src/i18n/locales/ja/cloud.json
  25. 2 0
      webview-ui/src/i18n/locales/ko/cloud.json
  26. 2 0
      webview-ui/src/i18n/locales/nl/cloud.json
  27. 2 0
      webview-ui/src/i18n/locales/pl/cloud.json
  28. 2 0
      webview-ui/src/i18n/locales/pt-BR/cloud.json
  29. 2 0
      webview-ui/src/i18n/locales/ru/cloud.json
  30. 2 0
      webview-ui/src/i18n/locales/tr/cloud.json
  31. 2 0
      webview-ui/src/i18n/locales/vi/cloud.json
  32. 2 0
      webview-ui/src/i18n/locales/zh-CN/cloud.json
  33. 2 0
      webview-ui/src/i18n/locales/zh-TW/cloud.json

+ 16 - 0
packages/cloud/src/CloudService.ts

@@ -8,6 +8,7 @@ import type {
 	AuthService,
 	SettingsService,
 	CloudUserInfo,
+	CloudOrganizationMembership,
 	OrganizationAllowList,
 	OrganizationSettings,
 	ShareVisibility,
@@ -242,6 +243,21 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements Di
 		return this.authService!.handleCallback(code, state, organizationId)
 	}
 
+	public async switchOrganization(organizationId: string | null): Promise<void> {
+		this.ensureInitialized()
+
+		// Perform the organization switch
+		// StaticTokenAuthService will throw an error if organization switching is not supported
+		await this.authService!.switchOrganization(organizationId)
+	}
+
+	public async getOrganizationMemberships(): Promise<CloudOrganizationMembership[]> {
+		this.ensureInitialized()
+
+		// StaticTokenAuthService will throw an error if organization memberships are not supported
+		return await this.authService!.getOrganizationMemberships()
+	}
+
 	// SettingsService
 
 	public getAllowList(): OrganizationAllowList {

+ 8 - 0
packages/cloud/src/StaticTokenAuthService.ts

@@ -63,6 +63,14 @@ export class StaticTokenAuthService extends EventEmitter<AuthServiceEvents> impl
 		throw new Error("Authentication methods are disabled in StaticTokenAuthService")
 	}
 
+	public async switchOrganization(_organizationId: string | null): Promise<void> {
+		throw new Error("Authentication methods are disabled in StaticTokenAuthService")
+	}
+
+	public async getOrganizationMemberships(): Promise<import("@roo-code/types").CloudOrganizationMembership[]> {
+		throw new Error("Authentication methods are disabled in StaticTokenAuthService")
+	}
+
 	public getState(): AuthState {
 		return this.state
 	}

+ 45 - 2
packages/cloud/src/WebAuthService.ts

@@ -141,7 +141,8 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
 				if (
 					this.credentials === null ||
 					this.credentials.clientToken !== credentials.clientToken ||
-					this.credentials.sessionId !== credentials.sessionId
+					this.credentials.sessionId !== credentials.sessionId ||
+					this.credentials.organizationId !== credentials.organizationId
 				) {
 					this.transitionToAttemptingSession(credentials)
 				}
@@ -174,6 +175,7 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
 
 		this.changeState("attempting-session")
 
+		this.timer.stop()
 		this.timer.start()
 	}
 
@@ -469,6 +471,42 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
 		return this.credentials?.organizationId || null
 	}
 
+	/**
+	 * Switch to a different organization context
+	 * @param organizationId The organization ID to switch to, or null for personal account
+	 */
+	public async switchOrganization(organizationId: string | null): Promise<void> {
+		if (!this.credentials) {
+			throw new Error("Cannot switch organization: not authenticated")
+		}
+
+		// Update the stored credentials with the new organization ID
+		const updatedCredentials: AuthCredentials = {
+			...this.credentials,
+			organizationId: organizationId,
+		}
+
+		// Store the updated credentials, handleCredentialsChange will handle the update
+		await this.storeCredentials(updatedCredentials)
+	}
+
+	/**
+	 * Get all organization memberships for the current user
+	 * @returns Array of organization memberships
+	 */
+	public async getOrganizationMemberships(): Promise<CloudOrganizationMembership[]> {
+		if (!this.credentials) {
+			return []
+		}
+
+		try {
+			return await this.clerkGetOrganizationMemberships()
+		} catch (error) {
+			this.log(`[auth] Failed to get organization memberships: ${error}`)
+			return []
+		}
+	}
+
 	private async clerkSignIn(ticket: string): Promise<AuthCredentials> {
 		const formData = new URLSearchParams()
 		formData.append("strategy", "ticket")
@@ -653,9 +691,14 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
 	}
 
 	private async clerkGetOrganizationMemberships(): Promise<CloudOrganizationMembership[]> {
+		if (!this.credentials) {
+			this.log("[auth] Cannot get organization memberships: missing credentials")
+			return []
+		}
+
 		const response = await fetch(`${getClerkBaseUrl()}/v1/me/organization_memberships`, {
 			headers: {
-				Authorization: `Bearer ${this.credentials!.clientToken}`,
+				Authorization: `Bearer ${this.credentials.clientToken}`,
 				"User-Agent": this.userAgent(),
 			},
 			signal: AbortSignal.timeout(10000),

+ 4 - 0
packages/types/src/cloud.ts

@@ -242,6 +242,7 @@ export interface AuthService extends EventEmitter<AuthServiceEvents> {
 	login(landingPageSlug?: string): Promise<void>
 	logout(): Promise<void>
 	handleCallback(code: string | null, state: string | null, organizationId?: string | null): Promise<void>
+	switchOrganization(organizationId: string | null): Promise<void>
 
 	// State methods
 	getState(): AuthState
@@ -253,6 +254,9 @@ export interface AuthService extends EventEmitter<AuthServiceEvents> {
 	getSessionToken(): string | undefined
 	getUserInfo(): CloudUserInfo | null
 	getStoredOrganizationId(): string | null
+
+	// Organization management
+	getOrganizationMemberships(): Promise<CloudOrganizationMembership[]>
 }
 
 /**

+ 12 - 0
src/core/webview/ClineProvider.ts

@@ -30,6 +30,7 @@ import {
 	type TerminalActionPromptType,
 	type HistoryItem,
 	type CloudUserInfo,
+	type CloudOrganizationMembership,
 	type CreateTaskOptions,
 	type TokenUsage,
 	RooCodeEventName,
@@ -1815,6 +1816,16 @@ export class ClineProvider
 			featureRoomoteControlEnabled,
 		} = await this.getState()
 
+		let cloudOrganizations: CloudOrganizationMembership[] = []
+
+		try {
+			cloudOrganizations = await CloudService.instance.getOrganizationMemberships()
+		} catch (error) {
+			console.error(
+				`[getStateToPostToWebview] failed to get cloud organizations: ${error instanceof Error ? error.message : String(error)}`,
+			)
+		}
+
 		const telemetryKey = process.env.POSTHOG_API_KEY
 		const machineId = vscode.env.machineId
 		const mergedAllowedCommands = this.mergeAllowedCommands(allowedCommands)
@@ -1916,6 +1927,7 @@ export class ClineProvider
 			historyPreviewCollapsed: historyPreviewCollapsed ?? false,
 			cloudUserInfo,
 			cloudIsAuthenticated: cloudIsAuthenticated ?? false,
+			cloudOrganizations,
 			sharingEnabled: sharingEnabled ?? false,
 			organizationAllowList,
 			organizationSettingsVersion,

+ 32 - 0
src/core/webview/webviewMessageHandler.ts

@@ -2379,6 +2379,38 @@ export const webviewMessageHandler = async (
 
 			break
 		}
+		case "switchOrganization": {
+			try {
+				const organizationId = message.organizationId ?? null
+
+				// Switch to the new organization context
+				await CloudService.instance.switchOrganization(organizationId)
+
+				// Refresh the state to update UI
+				await provider.postStateToWebview()
+
+				// Send success response back to webview
+				await provider.postMessageToWebview({
+					type: "organizationSwitchResult",
+					success: true,
+					organizationId: organizationId,
+				})
+			} catch (error) {
+				provider.log(`Organization switch failed: ${error}`)
+				const errorMessage = error instanceof Error ? error.message : String(error)
+
+				// Send error response back to webview
+				await provider.postMessageToWebview({
+					type: "organizationSwitchResult",
+					success: false,
+					error: errorMessage,
+					organizationId: message.organizationId ?? null,
+				})
+
+				vscode.window.showErrorMessage(`Failed to switch organization: ${errorMessage}`)
+			}
+			break
+		}
 
 		case "saveCodeIndexSettingsAtomic": {
 			if (!message.codeIndexSettings) {

+ 4 - 0
src/shared/ExtensionMessage.ts

@@ -10,6 +10,7 @@ import type {
 	MarketplaceItem,
 	TodoItem,
 	CloudUserInfo,
+	CloudOrganizationMembership,
 	OrganizationAllowList,
 	ShareVisibility,
 	QueuedMessage,
@@ -124,6 +125,7 @@ export interface ExtensionMessage {
 		| "commands"
 		| "insertTextIntoTextarea"
 		| "dismissedUpsells"
+		| "organizationSwitchResult"
 	text?: string
 	payload?: any // Add a generic payload for now, can refine later
 	action?:
@@ -202,6 +204,7 @@ export interface ExtensionMessage {
 	commands?: Command[]
 	queuedMessages?: QueuedMessage[]
 	list?: string[] // For dismissedUpsells
+	organizationId?: string | null // For organizationSwitchResult
 }
 
 export type ExtensionState = Pick<
@@ -327,6 +330,7 @@ export type ExtensionState = Pick<
 	cloudUserInfo: CloudUserInfo | null
 	cloudIsAuthenticated: boolean
 	cloudApiUrl?: string
+	cloudOrganizations?: CloudOrganizationMembership[]
 	sharingEnabled: boolean
 	organizationAllowList: OrganizationAllowList
 	organizationSettingsVersion?: number

+ 2 - 0
src/shared/WebviewMessage.ts

@@ -184,6 +184,7 @@ export interface WebviewMessage {
 		| "cloudLandingPageSignIn"
 		| "rooCloudSignOut"
 		| "rooCloudManualUrl"
+		| "switchOrganization"
 		| "condenseTaskContextRequest"
 		| "requestIndexingStatus"
 		| "startIndexing"
@@ -274,6 +275,7 @@ export interface WebviewMessage {
 	checkOnly?: boolean // For deleteCustomMode check
 	upsellId?: string // For dismissUpsell
 	list?: string[] // For dismissedUpsells response
+	organizationId?: string | null // For organization switching
 	codeIndexSettings?: {
 		// Global state settings
 		codebaseIndexEnabled: boolean

+ 2 - 0
webview-ui/src/App.tsx

@@ -76,6 +76,7 @@ const App = () => {
 		cloudUserInfo,
 		cloudIsAuthenticated,
 		cloudApiUrl,
+		cloudOrganizations,
 		renderContext,
 		mdmCompliant,
 	} = useExtensionState()
@@ -267,6 +268,7 @@ const App = () => {
 					userInfo={cloudUserInfo}
 					isAuthenticated={cloudIsAuthenticated}
 					cloudApiUrl={cloudApiUrl}
+					organizations={cloudOrganizations}
 					onDone={() => switchTab("chat")}
 				/>
 			)}

+ 13 - 1
webview-ui/src/components/chat/ChatTextArea.tsx

@@ -31,6 +31,7 @@ import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
 import ContextMenu from "./ContextMenu"
 import { IndexingStatusBadge } from "./IndexingStatusBadge"
 import { usePromptHistory } from "./hooks/usePromptHistory"
+import { CloudAccountSwitcher } from "../cloud/CloudAccountSwitcher"
 
 interface ChatTextAreaProps {
 	inputValue: string
@@ -87,6 +88,8 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 			taskHistory,
 			clineMessages,
 			commands,
+			cloudUserInfo,
+			cloudOrganizations,
 		} = useExtensionState()
 
 		// Find the ID and display text for the currently selected API configuration.
@@ -1232,7 +1235,13 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 						/>
 						<AutoApproveDropdown triggerClassName="min-w-[28px] text-ellipsis overflow-hidden flex-shrink" />
 					</div>
-					<div className="flex flex-shrink-0 items-center gap-0.5 pr-2">
+					<div
+						className={cn(
+							"flex flex-shrink-0 items-center gap-0.5",
+							!isEditMode && cloudOrganizations && cloudOrganizations.length > 0 && cloudUserInfo
+								? ""
+								: "pr-2",
+						)}>
 						{isTtsPlaying && (
 							<StandardTooltip content={t("chat:stopTts")}>
 								<button
@@ -1254,6 +1263,9 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 							</StandardTooltip>
 						)}
 						{!isEditMode ? <IndexingStatusBadge /> : null}
+						{!isEditMode && cloudOrganizations && cloudOrganizations.length > 0 && cloudUserInfo && (
+							<CloudAccountSwitcher />
+						)}
 					</div>
 				</div>
 			</div>

+ 149 - 0
webview-ui/src/components/cloud/CloudAccountSwitcher.tsx

@@ -0,0 +1,149 @@
+import { useState, useEffect } from "react"
+import { Building2 } from "lucide-react"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectSeparator } from "@/components/ui/select"
+import { StandardTooltip } from "@src/components/ui"
+import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { vscode } from "@src/utils/vscode"
+import { useExtensionState } from "@src/context/ExtensionStateContext"
+import { cn } from "@src/lib/utils"
+
+export const CloudAccountSwitcher = () => {
+	const { t } = useAppTranslation()
+	const { cloudUserInfo, cloudOrganizations = [] } = useExtensionState()
+	const [selectedOrgId, setSelectedOrgId] = useState<string | null>(cloudUserInfo?.organizationId || null)
+	const [isLoading, setIsLoading] = useState(false)
+
+	// Update selected org when userInfo changes
+	useEffect(() => {
+		setSelectedOrgId(cloudUserInfo?.organizationId || null)
+	}, [cloudUserInfo?.organizationId])
+
+	// Don't show the switcher if user has no organizations
+	if (!cloudOrganizations || cloudOrganizations.length === 0 || !cloudUserInfo) {
+		return null
+	}
+
+	const handleOrganizationChange = async (value: string) => {
+		const newOrgId = value === "personal" ? null : value
+
+		// Don't do anything if selecting the same organization
+		if (newOrgId === selectedOrgId) {
+			return
+		}
+
+		setIsLoading(true)
+
+		// Send message to switch organization
+		vscode.postMessage({
+			type: "switchOrganization",
+			organizationId: newOrgId,
+		})
+
+		// Update local state optimistically
+		setSelectedOrgId(newOrgId)
+
+		// Reset loading state after a delay
+		setTimeout(() => {
+			setIsLoading(false)
+		}, 1000)
+	}
+
+	const currentValue = selectedOrgId || "personal"
+	const currentOrg = cloudOrganizations.find((org) => org.organization.id === selectedOrgId)
+
+	// Render the account icon based on current context
+	const renderAccountIcon = () => {
+		if (selectedOrgId && currentOrg?.organization.image_url) {
+			// Organization with logo
+			return (
+				<img
+					src={currentOrg.organization.image_url}
+					alt={currentOrg.organization.name}
+					className="w-5 h-5 rounded object-cover"
+				/>
+			)
+		} else if (selectedOrgId) {
+			// Organization without logo
+			return <Building2 className="w-4.5 h-4.5" />
+		} else if (cloudUserInfo.picture) {
+			// Personal account with avatar
+			return (
+				<img
+					src={cloudUserInfo.picture}
+					alt={cloudUserInfo.name || cloudUserInfo.email}
+					className="w-5 h-5 rounded-full object-cover"
+				/>
+			)
+		} else {
+			// Personal account without avatar - show initials
+			const initial = cloudUserInfo.name?.charAt(0) || cloudUserInfo.email?.charAt(0) || "?"
+			return (
+				<div className="w-5 h-5 rounded-full flex items-center justify-center bg-vscode-button-background text-vscode-button-foreground text-xs">
+					{initial}
+				</div>
+			)
+		}
+	}
+
+	return (
+		<StandardTooltip content={t("cloud:switchAccount")}>
+			<div className="inline-block ml-1">
+				<Select value={currentValue} onValueChange={handleOrganizationChange} disabled={isLoading}>
+					<SelectTrigger
+						className={cn(
+							"h-4.5 w-4.5 p-0 gap-0",
+							"bg-transparent opacity-90 hover:opacity-50",
+							"flex items-center justify-center",
+							"rounded-lg overflow-clip",
+							"border border-vscode-dropdown-border",
+							"[&>svg]:hidden", // Hide the default chevron/caret
+							isLoading && "opacity-50",
+						)}
+						aria-label={selectedOrgId ? currentOrg?.organization.name : t("cloud:personalAccount")}>
+						{renderAccountIcon()}
+					</SelectTrigger>
+
+					<SelectContent>
+						{/* Personal Account Option */}
+						<SelectItem value="personal">
+							<div className="flex items-center gap-2">
+								{cloudUserInfo.picture ? (
+									<img
+										src={cloudUserInfo.picture}
+										alt={cloudUserInfo.name || cloudUserInfo.email}
+										className="w-4.5 h-4.5 rounded-full object-cover overflow-clip"
+									/>
+								) : (
+									<div className="w-4.5 h-4.5 rounded-full flex items-center justify-center bg-vscode-button-background text-vscode-button-foreground text-xs">
+										{cloudUserInfo.name?.charAt(0) || cloudUserInfo.email?.charAt(0) || "?"}
+									</div>
+								)}
+								<span>{t("cloud:personalAccount")}</span>
+							</div>
+						</SelectItem>
+
+						{cloudOrganizations.length > 0 && <SelectSeparator />}
+
+						{/* Organization Options */}
+						{cloudOrganizations.map((org) => (
+							<SelectItem key={org.organization.id} value={org.organization.id}>
+								<div className="flex items-center gap-2">
+									{org.organization.image_url ? (
+										<img
+											src={org.organization.image_url}
+											alt=""
+											className="w-4.5 h-4.5 rounded-full object-cover overflow-clip"
+										/>
+									) : (
+										<Building2 className="w-4.5 h-4.5" />
+									)}
+									<span className="truncate">{org.organization.name}</span>
+								</div>
+							</SelectItem>
+						))}
+					</SelectContent>
+				</Select>
+			</div>
+		</StandardTooltip>
+	)
+}

+ 46 - 39
webview-ui/src/components/cloud/CloudView.tsx

@@ -1,7 +1,7 @@
 import { useEffect, useRef, useState } from "react"
 import { VSCodeButton, VSCodeProgressRing, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
 
-import { type CloudUserInfo, TelemetryEventName } from "@roo-code/types"
+import { type CloudUserInfo, type CloudOrganizationMembership, TelemetryEventName } from "@roo-code/types"
 
 import { useAppTranslation } from "@src/i18n/TranslationContext"
 import { useExtensionState } from "@src/context/ExtensionStateContext"
@@ -9,10 +9,12 @@ import { vscode } from "@src/utils/vscode"
 import { telemetryClient } from "@src/utils/TelemetryClient"
 import { ToggleSwitch } from "@/components/ui/toggle-switch"
 import { renderCloudBenefitsContent } from "./CloudUpsellDialog"
-import { TriangleAlert } from "lucide-react"
+import { CircleAlert, Info, Lock, TriangleAlert } from "lucide-react"
 import { cn } from "@/lib/utils"
 import { Tab, TabContent, TabHeader } from "../common/Tab"
 import { Button } from "@/components/ui/button"
+import { OrganizationSwitcher } from "./OrganizationSwitcher"
+import { StandardTooltip } from "../ui"
 
 // Define the production URL constant locally to avoid importing from cloud package in tests
 const PRODUCTION_ROO_CODE_API_URL = "https://app.roocode.com"
@@ -22,9 +24,10 @@ type CloudViewProps = {
 	isAuthenticated: boolean
 	cloudApiUrl?: string
 	onDone: () => void
+	organizations?: CloudOrganizationMembership[]
 }
 
-export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: CloudViewProps) => {
+export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone, organizations = [] }: CloudViewProps) => {
 	const { t } = useAppTranslation()
 	const {
 		remoteControlEnabled,
@@ -161,11 +164,11 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
 				<Button onClick={onDone}>{t("settings:common.done")}</Button>
 			</TabHeader>
 
-			<TabContent>
+			<TabContent className="pt-10">
 				{isAuthenticated ? (
 					<>
 						{userInfo && (
-							<div className="flex flex-col items-center mb-6">
+							<div className="flex flex-col items-start ml-4 mb-6">
 								<div className="w-16 h-16 mb-3 rounded-full overflow-hidden">
 									{userInfo?.picture ? (
 										<img
@@ -185,23 +188,18 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
 								{userInfo?.email && (
 									<p className="text-sm text-vscode-descriptionForeground my-0">{userInfo?.email}</p>
 								)}
-								{userInfo?.organizationName && (
-									<div className="flex items-center gap-2 text-sm text-vscode-descriptionForeground mt-2">
-										{userInfo.organizationImageUrl && (
-											<img
-												src={userInfo.organizationImageUrl}
-												alt={userInfo.organizationName}
-												className="w-4 h-4 rounded object-cover"
-											/>
-										)}
-										<span>{userInfo.organizationName}</span>
+
+								{/* Organization Switcher - moved below email */}
+								{organizations && organizations.length > 0 && (
+									<div className="w-full max-w-60 mt-4">
+										<OrganizationSwitcher userInfo={userInfo} organizations={organizations} />
 									</div>
 								)}
 							</div>
 						)}
 
 						{/* Task Sync Toggle - Always shown when authenticated */}
-						<div className="border-t border-vscode-widget-border pt-4 mt-4">
+						<div className="mt-4 p-4 border-b border-t border-vscode-widget-border pl-4 max-w-140">
 							<div className="flex items-center gap-3 mb-2">
 								<ToggleSwitch
 									checked={taskSyncEnabled}
@@ -211,21 +209,25 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
 									data-testid="task-sync-toggle"
 									disabled={!!userInfo?.organizationId}
 								/>
-								<span className="font-medium text-vscode-foreground">{t("cloud:taskSync")}</span>
+								<span className="font-medium text-vscode-foreground flex items-center">
+									{t("cloud:taskSync")}
+									{userInfo?.organizationId && (
+										<StandardTooltip content={t("cloud:taskSyncManagedByOrganization")}>
+											<div className="bg-vscode-badge-background text-vscode-badge-foreground/80 p-1.5 ml-2 -mb-2 relative -top-1 rounded-full inline-block cursor-help">
+												<Lock className="size-3 block" />
+											</div>
+										</StandardTooltip>
+									)}
+								</span>
 							</div>
-							<div className="text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8">
+							<div className="text-vscode-descriptionForeground text-sm mt-1 ml-8">
 								{t("cloud:taskSyncDescription")}
 							</div>
-							{userInfo?.organizationId && (
-								<div className="text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8 italic">
-									{t("cloud:taskSyncManagedByOrganization")}
-								</div>
-							)}
 
 							{/* Remote Control Toggle - Only shown when both extensionBridgeEnabled and featureRoomoteControlEnabled are true */}
 							{userInfo?.extensionBridgeEnabled && featureRoomoteControlEnabled && (
 								<>
-									<div className="flex items-center gap-3 mb-2">
+									<div className="flex items-center gap-3 mt-4 mb-2">
 										<ToggleSwitch
 											checked={remoteControlEnabled}
 											onChange={handleRemoteControlToggle}
@@ -238,37 +240,42 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
 											{t("cloud:remoteControl")}
 										</span>
 									</div>
-									<div className="text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8">
+									<div className="text-vscode-descriptionForeground text-sm mt-1 mb-2 ml-8">
 										{t("cloud:remoteControlDescription")}
 										{!taskSyncEnabled && (
-											<div className="text-vscode-errorForeground mt-2">
+											<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>
 
-							{/* 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 className="text-vscode-descriptionForeground text-sm mt-4 mb-8 pl-4">
+							<Info className="inline size-3 mr-1 mb-0.5 text-vscode-descriptionForeground" />
+							{t("cloud:usageMetricsAlwaysReported")}
 						</div>
 
-						<div className="flex flex-col gap-2 mt-4">
-							<VSCodeButton appearance="secondary" onClick={handleVisitCloudWebsite} className="w-full">
+						<div className="flex flex-col gap-2 mt-4 pl-4">
+							<VSCodeButton
+								appearance="secondary"
+								onClick={handleVisitCloudWebsite}
+								className="w-full max-w-80">
 								{t("cloud:visitCloudWebsite")}
 							</VSCodeButton>
-							<VSCodeButton appearance="secondary" onClick={handleLogoutClick} className="w-full">
+							<VSCodeButton
+								appearance="secondary"
+								onClick={handleLogoutClick}
+								className="w-full max-w-80">
 								{t("cloud:logOut")}
 							</VSCodeButton>
 						</div>
 					</>
 				) : (
 					<>
-						<div className="flex flex-col items-start gap-4 px-8">
+						<div className="flex flex-col items-start gap-4 px-8 max-w-100">
 							<div className={cn(authInProgress && "opacity-50")}>{renderCloudBenefitsContent(t)}</div>
 
 							{!authInProgress && (
@@ -323,10 +330,10 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
 					</>
 				)}
 				{cloudApiUrl && cloudApiUrl !== PRODUCTION_ROO_CODE_API_URL && (
-					<div className="ml-8 mt-6 flex justify-start">
+					<div className="ml-4 mt-6 flex">
 						<div className="inline-flex items-center gap-2 text-xs">
-							<TriangleAlert className="size-4 text-vscode-descriptionForeground" />
-							<span className="text-vscode-foreground/75">{t("cloud:cloudUrlPillLabel")}: </span>
+							<TriangleAlert className="size-3 text-vscode-descriptionForeground" />
+							<span className="text-vscode-foreground/75">{t("cloud:cloudUrlPillLabel")} </span>
 							<button
 								onClick={handleOpenCloudUrl}
 								className="text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline cursor-pointer bg-transparent border-none p-0">

+ 146 - 0
webview-ui/src/components/cloud/OrganizationSwitcher.tsx

@@ -0,0 +1,146 @@
+import { useState, useEffect } from "react"
+import { Building2, User } from "lucide-react"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator } from "@/components/ui/select"
+import { type CloudUserInfo, type CloudOrganizationMembership } from "@roo-code/types"
+import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { vscode } from "@src/utils/vscode"
+import { type ExtensionMessage } from "@roo/ExtensionMessage"
+
+type OrganizationSwitcherProps = {
+	userInfo: CloudUserInfo
+	organizations: CloudOrganizationMembership[]
+	onOrganizationChange?: (organizationId: string | null) => void
+}
+
+export const OrganizationSwitcher = ({ userInfo, organizations, onOrganizationChange }: OrganizationSwitcherProps) => {
+	const { t } = useAppTranslation()
+	const [selectedOrgId, setSelectedOrgId] = useState<string | null>(userInfo.organizationId || null)
+	const [isLoading, setIsLoading] = useState(false)
+
+	// Update selected org when userInfo changes
+	useEffect(() => {
+		setSelectedOrgId(userInfo.organizationId || null)
+	}, [userInfo.organizationId])
+
+	// Listen for organization switch results
+	useEffect(() => {
+		const handleMessage = (event: MessageEvent) => {
+			const message = event.data as ExtensionMessage
+			if (message.type === "organizationSwitchResult") {
+				// Reset loading state when we receive the result
+				setIsLoading(false)
+
+				if (message.success) {
+					// Update selected org based on the result
+					setSelectedOrgId(message.organizationId ?? null)
+				} else {
+					// Revert to the previous organization on error
+					setSelectedOrgId(userInfo.organizationId || null)
+				}
+			}
+		}
+
+		window.addEventListener("message", handleMessage)
+		return () => window.removeEventListener("message", handleMessage)
+	}, [userInfo.organizationId])
+
+	const handleOrganizationChange = async (value: string) => {
+		const newOrgId = value === "personal" ? null : value
+
+		// Don't do anything if selecting the same organization
+		if (newOrgId === selectedOrgId) {
+			return
+		}
+
+		setIsLoading(true)
+
+		// Send message to switch organization
+		vscode.postMessage({
+			type: "switchOrganization",
+			organizationId: newOrgId,
+		})
+
+		// Update local state optimistically
+		setSelectedOrgId(newOrgId)
+
+		// Call the callback if provided
+		if (onOrganizationChange) {
+			onOrganizationChange(newOrgId)
+		}
+	}
+
+	// If user has no organizations, don't show the switcher
+	if (!organizations || organizations.length === 0) {
+		return null
+	}
+
+	const currentValue = selectedOrgId || "personal"
+
+	return (
+		<div className="w-full">
+			<Select value={currentValue} onValueChange={handleOrganizationChange} disabled={isLoading}>
+				<SelectTrigger className="w-full">
+					<SelectValue>
+						<div className="flex items-center gap-2">
+							{selectedOrgId ? (
+								<>
+									{organizations.find((org) => org.organization.id === selectedOrgId)?.organization
+										.image_url ? (
+										<img
+											src={
+												organizations.find((org) => org.organization.id === selectedOrgId)
+													?.organization.image_url
+											}
+											alt=""
+											className="w-4.5 h-4.5 rounded-full object-cover overflow-clip"
+										/>
+									) : (
+										<Building2 className="w-4.5 h-4.5" />
+									)}
+									<span className="truncate">
+										{
+											organizations.find((org) => org.organization.id === selectedOrgId)
+												?.organization.name
+										}
+									</span>
+								</>
+							) : (
+								<>
+									<div className="p-0.5 bg-vscode-button-background rounded-full flex items-center justify-center text-vscode-button-foreground text-xs">
+										<User className="w-4 h-4 text-vscode-button-foreground" />
+									</div>
+									<span>{t("cloud:personalAccount")}</span>
+								</>
+							)}
+						</div>
+					</SelectValue>
+				</SelectTrigger>
+				<SelectContent>
+					<SelectItem value="personal">
+						<div className="flex items-center gap-2">
+							<User className="w-4.5 h-4.5" />
+							<span>{t("cloud:personalAccount")}</span>
+						</div>
+					</SelectItem>
+					{organizations.length > 0 && <SelectSeparator />}
+					{organizations.map((org) => (
+						<SelectItem key={org.organization.id} value={org.organization.id}>
+							<div className="flex items-center gap-2">
+								{org.organization.image_url ? (
+									<img
+										src={org.organization.image_url}
+										alt=""
+										className="w-4.5 h-4.5 rounded-full object-cover overflow-clip"
+									/>
+								) : (
+									<Building2 className="w-4.5 h-4.5" />
+								)}
+								<span className="truncate">{org.organization.name}</span>
+							</div>
+						</SelectItem>
+					))}
+				</SelectContent>
+			</Select>
+		</div>
+	)
+}

+ 9 - 2
webview-ui/src/components/cloud/__tests__/CloudView.spec.tsx

@@ -308,8 +308,15 @@ describe("CloudView", () => {
 		expect(taskSyncToggle).toBeInTheDocument()
 		expect(taskSyncToggle).toHaveAttribute("tabindex", "-1")
 
-		// Check that the organization message is displayed
-		expect(screen.getByText("Task sync is managed by your organization")).toBeInTheDocument()
+		// Check that the lock icon is displayed (indicating organization control)
+		const lockIcon = screen.getByTestId("task-sync-toggle").parentElement?.querySelector(".lucide-lock")
+		expect(lockIcon).toBeInTheDocument()
+
+		// Check that the tooltip trigger is present (which contains the organization message)
+		const tooltipTrigger = screen
+			.getByTestId("task-sync-toggle")
+			.parentElement?.querySelector('[data-slot="tooltip-trigger"]')
+		expect(tooltipTrigger).toBeInTheDocument()
 	})
 
 	it("should enable task sync toggle for non-organization users", () => {

+ 4 - 0
webview-ui/src/context/ExtensionStateContext.tsx

@@ -9,6 +9,7 @@ import {
 	type TodoItem,
 	type TelemetrySetting,
 	type OrganizationAllowList,
+	type CloudOrganizationMembership,
 	ORGANIZATION_ALLOW_ALL,
 } from "@roo-code/types"
 
@@ -39,6 +40,7 @@ export interface ExtensionStateContextType extends ExtensionState {
 	organizationAllowList: OrganizationAllowList
 	organizationSettingsVersion: number
 	cloudIsAuthenticated: boolean
+	cloudOrganizations?: CloudOrganizationMembership[]
 	sharingEnabled: boolean
 	maxConcurrentFileReads?: number
 	mdmCompliant?: boolean
@@ -240,6 +242,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		historyPreviewCollapsed: false, // Initialize the new state (default to expanded)
 		cloudUserInfo: null,
 		cloudIsAuthenticated: false,
+		cloudOrganizations: [],
 		sharingEnabled: false,
 		organizationAllowList: ORGANIZATION_ALLOW_ALL,
 		organizationSettingsVersion: -1,
@@ -428,6 +431,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		screenshotQuality: state.screenshotQuality,
 		routerModels: extensionRouterModels,
 		cloudIsAuthenticated: state.cloudIsAuthenticated ?? false,
+		cloudOrganizations: state.cloudOrganizations ?? [],
 		organizationSettingsVersion: state.organizationSettingsVersion ?? -1,
 		marketplaceItems,
 		marketplaceInstalledMetadata,

+ 2 - 0
webview-ui/src/i18n/locales/ca/cloud.json

@@ -23,6 +23,8 @@
 	"havingTrouble": "Tens problemes?",
 	"pasteCallbackUrl": "Copia l'URL de redirect del teu navegador i enganxa-la aquí:",
 	"startOver": "Torna a començar",
+	"personalAccount": "Compte Personal",
+	"switchAccount": "Canviar Compte de Roo Code Cloud",
 	"upsell": {
 		"autoApprovePowerUser": "Donant-li una mica d'independència a Roo? Controla'l des de qualsevol lloc amb Roo Code Cloud. <learnMoreLink>Més informació</learnMoreLink>.",
 		"longRunningTask": "Això pot trigar una estona. Continua des de qualsevol lloc amb Cloud.",

+ 2 - 0
webview-ui/src/i18n/locales/de/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "Probleme?",
 	"pasteCallbackUrl": "Kopiere die Redirect-URL aus deinem Browser und füge sie hier ein:",
 	"startOver": "Von vorne beginnen",
+	"personalAccount": "Persönliches Konto",
+	"switchAccount": "Roo Code Cloud Konto wechseln",
 	"cloudUrlPillLabel": "Roo Code Cloud URL",
 	"upsell": {
 		"autoApprovePowerUser": "Roo etwas Unabhängigkeit geben? Kontrolliere es von überall mit Roo Code Cloud. <learnMoreLink>Mehr erfahren</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/en/cloud.json

@@ -23,6 +23,8 @@
 	"havingTrouble": "Having trouble?",
 	"pasteCallbackUrl": "Copy the redirect URL from your browser and paste it here:",
 	"startOver": "Start over",
+	"personalAccount": "Personal Account",
+	"switchAccount": "Switch Roo Code Cloud Account",
 	"upsell": {
 		"autoApprovePowerUser": "Giving Roo some independence? Control it from anywhere with Roo Code Cloud. <learnMoreLink>Learn more</learnMoreLink>.",
 		"longRunningTask": "This might take a while. Continue from anywhere with Cloud.",

+ 2 - 0
webview-ui/src/i18n/locales/es/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "¿Tienes problemas?",
 	"pasteCallbackUrl": "Copia la URL de redirect desde tu navegador y pégala aquí:",
 	"startOver": "Empezar de nuevo",
+	"personalAccount": "Cuenta Personal",
+	"switchAccount": "Cambiar Cuenta de Roo Code Cloud",
 	"cloudUrlPillLabel": "URL de Roo Code Cloud",
 	"upsell": {
 		"autoApprovePowerUser": "¿Dándole a Roo un poco de independencia? Contrólalo desde cualquier lugar con Roo Code Cloud. <learnMoreLink>Saber más</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/fr/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "Des difficultés ?",
 	"pasteCallbackUrl": "Copie l'URL de redirect depuis ton navigateur et colle-la ici :",
 	"startOver": "Recommencer",
+	"personalAccount": "Compte Personnel",
+	"switchAccount": "Changer de Compte Roo Code Cloud",
 	"cloudUrlPillLabel": "URL de Roo Code Cloud",
 	"upsell": {
 		"autoApprovePowerUser": "Donner à Roo un peu d'indépendance ? Contrôlez-le de n'importe où avec Roo Code Cloud. <learnMoreLink>En savoir plus</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/hi/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "समस्या हो रही है?",
 	"pasteCallbackUrl": "अपने ब्राउज़र से redirect URL कॉपी करें और यहाँ पेस्ट करें:",
 	"startOver": "फिर से शुरू करें",
+	"personalAccount": "व्यक्तिगत खाता",
+	"switchAccount": "Roo Code Cloud खाता बदलें",
 	"cloudUrlPillLabel": "Roo Code Cloud URL",
 	"upsell": {
 		"autoApprovePowerUser": "रू को थोड़ी स्वतंत्रता दे रहे हैं? रू कोड क्लाउड के साथ इसे कहीं से भी नियंत्रित करें। <learnMoreLink>और जानें</learnMoreLink>।",

+ 2 - 0
webview-ui/src/i18n/locales/id/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "Ada masalah?",
 	"pasteCallbackUrl": "Salin URL redirect dari browser dan tempel di sini:",
 	"startOver": "Mulai dari awal",
+	"personalAccount": "Akun Pribadi",
+	"switchAccount": "Ganti Akun Roo Code Cloud",
 	"cloudUrlPillLabel": "URL Roo Code Cloud",
 	"upsell": {
 		"autoApprovePowerUser": "Memberi Roo sedikit kebebasan? Kendalikan dari mana saja dengan Roo Code Cloud. <learnMoreLink>Pelajari lebih lanjut</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/it/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "Hai problemi?",
 	"pasteCallbackUrl": "Copia l'URL di redirect dal tuo browser e incollalo qui:",
 	"startOver": "Ricomincia",
+	"personalAccount": "Account Personale",
+	"switchAccount": "Cambia Account Roo Code Cloud",
 	"cloudUrlPillLabel": "URL di Roo Code Cloud",
 	"upsell": {
 		"autoApprovePowerUser": "Vuoi dare un po' di indipendenza a Roo? Controllalo da qualsiasi luogo con Roo Code Cloud. <learnMoreLink>Scopri di più</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/ja/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "問題が発生していますか?",
 	"pasteCallbackUrl": "ブラウザからリダイレクトURLをコピーし、ここに貼り付けてください:",
 	"startOver": "最初からやり直す",
+	"personalAccount": "個人アカウント",
+	"switchAccount": "Roo Code Cloud アカウントを切り替え",
 	"cloudUrlPillLabel": "Roo Code Cloud URL",
 	"upsell": {
 		"autoApprovePowerUser": "Rooに少し独立性を与えませんか?Roo Code Cloudでどこからでもコントロールできます。<learnMoreLink>詳細</learnMoreLink>。",

+ 2 - 0
webview-ui/src/i18n/locales/ko/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "문제가 있나요?",
 	"pasteCallbackUrl": "브라우저에서 리다이렉트 URL을 복사하여 여기에 붙여넣으세요:",
 	"startOver": "다시 시작",
+	"personalAccount": "개인 계정",
+	"switchAccount": "Roo Code Cloud 계정 전환",
 	"cloudUrlPillLabel": "Roo Code Cloud URL",
 	"upsell": {
 		"autoApprovePowerUser": "Roo에게 약간의 독립성을 부여하시겠습니까? Roo Code Cloud로 어디서든 제어하세요. <learnMoreLink>더 알아보기</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/nl/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "Problemen?",
 	"pasteCallbackUrl": "Kopieer de redirect-URL uit je browser en plak hem hier:",
 	"startOver": "Opnieuw beginnen",
+	"personalAccount": "Persoonlijk Account",
+	"switchAccount": "Wissel van Roo Code Cloud Account",
 	"cloudUrlPillLabel": "Roo Code Cloud URL",
 	"upsell": {
 		"autoApprovePowerUser": "Roo wat onafhankelijkheid geven? Bedien het overal met Roo Code Cloud. <learnMoreLink>Meer informatie</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/pl/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "Masz problemy?",
 	"pasteCallbackUrl": "Skopiuj URL redirect z przeglądarki i wklej tutaj:",
 	"startOver": "Zacznij od nowa",
+	"personalAccount": "Konto Osobiste",
+	"switchAccount": "Przełącz Konto Roo Code Cloud",
 	"cloudUrlPillLabel": "URL Roo Code Cloud",
 	"upsell": {
 		"autoApprovePowerUser": "Dać Roo trochę niezależności? Kontroluj go z dowolnego miejsca dzięki Roo Code Cloud. <learnMoreLink>Dowiedz się więcej</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/pt-BR/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "Tendo problemas?",
 	"pasteCallbackUrl": "Copie a URL de redirect do seu navegador e cole aqui:",
 	"startOver": "Recomeçar",
+	"personalAccount": "Conta Pessoal",
+	"switchAccount": "Alternar Conta do Roo Code Cloud",
 	"cloudUrlPillLabel": "URL do Roo Code Cloud ",
 	"upsell": {
 		"autoApprovePowerUser": "Dando um pouco de independência ao Roo? Controle-o de qualquer lugar com o Roo Code Cloud. <learnMoreLink>Saiba mais</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/ru/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "Проблемы?",
 	"pasteCallbackUrl": "Скопируй URL перенаправления из браузера и вставь его сюда:",
 	"startOver": "Начать заново",
+	"personalAccount": "Личный аккаунт",
+	"switchAccount": "Переключить аккаунт Roo Code Cloud",
 	"cloudUrlPillLabel": "URL Roo Code Cloud",
 	"upsell": {
 		"autoApprovePowerUser": "Предоставить Roo немного независимости? Управляйте им из любого места с помощью Roo Code Cloud. <learnMoreLink>Узнать больше</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/tr/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "Sorun yaşıyor musun?",
 	"pasteCallbackUrl": "Tarayıcından redirect URL'sini kopyala ve buraya yapıştır:",
 	"startOver": "Baştan başla",
+	"personalAccount": "Kişisel Hesap",
+	"switchAccount": "Roo Code Cloud Hesabını Değiştir",
 	"cloudUrlPillLabel": "Roo Code Cloud URL'si",
 	"upsell": {
 		"autoApprovePowerUser": "Roo'ya biraz bağımsızlık mı veriyorsunuz? Roo Code Cloud ile onu her yerden kontrol edin. <learnMoreLink>Daha fazla bilgi edinin</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/vi/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "Gặp vấn đề?",
 	"pasteCallbackUrl": "Sao chép URL redirect từ trình duyệt và dán vào đây:",
 	"startOver": "Bắt đầu lại",
+	"personalAccount": "Tài Khoản Cá Nhân",
+	"switchAccount": "Chuyển Tài Khoản Roo Code Cloud",
 	"cloudUrlPillLabel": "URL Roo Code Cloud",
 	"upsell": {
 		"autoApprovePowerUser": "Trao cho Roo một chút độc lập? Kiểm soát nó từ mọi nơi với Roo Code Cloud. <learnMoreLink>Tìm hiểu thêm</learnMoreLink>.",

+ 2 - 0
webview-ui/src/i18n/locales/zh-CN/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "遇到问题?",
 	"pasteCallbackUrl": "从浏览器复制重定向 URL 并粘贴到这里:",
 	"startOver": "重新开始",
+	"personalAccount": "个人账户",
+	"switchAccount": "切换 Roo Code Cloud 账户",
 	"cloudUrlPillLabel": "Roo Code Cloud URL",
 	"upsell": {
 		"autoApprovePowerUser": "给 Roo 一些独立性?使用 Roo Code Cloud 从任何地方控制它。 <learnMoreLink>了解更多</learnMoreLink>。",

+ 2 - 0
webview-ui/src/i18n/locales/zh-TW/cloud.json

@@ -22,6 +22,8 @@
 	"havingTrouble": "遇到問題?",
 	"pasteCallbackUrl": "從瀏覽器複製重新導向 URL 並貼上到這裡:",
 	"startOver": "重新開始",
+	"personalAccount": "個人帳戶",
+	"switchAccount": "切換 Roo Code Cloud 帳戶",
 	"cloudUrlPillLabel": "Roo Code Cloud URL",
 	"upsell": {
 		"autoApprovePowerUser": "給 Roo 一點獨立性?使用 Roo Code Cloud 隨時隨地控制它。<learnMoreLink>了解更多</learnMoreLink>。",