Просмотр исходного кода

Sharing improvements (#5082)

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: John Richmond <[email protected]>
Co-authored-by: cte <[email protected]>
Matt Rubens 7 месяцев назад
Родитель
Сommit
da8b09e655
89 измененных файлов с 2206 добавлено и 362 удалено
  1. 2 2
      packages/build/src/__tests__/index.test.ts
  2. 0 1
      packages/cloud/package.json
  3. 4 3
      packages/cloud/src/AuthService.ts
  4. 24 4
      packages/cloud/src/CloudService.ts
  5. 24 12
      packages/cloud/src/ShareService.ts
  6. 66 1
      packages/cloud/src/TelemetryClient.ts
  7. 156 0
      packages/cloud/src/__tests__/CloudService.test.ts
  8. 109 50
      packages/cloud/src/__tests__/ShareService.test.ts
  9. 311 0
      packages/cloud/src/__tests__/TelemetryClient.test.ts
  10. 1 0
      packages/types/src/index.ts
  11. 8 0
      packages/types/src/sharing.ts
  12. 21 0
      packages/types/src/telemetry.ts
  13. 0 3
      pnpm-lock.yaml
  14. 15 1
      src/core/webview/ClineProvider.ts
  15. 107 0
      src/core/webview/__tests__/ClineProvider.spec.ts
  16. 9 1
      src/core/webview/webviewMessageHandler.ts
  17. 3 9
      src/package.json
  18. 0 1
      src/package.nls.ca.json
  19. 0 1
      src/package.nls.de.json
  20. 0 1
      src/package.nls.es.json
  21. 0 1
      src/package.nls.fr.json
  22. 0 1
      src/package.nls.hi.json
  23. 0 1
      src/package.nls.id.json
  24. 0 1
      src/package.nls.it.json
  25. 0 1
      src/package.nls.ja.json
  26. 0 1
      src/package.nls.json
  27. 0 1
      src/package.nls.ko.json
  28. 0 1
      src/package.nls.nl.json
  29. 0 1
      src/package.nls.pl.json
  30. 0 1
      src/package.nls.pt-BR.json
  31. 0 1
      src/package.nls.ru.json
  32. 0 1
      src/package.nls.tr.json
  33. 0 1
      src/package.nls.vi.json
  34. 0 1
      src/package.nls.zh-CN.json
  35. 0 1
      src/package.nls.zh-TW.json
  36. 0 19
      src/services/mdm/MdmService.ts
  37. 0 96
      src/services/mdm/__tests__/MdmService.spec.ts
  38. 4 0
      src/shared/ExtensionMessage.ts
  39. 3 1
      src/shared/WebviewMessage.ts
  40. 2 0
      webview-ui/src/App.tsx
  41. 71 15
      webview-ui/src/components/account/AccountView.tsx
  42. 92 0
      webview-ui/src/components/account/__tests__/AccountView.spec.tsx
  43. 1 0
      webview-ui/src/components/chat/IconButton.tsx
  44. 259 0
      webview-ui/src/components/chat/ShareButton.tsx
  45. 2 67
      webview-ui/src/components/chat/TaskActions.tsx
  46. 2 0
      webview-ui/src/components/chat/TaskHeader.tsx
  47. 325 0
      webview-ui/src/components/chat/__tests__/ShareButton.spec.tsx
  48. 315 0
      webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx
  49. 7 2
      webview-ui/src/components/common/TelemetryBanner.tsx
  50. 6 1
      webview-ui/src/components/settings/About.tsx
  51. 8 2
      webview-ui/src/i18n/locales/ca/account.json
  52. 6 1
      webview-ui/src/i18n/locales/ca/chat.json
  53. 8 2
      webview-ui/src/i18n/locales/de/account.json
  54. 6 1
      webview-ui/src/i18n/locales/de/chat.json
  55. 8 2
      webview-ui/src/i18n/locales/en/account.json
  56. 6 1
      webview-ui/src/i18n/locales/en/chat.json
  57. 1 1
      webview-ui/src/i18n/locales/en/settings.json
  58. 1 1
      webview-ui/src/i18n/locales/en/welcome.json
  59. 8 2
      webview-ui/src/i18n/locales/es/account.json
  60. 6 1
      webview-ui/src/i18n/locales/es/chat.json
  61. 8 2
      webview-ui/src/i18n/locales/fr/account.json
  62. 6 1
      webview-ui/src/i18n/locales/fr/chat.json
  63. 8 2
      webview-ui/src/i18n/locales/hi/account.json
  64. 6 1
      webview-ui/src/i18n/locales/hi/chat.json
  65. 8 2
      webview-ui/src/i18n/locales/id/account.json
  66. 6 1
      webview-ui/src/i18n/locales/id/chat.json
  67. 8 2
      webview-ui/src/i18n/locales/it/account.json
  68. 6 1
      webview-ui/src/i18n/locales/it/chat.json
  69. 8 2
      webview-ui/src/i18n/locales/ja/account.json
  70. 6 1
      webview-ui/src/i18n/locales/ja/chat.json
  71. 8 2
      webview-ui/src/i18n/locales/ko/account.json
  72. 6 1
      webview-ui/src/i18n/locales/ko/chat.json
  73. 8 2
      webview-ui/src/i18n/locales/nl/account.json
  74. 6 1
      webview-ui/src/i18n/locales/nl/chat.json
  75. 8 2
      webview-ui/src/i18n/locales/pl/account.json
  76. 6 1
      webview-ui/src/i18n/locales/pl/chat.json
  77. 8 2
      webview-ui/src/i18n/locales/pt-BR/account.json
  78. 6 1
      webview-ui/src/i18n/locales/pt-BR/chat.json
  79. 8 2
      webview-ui/src/i18n/locales/ru/account.json
  80. 6 1
      webview-ui/src/i18n/locales/ru/chat.json
  81. 8 2
      webview-ui/src/i18n/locales/tr/account.json
  82. 6 1
      webview-ui/src/i18n/locales/tr/chat.json
  83. 8 2
      webview-ui/src/i18n/locales/vi/account.json
  84. 6 1
      webview-ui/src/i18n/locales/vi/chat.json
  85. 8 2
      webview-ui/src/i18n/locales/zh-CN/account.json
  86. 6 1
      webview-ui/src/i18n/locales/zh-CN/chat.json
  87. 8 2
      webview-ui/src/i18n/locales/zh-TW/account.json
  88. 6 1
      webview-ui/src/i18n/locales/zh-TW/chat.json
  89. 3 0
      webview-ui/vitest.setup.ts

+ 2 - 2
packages/build/src/__tests__/index.test.ts

@@ -70,7 +70,7 @@ describe("generatePackageJson", () => {
 							{
 								command: "roo-cline.accountButtonClicked",
 								group: "navigation@6",
-								when: "activeWebviewPanelId == roo-cline.TabPanelProvider && config.roo-cline.rooCodeCloudEnabled",
+								when: "activeWebviewPanelId == roo-cline.TabPanelProvider",
 							},
 						],
 					},
@@ -183,7 +183,7 @@ describe("generatePackageJson", () => {
 						{
 							command: "roo-code-nightly.accountButtonClicked",
 							group: "navigation@6",
-							when: "activeWebviewPanelId == roo-code-nightly.TabPanelProvider && config.roo-code-nightly.rooCodeCloudEnabled",
+							when: "activeWebviewPanelId == roo-code-nightly.TabPanelProvider",
 						},
 					],
 				},

+ 0 - 1
packages/cloud/package.json

@@ -13,7 +13,6 @@
 	"dependencies": {
 		"@roo-code/telemetry": "workspace:^",
 		"@roo-code/types": "workspace:^",
-		"axios": "^1.7.4",
 		"zod": "^3.25.61"
 	},
 	"devDependencies": {

+ 4 - 3
packages/cloud/src/AuthService.ts

@@ -42,8 +42,8 @@ const clerkCreateSessionTokenResponseSchema = z.object({
 
 const clerkMeResponseSchema = z.object({
 	response: z.object({
-		first_name: z.string().optional(),
-		last_name: z.string().optional(),
+		first_name: z.string().optional().nullable(),
+		last_name: z.string().optional().nullable(),
 		image_url: z.string().optional(),
 		primary_email_address_id: z.string().optional(),
 		email_addresses: z
@@ -531,7 +531,8 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
 
 		const userInfo: CloudUserInfo = {}
 
-		userInfo.name = `${userData.first_name} ${userData.last_name}`
+		const names = [userData.first_name, userData.last_name].filter((name) => !!name)
+		userInfo.name = names.length > 0 ? names.join(" ") : undefined
 		const primaryEmailAddressId = userData.primary_email_address_id
 		const emailAddresses = userData.email_addresses
 

+ 24 - 4
packages/cloud/src/CloudService.ts

@@ -1,13 +1,19 @@
 import * as vscode from "vscode"
 
-import type { CloudUserInfo, TelemetryEvent, OrganizationAllowList } from "@roo-code/types"
+import type {
+	CloudUserInfo,
+	TelemetryEvent,
+	OrganizationAllowList,
+	ClineMessage,
+	ShareVisibility,
+} from "@roo-code/types"
 import { TelemetryService } from "@roo-code/telemetry"
 
 import { CloudServiceCallbacks } from "./types"
 import { AuthService } from "./AuthService"
 import { SettingsService } from "./SettingsService"
 import { TelemetryClient } from "./TelemetryClient"
-import { ShareService } from "./ShareService"
+import { ShareService, TaskNotFoundError } from "./ShareService"
 
 export class CloudService {
 	private static _instance: CloudService | null = null
@@ -161,9 +167,23 @@ export class CloudService {
 
 	// ShareService
 
-	public async shareTask(taskId: string, visibility: "organization" | "public" = "organization") {
+	public async shareTask(
+		taskId: string,
+		visibility: ShareVisibility = "organization",
+		clineMessages?: ClineMessage[],
+	) {
 		this.ensureInitialized()
-		return this.shareService!.shareTask(taskId, visibility)
+
+		try {
+			return await this.shareService!.shareTask(taskId, visibility)
+		} catch (error) {
+			if (error instanceof TaskNotFoundError && clineMessages) {
+				// Backfill messages and retry
+				await this.telemetryClient!.backfillMessages(clineMessages, taskId)
+				return await this.shareService!.shareTask(taskId, visibility)
+			}
+			throw error
+		}
 	}
 
 	public async canShareTask(): Promise<boolean> {

+ 24 - 12
packages/cloud/src/ShareService.ts

@@ -1,4 +1,3 @@
-import axios from "axios"
 import * as vscode from "vscode"
 
 import { shareResponseSchema } from "@roo-code/types"
@@ -9,6 +8,13 @@ import { getUserAgent } from "./utils"
 
 export type ShareVisibility = "organization" | "public"
 
+export class TaskNotFoundError extends Error {
+	constructor(taskId?: string) {
+		super(taskId ? `Task '${taskId}' not found` : "Task not found")
+		Object.setPrototypeOf(this, TaskNotFoundError.prototype)
+	}
+}
+
 export class ShareService {
 	private authService: AuthService
 	private settingsService: SettingsService
@@ -31,19 +37,25 @@ export class ShareService {
 				throw new Error("Authentication required")
 			}
 
-			const response = await axios.post(
-				`${getRooCodeApiUrl()}/api/extension/share`,
-				{ taskId, visibility },
-				{
-					headers: {
-						"Content-Type": "application/json",
-						Authorization: `Bearer ${sessionToken}`,
-						"User-Agent": getUserAgent(),
-					},
+			const response = await fetch(`${getRooCodeApiUrl()}/api/extension/share`, {
+				method: "POST",
+				headers: {
+					"Content-Type": "application/json",
+					Authorization: `Bearer ${sessionToken}`,
+					"User-Agent": getUserAgent(),
 				},
-			)
+				body: JSON.stringify({ taskId, visibility }),
+				signal: AbortSignal.timeout(10000),
+			})
+
+			if (!response.ok) {
+				if (response.status === 404) {
+					throw new TaskNotFoundError(taskId)
+				}
+				throw new Error(`HTTP ${response.status}: ${response.statusText}`)
+			}
 
-			const data = shareResponseSchema.parse(response.data)
+			const data = shareResponseSchema.parse(await response.json())
 			this.log("[share] Share link created successfully:", data)
 
 			if (data.success && data.shareUrl) {

+ 66 - 1
packages/cloud/src/TelemetryClient.ts

@@ -1,4 +1,9 @@
-import { TelemetryEventName, type TelemetryEvent, rooCodeTelemetryEventSchema } from "@roo-code/types"
+import {
+	TelemetryEventName,
+	type TelemetryEvent,
+	rooCodeTelemetryEventSchema,
+	type ClineMessage,
+} from "@roo-code/types"
 import { BaseTelemetryClient } from "@roo-code/telemetry"
 
 import { getRooCodeApiUrl } from "./Config"
@@ -79,6 +84,66 @@ export class TelemetryClient extends BaseTelemetryClient {
 		}
 	}
 
+	public async backfillMessages(messages: ClineMessage[], taskId: string): Promise<void> {
+		if (!this.authService.isAuthenticated()) {
+			if (this.debug) {
+				console.info(`[TelemetryClient#backfillMessages] Skipping: Not authenticated`)
+			}
+			return
+		}
+
+		const token = this.authService.getSessionToken()
+
+		if (!token) {
+			console.error(`[TelemetryClient#backfillMessages] Unauthorized: No session token available.`)
+			return
+		}
+
+		try {
+			const mergedProperties = await this.getEventProperties({
+				event: TelemetryEventName.TASK_MESSAGE,
+				properties: { taskId },
+			})
+
+			const formData = new FormData()
+			formData.append("taskId", taskId)
+			formData.append("properties", JSON.stringify(mergedProperties))
+
+			formData.append(
+				"file",
+				new File([JSON.stringify(messages)], "task.json", {
+					type: "application/json",
+				}),
+			)
+
+			if (this.debug) {
+				console.info(
+					`[TelemetryClient#backfillMessages] Uploading ${messages.length} messages for task ${taskId}`,
+				)
+			}
+
+			// Custom fetch for multipart - don't set Content-Type header (let browser set it)
+			const response = await fetch(`${getRooCodeApiUrl()}/api/events/backfill`, {
+				method: "POST",
+				headers: {
+					Authorization: `Bearer ${token}`,
+					// Note: No Content-Type header - browser will set multipart/form-data with boundary
+				},
+				body: formData,
+			})
+
+			if (!response.ok) {
+				console.error(
+					`[TelemetryClient#backfillMessages] POST events/backfill -> ${response.status} ${response.statusText}`,
+				)
+			} else if (this.debug) {
+				console.info(`[TelemetryClient#backfillMessages] Successfully uploaded messages for task ${taskId}`)
+			}
+		} catch (error) {
+			console.error(`[TelemetryClient#backfillMessages] Error uploading messages: ${error}`)
+		}
+	}
+
 	public override updateTelemetryState(_didUserOptIn: boolean) {}
 
 	public override isTelemetryEnabled(): boolean {

+ 156 - 0
packages/cloud/src/__tests__/CloudService.test.ts

@@ -1,10 +1,13 @@
 // npx vitest run src/__tests__/CloudService.test.ts
 
 import * as vscode from "vscode"
+import type { ClineMessage } from "@roo-code/types"
 
 import { CloudService } from "../CloudService"
 import { AuthService } from "../AuthService"
 import { SettingsService } from "../SettingsService"
+import { ShareService, TaskNotFoundError } from "../ShareService"
+import { TelemetryClient } from "../TelemetryClient"
 import { TelemetryService } from "@roo-code/telemetry"
 import { CloudServiceCallbacks } from "../types"
 
@@ -28,6 +31,10 @@ vi.mock("../AuthService")
 
 vi.mock("../SettingsService")
 
+vi.mock("../ShareService")
+
+vi.mock("../TelemetryClient")
+
 describe("CloudService", () => {
 	let mockContext: vscode.ExtensionContext
 	let mockAuthService: {
@@ -36,6 +43,7 @@ describe("CloudService", () => {
 		logout: ReturnType<typeof vi.fn>
 		isAuthenticated: ReturnType<typeof vi.fn>
 		hasActiveSession: ReturnType<typeof vi.fn>
+		hasOrIsAcquiringActiveSession: ReturnType<typeof vi.fn>
 		getUserInfo: ReturnType<typeof vi.fn>
 		getState: ReturnType<typeof vi.fn>
 		getSessionToken: ReturnType<typeof vi.fn>
@@ -52,6 +60,13 @@ describe("CloudService", () => {
 		getAllowList: ReturnType<typeof vi.fn>
 		dispose: ReturnType<typeof vi.fn>
 	}
+	let mockShareService: {
+		shareTask: ReturnType<typeof vi.fn>
+		canShareTask: ReturnType<typeof vi.fn>
+	}
+	let mockTelemetryClient: {
+		backfillMessages: ReturnType<typeof vi.fn>
+	}
 	let mockTelemetryService: {
 		hasInstance: ReturnType<typeof vi.fn>
 		instance: {
@@ -63,15 +78,29 @@ describe("CloudService", () => {
 		CloudService.resetInstance()
 
 		mockContext = {
+			subscriptions: [],
+			workspaceState: {
+				get: vi.fn(),
+				update: vi.fn(),
+				keys: vi.fn().mockReturnValue([]),
+			},
 			secrets: {
 				get: vi.fn(),
 				store: vi.fn(),
 				delete: vi.fn(),
+				onDidChange: vi.fn().mockReturnValue({ dispose: vi.fn() }),
 			},
 			globalState: {
 				get: vi.fn(),
 				update: vi.fn(),
+				setKeysForSync: vi.fn(),
+				keys: vi.fn().mockReturnValue([]),
 			},
+			extensionUri: { scheme: "file", path: "/mock/path" },
+			extensionPath: "/mock/path",
+			extensionMode: 1,
+			asAbsolutePath: vi.fn((relativePath: string) => `/mock/path/${relativePath}`),
+			storageUri: { scheme: "file", path: "/mock/storage" },
 			extension: {
 				packageJSON: {
 					version: "1.0.0",
@@ -85,6 +114,7 @@ describe("CloudService", () => {
 			logout: vi.fn(),
 			isAuthenticated: vi.fn().mockReturnValue(false),
 			hasActiveSession: vi.fn().mockReturnValue(false),
+			hasOrIsAcquiringActiveSession: vi.fn().mockReturnValue(false),
 			getUserInfo: vi.fn(),
 			getState: vi.fn().mockReturnValue("logged-out"),
 			getSessionToken: vi.fn(),
@@ -103,6 +133,15 @@ describe("CloudService", () => {
 			dispose: vi.fn(),
 		}
 
+		mockShareService = {
+			shareTask: vi.fn(),
+			canShareTask: vi.fn().mockResolvedValue(true),
+		}
+
+		mockTelemetryClient = {
+			backfillMessages: vi.fn().mockResolvedValue(undefined),
+		}
+
 		mockTelemetryService = {
 			hasInstance: vi.fn().mockReturnValue(true),
 			instance: {
@@ -112,6 +151,8 @@ describe("CloudService", () => {
 
 		vi.mocked(AuthService).mockImplementation(() => mockAuthService as unknown as AuthService)
 		vi.mocked(SettingsService).mockImplementation(() => mockSettingsService as unknown as SettingsService)
+		vi.mocked(ShareService).mockImplementation(() => mockShareService as unknown as ShareService)
+		vi.mocked(TelemetryClient).mockImplementation(() => mockTelemetryClient as unknown as TelemetryClient)
 
 		vi.mocked(TelemetryService.hasInstance).mockReturnValue(true)
 		Object.defineProperty(TelemetryService, "instance", {
@@ -342,4 +383,119 @@ describe("CloudService", () => {
 			expect(mockSettingsService.dispose).toHaveBeenCalled()
 		})
 	})
+
+	describe("shareTask with ClineMessage retry logic", () => {
+		let cloudService: CloudService
+
+		beforeEach(async () => {
+			// Reset mocks for shareTask tests
+			vi.clearAllMocks()
+
+			// Reset authentication state for shareTask tests
+			mockAuthService.isAuthenticated.mockReturnValue(true)
+			mockAuthService.hasActiveSession.mockReturnValue(true)
+			mockAuthService.hasOrIsAcquiringActiveSession.mockReturnValue(true)
+			mockAuthService.getState.mockReturnValue("active")
+
+			cloudService = await CloudService.createInstance(mockContext, {})
+		})
+
+		it("should call shareTask without retry when successful", async () => {
+			const taskId = "test-task-id"
+			const visibility = "organization"
+			const clineMessages: ClineMessage[] = [
+				{
+					ts: Date.now(),
+					type: "say",
+					say: "text",
+					text: "Hello world",
+				},
+			]
+
+			const expectedResult = { success: true, shareUrl: "https://example.com/share/123" }
+			mockShareService.shareTask.mockResolvedValue(expectedResult)
+
+			const result = await cloudService.shareTask(taskId, visibility, clineMessages)
+
+			expect(mockShareService.shareTask).toHaveBeenCalledTimes(1)
+			expect(mockShareService.shareTask).toHaveBeenCalledWith(taskId, visibility)
+			expect(mockTelemetryClient.backfillMessages).not.toHaveBeenCalled()
+			expect(result).toEqual(expectedResult)
+		})
+
+		it("should retry with backfill when TaskNotFoundError occurs", async () => {
+			const taskId = "test-task-id"
+			const visibility = "organization"
+			const clineMessages: ClineMessage[] = [
+				{
+					ts: Date.now(),
+					type: "say",
+					say: "text",
+					text: "Hello world",
+				},
+			]
+
+			const expectedResult = { success: true, shareUrl: "https://example.com/share/123" }
+
+			// First call throws TaskNotFoundError, second call succeeds
+			mockShareService.shareTask
+				.mockRejectedValueOnce(new TaskNotFoundError(taskId))
+				.mockResolvedValueOnce(expectedResult)
+
+			const result = await cloudService.shareTask(taskId, visibility, clineMessages)
+
+			expect(mockShareService.shareTask).toHaveBeenCalledTimes(2)
+			expect(mockShareService.shareTask).toHaveBeenNthCalledWith(1, taskId, visibility)
+			expect(mockShareService.shareTask).toHaveBeenNthCalledWith(2, taskId, visibility)
+			expect(mockTelemetryClient.backfillMessages).toHaveBeenCalledTimes(1)
+			expect(mockTelemetryClient.backfillMessages).toHaveBeenCalledWith(clineMessages, taskId)
+			expect(result).toEqual(expectedResult)
+		})
+
+		it("should not retry when TaskNotFoundError occurs but no clineMessages provided", async () => {
+			const taskId = "test-task-id"
+			const visibility = "organization"
+
+			const taskNotFoundError = new TaskNotFoundError(taskId)
+			mockShareService.shareTask.mockRejectedValue(taskNotFoundError)
+
+			await expect(cloudService.shareTask(taskId, visibility)).rejects.toThrow(TaskNotFoundError)
+
+			expect(mockShareService.shareTask).toHaveBeenCalledTimes(1)
+			expect(mockTelemetryClient.backfillMessages).not.toHaveBeenCalled()
+		})
+
+		it("should not retry when non-TaskNotFoundError occurs", async () => {
+			const taskId = "test-task-id"
+			const visibility = "organization"
+			const clineMessages: ClineMessage[] = [
+				{
+					ts: Date.now(),
+					type: "say",
+					say: "text",
+					text: "Hello world",
+				},
+			]
+
+			const genericError = new Error("Some other error")
+			mockShareService.shareTask.mockRejectedValue(genericError)
+
+			await expect(cloudService.shareTask(taskId, visibility, clineMessages)).rejects.toThrow(genericError)
+
+			expect(mockShareService.shareTask).toHaveBeenCalledTimes(1)
+			expect(mockTelemetryClient.backfillMessages).not.toHaveBeenCalled()
+		})
+
+		it("should work with default parameters", async () => {
+			const taskId = "test-task-id"
+			const expectedResult = { success: true, shareUrl: "https://example.com/share/123" }
+			mockShareService.shareTask.mockResolvedValue(expectedResult)
+
+			const result = await cloudService.shareTask(taskId)
+
+			expect(mockShareService.shareTask).toHaveBeenCalledTimes(1)
+			expect(mockShareService.shareTask).toHaveBeenCalledWith(taskId, "organization")
+			expect(result).toEqual(expectedResult)
+		})
+	})
 })

+ 109 - 50
packages/cloud/src/__tests__/ShareService.test.ts

@@ -1,16 +1,15 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 
 import type { MockedFunction } from "vitest"
-import axios from "axios"
 import * as vscode from "vscode"
 
-import { ShareService } from "../ShareService"
+import { ShareService, TaskNotFoundError } from "../ShareService"
 import type { AuthService } from "../AuthService"
 import type { SettingsService } from "../SettingsService"
 
-// Mock axios
-vi.mock("axios")
-const mockedAxios = axios as any
+// Mock fetch
+const mockFetch = vi.fn()
+global.fetch = mockFetch as any
 
 // Mock vscode
 vi.mock("vscode", () => ({
@@ -53,6 +52,7 @@ describe("ShareService", () => {
 
 	beforeEach(() => {
 		vi.clearAllMocks()
+		mockFetch.mockClear()
 
 		mockLog = vi.fn()
 		mockAuthService = {
@@ -70,86 +70,99 @@ describe("ShareService", () => {
 
 	describe("shareTask", () => {
 		it("should share task with organization visibility and copy to clipboard", async () => {
-			const mockResponse = {
-				data: {
-					success: true,
-					shareUrl: "https://app.roocode.com/share/abc123",
-				},
+			const mockResponseData = {
+				success: true,
+				shareUrl: "https://app.roocode.com/share/abc123",
 			}
 
 			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			mockedAxios.post.mockResolvedValue(mockResponse)
+			mockFetch.mockResolvedValue({
+				ok: true,
+				json: vi.fn().mockResolvedValue(mockResponseData),
+			})
 
 			const result = await shareService.shareTask("task-123", "organization")
 
 			expect(result.success).toBe(true)
 			expect(result.shareUrl).toBe("https://app.roocode.com/share/abc123")
-			expect(mockedAxios.post).toHaveBeenCalledWith(
-				"https://app.roocode.com/api/extension/share",
-				{ taskId: "task-123", visibility: "organization" },
-				{
-					headers: {
-						"Content-Type": "application/json",
-						Authorization: "Bearer session-token",
-						"User-Agent": "Roo-Code 1.0.0",
-					},
+			expect(mockFetch).toHaveBeenCalledWith("https://app.roocode.com/api/extension/share", {
+				method: "POST",
+				headers: {
+					"Content-Type": "application/json",
+					Authorization: "Bearer session-token",
+					"User-Agent": "Roo-Code 1.0.0",
 				},
-			)
+				body: JSON.stringify({ taskId: "task-123", visibility: "organization" }),
+				signal: expect.any(AbortSignal),
+			})
 			expect(vscode.env.clipboard.writeText).toHaveBeenCalledWith("https://app.roocode.com/share/abc123")
 		})
 
 		it("should share task with public visibility", async () => {
-			const mockResponse = {
-				data: {
-					success: true,
-					shareUrl: "https://app.roocode.com/share/abc123",
-				},
+			const mockResponseData = {
+				success: true,
+				shareUrl: "https://app.roocode.com/share/abc123",
 			}
 
 			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			mockedAxios.post.mockResolvedValue(mockResponse)
+			mockFetch.mockResolvedValue({
+				ok: true,
+				json: vi.fn().mockResolvedValue(mockResponseData),
+			})
 
 			const result = await shareService.shareTask("task-123", "public")
 
 			expect(result.success).toBe(true)
-			expect(mockedAxios.post).toHaveBeenCalledWith(
-				"https://app.roocode.com/api/extension/share",
-				{ taskId: "task-123", visibility: "public" },
-				expect.any(Object),
-			)
+			expect(mockFetch).toHaveBeenCalledWith("https://app.roocode.com/api/extension/share", {
+				method: "POST",
+				headers: {
+					"Content-Type": "application/json",
+					Authorization: "Bearer session-token",
+					"User-Agent": "Roo-Code 1.0.0",
+				},
+				body: JSON.stringify({ taskId: "task-123", visibility: "public" }),
+				signal: expect.any(AbortSignal),
+			})
 		})
 
 		it("should default to organization visibility when not specified", async () => {
-			const mockResponse = {
-				data: {
-					success: true,
-					shareUrl: "https://app.roocode.com/share/abc123",
-				},
+			const mockResponseData = {
+				success: true,
+				shareUrl: "https://app.roocode.com/share/abc123",
 			}
 
 			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			mockedAxios.post.mockResolvedValue(mockResponse)
+			mockFetch.mockResolvedValue({
+				ok: true,
+				json: vi.fn().mockResolvedValue(mockResponseData),
+			})
 
 			const result = await shareService.shareTask("task-123")
 
 			expect(result.success).toBe(true)
-			expect(mockedAxios.post).toHaveBeenCalledWith(
-				"https://app.roocode.com/api/extension/share",
-				{ taskId: "task-123", visibility: "organization" },
-				expect.any(Object),
-			)
+			expect(mockFetch).toHaveBeenCalledWith("https://app.roocode.com/api/extension/share", {
+				method: "POST",
+				headers: {
+					"Content-Type": "application/json",
+					Authorization: "Bearer session-token",
+					"User-Agent": "Roo-Code 1.0.0",
+				},
+				body: JSON.stringify({ taskId: "task-123", visibility: "organization" }),
+				signal: expect.any(AbortSignal),
+			})
 		})
 
 		it("should handle API error response", async () => {
-			const mockResponse = {
-				data: {
-					success: false,
-					error: "Task not found",
-				},
+			const mockResponseData = {
+				success: false,
+				error: "Task not found",
 			}
 
 			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			mockedAxios.post.mockResolvedValue(mockResponse)
+			mockFetch.mockResolvedValue({
+				ok: true,
+				json: vi.fn().mockResolvedValue(mockResponseData),
+			})
 
 			const result = await shareService.shareTask("task-123", "organization")
 
@@ -165,10 +178,56 @@ describe("ShareService", () => {
 
 		it("should handle unexpected errors", async () => {
 			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			mockedAxios.post.mockRejectedValue(new Error("Network error"))
+			mockFetch.mockRejectedValue(new Error("Network error"))
 
 			await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow("Network error")
 		})
+
+		it("should throw TaskNotFoundError for 404 responses", async () => {
+			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
+			mockFetch.mockResolvedValue({
+				ok: false,
+				status: 404,
+				statusText: "Not Found",
+			})
+
+			await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow(TaskNotFoundError)
+			await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow(
+				"Task 'task-123' not found",
+			)
+		})
+
+		it("should throw generic Error for non-404 HTTP errors", async () => {
+			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
+			mockFetch.mockResolvedValue({
+				ok: false,
+				status: 500,
+				statusText: "Internal Server Error",
+			})
+
+			await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow(
+				"HTTP 500: Internal Server Error",
+			)
+			await expect(shareService.shareTask("task-123", "organization")).rejects.not.toThrow(TaskNotFoundError)
+		})
+
+		it("should create TaskNotFoundError with correct properties", async () => {
+			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
+			mockFetch.mockResolvedValue({
+				ok: false,
+				status: 404,
+				statusText: "Not Found",
+			})
+
+			try {
+				await shareService.shareTask("task-123", "organization")
+				expect.fail("Expected TaskNotFoundError to be thrown")
+			} catch (error) {
+				expect(error).toBeInstanceOf(TaskNotFoundError)
+				expect(error).toBeInstanceOf(Error)
+				expect((error as TaskNotFoundError).message).toBe("Task 'task-123' not found")
+			}
+		})
 	})
 
 	describe("canShareTask", () => {

+ 311 - 0
packages/cloud/src/__tests__/TelemetryClient.test.ts

@@ -424,4 +424,315 @@ describe("TelemetryClient", () => {
 			await client.shutdown()
 		})
 	})
+
+	describe("backfillMessages", () => {
+		it("should not send request when not authenticated", async () => {
+			mockAuthService.isAuthenticated.mockReturnValue(false)
+			const client = new TelemetryClient(mockAuthService, mockSettingsService)
+
+			const messages = [
+				{
+					ts: 1,
+					type: "say" as const,
+					say: "text" as const,
+					text: "test message",
+				},
+			]
+
+			await client.backfillMessages(messages, "test-task-id")
+
+			expect(mockFetch).not.toHaveBeenCalled()
+		})
+
+		it("should not send request when no session token available", async () => {
+			mockAuthService.getSessionToken.mockReturnValue(null)
+			const client = new TelemetryClient(mockAuthService, mockSettingsService)
+
+			const messages = [
+				{
+					ts: 1,
+					type: "say" as const,
+					say: "text" as const,
+					text: "test message",
+				},
+			]
+
+			await client.backfillMessages(messages, "test-task-id")
+
+			expect(mockFetch).not.toHaveBeenCalled()
+			expect(console.error).toHaveBeenCalledWith(
+				"[TelemetryClient#backfillMessages] Unauthorized: No session token available.",
+			)
+		})
+
+		it("should send FormData request with correct structure when authenticated", async () => {
+			const client = new TelemetryClient(mockAuthService, mockSettingsService)
+
+			const providerProperties = {
+				appName: "roo-code",
+				appVersion: "1.0.0",
+				vscodeVersion: "1.60.0",
+				platform: "darwin",
+				editorName: "vscode",
+				language: "en",
+				mode: "code",
+			}
+
+			const mockProvider: TelemetryPropertiesProvider = {
+				getTelemetryProperties: vi.fn().mockResolvedValue(providerProperties),
+			}
+
+			client.setProvider(mockProvider)
+
+			const messages = [
+				{
+					ts: 1,
+					type: "say" as const,
+					say: "text" as const,
+					text: "test message 1",
+				},
+				{
+					ts: 2,
+					type: "ask" as const,
+					ask: "followup" as const,
+					text: "test question",
+				},
+			]
+
+			await client.backfillMessages(messages, "test-task-id")
+
+			expect(mockFetch).toHaveBeenCalledWith(
+				"https://app.roocode.com/api/events/backfill",
+				expect.objectContaining({
+					method: "POST",
+					headers: {
+						Authorization: "Bearer mock-token",
+					},
+					body: expect.any(FormData),
+				}),
+			)
+
+			// Verify FormData contents
+			const call = mockFetch.mock.calls[0]
+			const formData = call[1].body as FormData
+
+			expect(formData.get("taskId")).toBe("test-task-id")
+
+			// Parse and compare properties as objects since JSON.stringify order can vary
+			const propertiesJson = formData.get("properties") as string
+			const parsedProperties = JSON.parse(propertiesJson)
+			expect(parsedProperties).toEqual({
+				taskId: "test-task-id",
+				...providerProperties,
+			})
+			// The messages are stored as a File object under the "file" key
+			const fileField = formData.get("file") as File
+			expect(fileField).toBeInstanceOf(File)
+			expect(fileField.name).toBe("task.json")
+			expect(fileField.type).toBe("application/json")
+
+			// Read the file content to verify the messages
+			const fileContent = await fileField.text()
+			expect(fileContent).toBe(JSON.stringify(messages))
+		})
+
+		it("should handle provider errors gracefully", async () => {
+			const client = new TelemetryClient(mockAuthService, mockSettingsService)
+
+			const mockProvider: TelemetryPropertiesProvider = {
+				getTelemetryProperties: vi.fn().mockRejectedValue(new Error("Provider error")),
+			}
+
+			client.setProvider(mockProvider)
+
+			const messages = [
+				{
+					ts: 1,
+					type: "say" as const,
+					say: "text" as const,
+					text: "test message",
+				},
+			]
+
+			await client.backfillMessages(messages, "test-task-id")
+
+			expect(mockFetch).toHaveBeenCalledWith(
+				"https://app.roocode.com/api/events/backfill",
+				expect.objectContaining({
+					method: "POST",
+					headers: {
+						Authorization: "Bearer mock-token",
+					},
+					body: expect.any(FormData),
+				}),
+			)
+
+			// Verify FormData contents - should still work with just taskId
+			const call = mockFetch.mock.calls[0]
+			const formData = call[1].body as FormData
+
+			expect(formData.get("taskId")).toBe("test-task-id")
+			expect(formData.get("properties")).toBe(
+				JSON.stringify({
+					taskId: "test-task-id",
+				}),
+			)
+			// The messages are stored as a File object under the "file" key
+			const fileField = formData.get("file") as File
+			expect(fileField).toBeInstanceOf(File)
+			expect(fileField.name).toBe("task.json")
+			expect(fileField.type).toBe("application/json")
+
+			// Read the file content to verify the messages
+			const fileContent = await fileField.text()
+			expect(fileContent).toBe(JSON.stringify(messages))
+		})
+
+		it("should work without provider set", async () => {
+			const client = new TelemetryClient(mockAuthService, mockSettingsService)
+
+			const messages = [
+				{
+					ts: 1,
+					type: "say" as const,
+					say: "text" as const,
+					text: "test message",
+				},
+			]
+
+			await client.backfillMessages(messages, "test-task-id")
+
+			expect(mockFetch).toHaveBeenCalledWith(
+				"https://app.roocode.com/api/events/backfill",
+				expect.objectContaining({
+					method: "POST",
+					headers: {
+						Authorization: "Bearer mock-token",
+					},
+					body: expect.any(FormData),
+				}),
+			)
+
+			// Verify FormData contents - should work with just taskId
+			const call = mockFetch.mock.calls[0]
+			const formData = call[1].body as FormData
+
+			expect(formData.get("taskId")).toBe("test-task-id")
+			expect(formData.get("properties")).toBe(
+				JSON.stringify({
+					taskId: "test-task-id",
+				}),
+			)
+			// The messages are stored as a File object under the "file" key
+			const fileField = formData.get("file") as File
+			expect(fileField).toBeInstanceOf(File)
+			expect(fileField.name).toBe("task.json")
+			expect(fileField.type).toBe("application/json")
+
+			// Read the file content to verify the messages
+			const fileContent = await fileField.text()
+			expect(fileContent).toBe(JSON.stringify(messages))
+		})
+
+		it("should handle fetch errors gracefully", async () => {
+			const client = new TelemetryClient(mockAuthService, mockSettingsService)
+
+			mockFetch.mockRejectedValue(new Error("Network error"))
+
+			const messages = [
+				{
+					ts: 1,
+					type: "say" as const,
+					say: "text" as const,
+					text: "test message",
+				},
+			]
+
+			await expect(client.backfillMessages(messages, "test-task-id")).resolves.not.toThrow()
+
+			expect(console.error).toHaveBeenCalledWith(
+				expect.stringContaining(
+					"[TelemetryClient#backfillMessages] Error uploading messages: Error: Network error",
+				),
+			)
+		})
+
+		it("should handle HTTP error responses", async () => {
+			const client = new TelemetryClient(mockAuthService, mockSettingsService)
+
+			mockFetch.mockResolvedValue({
+				ok: false,
+				status: 404,
+				statusText: "Not Found",
+			})
+
+			const messages = [
+				{
+					ts: 1,
+					type: "say" as const,
+					say: "text" as const,
+					text: "test message",
+				},
+			]
+
+			await client.backfillMessages(messages, "test-task-id")
+
+			expect(console.error).toHaveBeenCalledWith(
+				"[TelemetryClient#backfillMessages] POST events/backfill -> 404 Not Found",
+			)
+		})
+
+		it("should log debug information when debug is enabled", async () => {
+			const client = new TelemetryClient(mockAuthService, mockSettingsService, true)
+
+			const messages = [
+				{
+					ts: 1,
+					type: "say" as const,
+					say: "text" as const,
+					text: "test message",
+				},
+			]
+
+			await client.backfillMessages(messages, "test-task-id")
+
+			expect(console.info).toHaveBeenCalledWith(
+				"[TelemetryClient#backfillMessages] Uploading 1 messages for task test-task-id",
+			)
+			expect(console.info).toHaveBeenCalledWith(
+				"[TelemetryClient#backfillMessages] Successfully uploaded messages for task test-task-id",
+			)
+		})
+
+		it("should handle empty messages array", async () => {
+			const client = new TelemetryClient(mockAuthService, mockSettingsService)
+
+			await client.backfillMessages([], "test-task-id")
+
+			expect(mockFetch).toHaveBeenCalledWith(
+				"https://app.roocode.com/api/events/backfill",
+				expect.objectContaining({
+					method: "POST",
+					headers: {
+						Authorization: "Bearer mock-token",
+					},
+					body: expect.any(FormData),
+				}),
+			)
+
+			// Verify FormData contents
+			const call = mockFetch.mock.calls[0]
+			const formData = call[1].body as FormData
+
+			// The messages are stored as a File object under the "file" key
+			const fileField = formData.get("file") as File
+			expect(fileField).toBeInstanceOf(File)
+			expect(fileField.name).toBe("task.json")
+			expect(fileField.type).toBe("application/json")
+
+			// Read the file content to verify the empty messages array
+			const fileContent = await fileField.text()
+			expect(fileContent).toBe("[]")
+		})
+	})
 })

+ 1 - 0
packages/types/src/index.ts

@@ -13,6 +13,7 @@ export * from "./message.js"
 export * from "./mode.js"
 export * from "./model.js"
 export * from "./provider-settings.js"
+export * from "./sharing.js"
 export * from "./telemetry.js"
 export * from "./terminal.js"
 export * from "./tool.js"

+ 8 - 0
packages/types/src/sharing.ts

@@ -0,0 +1,8 @@
+/**
+ * Types related to task sharing functionality
+ */
+
+/**
+ * Visibility options for sharing tasks
+ */
+export type ShareVisibility = "organization" | "public"

+ 21 - 0
packages/types/src/telemetry.ts

@@ -50,6 +50,16 @@ export enum TelemetryEventName {
 	MARKETPLACE_TAB_VIEWED = "Marketplace Tab Viewed",
 	MARKETPLACE_INSTALL_BUTTON_CLICKED = "Marketplace Install Button Clicked",
 
+	SHARE_BUTTON_CLICKED = "Share Button Clicked",
+	SHARE_ORGANIZATION_CLICKED = "Share Organization Clicked",
+	SHARE_PUBLIC_CLICKED = "Share Public Clicked",
+	SHARE_CONNECT_TO_CLOUD_CLICKED = "Share Connect To Cloud Clicked",
+
+	ACCOUNT_CONNECT_CLICKED = "Account Connect Clicked",
+	ACCOUNT_CONNECT_SUCCESS = "Account Connect Success",
+	ACCOUNT_LOGOUT_CLICKED = "Account Logout Clicked",
+	ACCOUNT_LOGOUT_SUCCESS = "Account Logout Success",
+
 	SCHEMA_VALIDATION_ERROR = "Schema Validation Error",
 	DIFF_APPLICATION_ERROR = "Diff Application Error",
 	SHELL_INTEGRATION_ERROR = "Shell Integration Error",
@@ -68,6 +78,7 @@ export const appPropertiesSchema = z.object({
 	editorName: z.string(),
 	language: z.string(),
 	mode: z.string(),
+	cloudIsAuthenticated: z.boolean().optional(),
 })
 
 export const taskPropertiesSchema = z.object({
@@ -117,6 +128,16 @@ export const rooCodeTelemetryEventSchema = z.discriminatedUnion("type", [
 			TelemetryEventName.AUTHENTICATION_INITIATED,
 			TelemetryEventName.MARKETPLACE_ITEM_INSTALLED,
 			TelemetryEventName.MARKETPLACE_ITEM_REMOVED,
+			TelemetryEventName.MARKETPLACE_TAB_VIEWED,
+			TelemetryEventName.MARKETPLACE_INSTALL_BUTTON_CLICKED,
+			TelemetryEventName.SHARE_BUTTON_CLICKED,
+			TelemetryEventName.SHARE_ORGANIZATION_CLICKED,
+			TelemetryEventName.SHARE_PUBLIC_CLICKED,
+			TelemetryEventName.SHARE_CONNECT_TO_CLOUD_CLICKED,
+			TelemetryEventName.ACCOUNT_CONNECT_CLICKED,
+			TelemetryEventName.ACCOUNT_CONNECT_SUCCESS,
+			TelemetryEventName.ACCOUNT_LOGOUT_CLICKED,
+			TelemetryEventName.ACCOUNT_LOGOUT_SUCCESS,
 			TelemetryEventName.SCHEMA_VALIDATION_ERROR,
 			TelemetryEventName.DIFF_APPLICATION_ERROR,
 			TelemetryEventName.SHELL_INTEGRATION_ERROR,

+ 0 - 3
pnpm-lock.yaml

@@ -359,9 +359,6 @@ importers:
       '@roo-code/types':
         specifier: workspace:^
         version: link:../types
-      axios:
-        specifier: ^1.7.4
-        version: 1.9.0
       zod:
         specifier: ^3.25.61
         version: 3.25.61

+ 15 - 1
src/core/webview/ClineProvider.ts

@@ -30,7 +30,7 @@ import {
 	ORGANIZATION_ALLOW_ALL,
 } from "@roo-code/types"
 import { TelemetryService } from "@roo-code/telemetry"
-import { CloudService } from "@roo-code/cloud"
+import { CloudService, getRooCodeApiUrl } from "@roo-code/cloud"
 
 import { t } from "../../i18n"
 import { setPanel } from "../../activate/registerCommands"
@@ -1485,6 +1485,7 @@ export class ClineProvider
 			},
 			mdmCompliant: this.checkMdmCompliance(),
 			profileThresholds: profileThresholds ?? {},
+			cloudApiUrl: getRooCodeApiUrl(),
 			hasOpenedModeSelector: this.getGlobalState("hasOpenedModeSelector") ?? false,
 		}
 	}
@@ -1759,6 +1760,18 @@ export class ClineProvider
 
 		const packageJSON = this.context.extension?.packageJSON
 
+		// Get Roo Code Cloud authentication state
+		let cloudIsAuthenticated: boolean | undefined
+
+		try {
+			if (CloudService.hasInstance()) {
+				cloudIsAuthenticated = CloudService.instance.isAuthenticated()
+			}
+		} catch (error) {
+			// Silently handle errors to avoid breaking telemetry collection
+			this.log(`[getTelemetryProperties] Failed to get cloud auth state: ${error}`)
+		}
+
 		return {
 			appName: packageJSON?.name ?? Package.name,
 			appVersion: packageJSON?.version ?? Package.version,
@@ -1771,6 +1784,7 @@ export class ClineProvider
 			modelId: task?.api?.getModel().id,
 			diffStrategy: task?.diffStrategy?.getName(),
 			isSubtask: task ? !!task.parentTask : undefined,
+			cloudIsAuthenticated,
 		}
 	}
 }

+ 107 - 0
src/core/webview/__tests__/ClineProvider.spec.ts

@@ -308,6 +308,18 @@ vi.mock("../diff/strategies/multi-search-replace", () => ({
 	})),
 }))
 
+vi.mock("@roo-code/cloud", () => ({
+	CloudService: {
+		hasInstance: vi.fn().mockReturnValue(true),
+		get instance() {
+			return {
+				isAuthenticated: vi.fn().mockReturnValue(false),
+			}
+		},
+	},
+	getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
+}))
+
 afterAll(() => {
 	vi.restoreAllMocks()
 })
@@ -2090,6 +2102,11 @@ describe("getTelemetryProperties", () => {
 		// Reset mocks
 		vi.clearAllMocks()
 
+		// Initialize TelemetryService if not already initialized
+		if (!TelemetryService.hasInstance()) {
+			TelemetryService.createInstance([])
+		}
+
 		// Setup basic mocks
 		mockContext = {
 			globalState: {
@@ -2143,6 +2160,96 @@ describe("getTelemetryProperties", () => {
 
 		expect(properties).toHaveProperty("modelId", "claude-sonnet-4-20250514")
 	})
+
+	describe("cloud authentication telemetry", () => {
+		beforeEach(() => {
+			// Reset all mocks before each test
+			vi.clearAllMocks()
+		})
+
+		test("includes cloud authentication property when user is authenticated", async () => {
+			// Import the CloudService mock and update it
+			const { CloudService } = await import("@roo-code/cloud")
+			const mockCloudService = {
+				isAuthenticated: vi.fn().mockReturnValue(true),
+			}
+
+			// Update the existing mock
+			Object.defineProperty(CloudService, "instance", {
+				get: vi.fn().mockReturnValue(mockCloudService),
+				configurable: true,
+			})
+
+			const properties = await provider.getTelemetryProperties()
+
+			expect(properties).toHaveProperty("cloudIsAuthenticated", true)
+		})
+
+		test("includes cloud authentication property when user is not authenticated", async () => {
+			// Import the CloudService mock and update it
+			const { CloudService } = await import("@roo-code/cloud")
+			const mockCloudService = {
+				isAuthenticated: vi.fn().mockReturnValue(false),
+			}
+
+			// Update the existing mock
+			Object.defineProperty(CloudService, "instance", {
+				get: vi.fn().mockReturnValue(mockCloudService),
+				configurable: true,
+			})
+
+			const properties = await provider.getTelemetryProperties()
+
+			expect(properties).toHaveProperty("cloudIsAuthenticated", false)
+		})
+
+		test("handles CloudService errors gracefully", async () => {
+			// Import the CloudService mock and update it to throw an error
+			const { CloudService } = await import("@roo-code/cloud")
+			Object.defineProperty(CloudService, "instance", {
+				get: vi.fn().mockImplementation(() => {
+					throw new Error("CloudService not available")
+				}),
+				configurable: true,
+			})
+
+			const properties = await provider.getTelemetryProperties()
+
+			// Should still include basic telemetry properties
+			expect(properties).toHaveProperty("vscodeVersion")
+			expect(properties).toHaveProperty("platform")
+			expect(properties).toHaveProperty("appVersion", "1.0.0")
+
+			// Cloud property should be undefined when CloudService is not available
+			expect(properties).toHaveProperty("cloudIsAuthenticated", undefined)
+		})
+
+		test("handles CloudService method errors gracefully", async () => {
+			// Import the CloudService mock and update it
+			const { CloudService } = await import("@roo-code/cloud")
+			const mockCloudService = {
+				isAuthenticated: vi.fn().mockImplementation(() => {
+					throw new Error("Authentication check error")
+				}),
+			}
+
+			// Update the existing mock
+			Object.defineProperty(CloudService, "instance", {
+				get: vi.fn().mockReturnValue(mockCloudService),
+				configurable: true,
+			})
+
+			const properties = await provider.getTelemetryProperties()
+
+			// Should still include basic telemetry properties
+			expect(properties).toHaveProperty("vscodeVersion")
+			expect(properties).toHaveProperty("platform")
+			expect(properties).toHaveProperty("appVersion", "1.0.0")
+
+			// Property that errored should be undefined
+			expect(properties).toHaveProperty("cloudIsAuthenticated", undefined)
+		})
+	})
 })
 
 describe("ClineProvider - Router Models", () => {

+ 9 - 1
src/core/webview/webviewMessageHandler.ts

@@ -225,6 +225,7 @@ export const webviewMessageHandler = async (
 			break
 		case "shareCurrentTask":
 			const shareTaskId = provider.getCurrentCline()?.taskId
+			const clineMessages = provider.getCurrentCline()?.clineMessages
 			if (!shareTaskId) {
 				vscode.window.showErrorMessage(t("common:errors.share_no_active_task"))
 				break
@@ -232,7 +233,7 @@ export const webviewMessageHandler = async (
 
 			try {
 				const visibility = message.visibility || "organization"
-				const result = await CloudService.instance.shareTask(shareTaskId, visibility)
+				const result = await CloudService.instance.shareTask(shareTaskId, visibility, clineMessages)
 
 				if (result.success && result.shareUrl) {
 					// Show success notification
@@ -241,6 +242,13 @@ export const webviewMessageHandler = async (
 							? "common:info.public_share_link_copied"
 							: "common:info.organization_share_link_copied"
 					vscode.window.showInformationMessage(t(messageKey))
+
+					// Send success feedback to webview for inline display
+					await provider.postMessageToWebview({
+						type: "shareTaskSuccess",
+						visibility,
+						text: result.shareUrl,
+					})
 				} else {
 					// Handle error
 					const errorMessage = result.error || "Failed to create share link"

+ 3 - 9
src/package.json

@@ -98,8 +98,7 @@
 			{
 				"command": "roo-cline.accountButtonClicked",
 				"title": "Account",
-				"icon": "$(account)",
-				"when": "config.roo-cline.rooCodeCloudEnabled"
+				"icon": "$(account)"
 			},
 			{
 				"command": "roo-cline.settingsButtonClicked",
@@ -237,7 +236,7 @@
 				{
 					"command": "roo-cline.accountButtonClicked",
 					"group": "navigation@6",
-					"when": "view == roo-cline.SidebarProvider && config.roo-cline.rooCodeCloudEnabled"
+					"when": "view == roo-cline.SidebarProvider"
 				},
 				{
 					"command": "roo-cline.settingsButtonClicked",
@@ -269,7 +268,7 @@
 				{
 					"command": "roo-cline.accountButtonClicked",
 					"group": "navigation@5",
-					"when": "activeWebviewPanelId == roo-cline.TabPanelProvider && config.roo-cline.rooCodeCloudEnabled"
+					"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
 				},
 				{
 					"command": "roo-cline.settingsButtonClicked",
@@ -325,11 +324,6 @@
 					"default": "",
 					"description": "%settings.customStoragePath.description%"
 				},
-				"roo-cline.rooCodeCloudEnabled": {
-					"type": "boolean",
-					"default": false,
-					"description": "%settings.rooCodeCloudEnabled.description%"
-				},
 				"roo-cline.enableCodeActions": {
 					"type": "boolean",
 					"default": true,

+ 0 - 1
src/package.nls.ca.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "El proveïdor del model de llenguatge (p. ex. copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "La família del model de llenguatge (p. ex. gpt-4)",
 	"settings.customStoragePath.description": "Ruta d'emmagatzematge personalitzada. Deixeu-la buida per utilitzar la ubicació predeterminada. Admet rutes absolutes (p. ex. 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Habilitar Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Habilitar correccions ràpides de Roo Code."
 }

+ 0 - 1
src/package.nls.de.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "Der Anbieter des Sprachmodells (z.B. copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "Die Familie des Sprachmodells (z.B. gpt-4)",
 	"settings.customStoragePath.description": "Benutzerdefinierter Speicherpfad. Leer lassen, um den Standardspeicherort zu verwenden. Unterstützt absolute Pfade (z.B. 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Aktiviere Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Roo Code Schnelle Problembehebung aktivieren."
 }

+ 0 - 1
src/package.nls.es.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "El proveedor del modelo de lenguaje (ej. copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "La familia del modelo de lenguaje (ej. gpt-4)",
 	"settings.customStoragePath.description": "Ruta de almacenamiento personalizada. Dejar vacío para usar la ubicación predeterminada. Admite rutas absolutas (ej. 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Habilitar Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Habilitar correcciones rápidas de Roo Code."
 }

+ 0 - 1
src/package.nls.fr.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "Le fournisseur du modèle de langage (ex: copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "La famille du modèle de langage (ex: gpt-4)",
 	"settings.customStoragePath.description": "Chemin de stockage personnalisé. Laisser vide pour utiliser l'emplacement par défaut. Prend en charge les chemins absolus (ex: 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Activer Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Activer les correctifs rapides de Roo Code."
 }

+ 0 - 1
src/package.nls.hi.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "भाषा मॉडल का विक्रेता (उदा. copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "भाषा मॉडल का परिवार (उदा. gpt-4)",
 	"settings.customStoragePath.description": "कस्टम स्टोरेज पाथ। डिफ़ॉल्ट स्थान का उपयोग करने के लिए खाली छोड़ें। पूर्ण पथ का समर्थन करता है (उदा. 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Roo Code Cloud सक्षम करें।",
 	"settings.enableCodeActions.description": "Roo Code त्वरित सुधार सक्षम करें"
 }

+ 0 - 1
src/package.nls.id.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "Vendor dari model bahasa (misalnya copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "Keluarga dari model bahasa (misalnya gpt-4)",
 	"settings.customStoragePath.description": "Path penyimpanan kustom. Biarkan kosong untuk menggunakan lokasi default. Mendukung path absolut (misalnya 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Aktifkan Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Aktifkan perbaikan cepat Roo Code."
 }

+ 0 - 1
src/package.nls.it.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "Il fornitore del modello linguistico (es. copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "La famiglia del modello linguistico (es. gpt-4)",
 	"settings.customStoragePath.description": "Percorso di archiviazione personalizzato. Lasciare vuoto per utilizzare la posizione predefinita. Supporta percorsi assoluti (es. 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Abilita Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Abilita correzioni rapide di Roo Code."
 }

+ 0 - 1
src/package.nls.ja.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "言語モデルのベンダー(例:copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "言語モデルのファミリー(例:gpt-4)",
 	"settings.customStoragePath.description": "カスタムストレージパス。デフォルトの場所を使用する場合は空のままにします。絶対パスをサポートします(例:'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Roo Code Cloud を有効にする。",
 	"settings.enableCodeActions.description": "Roo Codeのクイック修正を有効にする。"
 }

+ 0 - 1
src/package.nls.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "The vendor of the language model (e.g. copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "The family of the language model (e.g. gpt-4)",
 	"settings.customStoragePath.description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Enable Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Enable Roo Code quick fixes"
 }

+ 0 - 1
src/package.nls.ko.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "언어 모델 공급자 (예: copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "언어 모델 계열 (예: gpt-4)",
 	"settings.customStoragePath.description": "사용자 지정 저장소 경로. 기본 위치를 사용하려면 비워두세요. 절대 경로를 지원합니다 (예: 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Roo Code Cloud 사용 설정",
 	"settings.enableCodeActions.description": "Roo Code 빠른 수정 사용 설정"
 }

+ 0 - 1
src/package.nls.nl.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "De leverancier van het taalmodel (bijv. copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "De familie van het taalmodel (bijv. gpt-4)",
 	"settings.customStoragePath.description": "Aangepast opslagpad. Laat leeg om de standaardlocatie te gebruiken. Ondersteunt absolute paden (bijv. 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Roo Code Cloud inschakelen.",
 	"settings.enableCodeActions.description": "Snelle correcties van Roo Code inschakelen."
 }

+ 0 - 1
src/package.nls.pl.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "Dostawca modelu językowego (np. copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "Rodzina modelu językowego (np. gpt-4)",
 	"settings.customStoragePath.description": "Niestandardowa ścieżka przechowywania. Pozostaw puste, aby użyć domyślnej lokalizacji. Obsługuje ścieżki bezwzględne (np. 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Włącz Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Włącz szybkie poprawki Roo Code."
 }

+ 0 - 1
src/package.nls.pt-BR.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "O fornecedor do modelo de linguagem (ex: copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "A família do modelo de linguagem (ex: gpt-4)",
 	"settings.customStoragePath.description": "Caminho de armazenamento personalizado. Deixe vazio para usar o local padrão. Suporta caminhos absolutos (ex: 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Habilitar Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Habilitar correções rápidas do Roo Code."
 }

+ 0 - 1
src/package.nls.ru.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "Поставщик языковой модели (например, copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "Семейство языковой модели (например, gpt-4)",
 	"settings.customStoragePath.description": "Пользовательский путь хранения. Оставьте пустым для использования пути по умолчанию. Поддерживает абсолютные пути (например, 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Включить Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Включить быстрые исправления Roo Code."
 }

+ 0 - 1
src/package.nls.tr.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "Dil modelinin sağlayıcısı (örn: copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "Dil modelinin ailesi (örn: gpt-4)",
 	"settings.customStoragePath.description": "Özel depolama yolu. Varsayılan konumu kullanmak için boş bırakın. Mutlak yolları destekler (örn: 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Roo Code Cloud'u Etkinleştir.",
 	"settings.enableCodeActions.description": "Roo Code hızlı düzeltmeleri etkinleştir."
 }

+ 0 - 1
src/package.nls.vi.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "Nhà cung cấp mô hình ngôn ngữ (ví dụ: copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "Họ mô hình ngôn ngữ (ví dụ: gpt-4)",
 	"settings.customStoragePath.description": "Đường dẫn lưu trữ tùy chỉnh. Để trống để sử dụng vị trí mặc định. Hỗ trợ đường dẫn tuyệt đối (ví dụ: 'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "Bật Roo Code Cloud.",
 	"settings.enableCodeActions.description": "Bật sửa lỗi nhanh Roo Code."
 }

+ 0 - 1
src/package.nls.zh-CN.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "语言模型的供应商(例如:copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "语言模型的系列(例如:gpt-4)",
 	"settings.customStoragePath.description": "自定义存储路径。留空以使用默认位置。支持绝对路径(例如:'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "启用 Roo Code Cloud。",
 	"settings.enableCodeActions.description": "启用 Roo Code 快速修复"
 }

+ 0 - 1
src/package.nls.zh-TW.json

@@ -30,6 +30,5 @@
 	"settings.vsCodeLmModelSelector.vendor.description": "語言模型供應商(例如:copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "語言模型系列(例如:gpt-4)",
 	"settings.customStoragePath.description": "自訂儲存路徑。留空以使用預設位置。支援絕對路徑(例如:'D:\\RooCodeStorage')",
-	"settings.rooCodeCloudEnabled.description": "啟用 Roo Code Cloud。",
 	"settings.enableCodeActions.description": "啟用 Roo Code 快速修復。"
 }

+ 0 - 19
src/services/mdm/MdmService.ts

@@ -35,8 +35,6 @@ export class MdmService {
 			this.mdmConfig = await this.loadMdmConfig()
 			if (this.mdmConfig) {
 				this.log("[MDM] Loaded MDM configuration:", this.mdmConfig)
-				// Automatically enable Roo Code Cloud when MDM config is present
-				await this.ensureCloudEnabled()
 			} else {
 				this.log("[MDM] No MDM configuration found")
 			}
@@ -60,23 +58,6 @@ export class MdmService {
 		return this.mdmConfig?.organizationId
 	}
 
-	/**
-	 * Ensure Roo Code Cloud is enabled when MDM config is present
-	 */
-	private async ensureCloudEnabled(): Promise<void> {
-		try {
-			const config = vscode.workspace.getConfiguration(Package.name)
-			const currentValue = config.get<boolean>("rooCodeCloudEnabled", false)
-
-			if (!currentValue) {
-				this.log("[MDM] Enabling Roo Code Cloud due to MDM policy")
-				await config.update("rooCodeCloudEnabled", true, vscode.ConfigurationTarget.Global)
-			}
-		} catch (error) {
-			this.log("[MDM] Error enabling Roo Code Cloud:", error)
-		}
-	}
-
 	/**
 	 * Check if the current state is compliant with MDM policy
 	 */

+ 0 - 96
src/services/mdm/__tests__/MdmService.spec.ts

@@ -340,102 +340,6 @@ describe("MdmService", () => {
 		})
 	})
 
-	describe("cloud enablement", () => {
-		it("should enable Roo Code Cloud when MDM config is present and setting is disabled", async () => {
-			const mockConfig = {
-				requireCloudAuth: true,
-				organizationId: "test-org-123",
-			}
-
-			mockFs.existsSync.mockReturnValue(true)
-			mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig))
-
-			const mockVsCodeConfig = {
-				get: vi.fn().mockReturnValue(false), // rooCodeCloudEnabled is false
-				update: vi.fn().mockResolvedValue(undefined),
-			}
-			mockVscode.workspace.getConfiguration.mockReturnValue(mockVsCodeConfig)
-
-			await MdmService.createInstance()
-
-			expect(mockVscode.workspace.getConfiguration).toHaveBeenCalledWith("roo-cline")
-			expect(mockVsCodeConfig.get).toHaveBeenCalledWith("rooCodeCloudEnabled", false)
-			expect(mockVsCodeConfig.update).toHaveBeenCalledWith("rooCodeCloudEnabled", true, 1) // ConfigurationTarget.Global
-		})
-
-		it("should not update setting when Roo Code Cloud is already enabled", async () => {
-			const mockConfig = {
-				requireCloudAuth: true,
-				organizationId: "test-org-123",
-			}
-
-			mockFs.existsSync.mockReturnValue(true)
-			mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig))
-
-			const mockVsCodeConfig = {
-				get: vi.fn().mockReturnValue(true), // rooCodeCloudEnabled is already true
-				update: vi.fn().mockResolvedValue(undefined),
-			}
-			mockVscode.workspace.getConfiguration.mockReturnValue(mockVsCodeConfig)
-
-			await MdmService.createInstance()
-
-			expect(mockVsCodeConfig.get).toHaveBeenCalledWith("rooCodeCloudEnabled", false)
-			expect(mockVsCodeConfig.update).not.toHaveBeenCalled()
-		})
-
-		it("should enable cloud even when requireCloudAuth is false", async () => {
-			const mockConfig = {
-				requireCloudAuth: false, // Cloud auth not required, but config file exists
-			}
-
-			mockFs.existsSync.mockReturnValue(true)
-			mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig))
-
-			const mockVsCodeConfig = {
-				get: vi.fn().mockReturnValue(false),
-				update: vi.fn().mockResolvedValue(undefined),
-			}
-			mockVscode.workspace.getConfiguration.mockReturnValue(mockVsCodeConfig)
-
-			await MdmService.createInstance()
-
-			expect(mockVsCodeConfig.update).toHaveBeenCalledWith("rooCodeCloudEnabled", true, 1)
-		})
-
-		it("should not enable cloud when no MDM config exists", async () => {
-			mockFs.existsSync.mockReturnValue(false)
-
-			const mockVsCodeConfig = {
-				get: vi.fn().mockReturnValue(false),
-				update: vi.fn().mockResolvedValue(undefined),
-			}
-			mockVscode.workspace.getConfiguration.mockReturnValue(mockVsCodeConfig)
-
-			await MdmService.createInstance()
-
-			expect(mockVsCodeConfig.update).not.toHaveBeenCalled()
-		})
-
-		it("should handle VSCode configuration errors gracefully", async () => {
-			const mockConfig = {
-				requireCloudAuth: true,
-			}
-
-			mockFs.existsSync.mockReturnValue(true)
-			mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig))
-
-			const mockVsCodeConfig = {
-				get: vi.fn().mockReturnValue(false),
-				update: vi.fn().mockRejectedValue(new Error("Configuration update failed")),
-			}
-			mockVscode.workspace.getConfiguration.mockReturnValue(mockVsCodeConfig)
-
-			// Should not throw
-			await expect(MdmService.createInstance()).resolves.toBeInstanceOf(MdmService)
-		})
-	})
-
 	describe("singleton pattern", () => {
 		it("should throw error when accessing instance before creation", () => {
 			expect(() => MdmService.getInstance()).toThrow("MdmService not initialized")

+ 4 - 0
src/shared/ExtensionMessage.ts

@@ -9,6 +9,7 @@ import type {
 	ClineMessage,
 	OrganizationAllowList,
 	CloudUserInfo,
+	ShareVisibility,
 } from "@roo-code/types"
 
 import { GitCommit } from "../utils/git"
@@ -97,6 +98,7 @@ export interface ExtensionMessage {
 		| "codebaseIndexConfig"
 		| "marketplaceInstallResult"
 		| "marketplaceData"
+		| "shareTaskSuccess"
 	text?: string
 	payload?: any // Add a generic payload for now, can refine later
 	action?:
@@ -145,6 +147,7 @@ export interface ExtensionMessage {
 	tab?: string
 	marketplaceItems?: MarketplaceItem[]
 	marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
+	visibility?: ShareVisibility
 }
 
 export type ExtensionState = Pick<
@@ -253,6 +256,7 @@ export type ExtensionState = Pick<
 
 	cloudUserInfo: CloudUserInfo | null
 	cloudIsAuthenticated: boolean
+	cloudApiUrl?: string
 	sharingEnabled: boolean
 	organizationAllowList: OrganizationAllowList
 

+ 3 - 1
src/shared/WebviewMessage.ts

@@ -6,6 +6,7 @@ import type {
 	ModeConfig,
 	InstallMarketplaceItemOptions,
 	MarketplaceItem,
+	ShareVisibility,
 } from "@roo-code/types"
 import { marketplaceItemSchema } from "@roo-code/types"
 
@@ -173,6 +174,7 @@ export interface WebviewMessage {
 		| "fetchMarketplaceData"
 		| "switchTab"
 		| "profileThresholds"
+		| "shareTaskSuccess"
 	text?: string
 	tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
 	disabled?: boolean
@@ -210,7 +212,7 @@ export interface WebviewMessage {
 	mpItem?: MarketplaceItem
 	mpInstallOptions?: InstallMarketplaceItemOptions
 	config?: Record<string, any> // Add config to the payload
-	visibility?: "organization" | "public" // For share visibility
+	visibility?: ShareVisibility // For share visibility
 }
 
 export const checkoutDiffPayloadSchema = z.object({

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

@@ -43,6 +43,7 @@ const App = () => {
 		machineId,
 		cloudUserInfo,
 		cloudIsAuthenticated,
+		cloudApiUrl,
 		renderContext,
 		mdmCompliant,
 	} = useExtensionState()
@@ -186,6 +187,7 @@ const App = () => {
 				<AccountView
 					userInfo={cloudUserInfo}
 					isAuthenticated={cloudIsAuthenticated}
+					cloudApiUrl={cloudApiUrl}
 					onDone={() => switchTab("chat")}
 				/>
 			)}

+ 71 - 15
webview-ui/src/components/account/AccountView.tsx

@@ -1,21 +1,56 @@
+import { useEffect, useRef } from "react"
 import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
 
 import type { CloudUserInfo } from "@roo-code/types"
+import { TelemetryEventName } from "@roo-code/types"
 
 import { useAppTranslation } from "@src/i18n/TranslationContext"
 import { vscode } from "@src/utils/vscode"
+import { telemetryClient } from "@src/utils/TelemetryClient"
 
 type AccountViewProps = {
 	userInfo: CloudUserInfo | null
 	isAuthenticated: boolean
+	cloudApiUrl?: string
 	onDone: () => void
 }
 
-export const AccountView = ({ userInfo, isAuthenticated, onDone }: AccountViewProps) => {
+export const AccountView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: AccountViewProps) => {
 	const { t } = useAppTranslation()
+	const wasAuthenticatedRef = useRef(false)
 
 	const rooLogoUri = (window as any).IMAGES_BASE_URI + "/roo-logo.svg"
 
+	// Track authentication state changes to detect successful logout
+	useEffect(() => {
+		if (isAuthenticated) {
+			wasAuthenticatedRef.current = true
+		} else if (wasAuthenticatedRef.current && !isAuthenticated) {
+			// User just logged out successfully
+			telemetryClient.capture(TelemetryEventName.ACCOUNT_LOGOUT_SUCCESS)
+			wasAuthenticatedRef.current = false
+		}
+	}, [isAuthenticated])
+
+	const handleConnectClick = () => {
+		// Send telemetry for account connect action
+		telemetryClient.capture(TelemetryEventName.ACCOUNT_CONNECT_CLICKED)
+		vscode.postMessage({ type: "rooCloudSignIn" })
+	}
+
+	const handleLogoutClick = () => {
+		// Send telemetry for account logout action
+		telemetryClient.capture(TelemetryEventName.ACCOUNT_LOGOUT_CLICKED)
+		vscode.postMessage({ type: "rooCloudSignOut" })
+	}
+
+	const handleVisitCloudWebsite = () => {
+		// Send telemetry for cloud website visit
+		telemetryClient.capture(TelemetryEventName.ACCOUNT_CONNECT_CLICKED)
+		const cloudUrl = cloudApiUrl || "https://app.roocode.com"
+		vscode.postMessage({ type: "openExternal", url: cloudUrl })
+	}
+
 	return (
 		<div className="flex flex-col h-full p-4 bg-vscode-editor-background">
 			<div className="flex justify-between items-center mb-6">
@@ -41,9 +76,9 @@ export const AccountView = ({ userInfo, isAuthenticated, onDone }: AccountViewPr
 									</div>
 								)}
 							</div>
-							<h2 className="text-lg font-medium text-vscode-foreground mb-0">
-								{userInfo?.name || t("account:unknownUser")}
-							</h2>
+							{userInfo.name && (
+								<h2 className="text-lg font-medium text-vscode-foreground mb-0">{userInfo.name}</h2>
+							)}
 							{userInfo?.email && (
 								<p className="text-sm text-vscode-descriptionForeground">{userInfo?.email}</p>
 							)}
@@ -62,18 +97,18 @@ export const AccountView = ({ userInfo, isAuthenticated, onDone }: AccountViewPr
 						</div>
 					)}
 					<div className="flex flex-col gap-2 mt-4">
-						<VSCodeButton
-							appearance="secondary"
-							onClick={() => vscode.postMessage({ type: "rooCloudSignOut" })}
-							className="w-full">
+						<VSCodeButton appearance="secondary" onClick={handleVisitCloudWebsite} className="w-full">
+							{t("account:visitCloudWebsite")}
+						</VSCodeButton>
+						<VSCodeButton appearance="secondary" onClick={handleLogoutClick} className="w-full">
 							{t("account:logOut")}
 						</VSCodeButton>
 					</div>
 				</>
 			) : (
 				<>
-					<div className="flex flex-col items-center mb-4 text-center">
-						<div className="w-16 h-16 mb-4 flex items-center justify-center">
+					<div className="flex flex-col items-center mb-1 text-center">
+						<div className="w-16 h-16 mb-1 flex items-center justify-center">
 							<div
 								className="w-12 h-12 bg-vscode-foreground"
 								style={{
@@ -88,12 +123,33 @@ export const AccountView = ({ userInfo, isAuthenticated, onDone }: AccountViewPr
 							</div>
 						</div>
 					</div>
+
+					<div className="flex flex-col mb-6 text-center">
+						<h2 className="text-lg font-medium text-vscode-foreground mb-2">
+							{t("account:cloudBenefitsTitle")}
+						</h2>
+						<p className="text-md text-vscode-descriptionForeground mb-4">
+							{t("account:cloudBenefitsSubtitle")}
+						</p>
+						<ul className="text-sm text-vscode-descriptionForeground space-y-2 max-w-xs mx-auto">
+							<li className="flex items-start">
+								<span className="mr-2 text-vscode-foreground">•</span>
+								{t("account:cloudBenefitHistory")}
+							</li>
+							<li className="flex items-start">
+								<span className="mr-2 text-vscode-foreground">•</span>
+								{t("account:cloudBenefitSharing")}
+							</li>
+							<li className="flex items-start">
+								<span className="mr-2 text-vscode-foreground">•</span>
+								{t("account:cloudBenefitMetrics")}
+							</li>
+						</ul>
+					</div>
+
 					<div className="flex flex-col gap-4">
-						<VSCodeButton
-							appearance="primary"
-							onClick={() => vscode.postMessage({ type: "rooCloudSignIn" })}
-							className="w-full">
-							{t("account:signIn")}
+						<VSCodeButton appearance="primary" onClick={handleConnectClick} className="w-full">
+							{t("account:connect")}
 						</VSCodeButton>
 					</div>
 				</>

+ 92 - 0
webview-ui/src/components/account/__tests__/AccountView.spec.tsx

@@ -0,0 +1,92 @@
+import { render, screen } from "@testing-library/react"
+import { describe, it, expect, vi } from "vitest"
+import { AccountView } from "../AccountView"
+
+// Mock the translation context
+vi.mock("@src/i18n/TranslationContext", () => ({
+	useAppTranslation: () => ({
+		t: (key: string) => {
+			const translations: Record<string, string> = {
+				"account:title": "Account",
+				"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:logOut": "Log out",
+			}
+			return translations[key] || key
+		},
+	}),
+}))
+
+// Mock vscode utilities
+vi.mock("@src/utils/vscode", () => ({
+	vscode: {
+		postMessage: vi.fn(),
+	},
+}))
+
+// Mock telemetry client
+vi.mock("@src/utils/TelemetryClient", () => ({
+	telemetryClient: {
+		capture: vi.fn(),
+	},
+}))
+
+// Mock window global for images
+Object.defineProperty(window, "IMAGES_BASE_URI", {
+	value: "/images",
+	writable: true,
+})
+
+describe("AccountView", () => {
+	it("should display benefits when user is not authenticated", () => {
+		render(
+			<AccountView
+				userInfo={null}
+				isAuthenticated={false}
+				cloudApiUrl="https://app.roocode.com"
+				onDone={() => {}}
+			/>,
+		)
+
+		// 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()
+
+		// Check that the connect button is also present
+		expect(screen.getByText("account:connect")).toBeInTheDocument()
+	})
+
+	it("should not display benefits when user is authenticated", () => {
+		const mockUserInfo = {
+			name: "Test User",
+			email: "[email protected]",
+		}
+
+		render(
+			<AccountView
+				userInfo={mockUserInfo}
+				isAuthenticated={true}
+				cloudApiUrl="https://app.roocode.com"
+				onDone={() => {}}
+			/>,
+		)
+
+		// 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()
+
+		// Check that user info is displayed instead
+		expect(screen.getByText("Test User")).toBeInTheDocument()
+		expect(screen.getByText("[email protected]")).toBeInTheDocument()
+	})
+})

+ 1 - 0
webview-ui/src/components/chat/IconButton.tsx

@@ -40,6 +40,7 @@ export const IconButton: React.FC<IconButtonProps> = ({
 			aria-label={title}
 			title={title}
 			className={buttonClasses}
+			disabled={disabled}
 			onClick={!disabled ? onClick : undefined}
 			style={{ fontSize: 16.5, ...style }}
 			{...props}>

+ 259 - 0
webview-ui/src/components/chat/ShareButton.tsx

@@ -0,0 +1,259 @@
+import { useState, useEffect, useRef } from "react"
+import { useTranslation } from "react-i18next"
+
+import type { HistoryItem, ShareVisibility } from "@roo-code/types"
+import { TelemetryEventName } from "@roo-code/types"
+
+import { vscode } from "@/utils/vscode"
+import { telemetryClient } from "@/utils/TelemetryClient"
+import { useExtensionState } from "@/context/ExtensionStateContext"
+import {
+	Button,
+	Popover,
+	PopoverContent,
+	PopoverTrigger,
+	Command,
+	CommandList,
+	CommandItem,
+	CommandGroup,
+	Dialog,
+	DialogContent,
+	DialogHeader,
+	DialogTitle,
+} from "@/components/ui"
+
+interface ShareButtonProps {
+	item?: HistoryItem
+	disabled?: boolean
+}
+
+export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
+	const [shareDropdownOpen, setShareDropdownOpen] = useState(false)
+	const [connectModalOpen, setConnectModalOpen] = useState(false)
+	const [shareSuccess, setShareSuccess] = useState<{ visibility: ShareVisibility; url: string } | null>(null)
+	const { t } = useTranslation()
+	const { sharingEnabled, cloudIsAuthenticated, cloudUserInfo } = useExtensionState()
+	const wasUnauthenticatedRef = useRef(false)
+
+	// Track authentication state changes to auto-open popover after login
+	useEffect(() => {
+		if (!cloudIsAuthenticated || !sharingEnabled) {
+			wasUnauthenticatedRef.current = true
+		} else if (wasUnauthenticatedRef.current && cloudIsAuthenticated && sharingEnabled) {
+			// User just authenticated, send telemetry, close modal, and open the popover
+			telemetryClient.capture(TelemetryEventName.ACCOUNT_CONNECT_SUCCESS)
+			setConnectModalOpen(false)
+			setShareDropdownOpen(true)
+			wasUnauthenticatedRef.current = false
+		}
+	}, [cloudIsAuthenticated, sharingEnabled])
+
+	// Listen for share success messages from the extension
+	useEffect(() => {
+		const handleMessage = (event: MessageEvent) => {
+			const message = event.data
+			if (message.type === "shareTaskSuccess") {
+				setShareSuccess({
+					visibility: message.visibility,
+					url: message.text,
+				})
+				// Auto-hide success message and close popover after 5 seconds
+				setTimeout(() => {
+					setShareSuccess(null)
+					setShareDropdownOpen(false)
+				}, 5000)
+			}
+		}
+
+		window.addEventListener("message", handleMessage)
+		return () => window.removeEventListener("message", handleMessage)
+	}, [])
+
+	const handleShare = (visibility: ShareVisibility) => {
+		// Clear any previous success state
+		setShareSuccess(null)
+
+		// Send telemetry for share action
+		if (visibility === "organization") {
+			telemetryClient.capture(TelemetryEventName.SHARE_ORGANIZATION_CLICKED)
+		} else {
+			telemetryClient.capture(TelemetryEventName.SHARE_PUBLIC_CLICKED)
+		}
+
+		vscode.postMessage({
+			type: "shareCurrentTask",
+			visibility,
+		})
+		// Don't close the dropdown immediately - let success message show first
+	}
+
+	const handleConnectToCloud = () => {
+		// Send telemetry for connect to cloud action
+		telemetryClient.capture(TelemetryEventName.SHARE_CONNECT_TO_CLOUD_CLICKED)
+
+		vscode.postMessage({ type: "rooCloudSignIn" })
+		setShareDropdownOpen(false)
+		setConnectModalOpen(false)
+	}
+
+	const handleShareButtonClick = () => {
+		// Send telemetry for share button click
+		telemetryClient.capture(TelemetryEventName.SHARE_BUTTON_CLICKED)
+
+		if (!cloudIsAuthenticated) {
+			// Show modal for unauthenticated users
+			setConnectModalOpen(true)
+		} else {
+			// Show popover for authenticated users
+			setShareDropdownOpen(true)
+		}
+	}
+
+	// Determine share button state
+	const getShareButtonState = () => {
+		if (!cloudIsAuthenticated) {
+			return {
+				disabled: false,
+				title: t("chat:task.share"),
+				showPopover: false, // We'll show modal instead
+			}
+		} else if (!sharingEnabled) {
+			return {
+				disabled: true,
+				title: t("chat:task.sharingDisabledByOrganization"),
+				showPopover: false,
+			}
+		} else {
+			return {
+				disabled: false,
+				title: t("chat:task.share"),
+				showPopover: true,
+			}
+		}
+	}
+
+	const shareButtonState = getShareButtonState()
+
+	// Don't render if no item ID
+	if (!item?.id) {
+		return null
+	}
+
+	return (
+		<>
+			{shareButtonState.showPopover ? (
+				<Popover open={shareDropdownOpen} onOpenChange={setShareDropdownOpen}>
+					<PopoverTrigger asChild>
+						<Button
+							variant="ghost"
+							size="icon"
+							disabled={disabled || shareButtonState.disabled}
+							className="h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
+							title={shareButtonState.title}
+							onClick={handleShareButtonClick}>
+							<span className="codicon codicon-link"></span>
+						</Button>
+					</PopoverTrigger>
+					<PopoverContent className="w-56 p-0" align="start">
+						{shareSuccess ? (
+							<div className="p-3">
+								<div className="flex items-center gap-2 text-sm text-green-600 dark:text-green-400">
+									<span className="codicon codicon-check"></span>
+									<span>
+										{shareSuccess.visibility === "public"
+											? t("chat:task.shareSuccessPublic")
+											: t("chat:task.shareSuccessOrganization")}
+									</span>
+								</div>
+							</div>
+						) : (
+							<Command>
+								<CommandList>
+									<CommandGroup>
+										{cloudUserInfo?.organizationName && (
+											<CommandItem
+												onSelect={() => handleShare("organization")}
+												className="cursor-pointer">
+												<div className="flex items-center gap-2">
+													<span className="codicon codicon-organization text-sm"></span>
+													<div className="flex flex-col">
+														<span className="text-sm">
+															{t("chat:task.shareWithOrganization")}
+														</span>
+														<span className="text-xs text-vscode-descriptionForeground">
+															{t("chat:task.shareWithOrganizationDescription")}
+														</span>
+													</div>
+												</div>
+											</CommandItem>
+										)}
+										<CommandItem onSelect={() => handleShare("public")} className="cursor-pointer">
+											<div className="flex items-center gap-2">
+												<span className="codicon codicon-globe text-sm"></span>
+												<div className="flex flex-col">
+													<span className="text-sm">{t("chat:task.sharePublicly")}</span>
+													<span className="text-xs text-vscode-descriptionForeground">
+														{t("chat:task.sharePubliclyDescription")}
+													</span>
+												</div>
+											</div>
+										</CommandItem>
+									</CommandGroup>
+								</CommandList>
+							</Command>
+						)}
+					</PopoverContent>
+				</Popover>
+			) : (
+				<Button
+					variant="ghost"
+					size="icon"
+					disabled={disabled || shareButtonState.disabled}
+					className="h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
+					title={shareButtonState.title}
+					onClick={handleShareButtonClick}>
+					<span className="codicon codicon-link"></span>
+				</Button>
+			)}
+
+			{/* Connect to Cloud Modal */}
+			<Dialog open={connectModalOpen} onOpenChange={setConnectModalOpen}>
+				<DialogContent className="max-w-sm">
+					<DialogHeader className="text-center">
+						<DialogTitle className="text-lg font-medium text-vscode-foreground">
+							{t("account:cloudBenefitsTitle")}
+						</DialogTitle>
+					</DialogHeader>
+
+					<div className="flex flex-col space-y-6">
+						<div>
+							<p className="text-md text-vscode-descriptionForeground mb-4">
+								{t("account:cloudBenefitsSubtitle")}
+							</p>
+							<ul className="text-sm text-vscode-descriptionForeground space-y-2">
+								<li className="flex items-start">
+									<span className="mr-2 text-vscode-foreground">•</span>
+									{t("account:cloudBenefitSharing")}
+								</li>
+								<li className="flex items-start">
+									<span className="mr-2 text-vscode-foreground">•</span>
+									{t("account:cloudBenefitHistory")}
+								</li>
+								<li className="flex items-start">
+									<span className="mr-2 text-vscode-foreground">•</span>
+									{t("account:cloudBenefitMetrics")}
+								</li>
+							</ul>
+						</div>
+
+						<div className="flex flex-col gap-4">
+							<Button onClick={handleConnectToCloud} className="w-full">
+								{t("account:connect")}
+							</Button>
+						</div>
+					</div>
+				</DialogContent>
+			</Dialog>
+		</>
+	)
+}

+ 2 - 67
webview-ui/src/components/chat/TaskActions.tsx

@@ -5,20 +5,10 @@ import { useTranslation } from "react-i18next"
 import type { HistoryItem } from "@roo-code/types"
 
 import { vscode } from "@/utils/vscode"
-import { useExtensionState } from "@/context/ExtensionStateContext"
-import {
-	Button,
-	Popover,
-	PopoverContent,
-	PopoverTrigger,
-	Command,
-	CommandList,
-	CommandItem,
-	CommandGroup,
-} from "@/components/ui"
 
 import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
 import { IconButton } from "./IconButton"
+import { ShareButton } from "./ShareButton"
 
 interface TaskActionsProps {
 	item?: HistoryItem
@@ -27,66 +17,11 @@ interface TaskActionsProps {
 
 export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
 	const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
-	const [shareDropdownOpen, setShareDropdownOpen] = useState(false)
 	const { t } = useTranslation()
-	const { sharingEnabled } = useExtensionState()
-
-	const handleShare = (visibility: "organization" | "public") => {
-		vscode.postMessage({
-			type: "shareCurrentTask",
-			visibility,
-		})
-		setShareDropdownOpen(false)
-	}
 
 	return (
 		<div className="flex flex-row gap-1">
-			{item?.id && sharingEnabled && (
-				<Popover open={shareDropdownOpen} onOpenChange={setShareDropdownOpen}>
-					<PopoverTrigger asChild>
-						<Button
-							variant="ghost"
-							size="icon"
-							disabled={buttonsDisabled}
-							className="h-6 w-6 p-0 hover:bg-vscode-toolbar-hoverBackground"
-							title={t("chat:task.share")}>
-							<span className="codicon codicon-link text-xs"></span>
-						</Button>
-					</PopoverTrigger>
-					<PopoverContent className="w-56 p-0" align="start">
-						<Command>
-							<CommandList>
-								<CommandGroup>
-									<CommandItem
-										onSelect={() => handleShare("organization")}
-										className="cursor-pointer">
-										<div className="flex items-center gap-2">
-											<span className="codicon codicon-organization text-sm"></span>
-											<div className="flex flex-col">
-												<span className="text-sm">{t("chat:task.shareWithOrganization")}</span>
-												<span className="text-xs text-vscode-descriptionForeground">
-													{t("chat:task.shareWithOrganizationDescription")}
-												</span>
-											</div>
-										</div>
-									</CommandItem>
-									<CommandItem onSelect={() => handleShare("public")} className="cursor-pointer">
-										<div className="flex items-center gap-2">
-											<span className="codicon codicon-globe text-sm"></span>
-											<div className="flex flex-col">
-												<span className="text-sm">{t("chat:task.sharePublicly")}</span>
-												<span className="text-xs text-vscode-descriptionForeground">
-													{t("chat:task.sharePubliclyDescription")}
-												</span>
-											</div>
-										</div>
-									</CommandItem>
-								</CommandGroup>
-							</CommandList>
-						</Command>
-					</PopoverContent>
-				</Popover>
-			)}
+			<ShareButton item={item} disabled={buttonsDisabled} />
 			<IconButton
 				iconClass="codicon-desktop-download"
 				title={t("chat:task.export")}

+ 2 - 0
webview-ui/src/components/chat/TaskHeader.tsx

@@ -17,6 +17,7 @@ import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel"
 import Thumbnails from "../common/Thumbnails"
 
 import { TaskActions } from "./TaskActions"
+import { ShareButton } from "./ShareButton"
 import { ContextWindowProgress } from "./ContextWindowProgress"
 import { Mention } from "./Mention"
 
@@ -118,6 +119,7 @@ const TaskHeader = ({
 							}
 						/>
 						{condenseButton}
+						<ShareButton item={currentTaskItem} disabled={buttonsDisabled} />
 						{!!totalCost && <VSCodeBadge>${totalCost.toFixed(2)}</VSCodeBadge>}
 					</div>
 				)}

+ 325 - 0
webview-ui/src/components/chat/__tests__/ShareButton.spec.tsx

@@ -0,0 +1,325 @@
+import { describe, test, expect, vi, beforeEach } from "vitest"
+import { render, screen, fireEvent, waitFor } from "@testing-library/react"
+import { ShareButton } from "../ShareButton"
+import { useTranslation } from "react-i18next"
+import { vscode } from "@/utils/vscode"
+
+// Mock the vscode utility
+vi.mock("@/utils/vscode", () => ({
+	vscode: {
+		postMessage: vi.fn(),
+	},
+}))
+
+// Mock react-i18next
+vi.mock("react-i18next")
+
+// Mock the extension state context
+vi.mock("@/context/ExtensionStateContext", () => ({
+	ExtensionStateContextProvider: ({ children }: { children: React.ReactNode }) => children,
+	useExtensionState: () => ({
+		sharingEnabled: true,
+		cloudIsAuthenticated: true,
+		cloudUserInfo: {
+			id: "test-user",
+			email: "[email protected]",
+			organizationName: "Test Organization",
+		},
+	}),
+}))
+
+// Mock telemetry client
+vi.mock("@/utils/TelemetryClient", () => ({
+	telemetryClient: {
+		capture: vi.fn(),
+	},
+}))
+
+const mockUseTranslation = vi.mocked(useTranslation)
+const mockVscode = vi.mocked(vscode)
+
+describe("ShareButton", () => {
+	const mockT = vi.fn((key: string) => key)
+	const mockItem = {
+		id: "test-task-id",
+		number: 1,
+		ts: Date.now(),
+		task: "Test Task",
+		tokensIn: 100,
+		tokensOut: 50,
+		totalCost: 0.01,
+	}
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+
+		mockUseTranslation.mockReturnValue({
+			t: mockT,
+			i18n: {} as any,
+			ready: true,
+		} as any)
+	})
+
+	test("renders share button", () => {
+		render(<ShareButton item={mockItem} />)
+
+		const button = screen.getByRole("button")
+		expect(button).toBeInTheDocument()
+	})
+
+	test("opens popover when clicked", async () => {
+		render(<ShareButton item={mockItem} />)
+
+		const button = screen.getByRole("button")
+		fireEvent.click(button)
+
+		await waitFor(() => {
+			expect(screen.getByText("chat:task.shareWithOrganization")).toBeInTheDocument()
+		})
+	})
+
+	test("sends organization share message when organization button clicked", async () => {
+		render(<ShareButton item={mockItem} />)
+
+		// Open popover
+		const button = screen.getByRole("button")
+		fireEvent.click(button)
+
+		await waitFor(() => {
+			expect(screen.getByText("chat:task.shareWithOrganization")).toBeInTheDocument()
+		})
+
+		// Click organization share button
+		const orgButton = screen.getByText("chat:task.shareWithOrganization")
+		fireEvent.click(orgButton)
+
+		expect(mockVscode.postMessage).toHaveBeenCalledWith({
+			type: "shareCurrentTask",
+			visibility: "organization",
+		})
+	})
+
+	test("sends public share message when public button clicked", async () => {
+		render(<ShareButton item={mockItem} />)
+
+		// Open popover
+		const button = screen.getByRole("button")
+		fireEvent.click(button)
+
+		await waitFor(() => {
+			expect(screen.getByText("chat:task.sharePublicly")).toBeInTheDocument()
+		})
+
+		// Click public share button
+		const publicButton = screen.getByText("chat:task.sharePublicly")
+		fireEvent.click(publicButton)
+
+		expect(mockVscode.postMessage).toHaveBeenCalledWith({
+			type: "shareCurrentTask",
+			visibility: "public",
+		})
+	})
+
+	test("displays success message when shareTaskSuccess message received", async () => {
+		const mockAddEventListener = vi.fn()
+		const mockRemoveEventListener = vi.fn()
+
+		// Mock window.addEventListener
+		Object.defineProperty(window, "addEventListener", {
+			value: mockAddEventListener,
+			writable: true,
+		})
+		Object.defineProperty(window, "removeEventListener", {
+			value: mockRemoveEventListener,
+			writable: true,
+		})
+
+		render(<ShareButton item={mockItem} />)
+
+		// Get the message event listener that was registered
+		const messageListener = mockAddEventListener.mock.calls.find((call) => call[0] === "message")?.[1]
+
+		expect(messageListener).toBeDefined()
+
+		// Open popover first
+		const button = screen.getByRole("button")
+		fireEvent.click(button)
+
+		await waitFor(() => {
+			expect(screen.getByText("chat:task.shareWithOrganization")).toBeInTheDocument()
+		})
+
+		// Simulate receiving a shareTaskSuccess message
+		const mockEvent = {
+			data: {
+				type: "shareTaskSuccess",
+				visibility: "organization",
+				text: "https://example.com/share/123",
+			},
+		}
+
+		messageListener(mockEvent)
+
+		await waitFor(() => {
+			expect(screen.getByText("chat:task.shareSuccessOrganization")).toBeInTheDocument()
+		})
+	})
+
+	test("displays different success messages based on visibility", async () => {
+		const mockAddEventListener = vi.fn()
+
+		Object.defineProperty(window, "addEventListener", {
+			value: mockAddEventListener,
+			writable: true,
+		})
+
+		render(<ShareButton item={mockItem} />)
+
+		const messageListener = mockAddEventListener.mock.calls.find((call) => call[0] === "message")?.[1]
+
+		// Open popover
+		const button = screen.getByRole("button")
+		fireEvent.click(button)
+
+		await waitFor(() => {
+			expect(screen.getByText("chat:task.shareWithOrganization")).toBeInTheDocument()
+		})
+
+		// Test public visibility success message
+		const publicEvent = {
+			data: {
+				type: "shareTaskSuccess",
+				visibility: "public",
+				text: "https://example.com/share/456",
+			},
+		}
+
+		messageListener(publicEvent)
+
+		await waitFor(() => {
+			expect(screen.getByText("chat:task.shareSuccessPublic")).toBeInTheDocument()
+		})
+	})
+
+	test("auto-hides success message after 5 seconds", async () => {
+		vi.useFakeTimers({ shouldAdvanceTime: true })
+
+		const mockAddEventListener = vi.fn()
+
+		Object.defineProperty(window, "addEventListener", {
+			value: mockAddEventListener,
+			writable: true,
+		})
+
+		render(<ShareButton item={mockItem} />)
+
+		const messageListener = mockAddEventListener.mock.calls.find((call) => call[0] === "message")?.[1]
+
+		// Open popover
+		const button = screen.getByRole("button")
+		fireEvent.click(button)
+
+		await vi.waitFor(() => {
+			expect(screen.getByText("chat:task.shareWithOrganization")).toBeInTheDocument()
+		})
+
+		// Simulate success message
+		const mockEvent = {
+			data: {
+				type: "shareTaskSuccess",
+				visibility: "organization",
+				text: "https://example.com/share/123",
+			},
+		}
+
+		messageListener(mockEvent)
+
+		await vi.waitFor(() => {
+			expect(screen.getByText("chat:task.shareSuccessOrganization")).toBeInTheDocument()
+		})
+
+		// Fast-forward 5 seconds
+		await vi.advanceTimersByTimeAsync(5000)
+
+		// The success message and share options should both be gone (popover closed)
+		expect(screen.queryByText("chat:task.shareSuccessOrganization")).not.toBeInTheDocument()
+		expect(screen.queryByText("chat:task.shareWithOrganization")).not.toBeInTheDocument()
+
+		vi.useRealTimers()
+	})
+
+	test("clears previous success state when sharing again", async () => {
+		vi.useFakeTimers({ shouldAdvanceTime: true })
+
+		const mockAddEventListener = vi.fn()
+
+		Object.defineProperty(window, "addEventListener", {
+			value: mockAddEventListener,
+			writable: true,
+		})
+
+		render(<ShareButton item={mockItem} />)
+
+		const messageListener = mockAddEventListener.mock.calls.find((call) => call[0] === "message")?.[1]
+
+		// Open popover
+		const button = screen.getByRole("button")
+		fireEvent.click(button)
+
+		await vi.waitFor(() => {
+			expect(screen.getByText("chat:task.shareWithOrganization")).toBeInTheDocument()
+		})
+
+		// Click organization share button first time
+		const orgButton = screen.getByText("chat:task.shareWithOrganization")
+		fireEvent.click(orgButton)
+
+		// Verify first share message was sent
+		expect(mockVscode.postMessage).toHaveBeenCalledWith({
+			type: "shareCurrentTask",
+			visibility: "organization",
+		})
+
+		// Clear mock to track new calls
+		mockVscode.postMessage.mockClear()
+
+		// Show success message
+		const mockEvent = {
+			data: {
+				type: "shareTaskSuccess",
+				visibility: "organization",
+				text: "https://example.com/share/123",
+			},
+		}
+
+		messageListener(mockEvent)
+
+		await vi.waitFor(() => {
+			expect(screen.getByText("chat:task.shareSuccessOrganization")).toBeInTheDocument()
+		})
+
+		// Wait for success message to auto-hide after 5 seconds
+		await vi.advanceTimersByTimeAsync(5000)
+
+		// Success message should be gone and popover should be closed
+		expect(screen.queryByText("chat:task.shareSuccessOrganization")).not.toBeInTheDocument()
+
+		// Open popover again
+		fireEvent.click(button)
+		await vi.waitFor(() => {
+			expect(screen.getByText("chat:task.shareWithOrganization")).toBeInTheDocument()
+		})
+
+		// Click share again
+		const orgButton2 = screen.getByText("chat:task.shareWithOrganization")
+		fireEvent.click(orgButton2)
+
+		// Verify the share message was sent again (no success message should be showing)
+		expect(mockVscode.postMessage).toHaveBeenCalledWith({
+			type: "shareCurrentTask",
+			visibility: "organization",
+		})
+
+		vi.useRealTimers()
+	})
+})

+ 315 - 0
webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx

@@ -0,0 +1,315 @@
+import { render, screen, fireEvent } from "@testing-library/react"
+import { vi, describe, it, expect, beforeEach } from "vitest"
+import { TaskActions } from "../TaskActions"
+import type { HistoryItem } from "@roo-code/types"
+import { vscode } from "@/utils/vscode"
+import { useExtensionState } from "@/context/ExtensionStateContext"
+
+// Mock scrollIntoView for JSDOM
+Object.defineProperty(Element.prototype, "scrollIntoView", {
+	value: vi.fn(),
+	writable: true,
+})
+
+// Mock the vscode utility
+vi.mock("@/utils/vscode", () => ({
+	vscode: {
+		postMessage: vi.fn(),
+	},
+}))
+
+// Mock the useExtensionState hook
+vi.mock("@/context/ExtensionStateContext", () => ({
+	useExtensionState: vi.fn(),
+}))
+
+const mockPostMessage = vi.mocked(vscode.postMessage)
+const mockUseExtensionState = vi.mocked(useExtensionState)
+
+// Mock react-i18next
+vi.mock("react-i18next", () => ({
+	useTranslation: () => ({
+		t: (key: string) => {
+			const translations: Record<string, string> = {
+				"chat:task.share": "Share task",
+				"chat:task.export": "Export task history",
+				"chat:task.delete": "Delete Task (Shift + Click to skip confirmation)",
+				"chat:task.shareWithOrganization": "Share with Organization",
+				"chat:task.shareWithOrganizationDescription": "Only members of your organization can access",
+				"chat:task.sharePublicly": "Share Publicly",
+				"chat:task.sharePubliclyDescription": "Anyone with the link can access",
+				"chat:task.connectToCloud": "Connect to Cloud",
+				"chat:task.connectToCloudDescription": "Sign in to Roo Code Cloud to share tasks",
+				"chat:task.sharingDisabledByOrganization": "Sharing disabled by organization",
+				"account:cloudBenefitsTitle": "Connect to Roo Code Cloud",
+				"account:cloudBenefitsSubtitle": "Sign in to Roo Code Cloud to share tasks",
+				"account:cloudBenefitHistory": "Access your task history from anywhere",
+				"account:cloudBenefitSharing": "Share tasks with your team",
+				"account:cloudBenefitMetrics": "Track usage and costs",
+				"account:connect": "Connect",
+			}
+			return translations[key] || key
+		},
+	}),
+	initReactI18next: {
+		type: "3rdParty",
+		init: vi.fn(),
+	},
+}))
+
+// Mock pretty-bytes
+vi.mock("pretty-bytes", () => ({
+	default: (bytes: number) => `${bytes} B`,
+}))
+
+describe("TaskActions", () => {
+	const mockItem: HistoryItem = {
+		id: "test-task-id",
+		number: 1,
+		ts: Date.now(),
+		task: "Test task",
+		tokensIn: 100,
+		tokensOut: 200,
+		totalCost: 0.01,
+		size: 1024,
+	}
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+		mockUseExtensionState.mockReturnValue({
+			sharingEnabled: true,
+			cloudIsAuthenticated: true,
+			cloudUserInfo: {
+				organizationName: "Test Organization",
+			},
+		} as any)
+	})
+
+	describe("Share Button Visibility", () => {
+		it("renders share button when item has id", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const shareButton = screen.getByTitle("Share task")
+			expect(shareButton).toBeInTheDocument()
+		})
+
+		it("does not render share button when item has no id", () => {
+			render(<TaskActions item={undefined} buttonsDisabled={false} />)
+
+			const shareButton = screen.queryByTitle("Share task")
+			expect(shareButton).not.toBeInTheDocument()
+		})
+
+		it("renders share button even when not authenticated", () => {
+			mockUseExtensionState.mockReturnValue({
+				sharingEnabled: false,
+				cloudIsAuthenticated: false,
+			} as any)
+
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const shareButton = screen.getByTitle("Share task")
+			expect(shareButton).toBeInTheDocument()
+		})
+	})
+
+	describe("Authenticated User Share Flow", () => {
+		it("shows organization and public share options when authenticated and sharing enabled", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const shareButton = screen.getByTitle("Share task")
+			fireEvent.click(shareButton)
+
+			expect(screen.getByText("Share with Organization")).toBeInTheDocument()
+			expect(screen.getByText("Share Publicly")).toBeInTheDocument()
+		})
+
+		it("sends shareCurrentTask message when organization option is selected", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const shareButton = screen.getByTitle("Share task")
+			fireEvent.click(shareButton)
+
+			const orgOption = screen.getByText("Share with Organization")
+			fireEvent.click(orgOption)
+
+			expect(mockPostMessage).toHaveBeenCalledWith({
+				type: "shareCurrentTask",
+				visibility: "organization",
+			})
+		})
+
+		it("sends shareCurrentTask message when public option is selected", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const shareButton = screen.getByTitle("Share task")
+			fireEvent.click(shareButton)
+
+			const publicOption = screen.getByText("Share Publicly")
+			fireEvent.click(publicOption)
+
+			expect(mockPostMessage).toHaveBeenCalledWith({
+				type: "shareCurrentTask",
+				visibility: "public",
+			})
+		})
+
+		it("does not show organization option when user is not in an organization", () => {
+			mockUseExtensionState.mockReturnValue({
+				sharingEnabled: true,
+				cloudIsAuthenticated: true,
+				cloudUserInfo: {
+					// No organizationName property
+				},
+			} as any)
+
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const shareButton = screen.getByTitle("Share task")
+			fireEvent.click(shareButton)
+
+			expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument()
+			expect(screen.getByText("Share Publicly")).toBeInTheDocument()
+		})
+	})
+
+	describe("Unauthenticated User Login Flow", () => {
+		beforeEach(() => {
+			mockUseExtensionState.mockReturnValue({
+				sharingEnabled: false,
+				cloudIsAuthenticated: false,
+			} as any)
+		})
+
+		it("shows connect to cloud option when not authenticated", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const shareButton = screen.getByTitle("Share task")
+			fireEvent.click(shareButton)
+
+			expect(screen.getByText("Connect to Roo Code Cloud")).toBeInTheDocument()
+			expect(screen.getByText("Sign in to Roo Code Cloud to share tasks")).toBeInTheDocument()
+			expect(screen.getByText("Connect")).toBeInTheDocument()
+		})
+
+		it("does not show organization and public options when not authenticated", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const shareButton = screen.getByTitle("Share task")
+			fireEvent.click(shareButton)
+
+			expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument()
+			expect(screen.queryByText("Share Publicly")).not.toBeInTheDocument()
+		})
+
+		it("sends rooCloudSignIn message when connect to cloud is selected", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const shareButton = screen.getByTitle("Share task")
+			fireEvent.click(shareButton)
+
+			const connectOption = screen.getByText("Connect")
+			fireEvent.click(connectOption)
+
+			expect(mockPostMessage).toHaveBeenCalledWith({
+				type: "rooCloudSignIn",
+			})
+		})
+	})
+
+	describe("Mixed Authentication States", () => {
+		it("shows disabled share button when authenticated but sharing not enabled", () => {
+			mockUseExtensionState.mockReturnValue({
+				sharingEnabled: false,
+				cloudIsAuthenticated: true,
+			} as any)
+
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const shareButton = screen.getByTitle("Sharing disabled by organization")
+			expect(shareButton).toBeInTheDocument()
+			expect(shareButton).toBeDisabled()
+
+			// Should not have a popover when sharing is disabled
+			fireEvent.click(shareButton)
+			expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument()
+			expect(screen.queryByText("Connect to Cloud")).not.toBeInTheDocument()
+		})
+
+		it("automatically opens popover when user becomes authenticated", () => {
+			// Start with unauthenticated state
+			mockUseExtensionState.mockReturnValue({
+				sharingEnabled: false,
+				cloudIsAuthenticated: false,
+			} as any)
+
+			const { rerender } = render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			// Verify popover is not open initially
+			expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument()
+
+			// Simulate user becoming authenticated
+			mockUseExtensionState.mockReturnValue({
+				sharingEnabled: true,
+				cloudIsAuthenticated: true,
+				cloudUserInfo: {
+					organizationName: "Test Organization",
+				},
+			} as any)
+
+			rerender(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			// Verify popover automatically opens and shows sharing options
+			expect(screen.getByText("Share with Organization")).toBeInTheDocument()
+			expect(screen.getByText("Share Publicly")).toBeInTheDocument()
+		})
+	})
+
+	describe("Other Actions", () => {
+		it("renders export button", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const exportButton = screen.getByTitle("Export task history")
+			expect(exportButton).toBeInTheDocument()
+		})
+
+		it("sends exportCurrentTask message when export button is clicked", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const exportButton = screen.getByTitle("Export task history")
+			fireEvent.click(exportButton)
+
+			expect(mockPostMessage).toHaveBeenCalledWith({
+				type: "exportCurrentTask",
+			})
+		})
+
+		it("renders delete button and file size when item has size", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
+
+			const deleteButton = screen.getByTitle("Delete Task (Shift + Click to skip confirmation)")
+			expect(deleteButton).toBeInTheDocument()
+			expect(screen.getByText("1024 B")).toBeInTheDocument()
+		})
+
+		it("does not render delete button when item has no size", () => {
+			const itemWithoutSize = { ...mockItem, size: 0 }
+			render(<TaskActions item={itemWithoutSize} buttonsDisabled={false} />)
+
+			const deleteButton = screen.queryByTitle("Delete Task (Shift + Click to skip confirmation)")
+			expect(deleteButton).not.toBeInTheDocument()
+		})
+	})
+
+	describe("Button States", () => {
+		it("disables buttons when buttonsDisabled is true", () => {
+			render(<TaskActions item={mockItem} buttonsDisabled={true} />)
+
+			const shareButton = screen.getByTitle("Share task")
+			const exportButton = screen.getByTitle("Export task history")
+
+			expect(shareButton).toBeDisabled()
+			expect(exportButton).toBeDisabled()
+		})
+	})
+})

+ 7 - 2
webview-ui/src/components/common/TelemetryBanner.tsx

@@ -45,7 +45,7 @@ const TelemetryBanner = () => {
 		window.postMessage({
 			type: "action",
 			action: "settingsButtonClicked",
-			values: { section: "advanced" }, // Link directly to advanced settings with telemetry controls
+			values: { section: "about" }, // Link directly to about settings with telemetry controls
 		})
 	}
 
@@ -54,7 +54,12 @@ const TelemetryBanner = () => {
 			<div>
 				<strong>{t("welcome:telemetry.title")}</strong>
 				<div className="mt-1">
-					{t("welcome:telemetry.anonymousTelemetry")}
+					<Trans
+						i18nKey="welcome:telemetry.anonymousTelemetry"
+						components={{
+							privacyLink: <VSCodeLink href="https://roocode.com/privacy" />,
+						}}
+					/>
 					<div className="mt-1">
 						<Trans
 							i18nKey="welcome:telemetry.changeSettings"

+ 6 - 1
webview-ui/src/components/settings/About.tsx

@@ -48,7 +48,12 @@ export const About = ({ telemetrySetting, setTelemetrySetting, className, ...pro
 						{t("settings:footer.telemetry.label")}
 					</VSCodeCheckbox>
 					<p className="text-vscode-descriptionForeground text-sm mt-0">
-						{t("settings:footer.telemetry.description")}
+						<Trans
+							i18nKey="settings:footer.telemetry.description"
+							components={{
+								privacyLink: <VSCodeLink href="https://roocode.com/privacy" />,
+							}}
+						/>
 					</p>
 				</div>
 

+ 8 - 2
webview-ui/src/i18n/locales/ca/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Compte",
 	"profilePicture": "Imatge de perfil",
-	"unknownUser": "Usuari desconegut",
 	"logOut": "Tancar sessió",
 	"testApiAuthentication": "Provar autenticació d'API",
-	"signIn": "Connecta't a Roo Code Cloud"
+	"signIn": "Connecta't a Roo Code Cloud",
+	"connect": "Connecta",
+	"cloudBenefitsTitle": "Connecta't a Roo Code Cloud",
+	"cloudBenefitsSubtitle": "Sincronitza els teus prompts i telemetria per habilitar:",
+	"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",
+	"visitCloudWebsite": "Visita Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/ca/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Compartir amb l'organització",
 		"shareWithOrganizationDescription": "Només els membres de la teva organització poden accedir",
 		"sharePublicly": "Compartir públicament",
-		"sharePubliclyDescription": "Qualsevol amb l'enllaç pot accedir"
+		"sharePubliclyDescription": "Qualsevol amb l'enllaç pot accedir",
+		"connectToCloud": "Connecta al núvol",
+		"connectToCloudDescription": "Inicia sessió a Roo Code Cloud per compartir tasques",
+		"sharingDisabledByOrganization": "Compartició deshabilitada per l'organització",
+		"shareSuccessOrganization": "Enllaç d'organització copiat al porta-retalls",
+		"shareSuccessPublic": "Enllaç públic copiat al porta-retalls"
 	},
 	"unpin": "Desfixar",
 	"pin": "Fixar",

+ 8 - 2
webview-ui/src/i18n/locales/de/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Konto",
 	"profilePicture": "Profilbild",
-	"unknownUser": "Unbekannter Benutzer",
 	"logOut": "Abmelden",
 	"testApiAuthentication": "API-Authentifizierung testen",
-	"signIn": "Mit Roo Code Cloud verbinden"
+	"signIn": "Mit Roo Code Cloud verbinden",
+	"connect": "Verbinden",
+	"cloudBenefitsTitle": "Mit Roo Code Cloud verbinden",
+	"cloudBenefitsSubtitle": "Synchronisiere deine Prompts und Telemetrie, um folgendes zu aktivieren:",
+	"cloudBenefitHistory": "Online-Aufgabenverlauf",
+	"cloudBenefitSharing": "Freigabe- und Kollaborationsfunktionen",
+	"cloudBenefitMetrics": "Aufgaben-, Token- und kostenbasierte Nutzungsmetriken",
+	"visitCloudWebsite": "Roo Code Cloud besuchen"
 }

+ 6 - 1
webview-ui/src/i18n/locales/de/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Mit Organisation teilen",
 		"shareWithOrganizationDescription": "Nur Mitglieder deiner Organisation können zugreifen",
 		"sharePublicly": "Öffentlich teilen",
-		"sharePubliclyDescription": "Jeder mit dem Link kann zugreifen"
+		"sharePubliclyDescription": "Jeder mit dem Link kann zugreifen",
+		"connectToCloud": "Mit Cloud verbinden",
+		"connectToCloudDescription": "Melde dich bei Roo Code Cloud an, um Aufgaben zu teilen",
+		"sharingDisabledByOrganization": "Freigabe von der Organisation deaktiviert",
+		"shareSuccessOrganization": "Organisationslink in die Zwischenablage kopiert",
+		"shareSuccessPublic": "Öffentlicher Link in die Zwischenablage kopiert"
 	},
 	"unpin": "Lösen von oben",
 	"pin": "Anheften",

+ 8 - 2
webview-ui/src/i18n/locales/en/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Account",
 	"profilePicture": "Profile picture",
-	"unknownUser": "Unknown User",
 	"logOut": "Log out",
 	"testApiAuthentication": "Test API Authentication",
-	"signIn": "Connect to Roo Code Cloud"
+	"signIn": "Connect to Roo Code Cloud",
+	"connect": "Connect",
+	"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"
 }

+ 6 - 1
webview-ui/src/i18n/locales/en/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Share with Organization",
 		"shareWithOrganizationDescription": "Only members of your organization can access",
 		"sharePublicly": "Share Publicly",
-		"sharePubliclyDescription": "Anyone with the link can access"
+		"sharePubliclyDescription": "Anyone with the link can access",
+		"connectToCloud": "Connect to Cloud",
+		"connectToCloudDescription": "Sign in to Roo Code Cloud to share tasks",
+		"sharingDisabledByOrganization": "Sharing disabled by organization",
+		"shareSuccessOrganization": "Organization link copied to clipboard",
+		"shareSuccessPublic": "Public link copied to clipboard"
 	},
 	"unpin": "Unpin",
 	"pin": "Pin",

+ 1 - 1
webview-ui/src/i18n/locales/en/settings.json

@@ -564,7 +564,7 @@
 		"feedback": "If you have any questions or feedback, feel free to open an issue at <githubLink>github.com/RooCodeInc/Roo-Code</githubLink> or join <redditLink>reddit.com/r/RooCode</redditLink> or <discordLink>discord.gg/roocode</discordLink>",
 		"telemetry": {
 			"label": "Allow anonymous error and usage reporting",
-			"description": "Help improve Roo Code by sending anonymous usage data and error reports. No code, prompts, or personal information is ever sent. See our privacy policy for more details."
+			"description": "Help improve Roo Code by sending anonymous usage data and error reports. No code, prompts, or personal information is ever sent (unless you connect to Roo Code Cloud). See our <privacyLink>privacy policy</privacyLink> for more details."
 		},
 		"settings": {
 			"import": "Import",

+ 1 - 1
webview-ui/src/i18n/locales/en/welcome.json

@@ -17,7 +17,7 @@
 	"startCustom": "Or you can bring your provider API key:",
 	"telemetry": {
 		"title": "Help Improve Roo Code",
-		"anonymousTelemetry": "Send anonymous error and usage data to help us fix bugs and improve the extension. No code, prompts, or personal information is ever sent.",
+		"anonymousTelemetry": "Send anonymous error and usage data to help us fix bugs and improve the extension. No code, prompts, or personal information is ever sent (unless you connect to Roo Code Cloud). See our <privacyLink>privacy policy</privacyLink> for more details.",
 		"changeSettings": "You can always change this at the bottom of the <settingsLink>settings</settingsLink>",
 		"settings": "settings",
 		"allow": "Allow",

+ 8 - 2
webview-ui/src/i18n/locales/es/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Cuenta",
 	"profilePicture": "Foto de perfil",
-	"unknownUser": "Usuario desconocido",
 	"logOut": "Cerrar sesión",
 	"testApiAuthentication": "Probar autenticación de API",
-	"signIn": "Conectar a Roo Code Cloud"
+	"signIn": "Conectar a Roo Code Cloud",
+	"connect": "Conectar",
+	"cloudBenefitsTitle": "Conectar a Roo Code Cloud",
+	"cloudBenefitsSubtitle": "Sincroniza tus prompts y telemetría para habilitar:",
+	"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",
+	"visitCloudWebsite": "Visitar Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/es/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Compartir con organización",
 		"shareWithOrganizationDescription": "Solo los miembros de tu organización pueden acceder",
 		"sharePublicly": "Compartir públicamente",
-		"sharePubliclyDescription": "Cualquiera con el enlace puede acceder"
+		"sharePubliclyDescription": "Cualquiera con el enlace puede acceder",
+		"connectToCloud": "Conectar al Cloud",
+		"connectToCloudDescription": "Inicia sesión en Roo Code Cloud para compartir tareas",
+		"sharingDisabledByOrganization": "Compartir deshabilitado por la organización",
+		"shareSuccessOrganization": "Enlace de organización copiado al portapapeles",
+		"shareSuccessPublic": "Enlace público copiado al portapapeles"
 	},
 	"unpin": "Desfijar",
 	"pin": "Fijar",

+ 8 - 2
webview-ui/src/i18n/locales/fr/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Compte",
 	"profilePicture": "Photo de profil",
-	"unknownUser": "Utilisateur inconnu",
 	"logOut": "Déconnexion",
 	"testApiAuthentication": "Tester l'authentification API",
-	"signIn": "Se connecter à Roo Code Cloud"
+	"signIn": "Se connecter à Roo Code Cloud",
+	"connect": "Se connecter",
+	"cloudBenefitsTitle": "Se connecter à Roo Code Cloud",
+	"cloudBenefitsSubtitle": "Synchronise tes prompts et télémétrie pour activer :",
+	"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",
+	"visitCloudWebsite": "Visiter Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/fr/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Partager avec l'organisation",
 		"shareWithOrganizationDescription": "Seuls les membres de ton organisation peuvent accéder",
 		"sharePublicly": "Partager publiquement",
-		"sharePubliclyDescription": "Toute personne avec le lien peut accéder"
+		"sharePubliclyDescription": "Toute personne avec le lien peut accéder",
+		"connectToCloud": "Se connecter au Cloud",
+		"connectToCloudDescription": "Connecte-toi à Roo Code Cloud pour partager des tâches",
+		"sharingDisabledByOrganization": "Partage désactivé par l'organisation",
+		"shareSuccessOrganization": "Lien d'organisation copié dans le presse-papiers",
+		"shareSuccessPublic": "Lien public copié dans le presse-papiers"
 	},
 	"unpin": "Désépingler",
 	"pin": "Épingler",

+ 8 - 2
webview-ui/src/i18n/locales/hi/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "खाता",
 	"profilePicture": "प्रोफाइल चित्र",
-	"unknownUser": "अज्ञात उपयोगकर्ता",
 	"logOut": "लॉग आउट",
 	"testApiAuthentication": "API प्रमाणीकरण का परीक्षण करें",
-	"signIn": "Roo Code Cloud से कनेक्ट करें"
+	"signIn": "Roo Code Cloud से कनेक्ट करें",
+	"connect": "कनेक्ट करें",
+	"cloudBenefitsTitle": "Roo Code Cloud से कनेक्ट करें",
+	"cloudBenefitsSubtitle": "निम्नलिखित को सक्षम करने के लिए अपने prompts और telemetry को sync करें:",
+	"cloudBenefitHistory": "ऑनलाइन कार्य इतिहास",
+	"cloudBenefitSharing": "साझाकरण और सहयोग सुविधाएं",
+	"cloudBenefitMetrics": "कार्य, token और लागत आधारित उपयोग मेट्रिक्स",
+	"visitCloudWebsite": "Roo Code Cloud पर जाएं"
 }

+ 6 - 1
webview-ui/src/i18n/locales/hi/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "संगठन के साथ साझा करें",
 		"shareWithOrganizationDescription": "केवल आपके संगठन के सदस्य पहुंच सकते हैं",
 		"sharePublicly": "सार्वजनिक रूप से साझा करें",
-		"sharePubliclyDescription": "लिंक वाला कोई भी व्यक्ति पहुंच सकता है"
+		"sharePubliclyDescription": "लिंक वाला कोई भी व्यक्ति पहुंच सकता है",
+		"connectToCloud": "Cloud से कनेक्ट करें",
+		"connectToCloudDescription": "कार्य साझा करने के लिए Roo Code Cloud में साइन इन करें",
+		"sharingDisabledByOrganization": "संगठन द्वारा साझाकरण अक्षम किया गया",
+		"shareSuccessOrganization": "संगठन लिंक क्लिपबोर्ड में कॉपी किया गया",
+		"shareSuccessPublic": "सार्वजनिक लिंक क्लिपबोर्ड में कॉपी किया गया"
 	},
 	"unpin": "पिन करें",
 	"pin": "अवपिन करें",

+ 8 - 2
webview-ui/src/i18n/locales/id/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Akun",
 	"profilePicture": "Foto profil",
-	"unknownUser": "Pengguna Tidak Dikenal",
 	"logOut": "Keluar",
 	"testApiAuthentication": "Uji Autentikasi API",
-	"signIn": "Hubungkan ke Roo Code Cloud"
+	"signIn": "Hubungkan ke Roo Code Cloud",
+	"connect": "Hubungkan",
+	"cloudBenefitsTitle": "Hubungkan ke Roo Code Cloud",
+	"cloudBenefitsSubtitle": "Sinkronkan prompt dan telemetri kamu untuk mengaktifkan:",
+	"cloudBenefitHistory": "Riwayat tugas online",
+	"cloudBenefitSharing": "Fitur berbagi dan kolaborasi",
+	"cloudBenefitMetrics": "Metrik penggunaan berdasarkan tugas, token, dan biaya",
+	"visitCloudWebsite": "Kunjungi Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/id/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Bagikan dengan organisasi",
 		"shareWithOrganizationDescription": "Hanya anggota organisasi Anda yang dapat mengakses",
 		"sharePublicly": "Bagikan secara publik",
-		"sharePubliclyDescription": "Siapa pun dengan tautan dapat mengakses"
+		"sharePubliclyDescription": "Siapa pun dengan tautan dapat mengakses",
+		"connectToCloud": "Hubungkan ke Cloud",
+		"connectToCloudDescription": "Masuk ke Roo Code Cloud untuk berbagi tugas",
+		"sharingDisabledByOrganization": "Berbagi dinonaktifkan oleh organisasi",
+		"shareSuccessOrganization": "Tautan organisasi disalin ke clipboard",
+		"shareSuccessPublic": "Tautan publik disalin ke clipboard"
 	},
 	"history": {
 		"title": "Riwayat"

+ 8 - 2
webview-ui/src/i18n/locales/it/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Account",
 	"profilePicture": "Immagine del profilo",
-	"unknownUser": "Utente sconosciuto",
 	"logOut": "Disconnetti",
 	"testApiAuthentication": "Verifica autenticazione API",
-	"signIn": "Connetti a Roo Code Cloud"
+	"signIn": "Connetti a Roo Code Cloud",
+	"connect": "Connetti",
+	"cloudBenefitsTitle": "Connetti a Roo Code Cloud",
+	"cloudBenefitsSubtitle": "Sincronizza i tuoi prompt e telemetria per abilitare:",
+	"cloudBenefitHistory": "Cronologia attività online",
+	"cloudBenefitSharing": "Funzionalità di condivisione e collaborazione",
+	"cloudBenefitMetrics": "Metriche di utilizzo basate su attività, token e costi",
+	"visitCloudWebsite": "Visita Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/it/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Condividi con l'organizzazione",
 		"shareWithOrganizationDescription": "Solo i membri della tua organizzazione possono accedere",
 		"sharePublicly": "Condividi pubblicamente",
-		"sharePubliclyDescription": "Chiunque con il link può accedere"
+		"sharePubliclyDescription": "Chiunque con il link può accedere",
+		"connectToCloud": "Connetti al Cloud",
+		"connectToCloudDescription": "Accedi a Roo Code Cloud per condividere attività",
+		"sharingDisabledByOrganization": "Condivisione disabilitata dall'organizzazione",
+		"shareSuccessOrganization": "Link organizzazione copiato negli appunti",
+		"shareSuccessPublic": "Link pubblico copiato negli appunti"
 	},
 	"unpin": "Rilascia",
 	"pin": "Fissa",

+ 8 - 2
webview-ui/src/i18n/locales/ja/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "アカウント",
 	"profilePicture": "プロフィール画像",
-	"unknownUser": "不明なユーザー",
 	"logOut": "ログアウト",
 	"testApiAuthentication": "API認証をテスト",
-	"signIn": "Roo Code Cloud に接続"
+	"signIn": "Roo Code Cloud に接続",
+	"connect": "接続",
+	"cloudBenefitsTitle": "Roo Code Cloudに接続",
+	"cloudBenefitsSubtitle": "プロンプトとテレメトリを同期して以下を有効にする:",
+	"cloudBenefitHistory": "オンラインタスク履歴",
+	"cloudBenefitSharing": "共有とコラボレーション機能",
+	"cloudBenefitMetrics": "タスク、Token、コストベースの使用メトリクス",
+	"visitCloudWebsite": "Roo Code Cloudを訪問"
 }

+ 6 - 1
webview-ui/src/i18n/locales/ja/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "組織と共有",
 		"shareWithOrganizationDescription": "組織のメンバーのみがアクセスできます",
 		"sharePublicly": "公開で共有",
-		"sharePubliclyDescription": "リンクを持つ誰でもアクセスできます"
+		"sharePubliclyDescription": "リンクを持つ誰でもアクセスできます",
+		"connectToCloud": "クラウドに接続",
+		"connectToCloudDescription": "タスクを共有するためにRoo Code Cloudにサインイン",
+		"sharingDisabledByOrganization": "組織により共有が無効化されています",
+		"shareSuccessOrganization": "組織リンクをクリップボードにコピーしました",
+		"shareSuccessPublic": "公開リンクをクリップボードにコピーしました"
 	},
 	"unpin": "ピン留めを解除",
 	"pin": "ピン留め",

+ 8 - 2
webview-ui/src/i18n/locales/ko/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "계정",
 	"profilePicture": "프로필 사진",
-	"unknownUser": "알 수 없는 사용자",
 	"logOut": "로그아웃",
 	"testApiAuthentication": "API 인증 테스트",
-	"signIn": "Roo Code Cloud에 연결"
+	"signIn": "Roo Code Cloud에 연결",
+	"connect": "연결",
+	"cloudBenefitsTitle": "Roo Code Cloud에 연결",
+	"cloudBenefitsSubtitle": "프롬프트와 텔레메트리를 동기화하여 다음을 활성화:",
+	"cloudBenefitHistory": "온라인 작업 기록",
+	"cloudBenefitSharing": "공유 및 협업 기능",
+	"cloudBenefitMetrics": "작업, 토큰, 비용 기반 사용 메트릭",
+	"visitCloudWebsite": "Roo Code Cloud 방문"
 }

+ 6 - 1
webview-ui/src/i18n/locales/ko/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "조직과 공유",
 		"shareWithOrganizationDescription": "조직 구성원만 액세스할 수 있습니다",
 		"sharePublicly": "공개적으로 공유",
-		"sharePubliclyDescription": "링크가 있는 누구나 액세스할 수 있습니다"
+		"sharePubliclyDescription": "링크가 있는 누구나 액세스할 수 있습니다",
+		"connectToCloud": "클라우드에 연결",
+		"connectToCloudDescription": "작업을 공유하려면 Roo Code Cloud에 로그인하세요",
+		"sharingDisabledByOrganization": "조직에서 공유가 비활성화됨",
+		"shareSuccessOrganization": "조직 링크가 클립보드에 복사되었습니다",
+		"shareSuccessPublic": "공개 링크가 클립보드에 복사되었습니다"
 	},
 	"unpin": "고정 해제하기",
 	"pin": "고정하기",

+ 8 - 2
webview-ui/src/i18n/locales/nl/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Account",
 	"profilePicture": "Profielfoto",
-	"unknownUser": "Onbekende gebruiker",
 	"logOut": "Uitloggen",
 	"testApiAuthentication": "API-authenticatie testen",
-	"signIn": "Verbind met Roo Code Cloud"
+	"signIn": "Verbind met Roo Code Cloud",
+	"connect": "Verbinden",
+	"cloudBenefitsTitle": "Verbind met Roo Code Cloud",
+	"cloudBenefitsSubtitle": "Synchroniseer je prompts en telemetrie om het volgende in te schakelen:",
+	"cloudBenefitHistory": "Online taakgeschiedenis",
+	"cloudBenefitSharing": "Deel- en samenwerkingsfuncties",
+	"cloudBenefitMetrics": "Taak-, token- en kostengebaseerde gebruiksstatistieken",
+	"visitCloudWebsite": "Bezoek Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/nl/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Delen met organisatie",
 		"shareWithOrganizationDescription": "Alleen leden van je organisatie kunnen toegang krijgen",
 		"sharePublicly": "Openbaar delen",
-		"sharePubliclyDescription": "Iedereen met de link kan toegang krijgen"
+		"sharePubliclyDescription": "Iedereen met de link kan toegang krijgen",
+		"connectToCloud": "Verbind met Cloud",
+		"connectToCloudDescription": "Meld je aan bij Roo Code Cloud om taken te delen",
+		"sharingDisabledByOrganization": "Delen uitgeschakeld door organisatie",
+		"shareSuccessOrganization": "Organisatielink gekopieerd naar klembord",
+		"shareSuccessPublic": "Openbare link gekopieerd naar klembord"
 	},
 	"unpin": "Losmaken",
 	"pin": "Vastmaken",

+ 8 - 2
webview-ui/src/i18n/locales/pl/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Konto",
 	"profilePicture": "Zdjęcie profilowe",
-	"unknownUser": "Nieznany użytkownik",
 	"logOut": "Wyloguj",
 	"testApiAuthentication": "Testuj uwierzytelnianie API",
-	"signIn": "Połącz z Roo Code Cloud"
+	"signIn": "Połącz z Roo Code Cloud",
+	"connect": "Połącz",
+	"cloudBenefitsTitle": "Połącz z Roo Code Cloud",
+	"cloudBenefitsSubtitle": "Synchronizuj swoje prompty i telemetrię, aby włączyć:",
+	"cloudBenefitHistory": "Historia zadań online",
+	"cloudBenefitSharing": "Funkcje udostępniania i współpracy",
+	"cloudBenefitMetrics": "Metryki użycia oparte na zadaniach, tokenach i kosztach",
+	"visitCloudWebsite": "Odwiedź Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/pl/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Udostępnij organizacji",
 		"shareWithOrganizationDescription": "Tylko członkowie twojej organizacji mogą uzyskać dostęp",
 		"sharePublicly": "Udostępnij publicznie",
-		"sharePubliclyDescription": "Każdy z linkiem może uzyskać dostęp"
+		"sharePubliclyDescription": "Każdy z linkiem może uzyskać dostęp",
+		"connectToCloud": "Połącz z chmurą",
+		"connectToCloudDescription": "Zaloguj się do Roo Code Cloud, aby udostępniać zadania",
+		"sharingDisabledByOrganization": "Udostępnianie wyłączone przez organizację",
+		"shareSuccessOrganization": "Link organizacji skopiowany do schowka",
+		"shareSuccessPublic": "Link publiczny skopiowany do schowka"
 	},
 	"unpin": "Odepnij",
 	"pin": "Przypnij",

+ 8 - 2
webview-ui/src/i18n/locales/pt-BR/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Conta",
 	"profilePicture": "Foto de perfil",
-	"unknownUser": "Usuário desconhecido",
 	"logOut": "Sair",
 	"testApiAuthentication": "Testar Autenticação de API",
-	"signIn": "Conectar ao Roo Code Cloud"
+	"signIn": "Conectar ao Roo Code Cloud",
+	"connect": "Conectar",
+	"cloudBenefitsTitle": "Conectar ao Roo Code Cloud",
+	"cloudBenefitsSubtitle": "Sincronize seus prompts e telemetria para habilitar:",
+	"cloudBenefitHistory": "Histórico de tarefas online",
+	"cloudBenefitSharing": "Recursos de compartilhamento e colaboração",
+	"cloudBenefitMetrics": "Métricas de uso baseadas em tarefas, tokens e custos",
+	"visitCloudWebsite": "Visitar Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/pt-BR/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Compartilhar com organização",
 		"shareWithOrganizationDescription": "Apenas membros da sua organização podem acessar",
 		"sharePublicly": "Compartilhar publicamente",
-		"sharePubliclyDescription": "Qualquer pessoa com o link pode acessar"
+		"sharePubliclyDescription": "Qualquer pessoa com o link pode acessar",
+		"connectToCloud": "Conectar ao Cloud",
+		"connectToCloudDescription": "Entre no Roo Code Cloud para compartilhar tarefas",
+		"sharingDisabledByOrganization": "Compartilhamento desabilitado pela organização",
+		"shareSuccessOrganization": "Link da organização copiado para a área de transferência",
+		"shareSuccessPublic": "Link público copiado para a área de transferência"
 	},
 	"unpin": "Desfixar",
 	"pin": "Fixar",

+ 8 - 2
webview-ui/src/i18n/locales/ru/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Учетная запись",
 	"profilePicture": "Фото профиля",
-	"unknownUser": "Неизвестный пользователь",
 	"logOut": "Выход",
 	"testApiAuthentication": "Проверить аутентификацию API",
-	"signIn": "Подключиться к Roo Code Cloud"
+	"signIn": "Подключиться к Roo Code Cloud",
+	"connect": "Подключиться",
+	"cloudBenefitsTitle": "Подключиться к Roo Code Cloud",
+	"cloudBenefitsSubtitle": "Синхронизируй свои промпты и телеметрию, чтобы включить:",
+	"cloudBenefitHistory": "Онлайн-история задач",
+	"cloudBenefitSharing": "Функции обмена и совместной работы",
+	"cloudBenefitMetrics": "Метрики использования на основе задач, токенов и затрат",
+	"visitCloudWebsite": "Посетить Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/ru/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Поделиться с организацией",
 		"shareWithOrganizationDescription": "Только члены вашей организации могут получить доступ",
 		"sharePublicly": "Поделиться публично",
-		"sharePubliclyDescription": "Любой, у кого есть ссылка, может получить доступ"
+		"sharePubliclyDescription": "Любой, у кого есть ссылка, может получить доступ",
+		"connectToCloud": "Подключиться к облаку",
+		"connectToCloudDescription": "Войди в Roo Code Cloud, чтобы делиться задачами",
+		"sharingDisabledByOrganization": "Обмен отключен организацией",
+		"shareSuccessOrganization": "Ссылка организации скопирована в буфер обмена",
+		"shareSuccessPublic": "Публичная ссылка скопирована в буфер обмена"
 	},
 	"unpin": "Открепить",
 	"pin": "Закрепить",

+ 8 - 2
webview-ui/src/i18n/locales/tr/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Hesap",
 	"profilePicture": "Profil resmi",
-	"unknownUser": "Bilinmeyen kullanıcı",
 	"logOut": "Çıkış yap",
 	"testApiAuthentication": "API Kimlik Doğrulamayı Test Et",
-	"signIn": "Roo Code Cloud'a bağlan"
+	"signIn": "Roo Code Cloud'a bağlan",
+	"connect": "Bağlan",
+	"cloudBenefitsTitle": "Roo Code Cloud'a bağlan",
+	"cloudBenefitsSubtitle": "Aşağıdakileri etkinleştirmek için promptlarını ve telemetriyi senkronize et:",
+	"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",
+	"visitCloudWebsite": "Roo Code Cloud'u ziyaret et"
 }

+ 6 - 1
webview-ui/src/i18n/locales/tr/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Kuruluşla paylaş",
 		"shareWithOrganizationDescription": "Sadece kuruluşunuzun üyeleri erişebilir",
 		"sharePublicly": "Herkese açık paylaş",
-		"sharePubliclyDescription": "Bağlantıya sahip herkes erişebilir"
+		"sharePubliclyDescription": "Bağlantıya sahip herkes erişebilir",
+		"connectToCloud": "Buluta bağlan",
+		"connectToCloudDescription": "Görevleri paylaşmak için Roo Code Cloud'a giriş yap",
+		"sharingDisabledByOrganization": "Paylaşım kuruluş tarafından devre dışı bırakıldı",
+		"shareSuccessOrganization": "Organizasyon bağlantısı panoya kopyalandı",
+		"shareSuccessPublic": "Genel bağlantı panoya kopyalandı"
 	},
 	"unpin": "Sabitlemeyi iptal et",
 	"pin": "Sabitle",

+ 8 - 2
webview-ui/src/i18n/locales/vi/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "Tài khoản",
 	"profilePicture": "Ảnh hồ sơ",
-	"unknownUser": "Người dùng không xác định",
 	"logOut": "Đăng xuất",
 	"testApiAuthentication": "Kiểm tra xác thực API",
-	"signIn": "Kết nối với Roo Code Cloud"
+	"signIn": "Kết nối với Roo Code Cloud",
+	"connect": "Kết nối",
+	"cloudBenefitsTitle": "Kết nối với Roo Code Cloud",
+	"cloudBenefitsSubtitle": "Đồng bộ prompts và telemetry của bạn để kích hoạt:",
+	"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í",
+	"visitCloudWebsite": "Truy cập Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/vi/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "Chia sẻ với tổ chức",
 		"shareWithOrganizationDescription": "Chỉ thành viên tổ chức của bạn mới có thể truy cập",
 		"sharePublicly": "Chia sẻ công khai",
-		"sharePubliclyDescription": "Bất kỳ ai có liên kết đều có thể truy cập"
+		"sharePubliclyDescription": "Bất kỳ ai có liên kết đều có thể truy cập",
+		"connectToCloud": "Kết nối với Cloud",
+		"connectToCloudDescription": "Đăng nhập vào Roo Code Cloud để chia sẻ tác vụ",
+		"sharingDisabledByOrganization": "Chia sẻ bị tổ chức vô hiệu hóa",
+		"shareSuccessOrganization": "Liên kết tổ chức đã được sao chép vào clipboard",
+		"shareSuccessPublic": "Liên kết công khai đã được sao chép vào clipboard"
 	},
 	"unpin": "Bỏ ghim khỏi đầu",
 	"pin": "Ghim lên đầu",

+ 8 - 2
webview-ui/src/i18n/locales/zh-CN/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "账户",
 	"profilePicture": "头像",
-	"unknownUser": "未知用户",
 	"logOut": "退出登录",
 	"testApiAuthentication": "测试 API 认证",
-	"signIn": "连接到 Roo Code Cloud"
+	"signIn": "连接到 Roo Code Cloud",
+	"connect": "连接",
+	"cloudBenefitsTitle": "连接到 Roo Code Cloud",
+	"cloudBenefitsSubtitle": "同步你的提示词和遥测数据以启用:",
+	"cloudBenefitHistory": "在线任务历史",
+	"cloudBenefitSharing": "共享和协作功能",
+	"cloudBenefitMetrics": "基于任务、Token 和成本的使用指标",
+	"visitCloudWebsite": "访问 Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/zh-CN/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "与组织分享",
 		"shareWithOrganizationDescription": "仅组织成员可访问",
 		"sharePublicly": "公开分享",
-		"sharePubliclyDescription": "任何拥有链接的人都可访问"
+		"sharePubliclyDescription": "任何拥有链接的人都可访问",
+		"connectToCloud": "连接到云端",
+		"connectToCloudDescription": "登录 Roo Code Cloud 以分享任务",
+		"sharingDisabledByOrganization": "组织已禁用分享功能",
+		"shareSuccessOrganization": "组织链接已复制到剪贴板",
+		"shareSuccessPublic": "公开链接已复制到剪贴板"
 	},
 	"unpin": "取消置顶",
 	"pin": "置顶",

+ 8 - 2
webview-ui/src/i18n/locales/zh-TW/account.json

@@ -1,8 +1,14 @@
 {
 	"title": "帳戶",
 	"profilePicture": "個人圖片",
-	"unknownUser": "未知使用者",
 	"logOut": "登出",
 	"testApiAuthentication": "測試 API 認證",
-	"signIn": "連接到 Roo Code Cloud"
+	"signIn": "連接到 Roo Code Cloud",
+	"connect": "連接",
+	"cloudBenefitsTitle": "連接到 Roo Code Cloud",
+	"cloudBenefitsSubtitle": "同步你的提示詞和遙測資料以啟用:",
+	"cloudBenefitHistory": "線上工作歷史",
+	"cloudBenefitSharing": "分享和協作功能",
+	"cloudBenefitMetrics": "基於工作、Token 和成本的使用指標",
+	"visitCloudWebsite": "造訪 Roo Code Cloud"
 }

+ 6 - 1
webview-ui/src/i18n/locales/zh-TW/chat.json

@@ -16,7 +16,12 @@
 		"shareWithOrganization": "與組織分享",
 		"shareWithOrganizationDescription": "僅組織成員可存取",
 		"sharePublicly": "公開分享",
-		"sharePubliclyDescription": "任何擁有連結的人都可存取"
+		"sharePubliclyDescription": "任何擁有連結的人都可存取",
+		"connectToCloud": "連接到雲端",
+		"connectToCloudDescription": "登入 Roo Code Cloud 以分享工作",
+		"sharingDisabledByOrganization": "組織已停用分享功能",
+		"shareSuccessOrganization": "組織連結已複製到剪貼簿",
+		"shareSuccessPublic": "公開連結已複製到剪貼簿"
 	},
 	"unpin": "取消置頂",
 	"pin": "置頂",

+ 3 - 0
webview-ui/vitest.setup.ts

@@ -42,6 +42,9 @@ Object.defineProperty(window, "matchMedia", {
 	})),
 })
 
+// Mock scrollIntoView which is not available in jsdom
+Element.prototype.scrollIntoView = vi.fn()
+
 // Suppress console.log during tests to reduce noise.
 // Keep console.error for actual errors.
 const originalConsoleLog = console.log