Prechádzať zdrojové kódy

Bring back "Use @roo-code/cloud from npm" (#6795)

Chris Estreich 4 mesiacov pred
rodič
commit
37330b0209
36 zmenil súbory, kde vykonal 151 pridanie a 5878 odobranie
  1. 0 1
      .dockerignore
  2. 0 4
      packages/cloud/eslint.config.mjs
  3. 0 25
      packages/cloud/package.json
  4. 0 122
      packages/cloud/src/CloudAPI.ts
  5. 0 288
      packages/cloud/src/CloudService.ts
  6. 0 152
      packages/cloud/src/CloudSettingsService.ts
  7. 0 43
      packages/cloud/src/CloudShareService.ts
  8. 0 154
      packages/cloud/src/RefreshTimer.ts
  9. 0 23
      packages/cloud/src/SettingsService.ts
  10. 0 41
      packages/cloud/src/StaticSettingsService.ts
  11. 0 169
      packages/cloud/src/TelemetryClient.ts
  12. 0 57
      packages/cloud/src/__mocks__/vscode.ts
  13. 0 146
      packages/cloud/src/__tests__/CloudService.integration.test.ts
  14. 0 604
      packages/cloud/src/__tests__/CloudService.test.ts
  15. 0 476
      packages/cloud/src/__tests__/CloudSettingsService.test.ts
  16. 0 310
      packages/cloud/src/__tests__/CloudShareService.test.ts
  17. 0 210
      packages/cloud/src/__tests__/RefreshTimer.test.ts
  18. 0 102
      packages/cloud/src/__tests__/StaticSettingsService.test.ts
  19. 0 738
      packages/cloud/src/__tests__/TelemetryClient.test.ts
  20. 0 174
      packages/cloud/src/__tests__/auth/StaticTokenAuthService.spec.ts
  21. 0 1113
      packages/cloud/src/__tests__/auth/WebAuthService.spec.ts
  22. 0 36
      packages/cloud/src/auth/AuthService.ts
  23. 0 71
      packages/cloud/src/auth/StaticTokenAuthService.ts
  24. 0 646
      packages/cloud/src/auth/WebAuthService.ts
  25. 0 3
      packages/cloud/src/auth/index.ts
  26. 0 5
      packages/cloud/src/config.ts
  27. 0 42
      packages/cloud/src/errors.ts
  28. 0 4
      packages/cloud/src/index.ts
  29. 0 4
      packages/cloud/src/types.ts
  30. 0 10
      packages/cloud/src/utils.ts
  31. 0 5
      packages/cloud/tsconfig.json
  32. 0 14
      packages/cloud/vitest.config.ts
  33. 0 2
      packages/evals/Dockerfile.runner
  34. 137 82
      pnpm-lock.yaml
  35. 13 1
      src/extension.ts
  36. 1 1
      src/package.json

+ 0 - 1
.dockerignore

@@ -80,7 +80,6 @@ src/node_modules
 !webview-ui/
 !packages/evals/.docker/entrypoints/runner.sh
 !packages/build/
-!packages/cloud/
 !packages/config-eslint/
 !packages/config-typescript/
 !packages/evals/

+ 0 - 4
packages/cloud/eslint.config.mjs

@@ -1,4 +0,0 @@
-import { config } from "@roo-code/config-eslint/base"
-
-/** @type {import("eslint").Linter.Config} */
-export default [...config]

+ 0 - 25
packages/cloud/package.json

@@ -1,25 +0,0 @@
-{
-	"name": "@roo-code/cloud",
-	"description": "Roo Code Cloud VSCode integration.",
-	"version": "0.0.0",
-	"type": "module",
-	"exports": "./src/index.ts",
-	"scripts": {
-		"lint": "eslint src --ext=ts --max-warnings=0",
-		"check-types": "tsc --noEmit",
-		"test": "vitest run",
-		"clean": "rimraf dist .turbo"
-	},
-	"dependencies": {
-		"@roo-code/telemetry": "workspace:^",
-		"@roo-code/types": "workspace:^",
-		"zod": "^3.25.61"
-	},
-	"devDependencies": {
-		"@roo-code/config-eslint": "workspace:^",
-		"@roo-code/config-typescript": "workspace:^",
-		"@types/node": "20.x",
-		"@types/vscode": "^1.84.0",
-		"vitest": "^3.2.3"
-	}
-}

+ 0 - 122
packages/cloud/src/CloudAPI.ts

@@ -1,122 +0,0 @@
-import { type ShareVisibility, type ShareResponse, shareResponseSchema } from "@roo-code/types"
-
-import { getRooCodeApiUrl } from "./config"
-import type { AuthService } from "./auth"
-import { getUserAgent } from "./utils"
-import { AuthenticationError, CloudAPIError, NetworkError, TaskNotFoundError } from "./errors"
-
-interface CloudAPIRequestOptions extends Omit<RequestInit, "headers"> {
-	timeout?: number
-	headers?: Record<string, string>
-}
-
-export class CloudAPI {
-	private authService: AuthService
-	private log: (...args: unknown[]) => void
-	private baseUrl: string
-
-	constructor(authService: AuthService, log?: (...args: unknown[]) => void) {
-		this.authService = authService
-		this.log = log || console.log
-		this.baseUrl = getRooCodeApiUrl()
-	}
-
-	private async request<T>(
-		endpoint: string,
-		options: CloudAPIRequestOptions & {
-			parseResponse?: (data: unknown) => T
-		} = {},
-	): Promise<T> {
-		const { timeout = 10000, parseResponse, headers = {}, ...fetchOptions } = options
-
-		const sessionToken = this.authService.getSessionToken()
-
-		if (!sessionToken) {
-			throw new AuthenticationError()
-		}
-
-		const url = `${this.baseUrl}${endpoint}`
-
-		const requestHeaders = {
-			"Content-Type": "application/json",
-			Authorization: `Bearer ${sessionToken}`,
-			"User-Agent": getUserAgent(),
-			...headers,
-		}
-
-		try {
-			const response = await fetch(url, {
-				...fetchOptions,
-				headers: requestHeaders,
-				signal: AbortSignal.timeout(timeout),
-			})
-
-			if (!response.ok) {
-				await this.handleErrorResponse(response, endpoint)
-			}
-
-			const data = await response.json()
-
-			if (parseResponse) {
-				return parseResponse(data)
-			}
-
-			return data as T
-		} catch (error) {
-			if (error instanceof TypeError && error.message.includes("fetch")) {
-				throw new NetworkError(`Network error while calling ${endpoint}`)
-			}
-
-			if (error instanceof CloudAPIError) {
-				throw error
-			}
-
-			if (error instanceof Error && error.name === "AbortError") {
-				throw new CloudAPIError(`Request to ${endpoint} timed out`, undefined, undefined)
-			}
-
-			throw new CloudAPIError(
-				`Unexpected error while calling ${endpoint}: ${error instanceof Error ? error.message : String(error)}`,
-			)
-		}
-	}
-
-	private async handleErrorResponse(response: Response, endpoint: string): Promise<never> {
-		let responseBody: unknown
-
-		try {
-			responseBody = await response.json()
-		} catch {
-			responseBody = await response.text()
-		}
-
-		switch (response.status) {
-			case 401:
-				throw new AuthenticationError()
-			case 404:
-				if (endpoint.includes("/share")) {
-					throw new TaskNotFoundError()
-				}
-				throw new CloudAPIError(`Resource not found: ${endpoint}`, 404, responseBody)
-			default:
-				throw new CloudAPIError(
-					`HTTP ${response.status}: ${response.statusText}`,
-					response.status,
-					responseBody,
-				)
-		}
-	}
-
-	async shareTask(taskId: string, visibility: ShareVisibility = "organization"): Promise<ShareResponse> {
-		this.log(`[CloudAPI] Sharing task ${taskId} with visibility: ${visibility}`)
-
-		const response = await this.request("/api/extension/share", {
-			method: "POST",
-			body: JSON.stringify({ taskId, visibility }),
-			parseResponse: (data) => shareResponseSchema.parse(data),
-		})
-
-		this.log("[CloudAPI] Share response:", response)
-		return response
-	}
-}

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

@@ -1,288 +0,0 @@
-import * as vscode from "vscode"
-import EventEmitter from "events"
-
-import type {
-	CloudUserInfo,
-	TelemetryEvent,
-	OrganizationAllowList,
-	OrganizationSettings,
-	ClineMessage,
-	ShareVisibility,
-} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
-
-import { CloudServiceEvents } from "./types"
-import { TaskNotFoundError } from "./errors"
-import type { AuthService } from "./auth"
-import { WebAuthService, StaticTokenAuthService } from "./auth"
-import type { SettingsService } from "./SettingsService"
-import { CloudSettingsService } from "./CloudSettingsService"
-import { StaticSettingsService } from "./StaticSettingsService"
-import { TelemetryClient } from "./TelemetryClient"
-import { CloudShareService } from "./CloudShareService"
-import { CloudAPI } from "./CloudAPI"
-
-type AuthStateChangedPayload = CloudServiceEvents["auth-state-changed"][0]
-type AuthUserInfoPayload = CloudServiceEvents["user-info"][0]
-type SettingsPayload = CloudServiceEvents["settings-updated"][0]
-
-export class CloudService extends EventEmitter<CloudServiceEvents> implements vscode.Disposable {
-	private static _instance: CloudService | null = null
-
-	private context: vscode.ExtensionContext
-	private authStateListener: (data: AuthStateChangedPayload) => void
-	private authUserInfoListener: (data: AuthUserInfoPayload) => void
-	private authService: AuthService | null = null
-	private settingsListener: (data: SettingsPayload) => void
-	private settingsService: SettingsService | null = null
-	private telemetryClient: TelemetryClient | null = null
-	private shareService: CloudShareService | null = null
-	private cloudAPI: CloudAPI | null = null
-	private isInitialized = false
-	private log: (...args: unknown[]) => void
-
-	private constructor(context: vscode.ExtensionContext, log?: (...args: unknown[]) => void) {
-		super()
-
-		this.context = context
-		this.log = log || console.log
-		this.authStateListener = (data: AuthStateChangedPayload) => {
-			this.emit("auth-state-changed", data)
-		}
-		this.authUserInfoListener = (data: AuthUserInfoPayload) => {
-			this.emit("user-info", data)
-		}
-		this.settingsListener = (data: SettingsPayload) => {
-			this.emit("settings-updated", data)
-		}
-	}
-
-	public async initialize(): Promise<void> {
-		if (this.isInitialized) {
-			return
-		}
-
-		try {
-			const cloudToken = process.env.ROO_CODE_CLOUD_TOKEN
-
-			if (cloudToken && cloudToken.length > 0) {
-				this.authService = new StaticTokenAuthService(this.context, cloudToken, this.log)
-			} else {
-				this.authService = new WebAuthService(this.context, this.log)
-			}
-
-			await this.authService.initialize()
-
-			this.authService.on("auth-state-changed", this.authStateListener)
-			this.authService.on("user-info", this.authUserInfoListener)
-
-			// Check for static settings environment variable.
-			const staticOrgSettings = process.env.ROO_CODE_CLOUD_ORG_SETTINGS
-
-			if (staticOrgSettings && staticOrgSettings.length > 0) {
-				this.settingsService = new StaticSettingsService(staticOrgSettings, this.log)
-			} else {
-				const cloudSettingsService = new CloudSettingsService(this.context, this.authService, this.log)
-				cloudSettingsService.initialize()
-
-				cloudSettingsService.on("settings-updated", this.settingsListener)
-
-				this.settingsService = cloudSettingsService
-			}
-
-			this.cloudAPI = new CloudAPI(this.authService, this.log)
-			this.telemetryClient = new TelemetryClient(this.authService, this.settingsService)
-			this.shareService = new CloudShareService(this.cloudAPI, this.settingsService, this.log)
-
-			try {
-				TelemetryService.instance.register(this.telemetryClient)
-			} catch (error) {
-				this.log("[CloudService] Failed to register TelemetryClient:", error)
-			}
-
-			this.isInitialized = true
-		} catch (error) {
-			this.log("[CloudService] Failed to initialize:", error)
-			throw new Error(`Failed to initialize CloudService: ${error}`)
-		}
-	}
-
-	// AuthService
-
-	public async login(): Promise<void> {
-		this.ensureInitialized()
-		return this.authService!.login()
-	}
-
-	public async logout(): Promise<void> {
-		this.ensureInitialized()
-		return this.authService!.logout()
-	}
-
-	public isAuthenticated(): boolean {
-		this.ensureInitialized()
-		return this.authService!.isAuthenticated()
-	}
-
-	public hasActiveSession(): boolean {
-		this.ensureInitialized()
-		return this.authService!.hasActiveSession()
-	}
-
-	public hasOrIsAcquiringActiveSession(): boolean {
-		this.ensureInitialized()
-		return this.authService!.hasOrIsAcquiringActiveSession()
-	}
-
-	public getUserInfo(): CloudUserInfo | null {
-		this.ensureInitialized()
-		return this.authService!.getUserInfo()
-	}
-
-	public getOrganizationId(): string | null {
-		this.ensureInitialized()
-		const userInfo = this.authService!.getUserInfo()
-		return userInfo?.organizationId || null
-	}
-
-	public getOrganizationName(): string | null {
-		this.ensureInitialized()
-		const userInfo = this.authService!.getUserInfo()
-		return userInfo?.organizationName || null
-	}
-
-	public getOrganizationRole(): string | null {
-		this.ensureInitialized()
-		const userInfo = this.authService!.getUserInfo()
-		return userInfo?.organizationRole || null
-	}
-
-	public hasStoredOrganizationId(): boolean {
-		this.ensureInitialized()
-		return this.authService!.getStoredOrganizationId() !== null
-	}
-
-	public getStoredOrganizationId(): string | null {
-		this.ensureInitialized()
-		return this.authService!.getStoredOrganizationId()
-	}
-
-	public getAuthState(): string {
-		this.ensureInitialized()
-		return this.authService!.getState()
-	}
-
-	public async handleAuthCallback(
-		code: string | null,
-		state: string | null,
-		organizationId?: string | null,
-	): Promise<void> {
-		this.ensureInitialized()
-		return this.authService!.handleCallback(code, state, organizationId)
-	}
-
-	// SettingsService
-
-	public getAllowList(): OrganizationAllowList {
-		this.ensureInitialized()
-		return this.settingsService!.getAllowList()
-	}
-
-	public getOrganizationSettings(): OrganizationSettings | undefined {
-		this.ensureInitialized()
-		return this.settingsService!.getSettings()
-	}
-
-	// TelemetryClient
-
-	public captureEvent(event: TelemetryEvent): void {
-		this.ensureInitialized()
-		this.telemetryClient!.capture(event)
-	}
-
-	// ShareService
-
-	public async shareTask(
-		taskId: string,
-		visibility: ShareVisibility = "organization",
-		clineMessages?: ClineMessage[],
-	) {
-		this.ensureInitialized()
-
-		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> {
-		this.ensureInitialized()
-		return this.shareService!.canShareTask()
-	}
-
-	// Lifecycle
-
-	public dispose(): void {
-		if (this.authService) {
-			this.authService.off("auth-state-changed", this.authStateListener)
-			this.authService.off("user-info", this.authUserInfoListener)
-		}
-
-		if (this.settingsService) {
-			if (this.settingsService instanceof CloudSettingsService) {
-				this.settingsService.off("settings-updated", this.settingsListener)
-			}
-			this.settingsService.dispose()
-		}
-
-		this.isInitialized = false
-	}
-
-	private ensureInitialized(): void {
-		if (!this.isInitialized) {
-			throw new Error("CloudService not initialized.")
-		}
-	}
-
-	static get instance(): CloudService {
-		if (!this._instance) {
-			throw new Error("CloudService not initialized")
-		}
-
-		return this._instance
-	}
-
-	static async createInstance(
-		context: vscode.ExtensionContext,
-		log?: (...args: unknown[]) => void,
-	): Promise<CloudService> {
-		if (this._instance) {
-			throw new Error("CloudService instance already created")
-		}
-
-		this._instance = new CloudService(context, log)
-		await this._instance.initialize()
-		return this._instance
-	}
-
-	static hasInstance(): boolean {
-		return this._instance !== null && this._instance.isInitialized
-	}
-
-	static resetInstance(): void {
-		if (this._instance) {
-			this._instance.dispose()
-			this._instance = null
-		}
-	}
-
-	static isEnabled(): boolean {
-		return !!this._instance?.isAuthenticated()
-	}
-}

+ 0 - 152
packages/cloud/src/CloudSettingsService.ts

@@ -1,152 +0,0 @@
-import * as vscode from "vscode"
-import EventEmitter from "events"
-
-import {
-	ORGANIZATION_ALLOW_ALL,
-	OrganizationAllowList,
-	OrganizationSettings,
-	organizationSettingsSchema,
-} from "@roo-code/types"
-
-import { getRooCodeApiUrl } from "./config"
-import type { AuthService, AuthState } from "./auth"
-import { RefreshTimer } from "./RefreshTimer"
-import type { SettingsService } from "./SettingsService"
-
-const ORGANIZATION_SETTINGS_CACHE_KEY = "organization-settings"
-
-export interface SettingsServiceEvents {
-	"settings-updated": [
-		data: {
-			settings: OrganizationSettings
-			previousSettings: OrganizationSettings | undefined
-		},
-	]
-}
-
-export class CloudSettingsService extends EventEmitter<SettingsServiceEvents> implements SettingsService {
-	private context: vscode.ExtensionContext
-	private authService: AuthService
-	private settings: OrganizationSettings | undefined = undefined
-	private timer: RefreshTimer
-	private log: (...args: unknown[]) => void
-
-	constructor(context: vscode.ExtensionContext, authService: AuthService, log?: (...args: unknown[]) => void) {
-		super()
-
-		this.context = context
-		this.authService = authService
-		this.log = log || console.log
-
-		this.timer = new RefreshTimer({
-			callback: async () => {
-				return await this.fetchSettings()
-			},
-			successInterval: 30000,
-			initialBackoffMs: 1000,
-			maxBackoffMs: 30000,
-		})
-	}
-
-	public initialize(): void {
-		this.loadCachedSettings()
-
-		// Clear cached settings if we have missed a log out.
-		if (this.authService.getState() == "logged-out" && this.settings) {
-			this.removeSettings()
-		}
-
-		this.authService.on("auth-state-changed", (data: { state: AuthState; previousState: AuthState }) => {
-			if (data.state === "active-session") {
-				this.timer.start()
-			} else if (data.previousState === "active-session") {
-				this.timer.stop()
-
-				if (data.state === "logged-out") {
-					this.removeSettings()
-				}
-			}
-		})
-
-		if (this.authService.hasActiveSession()) {
-			this.timer.start()
-		}
-	}
-
-	private async fetchSettings(): Promise<boolean> {
-		const token = this.authService.getSessionToken()
-
-		if (!token) {
-			return false
-		}
-
-		try {
-			const response = await fetch(`${getRooCodeApiUrl()}/api/organization-settings`, {
-				headers: {
-					Authorization: `Bearer ${token}`,
-				},
-			})
-
-			if (!response.ok) {
-				this.log(
-					"[cloud-settings] Failed to fetch organization settings:",
-					response.status,
-					response.statusText,
-				)
-				return false
-			}
-
-			const data = await response.json()
-			const result = organizationSettingsSchema.safeParse(data)
-
-			if (!result.success) {
-				this.log("[cloud-settings] Invalid organization settings format:", result.error)
-				return false
-			}
-
-			const newSettings = result.data
-
-			if (!this.settings || this.settings.version !== newSettings.version) {
-				const previousSettings = this.settings
-				this.settings = newSettings
-				await this.cacheSettings()
-
-				this.emit("settings-updated", {
-					settings: this.settings,
-					previousSettings,
-				})
-			}
-
-			return true
-		} catch (error) {
-			this.log("[cloud-settings] Error fetching organization settings:", error)
-			return false
-		}
-	}
-
-	private async cacheSettings(): Promise<void> {
-		await this.context.globalState.update(ORGANIZATION_SETTINGS_CACHE_KEY, this.settings)
-	}
-
-	private loadCachedSettings(): void {
-		this.settings = this.context.globalState.get<OrganizationSettings>(ORGANIZATION_SETTINGS_CACHE_KEY)
-	}
-
-	public getAllowList(): OrganizationAllowList {
-		return this.settings?.allowList || ORGANIZATION_ALLOW_ALL
-	}
-
-	public getSettings(): OrganizationSettings | undefined {
-		return this.settings
-	}
-
-	private async removeSettings(): Promise<void> {
-		this.settings = undefined
-		await this.cacheSettings()
-	}
-
-	public dispose(): void {
-		this.removeAllListeners()
-		this.timer.stop()
-	}
-}

+ 0 - 43
packages/cloud/src/CloudShareService.ts

@@ -1,43 +0,0 @@
-import * as vscode from "vscode"
-
-import type { ShareResponse, ShareVisibility } from "@roo-code/types"
-
-import type { CloudAPI } from "./CloudAPI"
-import type { SettingsService } from "./SettingsService"
-
-export class CloudShareService {
-	private cloudAPI: CloudAPI
-	private settingsService: SettingsService
-	private log: (...args: unknown[]) => void
-
-	constructor(cloudAPI: CloudAPI, settingsService: SettingsService, log?: (...args: unknown[]) => void) {
-		this.cloudAPI = cloudAPI
-		this.settingsService = settingsService
-		this.log = log || console.log
-	}
-
-	async shareTask(taskId: string, visibility: ShareVisibility = "organization"): Promise<ShareResponse> {
-		try {
-			const response = await this.cloudAPI.shareTask(taskId, visibility)
-
-			if (response.success && response.shareUrl) {
-				// Copy to clipboard.
-				await vscode.env.clipboard.writeText(response.shareUrl)
-			}
-
-			return response
-		} catch (error) {
-			this.log("[ShareService] Error sharing task:", error)
-			throw error
-		}
-	}
-
-	async canShareTask(): Promise<boolean> {
-		try {
-			return !!this.settingsService.getSettings()?.cloudSettings?.enableTaskSharing
-		} catch (error) {
-			this.log("[ShareService] Error checking if task can be shared:", error)
-			return false
-		}
-	}
-}

+ 0 - 154
packages/cloud/src/RefreshTimer.ts

@@ -1,154 +0,0 @@
-/**
- * RefreshTimer - A utility for executing a callback with configurable retry behavior
- *
- * This timer executes a callback function and schedules the next execution based on the result:
- * - If the callback succeeds (returns true), it schedules the next attempt after a fixed interval
- * - If the callback fails (returns false), it uses exponential backoff up to a maximum interval
- */
-
-/**
- * Configuration options for the RefreshTimer
- */
-export interface RefreshTimerOptions {
-	/**
-	 * The callback function to execute
-	 * Should return a Promise that resolves to a boolean indicating success (true) or failure (false)
-	 */
-	callback: () => Promise<boolean>
-
-	/**
-	 * Time in milliseconds to wait before next attempt after success
-	 * @default 50000 (50 seconds)
-	 */
-	successInterval?: number
-
-	/**
-	 * Initial backoff time in milliseconds for the first failure
-	 * @default 1000 (1 second)
-	 */
-	initialBackoffMs?: number
-
-	/**
-	 * Maximum backoff time in milliseconds
-	 * @default 300000 (5 minutes)
-	 */
-	maxBackoffMs?: number
-}
-
-/**
- * A timer utility that executes a callback with configurable retry behavior
- */
-export class RefreshTimer {
-	private callback: () => Promise<boolean>
-	private successInterval: number
-	private initialBackoffMs: number
-	private maxBackoffMs: number
-	private currentBackoffMs: number
-	private attemptCount: number
-	private timerId: NodeJS.Timeout | null
-	private isRunning: boolean
-
-	/**
-	 * Creates a new RefreshTimer
-	 *
-	 * @param options Configuration options for the timer
-	 */
-	constructor(options: RefreshTimerOptions) {
-		this.callback = options.callback
-		this.successInterval = options.successInterval ?? 50000 // 50 seconds
-		this.initialBackoffMs = options.initialBackoffMs ?? 1000 // 1 second
-		this.maxBackoffMs = options.maxBackoffMs ?? 300000 // 5 minutes
-		this.currentBackoffMs = this.initialBackoffMs
-		this.attemptCount = 0
-		this.timerId = null
-		this.isRunning = false
-	}
-
-	/**
-	 * Starts the timer and executes the callback immediately
-	 */
-	public start(): void {
-		if (this.isRunning) {
-			return
-		}
-
-		this.isRunning = true
-
-		// Execute the callback immediately
-		this.executeCallback()
-	}
-
-	/**
-	 * Stops the timer and cancels any pending execution
-	 */
-	public stop(): void {
-		if (!this.isRunning) {
-			return
-		}
-
-		if (this.timerId) {
-			clearTimeout(this.timerId)
-			this.timerId = null
-		}
-
-		this.isRunning = false
-	}
-
-	/**
-	 * Resets the backoff state and attempt count
-	 * Does not affect whether the timer is running
-	 */
-	public reset(): void {
-		this.currentBackoffMs = this.initialBackoffMs
-		this.attemptCount = 0
-	}
-
-	/**
-	 * Schedules the next attempt based on the success/failure of the current attempt
-	 *
-	 * @param wasSuccessful Whether the current attempt was successful
-	 */
-	private scheduleNextAttempt(wasSuccessful: boolean): void {
-		if (!this.isRunning) {
-			return
-		}
-
-		if (wasSuccessful) {
-			// Reset backoff on success
-			this.currentBackoffMs = this.initialBackoffMs
-			this.attemptCount = 0
-
-			this.timerId = setTimeout(() => this.executeCallback(), this.successInterval)
-		} else {
-			// Increment attempt count
-			this.attemptCount++
-
-			// Calculate backoff time with exponential increase
-			// Formula: initialBackoff * 2^(attemptCount - 1)
-			this.currentBackoffMs = Math.min(
-				this.initialBackoffMs * Math.pow(2, this.attemptCount - 1),
-				this.maxBackoffMs,
-			)
-
-			this.timerId = setTimeout(() => this.executeCallback(), this.currentBackoffMs)
-		}
-	}
-
-	/**
-	 * Executes the callback and handles the result
-	 */
-	private async executeCallback(): Promise<void> {
-		if (!this.isRunning) {
-			return
-		}
-
-		try {
-			const result = await this.callback()
-
-			this.scheduleNextAttempt(result)
-		} catch (_error) {
-			// Treat errors as failed attempts
-			this.scheduleNextAttempt(false)
-		}
-	}
-}

+ 0 - 23
packages/cloud/src/SettingsService.ts

@@ -1,23 +0,0 @@
-import type { OrganizationAllowList, OrganizationSettings } from "@roo-code/types"
-
-/**
- * Interface for settings services that provide organization settings
- */
-export interface SettingsService {
-	/**
-	 * Get the organization allow list
-	 * @returns The organization allow list or default if none available
-	 */
-	getAllowList(): OrganizationAllowList
-
-	/**
-	 * Get the current organization settings
-	 * @returns The organization settings or undefined if none available
-	 */
-	getSettings(): OrganizationSettings | undefined
-
-	/**
-	 * Dispose of the settings service and clean up resources
-	 */
-	dispose(): void
-}

+ 0 - 41
packages/cloud/src/StaticSettingsService.ts

@@ -1,41 +0,0 @@
-import {
-	ORGANIZATION_ALLOW_ALL,
-	OrganizationAllowList,
-	OrganizationSettings,
-	organizationSettingsSchema,
-} from "@roo-code/types"
-
-import type { SettingsService } from "./SettingsService"
-
-export class StaticSettingsService implements SettingsService {
-	private settings: OrganizationSettings
-	private log: (...args: unknown[]) => void
-
-	constructor(envValue: string, log?: (...args: unknown[]) => void) {
-		this.log = log || console.log
-		this.settings = this.parseEnvironmentSettings(envValue)
-	}
-
-	private parseEnvironmentSettings(envValue: string): OrganizationSettings {
-		try {
-			const decodedValue = Buffer.from(envValue, "base64").toString("utf-8")
-			const parsedJson = JSON.parse(decodedValue)
-			return organizationSettingsSchema.parse(parsedJson)
-		} catch (error) {
-			this.log(`[StaticSettingsService] failed to parse static settings: ${error.message}`, error)
-			throw new Error("Failed to parse static settings", { cause: error })
-		}
-	}
-
-	public getAllowList(): OrganizationAllowList {
-		return this.settings?.allowList || ORGANIZATION_ALLOW_ALL
-	}
-
-	public getSettings(): OrganizationSettings | undefined {
-		return this.settings
-	}
-
-	public dispose(): void {
-		// No resources to clean up for static settings.
-	}
-}

+ 0 - 169
packages/cloud/src/TelemetryClient.ts

@@ -1,169 +0,0 @@
-import {
-	TelemetryEventName,
-	type TelemetryEvent,
-	rooCodeTelemetryEventSchema,
-	type ClineMessage,
-} from "@roo-code/types"
-import { BaseTelemetryClient } from "@roo-code/telemetry"
-
-import { getRooCodeApiUrl } from "./config"
-import type { AuthService } from "./auth"
-import type { SettingsService } from "./SettingsService"
-
-export class TelemetryClient extends BaseTelemetryClient {
-	constructor(
-		private authService: AuthService,
-		private settingsService: SettingsService,
-		debug = false,
-	) {
-		super(
-			{
-				type: "exclude",
-				events: [TelemetryEventName.TASK_CONVERSATION_MESSAGE],
-			},
-			debug,
-		)
-	}
-
-	private async fetch(path: string, options: RequestInit) {
-		if (!this.authService.isAuthenticated()) {
-			return
-		}
-
-		const token = this.authService.getSessionToken()
-
-		if (!token) {
-			console.error(`[TelemetryClient#fetch] Unauthorized: No session token available.`)
-			return
-		}
-
-		const response = await fetch(`${getRooCodeApiUrl()}/api/${path}`, {
-			...options,
-			headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
-		})
-
-		if (!response.ok) {
-			console.error(
-				`[TelemetryClient#fetch] ${options.method} ${path} -> ${response.status} ${response.statusText}`,
-			)
-		}
-	}
-
-	public override async capture(event: TelemetryEvent) {
-		if (!this.isTelemetryEnabled() || !this.isEventCapturable(event.event)) {
-			if (this.debug) {
-				console.info(`[TelemetryClient#capture] Skipping event: ${event.event}`)
-			}
-
-			return
-		}
-
-		const payload = {
-			type: event.event,
-			properties: await this.getEventProperties(event),
-		}
-
-		if (this.debug) {
-			console.info(`[TelemetryClient#capture] ${JSON.stringify(payload)}`)
-		}
-
-		const result = rooCodeTelemetryEventSchema.safeParse(payload)
-
-		if (!result.success) {
-			console.error(
-				`[TelemetryClient#capture] Invalid telemetry event: ${result.error.message} - ${JSON.stringify(payload)}`,
-			)
-
-			return
-		}
-
-		try {
-			await this.fetch(`events`, { method: "POST", body: JSON.stringify(result.data) })
-		} catch (error) {
-			console.error(`[TelemetryClient#capture] Error sending telemetry event: ${error}`)
-		}
-	}
-
-	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 {
-		return true
-	}
-
-	protected override isEventCapturable(eventName: TelemetryEventName): boolean {
-		// Ensure that this event type is supported by the telemetry client
-		if (!super.isEventCapturable(eventName)) {
-			return false
-		}
-
-		// Only record message telemetry if a cloud account is present and explicitly configured to record messages
-		if (eventName === TelemetryEventName.TASK_MESSAGE) {
-			return this.settingsService.getSettings()?.cloudSettings?.recordTaskMessages || false
-		}
-
-		// Other telemetry types are capturable at this point
-		return true
-	}
-
-	public override async shutdown() {}
-}

+ 0 - 57
packages/cloud/src/__mocks__/vscode.ts

@@ -1,57 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
-export const window = {
-	showInformationMessage: vi.fn(),
-	showErrorMessage: vi.fn(),
-}
-
-export const env = {
-	openExternal: vi.fn(),
-}
-
-export const Uri = {
-	parse: vi.fn((uri: string) => ({ toString: () => uri })),
-}
-
-export interface ExtensionContext {
-	secrets: {
-		get: (key: string) => Promise<string | undefined>
-		store: (key: string, value: string) => Promise<void>
-		delete: (key: string) => Promise<void>
-		onDidChange: (listener: (e: { key: string }) => void) => { dispose: () => void }
-	}
-	globalState: {
-		get: <T>(key: string) => T | undefined
-		update: (key: string, value: any) => Promise<void>
-	}
-	subscriptions: any[]
-	extension?: {
-		packageJSON?: {
-			version?: string
-			publisher?: string
-			name?: string
-		}
-	}
-}
-
-// Mock implementation for tests
-export const mockExtensionContext: ExtensionContext = {
-	secrets: {
-		get: vi.fn().mockResolvedValue(undefined),
-		store: vi.fn().mockResolvedValue(undefined),
-		delete: vi.fn().mockResolvedValue(undefined),
-		onDidChange: vi.fn().mockReturnValue({ dispose: vi.fn() }),
-	},
-	globalState: {
-		get: vi.fn().mockReturnValue(undefined),
-		update: vi.fn().mockResolvedValue(undefined),
-	},
-	subscriptions: [],
-	extension: {
-		packageJSON: {
-			version: "1.0.0",
-			publisher: "RooVeterinaryInc",
-			name: "roo-cline",
-		},
-	},
-}

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

@@ -1,146 +0,0 @@
-// npx vitest run src/__tests__/CloudService.integration.test.ts
-
-import * as vscode from "vscode"
-import { CloudService } from "../CloudService"
-import { StaticSettingsService } from "../StaticSettingsService"
-import { CloudSettingsService } from "../CloudSettingsService"
-
-vi.mock("vscode", () => ({
-	ExtensionContext: vi.fn(),
-	window: {
-		showInformationMessage: vi.fn(),
-		showErrorMessage: vi.fn(),
-	},
-	env: {
-		openExternal: vi.fn(),
-	},
-	Uri: {
-		parse: vi.fn(),
-	},
-}))
-
-describe("CloudService Integration - Settings Service Selection", () => {
-	let mockContext: vscode.ExtensionContext
-
-	beforeEach(() => {
-		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",
-				},
-			},
-		} as unknown as vscode.ExtensionContext
-	})
-
-	afterEach(() => {
-		CloudService.resetInstance()
-		delete process.env.ROO_CODE_CLOUD_ORG_SETTINGS
-		delete process.env.ROO_CODE_CLOUD_TOKEN
-	})
-
-	it("should use CloudSettingsService when no environment variable is set", async () => {
-		// Ensure no environment variables are set
-		delete process.env.ROO_CODE_CLOUD_ORG_SETTINGS
-		delete process.env.ROO_CODE_CLOUD_TOKEN
-
-		const cloudService = await CloudService.createInstance(mockContext)
-
-		// Access the private settingsService to check its type
-		const settingsService = (cloudService as unknown as { settingsService: unknown }).settingsService
-		expect(settingsService).toBeInstanceOf(CloudSettingsService)
-	})
-
-	it("should use StaticSettingsService when ROO_CODE_CLOUD_ORG_SETTINGS is set", async () => {
-		const validSettings = {
-			version: 1,
-			cloudSettings: {
-				recordTaskMessages: true,
-				enableTaskSharing: true,
-				taskShareExpirationDays: 30,
-			},
-			defaultSettings: {
-				enableCheckpoints: true,
-			},
-			allowList: {
-				allowAll: true,
-				providers: {},
-			},
-		}
-
-		// Set the environment variable
-		process.env.ROO_CODE_CLOUD_ORG_SETTINGS = Buffer.from(JSON.stringify(validSettings)).toString("base64")
-
-		const cloudService = await CloudService.createInstance(mockContext)
-
-		// Access the private settingsService to check its type
-		const settingsService = (cloudService as unknown as { settingsService: unknown }).settingsService
-		expect(settingsService).toBeInstanceOf(StaticSettingsService)
-
-		// Verify the settings are correctly loaded
-		expect(cloudService.getAllowList()).toEqual(validSettings.allowList)
-	})
-
-	it("should throw error when ROO_CODE_CLOUD_ORG_SETTINGS contains invalid data", async () => {
-		// Set invalid environment variable
-		process.env.ROO_CODE_CLOUD_ORG_SETTINGS = "invalid-base64-data"
-
-		await expect(CloudService.createInstance(mockContext)).rejects.toThrow("Failed to initialize CloudService")
-	})
-
-	it("should prioritize static token auth when both environment variables are set", async () => {
-		const validSettings = {
-			version: 1,
-			cloudSettings: {
-				recordTaskMessages: true,
-				enableTaskSharing: true,
-				taskShareExpirationDays: 30,
-			},
-			defaultSettings: {
-				enableCheckpoints: true,
-			},
-			allowList: {
-				allowAll: true,
-				providers: {},
-			},
-		}
-
-		// Set both environment variables
-		process.env.ROO_CODE_CLOUD_TOKEN = "test-token"
-		process.env.ROO_CODE_CLOUD_ORG_SETTINGS = Buffer.from(JSON.stringify(validSettings)).toString("base64")
-
-		const cloudService = await CloudService.createInstance(mockContext)
-
-		// Should use StaticSettingsService for settings
-		const settingsService = (cloudService as unknown as { settingsService: unknown }).settingsService
-		expect(settingsService).toBeInstanceOf(StaticSettingsService)
-
-		// Should use StaticTokenAuthService for auth (from the existing logic)
-		expect(cloudService.isAuthenticated()).toBe(true)
-		expect(cloudService.hasActiveSession()).toBe(true)
-	})
-})

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

@@ -1,604 +0,0 @@
-// npx vitest run src/__tests__/CloudService.test.ts
-
-import * as vscode from "vscode"
-
-import type { ClineMessage } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
-
-import { CloudService } from "../CloudService"
-import { WebAuthService } from "../auth/WebAuthService"
-import { CloudSettingsService } from "../CloudSettingsService"
-import { CloudShareService } from "../CloudShareService"
-import { TelemetryClient } from "../TelemetryClient"
-import { TaskNotFoundError } from "../errors"
-
-vi.mock("vscode", () => ({
-	ExtensionContext: vi.fn(),
-	window: {
-		showInformationMessage: vi.fn(),
-		showErrorMessage: vi.fn(),
-	},
-	env: {
-		openExternal: vi.fn(),
-	},
-	Uri: {
-		parse: vi.fn(),
-	},
-}))
-
-vi.mock("@roo-code/telemetry")
-
-vi.mock("../auth/WebAuthService")
-
-vi.mock("../CloudSettingsService")
-
-vi.mock("../CloudShareService")
-
-vi.mock("../TelemetryClient")
-
-describe("CloudService", () => {
-	let mockContext: vscode.ExtensionContext
-	let mockAuthService: {
-		initialize: ReturnType<typeof vi.fn>
-		login: ReturnType<typeof vi.fn>
-		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>
-		handleCallback: ReturnType<typeof vi.fn>
-		getStoredOrganizationId: ReturnType<typeof vi.fn>
-		on: ReturnType<typeof vi.fn>
-		off: ReturnType<typeof vi.fn>
-		once: ReturnType<typeof vi.fn>
-		emit: ReturnType<typeof vi.fn>
-	}
-	let mockSettingsService: {
-		initialize: ReturnType<typeof vi.fn>
-		getSettings: ReturnType<typeof vi.fn>
-		getAllowList: ReturnType<typeof vi.fn>
-		dispose: ReturnType<typeof vi.fn>
-		on: ReturnType<typeof vi.fn>
-		off: 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: {
-			register: ReturnType<typeof vi.fn>
-		}
-	}
-
-	beforeEach(() => {
-		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",
-				},
-			},
-		} as unknown as vscode.ExtensionContext
-
-		mockAuthService = {
-			initialize: vi.fn().mockResolvedValue(undefined),
-			login: vi.fn(),
-			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(),
-			handleCallback: vi.fn(),
-			getStoredOrganizationId: vi.fn().mockReturnValue(null),
-			on: vi.fn(),
-			off: vi.fn(),
-			once: vi.fn(),
-			emit: vi.fn(),
-		}
-
-		mockSettingsService = {
-			initialize: vi.fn(),
-			getSettings: vi.fn(),
-			getAllowList: vi.fn(),
-			dispose: vi.fn(),
-			on: vi.fn(),
-			off: vi.fn(),
-		}
-
-		mockShareService = {
-			shareTask: vi.fn(),
-			canShareTask: vi.fn().mockResolvedValue(true),
-		}
-
-		mockTelemetryClient = {
-			backfillMessages: vi.fn().mockResolvedValue(undefined),
-		}
-
-		mockTelemetryService = {
-			hasInstance: vi.fn().mockReturnValue(true),
-			instance: {
-				register: vi.fn(),
-			},
-		}
-
-		vi.mocked(WebAuthService).mockImplementation(() => mockAuthService as unknown as WebAuthService)
-		vi.mocked(CloudSettingsService).mockImplementation(() => mockSettingsService as unknown as CloudSettingsService)
-		vi.mocked(CloudShareService).mockImplementation(() => mockShareService as unknown as CloudShareService)
-		vi.mocked(TelemetryClient).mockImplementation(() => mockTelemetryClient as unknown as TelemetryClient)
-
-		vi.mocked(TelemetryService.hasInstance).mockReturnValue(true)
-		Object.defineProperty(TelemetryService, "instance", {
-			get: () => mockTelemetryService.instance,
-			configurable: true,
-		})
-	})
-
-	afterEach(() => {
-		vi.clearAllMocks()
-		CloudService.resetInstance()
-	})
-
-	describe("createInstance", () => {
-		it("should create and initialize CloudService instance", async () => {
-			const mockLog = vi.fn()
-
-			const cloudService = await CloudService.createInstance(mockContext, mockLog)
-
-			expect(cloudService).toBeInstanceOf(CloudService)
-			expect(WebAuthService).toHaveBeenCalledWith(mockContext, expect.any(Function))
-			expect(CloudSettingsService).toHaveBeenCalledWith(mockContext, mockAuthService, expect.any(Function))
-		})
-
-		it("should set up event listeners for CloudSettingsService", async () => {
-			const mockLog = vi.fn()
-
-			await CloudService.createInstance(mockContext, mockLog)
-
-			expect(mockSettingsService.on).toHaveBeenCalledWith("settings-updated", expect.any(Function))
-		})
-
-		it("should throw error if instance already exists", async () => {
-			await CloudService.createInstance(mockContext)
-
-			await expect(CloudService.createInstance(mockContext)).rejects.toThrow(
-				"CloudService instance already created",
-			)
-		})
-	})
-
-	describe("authentication methods", () => {
-		let cloudService: CloudService
-
-		beforeEach(async () => {
-			cloudService = await CloudService.createInstance(mockContext)
-		})
-
-		it("should delegate login to AuthService", async () => {
-			await cloudService.login()
-			expect(mockAuthService.login).toHaveBeenCalled()
-		})
-
-		it("should delegate logout to AuthService", async () => {
-			await cloudService.logout()
-			expect(mockAuthService.logout).toHaveBeenCalled()
-		})
-
-		it("should delegate isAuthenticated to AuthService", () => {
-			const result = cloudService.isAuthenticated()
-			expect(mockAuthService.isAuthenticated).toHaveBeenCalled()
-			expect(result).toBe(false)
-		})
-
-		it("should delegate hasActiveSession to AuthService", () => {
-			const result = cloudService.hasActiveSession()
-			expect(mockAuthService.hasActiveSession).toHaveBeenCalled()
-			expect(result).toBe(false)
-		})
-
-		it("should delegate getUserInfo to AuthService", async () => {
-			await cloudService.getUserInfo()
-			expect(mockAuthService.getUserInfo).toHaveBeenCalled()
-		})
-
-		it("should return organization ID from user info", () => {
-			const mockUserInfo = {
-				name: "Test User",
-				email: "[email protected]",
-				organizationId: "org_123",
-				organizationName: "Test Org",
-				organizationRole: "admin",
-			}
-			mockAuthService.getUserInfo.mockReturnValue(mockUserInfo)
-
-			const result = cloudService.getOrganizationId()
-			expect(mockAuthService.getUserInfo).toHaveBeenCalled()
-			expect(result).toBe("org_123")
-		})
-
-		it("should return null when no organization ID available", () => {
-			mockAuthService.getUserInfo.mockReturnValue(null)
-
-			const result = cloudService.getOrganizationId()
-			expect(result).toBe(null)
-		})
-
-		it("should return organization name from user info", () => {
-			const mockUserInfo = {
-				name: "Test User",
-				email: "[email protected]",
-				organizationId: "org_123",
-				organizationName: "Test Org",
-				organizationRole: "admin",
-			}
-			mockAuthService.getUserInfo.mockReturnValue(mockUserInfo)
-
-			const result = cloudService.getOrganizationName()
-			expect(mockAuthService.getUserInfo).toHaveBeenCalled()
-			expect(result).toBe("Test Org")
-		})
-
-		it("should return null when no organization name available", () => {
-			mockAuthService.getUserInfo.mockReturnValue(null)
-
-			const result = cloudService.getOrganizationName()
-			expect(result).toBe(null)
-		})
-
-		it("should return organization role from user info", () => {
-			const mockUserInfo = {
-				name: "Test User",
-				email: "[email protected]",
-				organizationId: "org_123",
-				organizationName: "Test Org",
-				organizationRole: "admin",
-			}
-			mockAuthService.getUserInfo.mockReturnValue(mockUserInfo)
-
-			const result = cloudService.getOrganizationRole()
-			expect(mockAuthService.getUserInfo).toHaveBeenCalled()
-			expect(result).toBe("admin")
-		})
-
-		it("should return null when no organization role available", () => {
-			mockAuthService.getUserInfo.mockReturnValue(null)
-
-			const result = cloudService.getOrganizationRole()
-			expect(result).toBe(null)
-		})
-
-		it("should delegate getAuthState to AuthService", () => {
-			const result = cloudService.getAuthState()
-			expect(mockAuthService.getState).toHaveBeenCalled()
-			expect(result).toBe("logged-out")
-		})
-
-		it("should delegate handleAuthCallback to AuthService", async () => {
-			await cloudService.handleAuthCallback("code", "state")
-			expect(mockAuthService.handleCallback).toHaveBeenCalledWith("code", "state", undefined)
-		})
-
-		it("should delegate handleAuthCallback with organizationId to AuthService", async () => {
-			await cloudService.handleAuthCallback("code", "state", "org_123")
-			expect(mockAuthService.handleCallback).toHaveBeenCalledWith("code", "state", "org_123")
-		})
-
-		it("should return stored organization ID from AuthService", () => {
-			mockAuthService.getStoredOrganizationId.mockReturnValue("org_456")
-
-			const result = cloudService.getStoredOrganizationId()
-			expect(mockAuthService.getStoredOrganizationId).toHaveBeenCalled()
-			expect(result).toBe("org_456")
-		})
-
-		it("should return null when no stored organization ID available", () => {
-			mockAuthService.getStoredOrganizationId.mockReturnValue(null)
-
-			const result = cloudService.getStoredOrganizationId()
-			expect(result).toBe(null)
-		})
-
-		it("should return true when stored organization ID exists", () => {
-			mockAuthService.getStoredOrganizationId.mockReturnValue("org_789")
-
-			const result = cloudService.hasStoredOrganizationId()
-			expect(result).toBe(true)
-		})
-
-		it("should return false when no stored organization ID exists", () => {
-			mockAuthService.getStoredOrganizationId.mockReturnValue(null)
-
-			const result = cloudService.hasStoredOrganizationId()
-			expect(result).toBe(false)
-		})
-	})
-
-	describe("organization settings methods", () => {
-		let cloudService: CloudService
-
-		beforeEach(async () => {
-			cloudService = await CloudService.createInstance(mockContext)
-		})
-
-		it("should delegate getAllowList to SettingsService", () => {
-			cloudService.getAllowList()
-			expect(mockSettingsService.getAllowList).toHaveBeenCalled()
-		})
-	})
-
-	describe("error handling", () => {
-		it("should throw error when accessing methods before initialization", () => {
-			expect(() => CloudService.instance.login()).toThrow("CloudService not initialized")
-		})
-
-		it("should throw error when accessing instance before creation", () => {
-			expect(() => CloudService.instance).toThrow("CloudService not initialized")
-		})
-	})
-
-	describe("hasInstance", () => {
-		it("should return false when no instance exists", () => {
-			expect(CloudService.hasInstance()).toBe(false)
-		})
-
-		it("should return true when instance exists and is initialized", async () => {
-			await CloudService.createInstance(mockContext)
-			expect(CloudService.hasInstance()).toBe(true)
-		})
-	})
-
-	describe("dispose", () => {
-		it("should dispose of all services and clean up", async () => {
-			const cloudService = await CloudService.createInstance(mockContext)
-			cloudService.dispose()
-
-			expect(mockSettingsService.dispose).toHaveBeenCalled()
-		})
-
-		it("should remove event listeners from CloudSettingsService", async () => {
-			// Create a mock that will pass the instanceof check
-			const mockCloudSettingsService = Object.create(CloudSettingsService.prototype)
-			Object.assign(mockCloudSettingsService, {
-				initialize: vi.fn(),
-				getSettings: vi.fn(),
-				getAllowList: vi.fn(),
-				dispose: vi.fn(),
-				on: vi.fn(),
-				off: vi.fn(),
-			})
-
-			// Override the mock to return our properly typed instance
-			vi.mocked(CloudSettingsService).mockImplementation(() => mockCloudSettingsService)
-
-			const cloudService = await CloudService.createInstance(mockContext)
-
-			// Verify the listener was added
-			expect(mockCloudSettingsService.on).toHaveBeenCalledWith("settings-updated", expect.any(Function))
-
-			// Get the listener function that was registered
-			const registeredListener = mockCloudSettingsService.on.mock.calls.find(
-				(call: unknown[]) => call[0] === "settings-updated",
-			)?.[1]
-
-			cloudService.dispose()
-
-			// Verify the listener was removed with the same function
-			expect(mockCloudSettingsService.off).toHaveBeenCalledWith("settings-updated", registeredListener)
-		})
-
-		it("should handle disposal when using StaticSettingsService", async () => {
-			// Reset the instance first
-			CloudService.resetInstance()
-
-			// Mock a StaticSettingsService (which doesn't extend CloudSettingsService)
-			const mockStaticSettingsService = {
-				initialize: vi.fn(),
-				getSettings: vi.fn(),
-				getAllowList: vi.fn(),
-				dispose: vi.fn(),
-				on: vi.fn(), // Add on method to avoid initialization error
-				off: vi.fn(), // Add off method for disposal
-			}
-
-			// Override the mock to return a service that won't pass instanceof check
-			vi.mocked(CloudSettingsService).mockImplementation(
-				() => mockStaticSettingsService as unknown as CloudSettingsService,
-			)
-
-			// This should not throw even though the service doesn't pass instanceof check
-			const _cloudService = await CloudService.createInstance(mockContext)
-
-			// Should not throw when disposing
-			expect(() => _cloudService.dispose()).not.toThrow()
-
-			// Should still call dispose on the settings service
-			expect(mockStaticSettingsService.dispose).toHaveBeenCalled()
-			// Should NOT call off method since it's not a CloudSettingsService instance
-			expect(mockStaticSettingsService.off).not.toHaveBeenCalled()
-		})
-	})
-
-	describe("settings event handling", () => {
-		let _cloudService: CloudService
-
-		beforeEach(async () => {
-			_cloudService = await CloudService.createInstance(mockContext)
-		})
-
-		it("should emit settings-updated event when settings are updated", async () => {
-			const settingsListener = vi.fn()
-			_cloudService.on("settings-updated", settingsListener)
-
-			// Get the settings listener that was registered with the settings service
-			const serviceSettingsListener = mockSettingsService.on.mock.calls.find(
-				(call) => call[0] === "settings-updated",
-			)?.[1]
-
-			expect(serviceSettingsListener).toBeDefined()
-
-			// Simulate settings update event
-			const settingsData = {
-				settings: {
-					version: 2,
-					defaultSettings: {},
-					allowList: { allowAll: true, providers: {} },
-				},
-				previousSettings: {
-					version: 1,
-					defaultSettings: {},
-					allowList: { allowAll: true, providers: {} },
-				},
-			}
-			serviceSettingsListener(settingsData)
-
-			expect(settingsListener).toHaveBeenCalledWith(settingsData)
-		})
-	})
-
-	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)
-		})
-	})
-})

+ 0 - 476
packages/cloud/src/__tests__/CloudSettingsService.test.ts

@@ -1,476 +0,0 @@
-import * as vscode from "vscode"
-import { CloudSettingsService } from "../CloudSettingsService"
-import { RefreshTimer } from "../RefreshTimer"
-import type { AuthService } from "../auth"
-import type { OrganizationSettings } from "@roo-code/types"
-
-// Mock dependencies
-vi.mock("../RefreshTimer")
-vi.mock("../config", () => ({
-	getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
-}))
-
-// Mock fetch globally
-global.fetch = vi.fn()
-
-describe("CloudSettingsService", () => {
-	let mockContext: vscode.ExtensionContext
-	let mockAuthService: {
-		getState: ReturnType<typeof vi.fn>
-		getSessionToken: ReturnType<typeof vi.fn>
-		hasActiveSession: ReturnType<typeof vi.fn>
-		on: ReturnType<typeof vi.fn>
-	}
-	let mockRefreshTimer: {
-		start: ReturnType<typeof vi.fn>
-		stop: ReturnType<typeof vi.fn>
-	}
-	let cloudSettingsService: CloudSettingsService
-	let mockLog: ReturnType<typeof vi.fn>
-
-	const mockSettings: OrganizationSettings = {
-		version: 1,
-		defaultSettings: {},
-		allowList: {
-			allowAll: true,
-			providers: {},
-		},
-	}
-
-	beforeEach(() => {
-		vi.clearAllMocks()
-
-		mockContext = {
-			globalState: {
-				get: vi.fn(),
-				update: vi.fn().mockResolvedValue(undefined),
-			},
-		} as unknown as vscode.ExtensionContext
-
-		mockAuthService = {
-			getState: vi.fn().mockReturnValue("logged-out"),
-			getSessionToken: vi.fn(),
-			hasActiveSession: vi.fn().mockReturnValue(false),
-			on: vi.fn(),
-		}
-
-		mockRefreshTimer = {
-			start: vi.fn(),
-			stop: vi.fn(),
-		}
-
-		mockLog = vi.fn()
-
-		// Mock RefreshTimer constructor
-		vi.mocked(RefreshTimer).mockImplementation(() => mockRefreshTimer as unknown as RefreshTimer)
-
-		cloudSettingsService = new CloudSettingsService(mockContext, mockAuthService as unknown as AuthService, mockLog)
-	})
-
-	afterEach(() => {
-		cloudSettingsService.dispose()
-	})
-
-	describe("constructor", () => {
-		it("should create CloudSettingsService with proper dependencies", () => {
-			expect(cloudSettingsService).toBeInstanceOf(CloudSettingsService)
-			expect(RefreshTimer).toHaveBeenCalledWith({
-				callback: expect.any(Function),
-				successInterval: 30000,
-				initialBackoffMs: 1000,
-				maxBackoffMs: 30000,
-			})
-		})
-
-		it("should use console.log as default logger when none provided", () => {
-			const service = new CloudSettingsService(mockContext, mockAuthService as unknown as AuthService)
-			expect(service).toBeInstanceOf(CloudSettingsService)
-		})
-	})
-
-	describe("initialize", () => {
-		it("should load cached settings on initialization", () => {
-			const cachedSettings = {
-				version: 1,
-				defaultSettings: {},
-				allowList: { allowAll: true, providers: {} },
-			}
-
-			// Create a fresh mock context for this test
-			const testContext = {
-				globalState: {
-					get: vi.fn().mockReturnValue(cachedSettings),
-					update: vi.fn().mockResolvedValue(undefined),
-				},
-			} as unknown as vscode.ExtensionContext
-
-			// Mock auth service to not be logged out
-			const testAuthService = {
-				getState: vi.fn().mockReturnValue("active"),
-				getSessionToken: vi.fn(),
-				hasActiveSession: vi.fn().mockReturnValue(false),
-				on: vi.fn(),
-			}
-
-			// Create a new instance to test initialization
-			const testService = new CloudSettingsService(
-				testContext,
-				testAuthService as unknown as AuthService,
-				mockLog,
-			)
-			testService.initialize()
-
-			expect(testContext.globalState.get).toHaveBeenCalledWith("organization-settings")
-			expect(testService.getSettings()).toEqual(cachedSettings)
-
-			testService.dispose()
-		})
-
-		it("should clear cached settings if user is logged out", async () => {
-			const cachedSettings = {
-				version: 1,
-				defaultSettings: {},
-				allowList: { allowAll: true, providers: {} },
-			}
-			mockContext.globalState.get = vi.fn().mockReturnValue(cachedSettings)
-			mockAuthService.getState.mockReturnValue("logged-out")
-
-			cloudSettingsService.initialize()
-
-			expect(mockContext.globalState.update).toHaveBeenCalledWith("organization-settings", undefined)
-		})
-
-		it("should set up auth service event listeners", () => {
-			cloudSettingsService.initialize()
-
-			expect(mockAuthService.on).toHaveBeenCalledWith("auth-state-changed", expect.any(Function))
-		})
-
-		it("should start timer if user has active session", () => {
-			mockAuthService.hasActiveSession.mockReturnValue(true)
-
-			cloudSettingsService.initialize()
-
-			expect(mockRefreshTimer.start).toHaveBeenCalled()
-		})
-
-		it("should not start timer if user has no active session", () => {
-			mockAuthService.hasActiveSession.mockReturnValue(false)
-
-			cloudSettingsService.initialize()
-
-			expect(mockRefreshTimer.start).not.toHaveBeenCalled()
-		})
-	})
-
-	describe("event emission", () => {
-		beforeEach(() => {
-			cloudSettingsService.initialize()
-		})
-
-		it("should emit 'settings-updated' event when settings change", async () => {
-			const eventSpy = vi.fn()
-			cloudSettingsService.on("settings-updated", eventSpy)
-
-			mockAuthService.getSessionToken.mockReturnValue("valid-token")
-			vi.mocked(fetch).mockResolvedValue({
-				ok: true,
-				json: vi.fn().mockResolvedValue(mockSettings),
-			} as unknown as Response)
-
-			// Get the callback function passed to RefreshTimer
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await timerCallback()
-
-			expect(eventSpy).toHaveBeenCalledWith({
-				settings: mockSettings,
-				previousSettings: undefined,
-			})
-		})
-
-		it("should emit event with previous settings when updating existing settings", async () => {
-			const eventSpy = vi.fn()
-
-			const previousSettings = {
-				version: 1,
-				defaultSettings: {},
-				allowList: { allowAll: true, providers: {} },
-			}
-			const newSettings = {
-				version: 2,
-				defaultSettings: {},
-				allowList: { allowAll: true, providers: {} },
-			}
-
-			// Create a fresh mock context for this test
-			const testContext = {
-				globalState: {
-					get: vi.fn().mockReturnValue(previousSettings),
-					update: vi.fn().mockResolvedValue(undefined),
-				},
-			} as unknown as vscode.ExtensionContext
-
-			// Mock auth service to not be logged out
-			const testAuthService = {
-				getState: vi.fn().mockReturnValue("active"),
-				getSessionToken: vi.fn().mockReturnValue("valid-token"),
-				hasActiveSession: vi.fn().mockReturnValue(false),
-				on: vi.fn(),
-			}
-
-			// Create a new service instance with cached settings
-			const testService = new CloudSettingsService(
-				testContext,
-				testAuthService as unknown as AuthService,
-				mockLog,
-			)
-			testService.on("settings-updated", eventSpy)
-			testService.initialize()
-
-			vi.mocked(fetch).mockResolvedValue({
-				ok: true,
-				json: vi.fn().mockResolvedValue(newSettings),
-			} as unknown as Response)
-
-			// Get the callback function passed to RefreshTimer for this instance
-			const timerCallback =
-				vi.mocked(RefreshTimer).mock.calls[vi.mocked(RefreshTimer).mock.calls.length - 1][0].callback
-			await timerCallback()
-
-			expect(eventSpy).toHaveBeenCalledWith({
-				settings: newSettings,
-				previousSettings,
-			})
-
-			testService.dispose()
-		})
-
-		it("should not emit event when settings version is unchanged", async () => {
-			const eventSpy = vi.fn()
-
-			// Create a fresh mock context for this test
-			const testContext = {
-				globalState: {
-					get: vi.fn().mockReturnValue(mockSettings),
-					update: vi.fn().mockResolvedValue(undefined),
-				},
-			} as unknown as vscode.ExtensionContext
-
-			// Mock auth service to not be logged out
-			const testAuthService = {
-				getState: vi.fn().mockReturnValue("active"),
-				getSessionToken: vi.fn().mockReturnValue("valid-token"),
-				hasActiveSession: vi.fn().mockReturnValue(false),
-				on: vi.fn(),
-			}
-
-			// Create a new service instance with cached settings
-			const testService = new CloudSettingsService(
-				testContext,
-				testAuthService as unknown as AuthService,
-				mockLog,
-			)
-			testService.on("settings-updated", eventSpy)
-			testService.initialize()
-
-			vi.mocked(fetch).mockResolvedValue({
-				ok: true,
-				json: vi.fn().mockResolvedValue(mockSettings), // Same version
-			} as unknown as Response)
-
-			// Get the callback function passed to RefreshTimer for this instance
-			const timerCallback =
-				vi.mocked(RefreshTimer).mock.calls[vi.mocked(RefreshTimer).mock.calls.length - 1][0].callback
-			await timerCallback()
-
-			expect(eventSpy).not.toHaveBeenCalled()
-
-			testService.dispose()
-		})
-
-		it("should not emit event when fetch fails", async () => {
-			const eventSpy = vi.fn()
-			cloudSettingsService.on("settings-updated", eventSpy)
-
-			mockAuthService.getSessionToken.mockReturnValue("valid-token")
-			vi.mocked(fetch).mockResolvedValue({
-				ok: false,
-				status: 500,
-				statusText: "Internal Server Error",
-			} as unknown as Response)
-
-			// Get the callback function passed to RefreshTimer
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await timerCallback()
-
-			expect(eventSpy).not.toHaveBeenCalled()
-		})
-
-		it("should not emit event when no auth token available", async () => {
-			const eventSpy = vi.fn()
-			cloudSettingsService.on("settings-updated", eventSpy)
-
-			mockAuthService.getSessionToken.mockReturnValue(null)
-
-			// Get the callback function passed to RefreshTimer
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await timerCallback()
-
-			expect(eventSpy).not.toHaveBeenCalled()
-			expect(fetch).not.toHaveBeenCalled()
-		})
-	})
-
-	describe("fetchSettings", () => {
-		beforeEach(() => {
-			cloudSettingsService.initialize()
-		})
-
-		it("should fetch and cache settings successfully", async () => {
-			mockAuthService.getSessionToken.mockReturnValue("valid-token")
-			vi.mocked(fetch).mockResolvedValue({
-				ok: true,
-				json: vi.fn().mockResolvedValue(mockSettings),
-			} as unknown as Response)
-
-			// Get the callback function passed to RefreshTimer
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			const result = await timerCallback()
-
-			expect(result).toBe(true)
-			expect(fetch).toHaveBeenCalledWith("https://app.roocode.com/api/organization-settings", {
-				headers: {
-					Authorization: "Bearer valid-token",
-				},
-			})
-			expect(mockContext.globalState.update).toHaveBeenCalledWith("organization-settings", mockSettings)
-		})
-
-		it("should handle fetch errors gracefully", async () => {
-			mockAuthService.getSessionToken.mockReturnValue("valid-token")
-			vi.mocked(fetch).mockRejectedValue(new Error("Network error"))
-
-			// Get the callback function passed to RefreshTimer
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			const result = await timerCallback()
-
-			expect(result).toBe(false)
-			expect(mockLog).toHaveBeenCalledWith(
-				"[cloud-settings] Error fetching organization settings:",
-				expect.any(Error),
-			)
-		})
-
-		it("should handle invalid response format", async () => {
-			mockAuthService.getSessionToken.mockReturnValue("valid-token")
-			vi.mocked(fetch).mockResolvedValue({
-				ok: true,
-				json: vi.fn().mockResolvedValue({ invalid: "data" }),
-			} as unknown as Response)
-
-			// Get the callback function passed to RefreshTimer
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			const result = await timerCallback()
-
-			expect(result).toBe(false)
-			expect(mockLog).toHaveBeenCalledWith(
-				"[cloud-settings] Invalid organization settings format:",
-				expect.any(Object),
-			)
-		})
-	})
-
-	describe("getAllowList", () => {
-		it("should return settings allowList when available", () => {
-			mockContext.globalState.get = vi.fn().mockReturnValue(mockSettings)
-			cloudSettingsService.initialize()
-
-			const allowList = cloudSettingsService.getAllowList()
-			expect(allowList).toEqual(mockSettings.allowList)
-		})
-
-		it("should return default allow all when no settings available", () => {
-			const allowList = cloudSettingsService.getAllowList()
-			expect(allowList).toEqual({ allowAll: true, providers: {} })
-		})
-	})
-
-	describe("getSettings", () => {
-		it("should return current settings", () => {
-			// Create a fresh mock context for this test
-			const testContext = {
-				globalState: {
-					get: vi.fn().mockReturnValue(mockSettings),
-					update: vi.fn().mockResolvedValue(undefined),
-				},
-			} as unknown as vscode.ExtensionContext
-
-			// Mock auth service to not be logged out
-			const testAuthService = {
-				getState: vi.fn().mockReturnValue("active"),
-				getSessionToken: vi.fn(),
-				hasActiveSession: vi.fn().mockReturnValue(false),
-				on: vi.fn(),
-			}
-
-			const testService = new CloudSettingsService(
-				testContext,
-				testAuthService as unknown as AuthService,
-				mockLog,
-			)
-			testService.initialize()
-
-			const settings = testService.getSettings()
-			expect(settings).toEqual(mockSettings)
-
-			testService.dispose()
-		})
-
-		it("should return undefined when no settings available", () => {
-			const settings = cloudSettingsService.getSettings()
-			expect(settings).toBeUndefined()
-		})
-	})
-
-	describe("dispose", () => {
-		it("should remove all listeners and stop timer", () => {
-			const removeAllListenersSpy = vi.spyOn(cloudSettingsService, "removeAllListeners")
-
-			cloudSettingsService.dispose()
-
-			expect(removeAllListenersSpy).toHaveBeenCalled()
-			expect(mockRefreshTimer.stop).toHaveBeenCalled()
-		})
-	})
-
-	describe("auth service event handlers", () => {
-		it("should start timer when auth-state-changed event is triggered with active-session", () => {
-			cloudSettingsService.initialize()
-
-			// Get the auth-state-changed handler
-			const authStateChangedHandler = mockAuthService.on.mock.calls.find(
-				(call) => call[0] === "auth-state-changed",
-			)?.[1]
-			expect(authStateChangedHandler).toBeDefined()
-
-			// Simulate active-session state change
-			authStateChangedHandler({ state: "active-session", previousState: "attempting-session" })
-			expect(mockRefreshTimer.start).toHaveBeenCalled()
-		})
-
-		it("should stop timer and remove settings when auth-state-changed event is triggered with logged-out", async () => {
-			cloudSettingsService.initialize()
-
-			// Get the auth-state-changed handler
-			const authStateChangedHandler = mockAuthService.on.mock.calls.find(
-				(call) => call[0] === "auth-state-changed",
-			)?.[1]
-			expect(authStateChangedHandler).toBeDefined()
-
-			// Simulate logged-out state change from active-session
-			await authStateChangedHandler({ state: "logged-out", previousState: "active-session" })
-			expect(mockRefreshTimer.stop).toHaveBeenCalled()
-			expect(mockContext.globalState.update).toHaveBeenCalledWith("organization-settings", undefined)
-		})
-	})
-})

+ 0 - 310
packages/cloud/src/__tests__/CloudShareService.test.ts

@@ -1,310 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
-import type { MockedFunction } from "vitest"
-import * as vscode from "vscode"
-
-import { CloudAPI } from "../CloudAPI"
-import { CloudShareService } from "../CloudShareService"
-import type { SettingsService } from "../SettingsService"
-import type { AuthService } from "../auth"
-import { CloudAPIError, TaskNotFoundError } from "../errors"
-
-// Mock fetch
-const mockFetch = vi.fn()
-global.fetch = mockFetch as any
-
-// Mock vscode
-vi.mock("vscode", () => ({
-	window: {
-		showInformationMessage: vi.fn(),
-		showErrorMessage: vi.fn(),
-		showQuickPick: vi.fn(),
-	},
-	env: {
-		clipboard: {
-			writeText: vi.fn(),
-		},
-		openExternal: vi.fn(),
-	},
-	Uri: {
-		parse: vi.fn(),
-	},
-	extensions: {
-		getExtension: vi.fn(() => ({
-			packageJSON: { version: "1.0.0" },
-		})),
-	},
-}))
-
-// Mock config
-vi.mock("../Config", () => ({
-	getRooCodeApiUrl: () => "https://app.roocode.com",
-}))
-
-// Mock utils
-vi.mock("../utils", () => ({
-	getUserAgent: () => "Roo-Code 1.0.0",
-}))
-
-describe("CloudShareService", () => {
-	let shareService: CloudShareService
-	let mockAuthService: AuthService
-	let mockSettingsService: SettingsService
-	let mockCloudAPI: CloudAPI
-	let mockLog: MockedFunction<(...args: unknown[]) => void>
-
-	beforeEach(() => {
-		vi.clearAllMocks()
-		mockFetch.mockClear()
-
-		mockLog = vi.fn()
-		mockAuthService = {
-			hasActiveSession: vi.fn(),
-			getSessionToken: vi.fn(),
-			isAuthenticated: vi.fn(),
-		} as any
-
-		mockSettingsService = {
-			getSettings: vi.fn(),
-		} as any
-
-		mockCloudAPI = new CloudAPI(mockAuthService, mockLog)
-		shareService = new CloudShareService(mockCloudAPI, mockSettingsService, mockLog)
-	})
-
-	describe("shareTask", () => {
-		it("should share task with organization visibility and copy to clipboard", async () => {
-			const mockResponseData = {
-				success: true,
-				shareUrl: "https://app.roocode.com/share/abc123",
-			}
-
-			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			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(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 mockResponseData = {
-				success: true,
-				shareUrl: "https://app.roocode.com/share/abc123",
-			}
-
-			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			mockFetch.mockResolvedValue({
-				ok: true,
-				json: vi.fn().mockResolvedValue(mockResponseData),
-			})
-
-			const result = await shareService.shareTask("task-123", "public")
-
-			expect(result.success).toBe(true)
-			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 mockResponseData = {
-				success: true,
-				shareUrl: "https://app.roocode.com/share/abc123",
-			}
-
-			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			mockFetch.mockResolvedValue({
-				ok: true,
-				json: vi.fn().mockResolvedValue(mockResponseData),
-			})
-
-			const result = await shareService.shareTask("task-123")
-
-			expect(result.success).toBe(true)
-			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 mockResponseData = {
-				success: false,
-				error: "Task not found",
-			}
-
-			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			mockFetch.mockResolvedValue({
-				ok: true,
-				json: vi.fn().mockResolvedValue(mockResponseData),
-			})
-
-			const result = await shareService.shareTask("task-123", "organization")
-
-			expect(result.success).toBe(false)
-			expect(result.error).toBe("Task not found")
-		})
-
-		it("should handle authentication errors", async () => {
-			;(mockAuthService.getSessionToken as any).mockReturnValue(null)
-
-			await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow("Authentication required")
-		})
-
-		it("should handle unexpected errors", async () => {
-			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			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",
-				json: vi.fn().mockRejectedValue(new Error("Invalid JSON")),
-				text: vi.fn().mockResolvedValue("Not Found"),
-			})
-
-			await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow(TaskNotFoundError)
-			await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow("Task 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",
-				json: vi.fn().mockRejectedValue(new Error("Invalid JSON")),
-				text: vi.fn().mockResolvedValue("Internal Server Error"),
-			})
-
-			await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow(CloudAPIError)
-			await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow(
-				"HTTP 500: Internal Server Error",
-			)
-		})
-
-		it("should create TaskNotFoundError with correct properties", async () => {
-			;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
-			mockFetch.mockResolvedValue({
-				ok: false,
-				status: 404,
-				statusText: "Not Found",
-				json: vi.fn().mockRejectedValue(new Error("Invalid JSON")),
-				text: vi.fn().mockResolvedValue("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 not found")
-			}
-		})
-	})
-
-	describe("canShareTask", () => {
-		it("should return true when authenticated and sharing is enabled", async () => {
-			;(mockAuthService.isAuthenticated as any).mockReturnValue(true)
-			;(mockSettingsService.getSettings as any).mockReturnValue({
-				cloudSettings: {
-					enableTaskSharing: true,
-				},
-			})
-
-			const result = await shareService.canShareTask()
-
-			expect(result).toBe(true)
-		})
-
-		it("should return false when authenticated but sharing is disabled", async () => {
-			;(mockAuthService.isAuthenticated as any).mockReturnValue(true)
-			;(mockSettingsService.getSettings as any).mockReturnValue({
-				cloudSettings: {
-					enableTaskSharing: false,
-				},
-			})
-
-			const result = await shareService.canShareTask()
-
-			expect(result).toBe(false)
-		})
-
-		it("should return false when authenticated and sharing setting is undefined (default)", async () => {
-			;(mockAuthService.isAuthenticated as any).mockReturnValue(true)
-			;(mockSettingsService.getSettings as any).mockReturnValue({
-				cloudSettings: {},
-			})
-
-			const result = await shareService.canShareTask()
-
-			expect(result).toBe(false)
-		})
-
-		it("should return false when authenticated and no settings available (default)", async () => {
-			;(mockAuthService.isAuthenticated as any).mockReturnValue(true)
-			;(mockSettingsService.getSettings as any).mockReturnValue(undefined)
-
-			const result = await shareService.canShareTask()
-
-			expect(result).toBe(false)
-		})
-
-		it("should return false when settings service returns undefined", async () => {
-			;(mockSettingsService.getSettings as any).mockReturnValue(undefined)
-
-			const result = await shareService.canShareTask()
-
-			expect(result).toBe(false)
-		})
-
-		it("should handle errors gracefully", async () => {
-			;(mockSettingsService.getSettings as any).mockImplementation(() => {
-				throw new Error("Settings error")
-			})
-
-			const result = await shareService.canShareTask()
-
-			expect(result).toBe(false)
-			expect(mockLog).toHaveBeenCalledWith(
-				"[ShareService] Error checking if task can be shared:",
-				expect.any(Error),
-			)
-		})
-	})
-})

+ 0 - 210
packages/cloud/src/__tests__/RefreshTimer.test.ts

@@ -1,210 +0,0 @@
-// npx vitest run src/__tests__/RefreshTimer.test.ts
-
-import type { Mock } from "vitest"
-
-import { RefreshTimer } from "../RefreshTimer"
-
-vi.useFakeTimers()
-
-describe("RefreshTimer", () => {
-	let mockCallback: Mock
-	let refreshTimer: RefreshTimer
-
-	beforeEach(() => {
-		mockCallback = vi.fn()
-		mockCallback.mockResolvedValue(true)
-	})
-
-	afterEach(() => {
-		if (refreshTimer) {
-			refreshTimer.stop()
-		}
-
-		vi.clearAllTimers()
-		vi.clearAllMocks()
-	})
-
-	it("should execute callback immediately when started", () => {
-		refreshTimer = new RefreshTimer({
-			callback: mockCallback,
-		})
-
-		refreshTimer.start()
-
-		expect(mockCallback).toHaveBeenCalledTimes(1)
-	})
-
-	it("should schedule next attempt after success interval when callback succeeds", async () => {
-		mockCallback.mockResolvedValue(true)
-
-		refreshTimer = new RefreshTimer({
-			callback: mockCallback,
-			successInterval: 50000, // 50 seconds
-		})
-
-		refreshTimer.start()
-
-		// Fast-forward to execute the first callback
-		await Promise.resolve()
-
-		expect(mockCallback).toHaveBeenCalledTimes(1)
-
-		// Fast-forward 50 seconds
-		vi.advanceTimersByTime(50000)
-
-		// Callback should be called again
-		expect(mockCallback).toHaveBeenCalledTimes(2)
-	})
-
-	it("should use exponential backoff when callback fails", async () => {
-		mockCallback.mockResolvedValue(false)
-
-		refreshTimer = new RefreshTimer({
-			callback: mockCallback,
-			initialBackoffMs: 1000, // 1 second
-		})
-
-		refreshTimer.start()
-
-		// Fast-forward to execute the first callback
-		await Promise.resolve()
-
-		expect(mockCallback).toHaveBeenCalledTimes(1)
-
-		// Fast-forward 1 second
-		vi.advanceTimersByTime(1000)
-
-		// Callback should be called again
-		expect(mockCallback).toHaveBeenCalledTimes(2)
-
-		// Fast-forward to execute the second callback
-		await Promise.resolve()
-
-		// Fast-forward 2 seconds
-		vi.advanceTimersByTime(2000)
-
-		// Callback should be called again
-		expect(mockCallback).toHaveBeenCalledTimes(3)
-
-		// Fast-forward to execute the third callback
-		await Promise.resolve()
-	})
-
-	it("should not exceed maximum backoff interval", async () => {
-		mockCallback.mockResolvedValue(false)
-
-		refreshTimer = new RefreshTimer({
-			callback: mockCallback,
-			initialBackoffMs: 1000, // 1 second
-			maxBackoffMs: 5000, // 5 seconds
-		})
-
-		refreshTimer.start()
-
-		// Fast-forward through multiple failures to reach max backoff
-		await Promise.resolve() // First attempt
-		vi.advanceTimersByTime(1000)
-
-		await Promise.resolve() // Second attempt (backoff = 2000ms)
-		vi.advanceTimersByTime(2000)
-
-		await Promise.resolve() // Third attempt (backoff = 4000ms)
-		vi.advanceTimersByTime(4000)
-
-		await Promise.resolve() // Fourth attempt (backoff would be 8000ms but max is 5000ms)
-
-		// Should be capped at maxBackoffMs (no way to verify without logger)
-	})
-
-	it("should reset backoff after a successful attempt", async () => {
-		// First call fails, second succeeds, third fails
-		mockCallback.mockResolvedValueOnce(false).mockResolvedValueOnce(true).mockResolvedValueOnce(false)
-
-		refreshTimer = new RefreshTimer({
-			callback: mockCallback,
-			initialBackoffMs: 1000,
-			successInterval: 5000,
-		})
-
-		refreshTimer.start()
-
-		// First attempt (fails)
-		await Promise.resolve()
-
-		// Fast-forward 1 second
-		vi.advanceTimersByTime(1000)
-
-		// Second attempt (succeeds)
-		await Promise.resolve()
-
-		// Fast-forward 5 seconds
-		vi.advanceTimersByTime(5000)
-
-		// Third attempt (fails)
-		await Promise.resolve()
-
-		// Backoff should be reset to initial value (no way to verify without logger)
-	})
-
-	it("should handle errors in callback as failures", async () => {
-		mockCallback.mockRejectedValue(new Error("Test error"))
-
-		refreshTimer = new RefreshTimer({
-			callback: mockCallback,
-			initialBackoffMs: 1000,
-		})
-
-		refreshTimer.start()
-
-		// Fast-forward to execute the callback
-		await Promise.resolve()
-
-		// Error should be treated as a failure (no way to verify without logger)
-	})
-
-	it("should stop the timer and cancel pending executions", () => {
-		refreshTimer = new RefreshTimer({
-			callback: mockCallback,
-		})
-
-		refreshTimer.start()
-
-		// Stop the timer
-		refreshTimer.stop()
-
-		// Fast-forward a long time
-		vi.advanceTimersByTime(1000000)
-
-		// Callback should only have been called once (the initial call)
-		expect(mockCallback).toHaveBeenCalledTimes(1)
-	})
-
-	it("should reset the backoff state", async () => {
-		mockCallback.mockResolvedValue(false)
-
-		refreshTimer = new RefreshTimer({
-			callback: mockCallback,
-			initialBackoffMs: 1000,
-		})
-
-		refreshTimer.start()
-
-		// Fast-forward through a few failures
-		await Promise.resolve()
-		vi.advanceTimersByTime(1000)
-
-		await Promise.resolve()
-		vi.advanceTimersByTime(2000)
-
-		// Reset the timer
-		refreshTimer.reset()
-
-		// Stop and restart to trigger a new execution
-		refreshTimer.stop()
-		refreshTimer.start()
-
-		await Promise.resolve()
-
-		// Backoff should be back to initial value (no way to verify without logger)
-	})
-})

+ 0 - 102
packages/cloud/src/__tests__/StaticSettingsService.test.ts

@@ -1,102 +0,0 @@
-// npx vitest run src/__tests__/StaticSettingsService.test.ts
-
-import { StaticSettingsService } from "../StaticSettingsService"
-
-describe("StaticSettingsService", () => {
-	const validSettings = {
-		version: 1,
-		cloudSettings: {
-			recordTaskMessages: true,
-			enableTaskSharing: true,
-			taskShareExpirationDays: 30,
-		},
-		defaultSettings: {
-			enableCheckpoints: true,
-			maxOpenTabsContext: 10,
-		},
-		allowList: {
-			allowAll: false,
-			providers: {
-				anthropic: {
-					allowAll: true,
-				},
-			},
-		},
-	}
-
-	const validBase64 = Buffer.from(JSON.stringify(validSettings)).toString("base64")
-
-	describe("constructor", () => {
-		it("should parse valid base64 encoded JSON settings", () => {
-			const service = new StaticSettingsService(validBase64)
-			expect(service.getSettings()).toEqual(validSettings)
-		})
-
-		it("should throw error for invalid base64", () => {
-			expect(() => new StaticSettingsService("invalid-base64!@#")).toThrow("Failed to parse static settings")
-		})
-
-		it("should throw error for invalid JSON", () => {
-			const invalidJson = Buffer.from("{ invalid json }").toString("base64")
-			expect(() => new StaticSettingsService(invalidJson)).toThrow("Failed to parse static settings")
-		})
-
-		it("should throw error for invalid schema", () => {
-			const invalidSettings = { invalid: "schema" }
-			const invalidBase64 = Buffer.from(JSON.stringify(invalidSettings)).toString("base64")
-			expect(() => new StaticSettingsService(invalidBase64)).toThrow("Failed to parse static settings")
-		})
-	})
-
-	describe("getAllowList", () => {
-		it("should return the allow list from settings", () => {
-			const service = new StaticSettingsService(validBase64)
-			expect(service.getAllowList()).toEqual(validSettings.allowList)
-		})
-	})
-
-	describe("getSettings", () => {
-		it("should return the parsed settings", () => {
-			const service = new StaticSettingsService(validBase64)
-			expect(service.getSettings()).toEqual(validSettings)
-		})
-	})
-
-	describe("dispose", () => {
-		it("should be a no-op for static settings", () => {
-			const service = new StaticSettingsService(validBase64)
-			expect(() => service.dispose()).not.toThrow()
-		})
-	})
-
-	describe("logging", () => {
-		it("should use provided logger for errors", () => {
-			const mockLog = vi.fn()
-			expect(() => new StaticSettingsService("invalid-base64!@#", mockLog)).toThrow()
-
-			expect(mockLog).toHaveBeenCalledWith(
-				expect.stringContaining("[StaticSettingsService] failed to parse static settings:"),
-				expect.any(Error),
-			)
-		})
-
-		it("should use console.log as default logger for errors", () => {
-			const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {})
-			expect(() => new StaticSettingsService("invalid-base64!@#")).toThrow()
-
-			expect(consoleSpy).toHaveBeenCalledWith(
-				expect.stringContaining("[StaticSettingsService] failed to parse static settings:"),
-				expect.any(Error),
-			)
-
-			consoleSpy.mockRestore()
-		})
-
-		it("should not log anything for successful parsing", () => {
-			const mockLog = vi.fn()
-			new StaticSettingsService(validBase64, mockLog)
-
-			expect(mockLog).not.toHaveBeenCalled()
-		})
-	})
-})

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

@@ -1,738 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
-// npx vitest run src/__tests__/TelemetryClient.test.ts
-
-import { type TelemetryPropertiesProvider, TelemetryEventName } from "@roo-code/types"
-
-import { TelemetryClient } from "../TelemetryClient"
-
-const mockFetch = vi.fn()
-global.fetch = mockFetch as any
-
-describe("TelemetryClient", () => {
-	const getPrivateProperty = <T>(instance: any, propertyName: string): T => {
-		return instance[propertyName]
-	}
-
-	let mockAuthService: any
-	let mockSettingsService: any
-
-	beforeEach(() => {
-		vi.clearAllMocks()
-
-		// Create a mock AuthService instead of using the singleton
-		mockAuthService = {
-			getSessionToken: vi.fn().mockReturnValue("mock-token"),
-			getState: vi.fn().mockReturnValue("active-session"),
-			isAuthenticated: vi.fn().mockReturnValue(true),
-			hasActiveSession: vi.fn().mockReturnValue(true),
-		}
-
-		// Create a mock SettingsService
-		mockSettingsService = {
-			getSettings: vi.fn().mockReturnValue({
-				cloudSettings: {
-					recordTaskMessages: true,
-				},
-			}),
-		}
-
-		mockFetch.mockResolvedValue({
-			ok: true,
-			json: vi.fn().mockResolvedValue({}),
-		})
-
-		vi.spyOn(console, "info").mockImplementation(() => {})
-		vi.spyOn(console, "error").mockImplementation(() => {})
-	})
-
-	afterEach(() => {
-		vi.restoreAllMocks()
-	})
-
-	describe("isEventCapturable", () => {
-		it("should return true for events not in exclude list", () => {
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
-				client,
-				"isEventCapturable",
-			).bind(client)
-
-			expect(isEventCapturable(TelemetryEventName.TASK_CREATED)).toBe(true)
-			expect(isEventCapturable(TelemetryEventName.LLM_COMPLETION)).toBe(true)
-			expect(isEventCapturable(TelemetryEventName.MODE_SWITCH)).toBe(true)
-			expect(isEventCapturable(TelemetryEventName.TOOL_USED)).toBe(true)
-		})
-
-		it("should return false for events in exclude list", () => {
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
-				client,
-				"isEventCapturable",
-			).bind(client)
-
-			expect(isEventCapturable(TelemetryEventName.TASK_CONVERSATION_MESSAGE)).toBe(false)
-		})
-
-		it("should return true for TASK_MESSAGE events when recordTaskMessages is true", () => {
-			mockSettingsService.getSettings.mockReturnValue({
-				cloudSettings: {
-					recordTaskMessages: true,
-				},
-			})
-
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
-				client,
-				"isEventCapturable",
-			).bind(client)
-
-			expect(isEventCapturable(TelemetryEventName.TASK_MESSAGE)).toBe(true)
-		})
-
-		it("should return false for TASK_MESSAGE events when recordTaskMessages is false", () => {
-			mockSettingsService.getSettings.mockReturnValue({
-				cloudSettings: {
-					recordTaskMessages: false,
-				},
-			})
-
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
-				client,
-				"isEventCapturable",
-			).bind(client)
-
-			expect(isEventCapturable(TelemetryEventName.TASK_MESSAGE)).toBe(false)
-		})
-
-		it("should return false for TASK_MESSAGE events when recordTaskMessages is undefined", () => {
-			mockSettingsService.getSettings.mockReturnValue({
-				cloudSettings: {},
-			})
-
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
-				client,
-				"isEventCapturable",
-			).bind(client)
-
-			expect(isEventCapturable(TelemetryEventName.TASK_MESSAGE)).toBe(false)
-		})
-
-		it("should return false for TASK_MESSAGE events when cloudSettings is undefined", () => {
-			mockSettingsService.getSettings.mockReturnValue({})
-
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
-				client,
-				"isEventCapturable",
-			).bind(client)
-
-			expect(isEventCapturable(TelemetryEventName.TASK_MESSAGE)).toBe(false)
-		})
-
-		it("should return false for TASK_MESSAGE events when getSettings returns undefined", () => {
-			mockSettingsService.getSettings.mockReturnValue(undefined)
-
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
-				client,
-				"isEventCapturable",
-			).bind(client)
-
-			expect(isEventCapturable(TelemetryEventName.TASK_MESSAGE)).toBe(false)
-		})
-	})
-
-	describe("getEventProperties", () => {
-		it("should merge provider properties with event properties", async () => {
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			const mockProvider: TelemetryPropertiesProvider = {
-				getTelemetryProperties: vi.fn().mockResolvedValue({
-					appVersion: "1.0.0",
-					vscodeVersion: "1.60.0",
-					platform: "darwin",
-					editorName: "vscode",
-					language: "en",
-					mode: "code",
-				}),
-			}
-
-			client.setProvider(mockProvider)
-
-			const getEventProperties = getPrivateProperty<
-				(event: { event: TelemetryEventName; properties?: Record<string, any> }) => Promise<Record<string, any>>
-			>(client, "getEventProperties").bind(client)
-
-			const result = await getEventProperties({
-				event: TelemetryEventName.TASK_CREATED,
-				properties: {
-					customProp: "value",
-					mode: "override", // This should override the provider's mode.
-				},
-			})
-
-			expect(result).toEqual({
-				appVersion: "1.0.0",
-				vscodeVersion: "1.60.0",
-				platform: "darwin",
-				editorName: "vscode",
-				language: "en",
-				mode: "override", // Event property takes precedence.
-				customProp: "value",
-			})
-
-			expect(mockProvider.getTelemetryProperties).toHaveBeenCalledTimes(1)
-		})
-
-		it("should handle errors from provider gracefully", async () => {
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			const mockProvider: TelemetryPropertiesProvider = {
-				getTelemetryProperties: vi.fn().mockRejectedValue(new Error("Provider error")),
-			}
-
-			const consoleErrorSpy = vi.spyOn(console, "error")
-
-			client.setProvider(mockProvider)
-
-			const getEventProperties = getPrivateProperty<
-				(event: { event: TelemetryEventName; properties?: Record<string, any> }) => Promise<Record<string, any>>
-			>(client, "getEventProperties").bind(client)
-
-			const result = await getEventProperties({
-				event: TelemetryEventName.TASK_CREATED,
-				properties: { customProp: "value" },
-			})
-
-			expect(result).toEqual({ customProp: "value" })
-			expect(consoleErrorSpy).toHaveBeenCalledWith(
-				expect.stringContaining("Error getting telemetry properties: Provider error"),
-			)
-		})
-
-		it("should return event properties when no provider is set", async () => {
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			const getEventProperties = getPrivateProperty<
-				(event: { event: TelemetryEventName; properties?: Record<string, any> }) => Promise<Record<string, any>>
-			>(client, "getEventProperties").bind(client)
-
-			const result = await getEventProperties({
-				event: TelemetryEventName.TASK_CREATED,
-				properties: { customProp: "value" },
-			})
-
-			expect(result).toEqual({ customProp: "value" })
-		})
-	})
-
-	describe("capture", () => {
-		it("should not capture events that are not capturable", async () => {
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			await client.capture({
-				event: TelemetryEventName.TASK_CONVERSATION_MESSAGE, // In exclude list.
-				properties: { test: "value" },
-			})
-
-			expect(mockFetch).not.toHaveBeenCalled()
-		})
-
-		it("should not capture TASK_MESSAGE events when recordTaskMessages is false", async () => {
-			mockSettingsService.getSettings.mockReturnValue({
-				cloudSettings: {
-					recordTaskMessages: false,
-				},
-			})
-
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			await client.capture({
-				event: TelemetryEventName.TASK_MESSAGE,
-				properties: {
-					taskId: "test-task-id",
-					message: {
-						ts: 1,
-						type: "say",
-						say: "text",
-						text: "test message",
-					},
-				},
-			})
-
-			expect(mockFetch).not.toHaveBeenCalled()
-		})
-
-		it("should not capture TASK_MESSAGE events when recordTaskMessages is undefined", async () => {
-			mockSettingsService.getSettings.mockReturnValue({
-				cloudSettings: {},
-			})
-
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			await client.capture({
-				event: TelemetryEventName.TASK_MESSAGE,
-				properties: {
-					taskId: "test-task-id",
-					message: {
-						ts: 1,
-						type: "say",
-						say: "text",
-						text: "test message",
-					},
-				},
-			})
-
-			expect(mockFetch).not.toHaveBeenCalled()
-		})
-
-		it("should not send request when schema validation fails", async () => {
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			await client.capture({
-				event: TelemetryEventName.TASK_CREATED,
-				properties: { test: "value" },
-			})
-
-			expect(mockFetch).not.toHaveBeenCalled()
-			expect(console.error).toHaveBeenCalledWith(expect.stringContaining("Invalid telemetry event"))
-		})
-
-		it("should send request when event is capturable and validation passes", 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 eventProperties = {
-				taskId: "test-task-id",
-			}
-
-			const mockValidatedData = {
-				type: TelemetryEventName.TASK_CREATED,
-				properties: {
-					...providerProperties,
-					taskId: "test-task-id",
-				},
-			}
-
-			const mockProvider: TelemetryPropertiesProvider = {
-				getTelemetryProperties: vi.fn().mockResolvedValue(providerProperties),
-			}
-
-			client.setProvider(mockProvider)
-
-			await client.capture({
-				event: TelemetryEventName.TASK_CREATED,
-				properties: eventProperties,
-			})
-
-			expect(mockFetch).toHaveBeenCalledWith(
-				"https://app.roocode.com/api/events",
-				expect.objectContaining({
-					method: "POST",
-					body: JSON.stringify(mockValidatedData),
-				}),
-			)
-		})
-
-		it("should attempt to capture TASK_MESSAGE events when recordTaskMessages is true", async () => {
-			mockSettingsService.getSettings.mockReturnValue({
-				cloudSettings: {
-					recordTaskMessages: true,
-				},
-			})
-
-			const eventProperties = {
-				appName: "roo-code",
-				appVersion: "1.0.0",
-				vscodeVersion: "1.60.0",
-				platform: "darwin",
-				editorName: "vscode",
-				language: "en",
-				mode: "code",
-				taskId: "test-task-id",
-				message: {
-					ts: 1,
-					type: "say",
-					say: "text",
-					text: "test message",
-				},
-			}
-
-			const mockValidatedData = {
-				type: TelemetryEventName.TASK_MESSAGE,
-				properties: eventProperties,
-			}
-
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			await client.capture({
-				event: TelemetryEventName.TASK_MESSAGE,
-				properties: eventProperties,
-			})
-
-			expect(mockFetch).toHaveBeenCalledWith(
-				"https://app.roocode.com/api/events",
-				expect.objectContaining({
-					method: "POST",
-					body: JSON.stringify(mockValidatedData),
-				}),
-			)
-		})
-
-		it("should handle fetch errors gracefully", async () => {
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
-			mockFetch.mockRejectedValue(new Error("Network error"))
-
-			await expect(
-				client.capture({
-					event: TelemetryEventName.TASK_CREATED,
-					properties: { test: "value" },
-				}),
-			).resolves.not.toThrow()
-		})
-	})
-
-	describe("telemetry state methods", () => {
-		it("should always return true for isTelemetryEnabled", () => {
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-			expect(client.isTelemetryEnabled()).toBe(true)
-		})
-
-		it("should have empty implementations for updateTelemetryState and shutdown", async () => {
-			const client = new TelemetryClient(mockAuthService, mockSettingsService)
-			client.updateTelemetryState(true)
-			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("[]")
-		})
-	})
-})

+ 0 - 174
packages/cloud/src/__tests__/auth/StaticTokenAuthService.spec.ts

@@ -1,174 +0,0 @@
-import { describe, it, expect, beforeEach, vi } from "vitest"
-import * as vscode from "vscode"
-
-import { StaticTokenAuthService } from "../../auth/StaticTokenAuthService"
-
-// Mock vscode
-vi.mock("vscode", () => ({
-	window: {
-		showInformationMessage: vi.fn(),
-	},
-	env: {
-		openExternal: vi.fn(),
-		uriScheme: "vscode",
-	},
-	Uri: {
-		parse: vi.fn(),
-	},
-}))
-
-describe("StaticTokenAuthService", () => {
-	let authService: StaticTokenAuthService
-	let mockContext: vscode.ExtensionContext
-	let mockLog: (...args: unknown[]) => void
-	const testToken = "test-static-token"
-
-	beforeEach(() => {
-		mockLog = vi.fn()
-
-		// Create a minimal mock that satisfies the constructor requirements
-		const mockContextPartial = {
-			extension: {
-				packageJSON: {
-					publisher: "TestPublisher",
-					name: "test-extension",
-				},
-			},
-			globalState: {
-				get: vi.fn(),
-				update: vi.fn(),
-			},
-			secrets: {
-				get: vi.fn(),
-				store: vi.fn(),
-				delete: vi.fn(),
-				onDidChange: vi.fn(),
-			},
-			subscriptions: [],
-		}
-
-		// Use type assertion for test mocking
-		mockContext = mockContextPartial as unknown as vscode.ExtensionContext
-
-		authService = new StaticTokenAuthService(mockContext, testToken, mockLog)
-	})
-
-	afterEach(() => {
-		vi.clearAllMocks()
-	})
-
-	describe("constructor", () => {
-		it("should create instance and log static token mode", () => {
-			expect(authService).toBeInstanceOf(StaticTokenAuthService)
-			expect(mockLog).toHaveBeenCalledWith("[auth] Using static token authentication mode")
-		})
-
-		it("should use console.log as default logger", () => {
-			const serviceWithoutLog = new StaticTokenAuthService(
-				mockContext as unknown as vscode.ExtensionContext,
-				testToken,
-			)
-			// Can't directly test console.log usage, but constructor should not throw
-			expect(serviceWithoutLog).toBeInstanceOf(StaticTokenAuthService)
-		})
-	})
-
-	describe("initialize", () => {
-		it("should start in active-session state", async () => {
-			await authService.initialize()
-			expect(authService.getState()).toBe("active-session")
-		})
-
-		it("should emit auth-state-changed event on initialize", async () => {
-			const spy = vi.fn()
-			authService.on("auth-state-changed", spy)
-
-			await authService.initialize()
-
-			expect(spy).toHaveBeenCalledWith({ state: "active-session", previousState: "initializing" })
-		})
-
-		it("should log successful initialization", async () => {
-			await authService.initialize()
-			expect(mockLog).toHaveBeenCalledWith("[auth] Static token auth service initialized in active-session state")
-		})
-	})
-
-	describe("getSessionToken", () => {
-		it("should return the provided token", () => {
-			expect(authService.getSessionToken()).toBe(testToken)
-		})
-
-		it("should return different token when constructed with different token", () => {
-			const differentToken = "different-token"
-			const differentService = new StaticTokenAuthService(mockContext, differentToken, mockLog)
-			expect(differentService.getSessionToken()).toBe(differentToken)
-		})
-	})
-
-	describe("getUserInfo", () => {
-		it("should return empty object", () => {
-			expect(authService.getUserInfo()).toEqual({})
-		})
-	})
-
-	describe("getStoredOrganizationId", () => {
-		it("should return null", () => {
-			expect(authService.getStoredOrganizationId()).toBeNull()
-		})
-	})
-
-	describe("authentication state methods", () => {
-		it("should always return true for isAuthenticated", () => {
-			expect(authService.isAuthenticated()).toBe(true)
-		})
-
-		it("should always return true for hasActiveSession", () => {
-			expect(authService.hasActiveSession()).toBe(true)
-		})
-
-		it("should always return true for hasOrIsAcquiringActiveSession", () => {
-			expect(authService.hasOrIsAcquiringActiveSession()).toBe(true)
-		})
-
-		it("should return active-session for getState", () => {
-			expect(authService.getState()).toBe("active-session")
-		})
-	})
-
-	describe("disabled authentication methods", () => {
-		const expectedErrorMessage = "Authentication methods are disabled in StaticTokenAuthService"
-
-		it("should throw error for login", async () => {
-			await expect(authService.login()).rejects.toThrow(expectedErrorMessage)
-		})
-
-		it("should throw error for logout", async () => {
-			await expect(authService.logout()).rejects.toThrow(expectedErrorMessage)
-		})
-
-		it("should throw error for handleCallback", async () => {
-			await expect(authService.handleCallback("code", "state")).rejects.toThrow(expectedErrorMessage)
-		})
-
-		it("should throw error for handleCallback with organization", async () => {
-			await expect(authService.handleCallback("code", "state", "org_123")).rejects.toThrow(expectedErrorMessage)
-		})
-	})
-
-	describe("event emission", () => {
-		it("should be able to register and emit events", async () => {
-			const authStateChangedSpy = vi.fn()
-			const userInfoSpy = vi.fn()
-
-			authService.on("auth-state-changed", authStateChangedSpy)
-			authService.on("user-info", userInfoSpy)
-
-			await authService.initialize()
-
-			expect(authStateChangedSpy).toHaveBeenCalledWith({ state: "active-session", previousState: "initializing" })
-			// user-info event is not emitted in static token mode
-			expect(userInfoSpy).not.toHaveBeenCalled()
-		})
-	})
-})

+ 0 - 1113
packages/cloud/src/__tests__/auth/WebAuthService.spec.ts

@@ -1,1113 +0,0 @@
-// npx vitest run src/__tests__/auth/WebAuthService.spec.ts
-
-import { type Mock } from "vitest"
-import crypto from "crypto"
-import * as vscode from "vscode"
-
-import { WebAuthService } from "../../auth/WebAuthService"
-import { RefreshTimer } from "../../RefreshTimer"
-import { getClerkBaseUrl, getRooCodeApiUrl } from "../../config"
-import { getUserAgent } from "../../utils"
-
-// Mock external dependencies
-vi.mock("../../RefreshTimer")
-vi.mock("../../config")
-vi.mock("../../utils")
-vi.mock("crypto")
-
-// Mock fetch globally
-const mockFetch = vi.fn()
-global.fetch = mockFetch
-
-// Mock vscode module
-vi.mock("vscode", () => ({
-	window: {
-		showInformationMessage: vi.fn(),
-		showErrorMessage: vi.fn(),
-	},
-	env: {
-		openExternal: vi.fn(),
-		uriScheme: "vscode",
-	},
-	Uri: {
-		parse: vi.fn((uri: string) => ({ toString: () => uri })),
-	},
-}))
-
-describe("WebAuthService", () => {
-	let authService: WebAuthService
-	let mockTimer: {
-		start: Mock
-		stop: Mock
-		reset: Mock
-	}
-	let mockLog: Mock
-	let mockContext: {
-		subscriptions: { push: Mock }
-		secrets: {
-			get: Mock
-			store: Mock
-			delete: Mock
-			onDidChange: Mock
-		}
-		globalState: {
-			get: Mock
-			update: Mock
-		}
-		extension: {
-			packageJSON: {
-				version: string
-				publisher: string
-				name: string
-			}
-		}
-	}
-
-	beforeEach(() => {
-		// Reset all mocks
-		vi.clearAllMocks()
-
-		// Setup mock context with proper subscriptions array
-		mockContext = {
-			subscriptions: {
-				push: vi.fn(),
-			},
-			secrets: {
-				get: vi.fn().mockResolvedValue(undefined),
-				store: vi.fn().mockResolvedValue(undefined),
-				delete: vi.fn().mockResolvedValue(undefined),
-				onDidChange: vi.fn().mockReturnValue({ dispose: vi.fn() }),
-			},
-			globalState: {
-				get: vi.fn().mockReturnValue(undefined),
-				update: vi.fn().mockResolvedValue(undefined),
-			},
-			extension: {
-				packageJSON: {
-					version: "1.0.0",
-					publisher: "RooVeterinaryInc",
-					name: "roo-cline",
-				},
-			},
-		}
-
-		// Setup timer mock
-		mockTimer = {
-			start: vi.fn(),
-			stop: vi.fn(),
-			reset: vi.fn(),
-		}
-		const MockedRefreshTimer = vi.mocked(RefreshTimer)
-		MockedRefreshTimer.mockImplementation(() => mockTimer as unknown as RefreshTimer)
-
-		// Setup config mocks - use production URL by default to maintain existing test behavior
-		vi.mocked(getClerkBaseUrl).mockReturnValue("https://clerk.roocode.com")
-		vi.mocked(getRooCodeApiUrl).mockReturnValue("https://api.test.com")
-
-		// Setup utils mock
-		vi.mocked(getUserAgent).mockReturnValue("Roo-Code 1.0.0")
-
-		// Setup crypto mock
-		vi.mocked(crypto.randomBytes).mockReturnValue(Buffer.from("test-random-bytes") as never)
-
-		// Setup log mock
-		mockLog = vi.fn()
-
-		authService = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
-	})
-
-	afterEach(() => {
-		vi.clearAllMocks()
-	})
-
-	describe("constructor", () => {
-		it("should initialize with correct default values", () => {
-			expect(authService.getState()).toBe("initializing")
-			expect(authService.isAuthenticated()).toBe(false)
-			expect(authService.hasActiveSession()).toBe(false)
-			expect(authService.getSessionToken()).toBeUndefined()
-			expect(authService.getUserInfo()).toBeNull()
-		})
-
-		it("should create RefreshTimer with correct configuration", () => {
-			expect(RefreshTimer).toHaveBeenCalledWith({
-				callback: expect.any(Function),
-				successInterval: 50_000,
-				initialBackoffMs: 1_000,
-				maxBackoffMs: 300_000,
-			})
-		})
-
-		it("should use console.log as default logger", () => {
-			const serviceWithoutLog = new WebAuthService(mockContext as unknown as vscode.ExtensionContext)
-			// Can't directly test console.log usage, but constructor should not throw
-			expect(serviceWithoutLog).toBeInstanceOf(WebAuthService)
-		})
-	})
-
-	describe("initialize", () => {
-		it("should handle credentials change and setup event listener", async () => {
-			await authService.initialize()
-
-			expect(mockContext.subscriptions.push).toHaveBeenCalled()
-			expect(mockContext.secrets.onDidChange).toHaveBeenCalled()
-		})
-
-		it("should not initialize twice", async () => {
-			await authService.initialize()
-			const firstCallCount = vi.mocked(mockContext.secrets.onDidChange).mock.calls.length
-
-			await authService.initialize()
-			expect(mockContext.secrets.onDidChange).toHaveBeenCalledTimes(firstCallCount)
-			expect(mockLog).toHaveBeenCalledWith("[auth] initialize() called after already initialized")
-		})
-
-		it("should transition to logged-out when no credentials exist", async () => {
-			mockContext.secrets.get.mockResolvedValue(undefined)
-
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			await authService.initialize()
-
-			expect(authService.getState()).toBe("logged-out")
-			expect(authStateChangedSpy).toHaveBeenCalledWith({ state: "logged-out", previousState: "initializing" })
-		})
-
-		it("should transition to attempting-session when valid credentials exist", async () => {
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			await authService.initialize()
-
-			expect(authService.getState()).toBe("attempting-session")
-			expect(authStateChangedSpy).toHaveBeenCalledWith({
-				state: "attempting-session",
-				previousState: "initializing",
-			})
-			expect(mockTimer.start).toHaveBeenCalled()
-		})
-
-		it("should handle invalid credentials gracefully", async () => {
-			mockContext.secrets.get.mockResolvedValue("invalid-json")
-
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			await authService.initialize()
-
-			expect(authService.getState()).toBe("logged-out")
-			expect(mockLog).toHaveBeenCalledWith("[auth] Failed to parse stored credentials:", expect.any(Error))
-		})
-
-		it("should handle credentials change events", async () => {
-			let onDidChangeCallback: (e: { key: string }) => void
-
-			mockContext.secrets.onDidChange.mockImplementation((callback: (e: { key: string }) => void) => {
-				onDidChangeCallback = callback
-				return { dispose: vi.fn() }
-			})
-
-			await authService.initialize()
-
-			// Simulate credentials change event
-			const newCredentials = { clientToken: "new-token", sessionId: "new-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(newCredentials))
-
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			onDidChangeCallback!({ key: "clerk-auth-credentials" })
-			await new Promise((resolve) => setTimeout(resolve, 0)) // Wait for async handling
-
-			expect(authStateChangedSpy).toHaveBeenCalled()
-		})
-	})
-
-	describe("login", () => {
-		beforeEach(async () => {
-			await authService.initialize()
-		})
-
-		it("should generate state and open external URL", async () => {
-			const mockOpenExternal = vi.fn()
-			const vscode = await import("vscode")
-			vi.mocked(vscode.env.openExternal).mockImplementation(mockOpenExternal)
-
-			await authService.login()
-
-			expect(crypto.randomBytes).toHaveBeenCalledWith(16)
-			expect(mockContext.globalState.update).toHaveBeenCalledWith(
-				"clerk-auth-state",
-				"746573742d72616e646f6d2d6279746573",
-			)
-			expect(mockOpenExternal).toHaveBeenCalledWith(
-				expect.objectContaining({
-					toString: expect.any(Function),
-				}),
-			)
-		})
-
-		it("should use package.json values for redirect URI", async () => {
-			const mockOpenExternal = vi.fn()
-			const vscode = await import("vscode")
-			vi.mocked(vscode.env.openExternal).mockImplementation(mockOpenExternal)
-
-			await authService.login()
-
-			const expectedUrl =
-				"https://api.test.com/extension/sign-in?state=746573742d72616e646f6d2d6279746573&auth_redirect=vscode%3A%2F%2FRooVeterinaryInc.roo-cline"
-			expect(mockOpenExternal).toHaveBeenCalledWith(
-				expect.objectContaining({
-					toString: expect.any(Function),
-				}),
-			)
-
-			// Verify the actual URL
-			const calledUri = mockOpenExternal.mock.calls[0][0]
-			expect(calledUri.toString()).toBe(expectedUrl)
-		})
-
-		it("should handle errors during login", async () => {
-			vi.mocked(crypto.randomBytes).mockImplementation(() => {
-				throw new Error("Crypto error")
-			})
-
-			await expect(authService.login()).rejects.toThrow("Failed to initiate Roo Code Cloud authentication")
-			expect(mockLog).toHaveBeenCalledWith("[auth] Error initiating Roo Code Cloud auth: Error: Crypto error")
-		})
-	})
-
-	describe("handleCallback", () => {
-		beforeEach(async () => {
-			await authService.initialize()
-		})
-
-		it("should handle invalid parameters", async () => {
-			const vscode = await import("vscode")
-			const mockShowInfo = vi.fn()
-			vi.mocked(vscode.window.showInformationMessage).mockImplementation(mockShowInfo)
-
-			await authService.handleCallback(null, "state")
-			expect(mockShowInfo).toHaveBeenCalledWith("Invalid Roo Code Cloud sign in url")
-
-			await authService.handleCallback("code", null)
-			expect(mockShowInfo).toHaveBeenCalledWith("Invalid Roo Code Cloud sign in url")
-		})
-
-		it("should validate state parameter", async () => {
-			mockContext.globalState.get.mockReturnValue("stored-state")
-
-			await expect(authService.handleCallback("code", "different-state")).rejects.toThrow(
-				"Failed to handle Roo Code Cloud callback",
-			)
-			expect(mockLog).toHaveBeenCalledWith("[auth] State mismatch in callback")
-		})
-
-		it("should successfully handle valid callback", async () => {
-			const storedState = "valid-state"
-			mockContext.globalState.get.mockReturnValue(storedState)
-
-			// Mock successful Clerk sign-in response
-			const mockResponse = {
-				ok: true,
-				json: () =>
-					Promise.resolve({
-						response: { created_session_id: "session-123" },
-					}),
-				headers: {
-					get: (header: string) => (header === "authorization" ? "Bearer token-123" : null),
-				},
-			}
-			mockFetch.mockResolvedValue(mockResponse)
-
-			const vscode = await import("vscode")
-			const mockShowInfo = vi.fn()
-			vi.mocked(vscode.window.showInformationMessage).mockImplementation(mockShowInfo)
-
-			await authService.handleCallback("auth-code", storedState)
-
-			expect(mockContext.secrets.store).toHaveBeenCalledWith(
-				"clerk-auth-credentials",
-				JSON.stringify({ clientToken: "Bearer token-123", sessionId: "session-123", organizationId: null }),
-			)
-			expect(mockShowInfo).toHaveBeenCalledWith("Successfully authenticated with Roo Code Cloud")
-		})
-
-		it("should handle Clerk API errors", async () => {
-			const storedState = "valid-state"
-			mockContext.globalState.get.mockReturnValue(storedState)
-
-			mockFetch.mockResolvedValue({
-				ok: false,
-				status: 400,
-				statusText: "Bad Request",
-			})
-
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			await expect(authService.handleCallback("auth-code", storedState)).rejects.toThrow(
-				"Failed to handle Roo Code Cloud callback",
-			)
-			expect(authStateChangedSpy).toHaveBeenCalled()
-		})
-	})
-
-	describe("logout", () => {
-		beforeEach(async () => {
-			await authService.initialize()
-		})
-
-		it("should clear credentials and call Clerk logout", async () => {
-			// Set up credentials first by simulating a login state
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-
-			// Manually set the credentials in the service
-			authService["credentials"] = credentials
-
-			// Mock successful logout response
-			mockFetch.mockResolvedValue({ ok: true })
-
-			const vscode = await import("vscode")
-			const mockShowInfo = vi.fn()
-			vi.mocked(vscode.window.showInformationMessage).mockImplementation(mockShowInfo)
-
-			await authService.logout()
-
-			expect(mockContext.secrets.delete).toHaveBeenCalledWith("clerk-auth-credentials")
-			expect(mockContext.globalState.update).toHaveBeenCalledWith("clerk-auth-state", undefined)
-			expect(mockFetch).toHaveBeenCalledWith(
-				"https://clerk.roocode.com/v1/client/sessions/test-session/remove",
-				expect.objectContaining({
-					method: "POST",
-					headers: expect.objectContaining({
-						Authorization: "Bearer test-token",
-					}),
-				}),
-			)
-			expect(mockShowInfo).toHaveBeenCalledWith("Logged out from Roo Code Cloud")
-		})
-
-		it("should handle logout without credentials", async () => {
-			const vscode = await import("vscode")
-			const mockShowInfo = vi.fn()
-			vi.mocked(vscode.window.showInformationMessage).mockImplementation(mockShowInfo)
-
-			await authService.logout()
-
-			expect(mockContext.secrets.delete).toHaveBeenCalled()
-			expect(mockFetch).not.toHaveBeenCalled()
-			expect(mockShowInfo).toHaveBeenCalledWith("Logged out from Roo Code Cloud")
-		})
-
-		it("should handle Clerk logout errors gracefully", async () => {
-			// Set up credentials first by simulating a login state
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-
-			// Manually set the credentials in the service
-			authService["credentials"] = credentials
-
-			// Mock failed logout response
-			mockFetch.mockRejectedValue(new Error("Network error"))
-
-			const vscode = await import("vscode")
-			const mockShowInfo = vi.fn()
-			vi.mocked(vscode.window.showInformationMessage).mockImplementation(mockShowInfo)
-
-			await authService.logout()
-
-			expect(mockLog).toHaveBeenCalledWith("[auth] Error calling clerkLogout:", expect.any(Error))
-			expect(mockShowInfo).toHaveBeenCalledWith("Logged out from Roo Code Cloud")
-		})
-	})
-
-	describe("state management", () => {
-		it("should return correct state", () => {
-			expect(authService.getState()).toBe("initializing")
-		})
-
-		it("should return correct authentication status", async () => {
-			await authService.initialize()
-			expect(authService.isAuthenticated()).toBe(false)
-
-			// Create a new service instance with credentials
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-
-			const authenticatedService = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
-			await authenticatedService.initialize()
-
-			expect(authenticatedService.isAuthenticated()).toBe(true)
-			expect(authenticatedService.hasActiveSession()).toBe(false)
-		})
-
-		it("should return session token only for active sessions", () => {
-			expect(authService.getSessionToken()).toBeUndefined()
-
-			// Manually set state to active-session for testing
-			// This would normally happen through refreshSession
-			authService["state"] = "active-session"
-			authService["sessionToken"] = "test-jwt"
-
-			expect(authService.getSessionToken()).toBe("test-jwt")
-		})
-
-		it("should return correct values for new methods", async () => {
-			await authService.initialize()
-			expect(authService.hasOrIsAcquiringActiveSession()).toBe(false)
-
-			// Create a new service instance with credentials (attempting-session)
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-
-			const attemptingService = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
-			await attemptingService.initialize()
-
-			expect(attemptingService.hasOrIsAcquiringActiveSession()).toBe(true)
-			expect(attemptingService.hasActiveSession()).toBe(false)
-
-			// Manually set state to active-session for testing
-			attemptingService["state"] = "active-session"
-			expect(attemptingService.hasOrIsAcquiringActiveSession()).toBe(true)
-			expect(attemptingService.hasActiveSession()).toBe(true)
-		})
-	})
-
-	describe("session refresh", () => {
-		beforeEach(async () => {
-			// Set up with credentials
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-			await authService.initialize()
-		})
-
-		it("should refresh session successfully", async () => {
-			// Mock successful token creation and user info fetch
-			mockFetch
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () => Promise.resolve({ jwt: "new-jwt-token" }),
-				})
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () =>
-						Promise.resolve({
-							response: {
-								first_name: "John",
-								last_name: "Doe",
-								image_url: "https://example.com/avatar.jpg",
-								primary_email_address_id: "email-1",
-								email_addresses: [{ id: "email-1", email_address: "[email protected]" }],
-							},
-						}),
-				})
-
-			const authStateChangedSpy = vi.fn()
-			const userInfoSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-			authService.on("user-info", userInfoSpy)
-
-			// Trigger refresh by calling the timer callback
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await timerCallback()
-
-			// Wait for async operations to complete
-			await new Promise((resolve) => setTimeout(resolve, 0))
-
-			expect(authService.getState()).toBe("active-session")
-			expect(authService.hasActiveSession()).toBe(true)
-			expect(authService.getSessionToken()).toBe("new-jwt-token")
-			expect(authStateChangedSpy).toHaveBeenCalledWith({
-				state: "active-session",
-				previousState: "attempting-session",
-			})
-			expect(userInfoSpy).toHaveBeenCalledWith({
-				userInfo: {
-					name: "John Doe",
-					email: "[email protected]",
-					picture: "https://example.com/avatar.jpg",
-				},
-			})
-		})
-
-		it("should handle invalid client token error", async () => {
-			// Mock 401 response (invalid token)
-			mockFetch.mockResolvedValue({
-				ok: false,
-				status: 401,
-				statusText: "Unauthorized",
-			})
-
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-
-			await expect(timerCallback()).rejects.toThrow()
-			expect(mockContext.secrets.delete).toHaveBeenCalledWith("clerk-auth-credentials")
-			expect(mockLog).toHaveBeenCalledWith("[auth] Invalid/Expired client token: clearing credentials")
-		})
-
-		it("should handle network errors during refresh", async () => {
-			mockFetch.mockRejectedValue(new Error("Network error"))
-
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-
-			await expect(timerCallback()).rejects.toThrow("Network error")
-			expect(mockLog).toHaveBeenCalledWith("[auth] Failed to refresh session", expect.any(Error))
-		})
-
-		it("should transition to inactive-session on first attempt failure", async () => {
-			// Mock failed token creation response
-			mockFetch.mockResolvedValue({
-				ok: false,
-				status: 500,
-				statusText: "Internal Server Error",
-			})
-
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			// Verify we start in attempting-session state
-			expect(authService.getState()).toBe("attempting-session")
-			expect(authService["isFirstRefreshAttempt"]).toBe(true)
-
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-
-			await expect(timerCallback()).rejects.toThrow()
-
-			// Should transition to inactive-session after first failure
-			expect(authService.getState()).toBe("inactive-session")
-			expect(authService["isFirstRefreshAttempt"]).toBe(false)
-			expect(authStateChangedSpy).toHaveBeenCalledWith({
-				state: "inactive-session",
-				previousState: "attempting-session",
-			})
-		})
-
-		it("should not transition to inactive-session on subsequent failures", async () => {
-			// First, transition to inactive-session by failing the first attempt
-			mockFetch.mockResolvedValue({
-				ok: false,
-				status: 500,
-				statusText: "Internal Server Error",
-			})
-
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await expect(timerCallback()).rejects.toThrow()
-
-			// Verify we're now in inactive-session
-			expect(authService.getState()).toBe("inactive-session")
-			expect(authService["isFirstRefreshAttempt"]).toBe(false)
-
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			// Subsequent failure should not trigger another transition
-			await expect(timerCallback()).rejects.toThrow()
-
-			expect(authService.getState()).toBe("inactive-session")
-			expect(authStateChangedSpy).not.toHaveBeenCalled()
-		})
-
-		it("should clear credentials on 401 during first refresh attempt (bug fix)", async () => {
-			// Mock 401 response during first refresh attempt
-			mockFetch.mockResolvedValue({
-				ok: false,
-				status: 401,
-				statusText: "Unauthorized",
-			})
-
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await expect(timerCallback()).rejects.toThrow()
-
-			// Should clear credentials (not just transition to inactive-session)
-			expect(mockContext.secrets.delete).toHaveBeenCalledWith("clerk-auth-credentials")
-			expect(mockLog).toHaveBeenCalledWith("[auth] Invalid/Expired client token: clearing credentials")
-
-			// Simulate credentials cleared event
-			mockContext.secrets.get.mockResolvedValue(undefined)
-			await authService["handleCredentialsChange"]()
-
-			expect(authService.getState()).toBe("logged-out")
-			expect(authStateChangedSpy).toHaveBeenCalledWith({
-				state: "logged-out",
-				previousState: "attempting-session",
-			})
-		})
-	})
-
-	describe("user info", () => {
-		it("should return null initially", () => {
-			expect(authService.getUserInfo()).toBeNull()
-		})
-
-		it("should parse user info correctly for personal accounts", async () => {
-			// Set up with credentials for personal account (no organizationId)
-			const credentials = { clientToken: "test-token", sessionId: "test-session", organizationId: null }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-			await authService.initialize()
-
-			// Clear previous mock calls
-			mockFetch.mockClear()
-
-			// Mock successful responses
-			mockFetch
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () => Promise.resolve({ jwt: "jwt-token" }),
-				})
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () =>
-						Promise.resolve({
-							response: {
-								first_name: "Jane",
-								last_name: "Smith",
-								image_url: "https://example.com/jane.jpg",
-								primary_email_address_id: "email-2",
-								email_addresses: [
-									{ id: "email-1", email_address: "[email protected]" },
-									{ id: "email-2", email_address: "[email protected]" },
-								],
-							},
-						}),
-				})
-
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await timerCallback()
-
-			// Wait for async operations to complete
-			await new Promise((resolve) => setTimeout(resolve, 0))
-
-			const userInfo = authService.getUserInfo()
-			expect(userInfo).toEqual({
-				name: "Jane Smith",
-				email: "[email protected]",
-				picture: "https://example.com/jane.jpg",
-			})
-		})
-
-		it("should parse user info correctly for organization accounts", async () => {
-			// Set up with credentials for organization account
-			const credentials = { clientToken: "test-token", sessionId: "test-session", organizationId: "org_1" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-			await authService.initialize()
-
-			// Clear previous mock calls
-			mockFetch.mockClear()
-
-			// Mock successful responses
-			mockFetch
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () => Promise.resolve({ jwt: "jwt-token" }),
-				})
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () =>
-						Promise.resolve({
-							response: {
-								first_name: "Jane",
-								last_name: "Smith",
-								image_url: "https://example.com/jane.jpg",
-								primary_email_address_id: "email-2",
-								email_addresses: [
-									{ id: "email-1", email_address: "[email protected]" },
-									{ id: "email-2", email_address: "[email protected]" },
-								],
-							},
-						}),
-				})
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () =>
-						Promise.resolve({
-							response: [
-								{
-									id: "org_member_id_1",
-									role: "member",
-									organization: {
-										id: "org_1",
-										name: "Org 1",
-									},
-								},
-							],
-						}),
-				})
-
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await timerCallback()
-
-			// Wait for async operations to complete
-			await new Promise((resolve) => setTimeout(resolve, 0))
-
-			const userInfo = authService.getUserInfo()
-			expect(userInfo).toEqual({
-				name: "Jane Smith",
-				email: "[email protected]",
-				picture: "https://example.com/jane.jpg",
-				organizationId: "org_1",
-				organizationName: "Org 1",
-				organizationRole: "member",
-			})
-		})
-
-		it("should handle missing user info fields", async () => {
-			// Set up with credentials for personal account (no organizationId)
-			const credentials = { clientToken: "test-token", sessionId: "test-session", organizationId: null }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-			await authService.initialize()
-
-			// Clear previous mock calls
-			mockFetch.mockClear()
-
-			// Mock responses with minimal data
-			mockFetch
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () => Promise.resolve({ jwt: "jwt-token" }),
-				})
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () =>
-						Promise.resolve({
-							response: {
-								first_name: "John",
-								last_name: "Doe",
-								// Missing other fields
-							},
-						}),
-				})
-
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await timerCallback()
-
-			// Wait for async operations to complete
-			await new Promise((resolve) => setTimeout(resolve, 0))
-
-			const userInfo = authService.getUserInfo()
-			expect(userInfo).toEqual({
-				name: "John Doe",
-				email: undefined,
-				picture: undefined,
-			})
-		})
-	})
-
-	describe("event emissions", () => {
-		it("should emit auth-state-changed event for logged-out", async () => {
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			await authService.initialize()
-
-			expect(authStateChangedSpy).toHaveBeenCalledWith({ state: "logged-out", previousState: "initializing" })
-		})
-
-		it("should emit auth-state-changed event for attempting-session", async () => {
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			await authService.initialize()
-
-			expect(authStateChangedSpy).toHaveBeenCalledWith({
-				state: "attempting-session",
-				previousState: "initializing",
-			})
-		})
-
-		it("should emit auth-state-changed event for active-session", async () => {
-			// Set up with credentials
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-			await authService.initialize()
-
-			// Clear previous mock calls
-			mockFetch.mockClear()
-
-			// Mock both the token creation and user info fetch
-			mockFetch
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () => Promise.resolve({ jwt: "jwt-token" }),
-				})
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () =>
-						Promise.resolve({
-							response: {
-								first_name: "Test",
-								last_name: "User",
-							},
-						}),
-				})
-
-			const authStateChangedSpy = vi.fn()
-			authService.on("auth-state-changed", authStateChangedSpy)
-
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await timerCallback()
-
-			// Wait for async operations to complete
-			await new Promise((resolve) => setTimeout(resolve, 0))
-
-			expect(authStateChangedSpy).toHaveBeenCalledWith({
-				state: "active-session",
-				previousState: "attempting-session",
-			})
-		})
-
-		it("should emit user-info event", async () => {
-			// Set up with credentials
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-			await authService.initialize()
-
-			// Clear previous mock calls
-			mockFetch.mockClear()
-
-			mockFetch
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () => Promise.resolve({ jwt: "jwt-token" }),
-				})
-				.mockResolvedValueOnce({
-					ok: true,
-					json: () =>
-						Promise.resolve({
-							response: {
-								first_name: "Test",
-								last_name: "User",
-							},
-						}),
-				})
-
-			const userInfoSpy = vi.fn()
-			authService.on("user-info", userInfoSpy)
-
-			const timerCallback = vi.mocked(RefreshTimer).mock.calls[0][0].callback
-			await timerCallback()
-
-			// Wait for async operations to complete
-			await new Promise((resolve) => setTimeout(resolve, 0))
-
-			expect(userInfoSpy).toHaveBeenCalledWith({
-				userInfo: {
-					name: "Test User",
-					email: undefined,
-					picture: undefined,
-				},
-			})
-		})
-	})
-
-	describe("error handling", () => {
-		it("should handle credentials change errors", async () => {
-			mockContext.secrets.get.mockRejectedValue(new Error("Storage error"))
-
-			await authService.initialize()
-
-			expect(mockLog).toHaveBeenCalledWith("[auth] Error handling credentials change:", expect.any(Error))
-		})
-
-		it("should handle malformed JSON in credentials", async () => {
-			mockContext.secrets.get.mockResolvedValue("invalid-json{")
-
-			await authService.initialize()
-
-			expect(authService.getState()).toBe("logged-out")
-			expect(mockLog).toHaveBeenCalledWith("[auth] Failed to parse stored credentials:", expect.any(Error))
-		})
-
-		it("should handle invalid credentials schema", async () => {
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify({ invalid: "data" }))
-
-			await authService.initialize()
-
-			expect(authService.getState()).toBe("logged-out")
-			expect(mockLog).toHaveBeenCalledWith("[auth] Invalid credentials format:", expect.any(Array))
-		})
-
-		it("should handle missing authorization header in sign-in response", async () => {
-			const storedState = "valid-state"
-			mockContext.globalState.get.mockReturnValue(storedState)
-
-			mockFetch.mockResolvedValue({
-				ok: true,
-				json: () =>
-					Promise.resolve({
-						response: { created_session_id: "session-123" },
-					}),
-				headers: {
-					get: () => null, // No authorization header
-				},
-			})
-
-			await expect(authService.handleCallback("auth-code", storedState)).rejects.toThrow(
-				"Failed to handle Roo Code Cloud callback",
-			)
-		})
-	})
-
-	describe("timer integration", () => {
-		it("should stop timer on logged-out transition", async () => {
-			await authService.initialize()
-
-			expect(mockTimer.stop).toHaveBeenCalled()
-		})
-
-		it("should start timer on attempting-session transition", async () => {
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-
-			await authService.initialize()
-
-			expect(mockTimer.start).toHaveBeenCalled()
-		})
-	})
-
-	describe("auth credentials key scoping", () => {
-		it("should use default key when getClerkBaseUrl returns production URL", async () => {
-			// Mock getClerkBaseUrl to return production URL
-			vi.mocked(getClerkBaseUrl).mockReturnValue("https://clerk.roocode.com")
-
-			const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-
-			await service.initialize()
-			await service["storeCredentials"](credentials)
-
-			expect(mockContext.secrets.store).toHaveBeenCalledWith(
-				"clerk-auth-credentials",
-				JSON.stringify(credentials),
-			)
-		})
-
-		it("should use scoped key when getClerkBaseUrl returns custom URL", async () => {
-			const customUrl = "https://custom.clerk.com"
-			// Mock getClerkBaseUrl to return custom URL
-			vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
-
-			const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-
-			await service.initialize()
-			await service["storeCredentials"](credentials)
-
-			expect(mockContext.secrets.store).toHaveBeenCalledWith(
-				`clerk-auth-credentials-${customUrl}`,
-				JSON.stringify(credentials),
-			)
-		})
-
-		it("should load credentials using scoped key", async () => {
-			const customUrl = "https://custom.clerk.com"
-			vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
-
-			const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
-			const credentials = { clientToken: "test-token", sessionId: "test-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
-
-			await service.initialize()
-			const loadedCredentials = await service["loadCredentials"]()
-
-			expect(mockContext.secrets.get).toHaveBeenCalledWith(`clerk-auth-credentials-${customUrl}`)
-			expect(loadedCredentials).toEqual(credentials)
-		})
-
-		it("should clear credentials using scoped key", async () => {
-			const customUrl = "https://custom.clerk.com"
-			vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
-
-			const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
-
-			await service.initialize()
-			await service["clearCredentials"]()
-
-			expect(mockContext.secrets.delete).toHaveBeenCalledWith(`clerk-auth-credentials-${customUrl}`)
-		})
-
-		it("should listen for changes on scoped key", async () => {
-			const customUrl = "https://custom.clerk.com"
-			vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
-
-			let onDidChangeCallback: (e: { key: string }) => void
-
-			mockContext.secrets.onDidChange.mockImplementation((callback: (e: { key: string }) => void) => {
-				onDidChangeCallback = callback
-				return { dispose: vi.fn() }
-			})
-
-			const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
-			await service.initialize()
-
-			// Simulate credentials change event with scoped key
-			const newCredentials = { clientToken: "new-token", sessionId: "new-session" }
-			mockContext.secrets.get.mockResolvedValue(JSON.stringify(newCredentials))
-
-			const authStateChangedSpy = vi.fn()
-			service.on("auth-state-changed", authStateChangedSpy)
-
-			onDidChangeCallback!({ key: `clerk-auth-credentials-${customUrl}` })
-			await new Promise((resolve) => setTimeout(resolve, 0)) // Wait for async handling
-
-			expect(authStateChangedSpy).toHaveBeenCalled()
-		})
-
-		it("should not respond to changes on different scoped keys", async () => {
-			const customUrl = "https://custom.clerk.com"
-			vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
-
-			let onDidChangeCallback: (e: { key: string }) => void
-
-			mockContext.secrets.onDidChange.mockImplementation((callback: (e: { key: string }) => void) => {
-				onDidChangeCallback = callback
-				return { dispose: vi.fn() }
-			})
-
-			const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
-			await service.initialize()
-
-			const authStateChangedSpy = vi.fn()
-			service.on("auth-state-changed", authStateChangedSpy)
-
-			// Simulate credentials change event with different scoped key
-			onDidChangeCallback!({ key: "clerk-auth-credentials-https://other.clerk.com" })
-			await new Promise((resolve) => setTimeout(resolve, 0)) // Wait for async handling
-
-			expect(authStateChangedSpy).not.toHaveBeenCalled()
-		})
-
-		it("should not respond to changes on default key when using scoped key", async () => {
-			const customUrl = "https://custom.clerk.com"
-			vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
-
-			let onDidChangeCallback: (e: { key: string }) => void
-
-			mockContext.secrets.onDidChange.mockImplementation((callback: (e: { key: string }) => void) => {
-				onDidChangeCallback = callback
-				return { dispose: vi.fn() }
-			})
-
-			const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
-			await service.initialize()
-
-			const authStateChangedSpy = vi.fn()
-			service.on("auth-state-changed", authStateChangedSpy)
-
-			// Simulate credentials change event with default key
-			onDidChangeCallback!({ key: "clerk-auth-credentials" })
-			await new Promise((resolve) => setTimeout(resolve, 0)) // Wait for async handling
-
-			expect(authStateChangedSpy).not.toHaveBeenCalled()
-		})
-	})
-})

+ 0 - 36
packages/cloud/src/auth/AuthService.ts

@@ -1,36 +0,0 @@
-import EventEmitter from "events"
-
-import type { CloudUserInfo } from "@roo-code/types"
-
-export interface AuthServiceEvents {
-	"auth-state-changed": [
-		data: {
-			state: AuthState
-			previousState: AuthState
-		},
-	]
-	"user-info": [data: { userInfo: CloudUserInfo }]
-}
-
-export type AuthState = "initializing" | "logged-out" | "active-session" | "attempting-session" | "inactive-session"
-
-export interface AuthService extends EventEmitter<AuthServiceEvents> {
-	// Lifecycle
-	initialize(): Promise<void>
-
-	// Authentication methods
-	login(): Promise<void>
-	logout(): Promise<void>
-	handleCallback(code: string | null, state: string | null, organizationId?: string | null): Promise<void>
-
-	// State methods
-	getState(): AuthState
-	isAuthenticated(): boolean
-	hasActiveSession(): boolean
-	hasOrIsAcquiringActiveSession(): boolean
-
-	// Token and user info
-	getSessionToken(): string | undefined
-	getUserInfo(): CloudUserInfo | null
-	getStoredOrganizationId(): string | null
-}

+ 0 - 71
packages/cloud/src/auth/StaticTokenAuthService.ts

@@ -1,71 +0,0 @@
-import EventEmitter from "events"
-
-import * as vscode from "vscode"
-
-import type { CloudUserInfo } from "@roo-code/types"
-
-import type { AuthService, AuthServiceEvents, AuthState } from "./AuthService"
-
-export class StaticTokenAuthService extends EventEmitter<AuthServiceEvents> implements AuthService {
-	private state: AuthState = "active-session"
-	private token: string
-	private log: (...args: unknown[]) => void
-
-	constructor(context: vscode.ExtensionContext, token: string, log?: (...args: unknown[]) => void) {
-		super()
-		this.token = token
-		this.log = log || console.log
-		this.log("[auth] Using static token authentication mode")
-	}
-
-	public async initialize(): Promise<void> {
-		const previousState: AuthState = "initializing"
-		this.state = "active-session"
-		this.emit("auth-state-changed", { state: this.state, previousState })
-		this.log("[auth] Static token auth service initialized in active-session state")
-	}
-
-	public async login(): Promise<void> {
-		throw new Error("Authentication methods are disabled in StaticTokenAuthService")
-	}
-
-	public async logout(): Promise<void> {
-		throw new Error("Authentication methods are disabled in StaticTokenAuthService")
-	}
-
-	public async handleCallback(
-		_code: string | null,
-		_state: string | null,
-		_organizationId?: string | null,
-	): Promise<void> {
-		throw new Error("Authentication methods are disabled in StaticTokenAuthService")
-	}
-
-	public getState(): AuthState {
-		return this.state
-	}
-
-	public getSessionToken(): string | undefined {
-		return this.token
-	}
-
-	public isAuthenticated(): boolean {
-		return true
-	}
-
-	public hasActiveSession(): boolean {
-		return true
-	}
-
-	public hasOrIsAcquiringActiveSession(): boolean {
-		return true
-	}
-
-	public getUserInfo(): CloudUserInfo | null {
-		return {}
-	}
-
-	public getStoredOrganizationId(): string | null {
-		return null
-	}
-}

+ 0 - 646
packages/cloud/src/auth/WebAuthService.ts

@@ -1,646 +0,0 @@
-import crypto from "crypto"
-import EventEmitter from "events"
-
-import * as vscode from "vscode"
-import { z } from "zod"
-
-import type { CloudUserInfo, CloudOrganizationMembership } from "@roo-code/types"
-
-import { getClerkBaseUrl, getRooCodeApiUrl, PRODUCTION_CLERK_BASE_URL } from "../config"
-import { getUserAgent } from "../utils"
-import { InvalidClientTokenError } from "../errors"
-import { RefreshTimer } from "../RefreshTimer"
-
-import type { AuthService, AuthServiceEvents, AuthState } from "./AuthService"
-
-const AUTH_STATE_KEY = "clerk-auth-state"
-
-/**
- * AuthCredentials
- */
-
-const authCredentialsSchema = z.object({
-	clientToken: z.string().min(1, "Client token cannot be empty"),
-	sessionId: z.string().min(1, "Session ID cannot be empty"),
-	organizationId: z.string().nullable().optional(),
-})
-
-type AuthCredentials = z.infer<typeof authCredentialsSchema>
-
-/**
- * Clerk Schemas
- */
-
-const clerkSignInResponseSchema = z.object({
-	response: z.object({
-		created_session_id: z.string(),
-	}),
-})
-
-const clerkCreateSessionTokenResponseSchema = z.object({
-	jwt: z.string(),
-})
-
-const clerkMeResponseSchema = z.object({
-	response: z.object({
-		id: z.string().optional(),
-		first_name: z.string().nullish(),
-		last_name: z.string().nullish(),
-		image_url: z.string().optional(),
-		primary_email_address_id: z.string().optional(),
-		email_addresses: z
-			.array(
-				z.object({
-					id: z.string(),
-					email_address: z.string(),
-				}),
-			)
-			.optional(),
-	}),
-})
-
-const clerkOrganizationMembershipsSchema = z.object({
-	response: z.array(
-		z.object({
-			id: z.string(),
-			role: z.string(),
-			permissions: z.array(z.string()).optional(),
-			created_at: z.number().optional(),
-			updated_at: z.number().optional(),
-			organization: z.object({
-				id: z.string(),
-				name: z.string(),
-				slug: z.string().optional(),
-				image_url: z.string().optional(),
-				has_image: z.boolean().optional(),
-				created_at: z.number().optional(),
-				updated_at: z.number().optional(),
-			}),
-		}),
-	),
-})
-
-export class WebAuthService extends EventEmitter<AuthServiceEvents> implements AuthService {
-	private context: vscode.ExtensionContext
-	private timer: RefreshTimer
-	private state: AuthState = "initializing"
-	private log: (...args: unknown[]) => void
-	private readonly authCredentialsKey: string
-
-	private credentials: AuthCredentials | null = null
-	private sessionToken: string | null = null
-	private userInfo: CloudUserInfo | null = null
-	private isFirstRefreshAttempt: boolean = false
-
-	constructor(context: vscode.ExtensionContext, log?: (...args: unknown[]) => void) {
-		super()
-
-		this.context = context
-		this.log = log || console.log
-
-		// Calculate auth credentials key based on Clerk base URL.
-		const clerkBaseUrl = getClerkBaseUrl()
-
-		if (clerkBaseUrl !== PRODUCTION_CLERK_BASE_URL) {
-			this.authCredentialsKey = `clerk-auth-credentials-${clerkBaseUrl}`
-		} else {
-			this.authCredentialsKey = "clerk-auth-credentials"
-		}
-
-		this.timer = new RefreshTimer({
-			callback: async () => {
-				await this.refreshSession()
-				return true
-			},
-			successInterval: 50_000,
-			initialBackoffMs: 1_000,
-			maxBackoffMs: 300_000,
-		})
-	}
-
-	private changeState(newState: AuthState): void {
-		const previousState = this.state
-		this.state = newState
-		this.emit("auth-state-changed", { state: newState, previousState })
-	}
-
-	private async handleCredentialsChange(): Promise<void> {
-		try {
-			const credentials = await this.loadCredentials()
-
-			if (credentials) {
-				if (
-					this.credentials === null ||
-					this.credentials.clientToken !== credentials.clientToken ||
-					this.credentials.sessionId !== credentials.sessionId
-				) {
-					this.transitionToAttemptingSession(credentials)
-				}
-			} else {
-				if (this.state !== "logged-out") {
-					this.transitionToLoggedOut()
-				}
-			}
-		} catch (error) {
-			this.log("[auth] Error handling credentials change:", error)
-		}
-	}
-
-	private transitionToLoggedOut(): void {
-		this.timer.stop()
-
-		this.credentials = null
-		this.sessionToken = null
-		this.userInfo = null
-
-		this.changeState("logged-out")
-
-		this.log("[auth] Transitioned to logged-out state")
-	}
-
-	private transitionToAttemptingSession(credentials: AuthCredentials): void {
-		this.credentials = credentials
-
-		this.sessionToken = null
-		this.userInfo = null
-		this.isFirstRefreshAttempt = true
-
-		this.changeState("attempting-session")
-
-		this.timer.start()
-
-		this.log("[auth] Transitioned to attempting-session state")
-	}
-
-	private transitionToInactiveSession(): void {
-		this.sessionToken = null
-		this.userInfo = null
-
-		this.changeState("inactive-session")
-
-		this.log("[auth] Transitioned to inactive-session state")
-	}
-
-	/**
-	 * Initialize the auth state
-	 *
-	 * This method loads tokens from storage and determines the current auth state.
-	 * It also starts the refresh timer if we have an active session.
-	 */
-	public async initialize(): Promise<void> {
-		if (this.state !== "initializing") {
-			this.log("[auth] initialize() called after already initialized")
-			return
-		}
-
-		await this.handleCredentialsChange()
-
-		this.context.subscriptions.push(
-			this.context.secrets.onDidChange((e) => {
-				if (e.key === this.authCredentialsKey) {
-					this.handleCredentialsChange()
-				}
-			}),
-		)
-	}
-
-	private async storeCredentials(credentials: AuthCredentials): Promise<void> {
-		await this.context.secrets.store(this.authCredentialsKey, JSON.stringify(credentials))
-	}
-
-	private async loadCredentials(): Promise<AuthCredentials | null> {
-		const credentialsJson = await this.context.secrets.get(this.authCredentialsKey)
-		if (!credentialsJson) return null
-
-		try {
-			const parsedJson = JSON.parse(credentialsJson)
-			const credentials = authCredentialsSchema.parse(parsedJson)
-
-			// Migration: If no organizationId but we have userInfo, add it
-			if (credentials.organizationId === undefined && this.userInfo?.organizationId) {
-				credentials.organizationId = this.userInfo.organizationId
-				await this.storeCredentials(credentials)
-				this.log("[auth] Migrated credentials with organizationId")
-			}
-
-			return credentials
-		} catch (error) {
-			if (error instanceof z.ZodError) {
-				this.log("[auth] Invalid credentials format:", error.errors)
-			} else {
-				this.log("[auth] Failed to parse stored credentials:", error)
-			}
-			return null
-		}
-	}
-
-	private async clearCredentials(): Promise<void> {
-		await this.context.secrets.delete(this.authCredentialsKey)
-	}
-
-	/**
-	 * Start the login process
-	 *
-	 * This method initiates the authentication flow by generating a state parameter
-	 * and opening the browser to the authorization URL.
-	 */
-	public async login(): Promise<void> {
-		try {
-			// Generate a cryptographically random state parameter.
-			const state = crypto.randomBytes(16).toString("hex")
-			await this.context.globalState.update(AUTH_STATE_KEY, state)
-			const packageJSON = this.context.extension?.packageJSON
-			const publisher = packageJSON?.publisher ?? "RooVeterinaryInc"
-			const name = packageJSON?.name ?? "roo-cline"
-			const params = new URLSearchParams({
-				state,
-				auth_redirect: `${vscode.env.uriScheme}://${publisher}.${name}`,
-			})
-			const url = `${getRooCodeApiUrl()}/extension/sign-in?${params.toString()}`
-			await vscode.env.openExternal(vscode.Uri.parse(url))
-		} catch (error) {
-			this.log(`[auth] Error initiating Roo Code Cloud auth: ${error}`)
-			throw new Error(`Failed to initiate Roo Code Cloud authentication: ${error}`)
-		}
-	}
-
-	/**
-	 * Handle the callback from Roo Code Cloud
-	 *
-	 * This method is called when the user is redirected back to the extension
-	 * after authenticating with Roo Code Cloud.
-	 *
-	 * @param code The authorization code from the callback
-	 * @param state The state parameter from the callback
-	 * @param organizationId The organization ID from the callback (null for personal accounts)
-	 */
-	public async handleCallback(
-		code: string | null,
-		state: string | null,
-		organizationId?: string | null,
-	): Promise<void> {
-		if (!code || !state) {
-			vscode.window.showInformationMessage("Invalid Roo Code Cloud sign in url")
-			return
-		}
-
-		try {
-			// Validate state parameter to prevent CSRF attacks.
-			const storedState = this.context.globalState.get(AUTH_STATE_KEY)
-
-			if (state !== storedState) {
-				this.log("[auth] State mismatch in callback")
-				throw new Error("Invalid state parameter. Authentication request may have been tampered with.")
-			}
-
-			const credentials = await this.clerkSignIn(code)
-
-			// Set organizationId (null for personal accounts)
-			credentials.organizationId = organizationId || null
-
-			await this.storeCredentials(credentials)
-
-			vscode.window.showInformationMessage("Successfully authenticated with Roo Code Cloud")
-			this.log("[auth] Successfully authenticated with Roo Code Cloud")
-		} catch (error) {
-			this.log(`[auth] Error handling Roo Code Cloud callback: ${error}`)
-			this.changeState("logged-out")
-			throw new Error(`Failed to handle Roo Code Cloud callback: ${error}`)
-		}
-	}
-
-	/**
-	 * Log out
-	 *
-	 * This method removes all stored tokens and stops the refresh timer.
-	 */
-	public async logout(): Promise<void> {
-		const oldCredentials = this.credentials
-
-		try {
-			// Clear credentials from storage - onDidChange will handle state transitions
-			await this.clearCredentials()
-			await this.context.globalState.update(AUTH_STATE_KEY, undefined)
-
-			if (oldCredentials) {
-				try {
-					await this.clerkLogout(oldCredentials)
-				} catch (error) {
-					this.log("[auth] Error calling clerkLogout:", error)
-				}
-			}
-
-			vscode.window.showInformationMessage("Logged out from Roo Code Cloud")
-			this.log("[auth] Logged out from Roo Code Cloud")
-		} catch (error) {
-			this.log(`[auth] Error logging out from Roo Code Cloud: ${error}`)
-			throw new Error(`Failed to log out from Roo Code Cloud: ${error}`)
-		}
-	}
-
-	public getState(): AuthState {
-		return this.state
-	}
-
-	public getSessionToken(): string | undefined {
-		if (this.state === "active-session" && this.sessionToken) {
-			return this.sessionToken
-		}
-
-		return
-	}
-
-	/**
-	 * Check if the user is authenticated
-	 *
-	 * @returns True if the user is authenticated (has an active, attempting, or inactive session)
-	 */
-	public isAuthenticated(): boolean {
-		return (
-			this.state === "active-session" || this.state === "attempting-session" || this.state === "inactive-session"
-		)
-	}
-
-	public hasActiveSession(): boolean {
-		return this.state === "active-session"
-	}
-
-	/**
-	 * Check if the user has an active session or is currently attempting to acquire one
-	 *
-	 * @returns True if the user has an active session or is attempting to get one
-	 */
-	public hasOrIsAcquiringActiveSession(): boolean {
-		return this.state === "active-session" || this.state === "attempting-session"
-	}
-
-	/**
-	 * Refresh the session
-	 *
-	 * This method refreshes the session token using the client token.
-	 */
-	private async refreshSession(): Promise<void> {
-		if (!this.credentials) {
-			this.log("[auth] Cannot refresh session: missing credentials")
-			return
-		}
-
-		try {
-			const previousState = this.state
-			this.sessionToken = await this.clerkCreateSessionToken()
-
-			if (previousState !== "active-session") {
-				this.changeState("active-session")
-				this.log("[auth] Transitioned to active-session state")
-				this.fetchUserInfo()
-			} else {
-				this.state = "active-session"
-			}
-		} catch (error) {
-			if (error instanceof InvalidClientTokenError) {
-				this.log("[auth] Invalid/Expired client token: clearing credentials")
-				this.clearCredentials()
-			} else if (this.isFirstRefreshAttempt && this.state === "attempting-session") {
-				this.isFirstRefreshAttempt = false
-				this.transitionToInactiveSession()
-			}
-			this.log("[auth] Failed to refresh session", error)
-			throw error
-		}
-	}
-
-	private async fetchUserInfo(): Promise<void> {
-		if (!this.credentials) {
-			return
-		}
-
-		this.userInfo = await this.clerkMe()
-		this.emit("user-info", { userInfo: this.userInfo })
-	}
-
-	/**
-	 * Extract user information from the ID token
-	 *
-	 * @returns User information from ID token claims or null if no ID token available
-	 */
-	public getUserInfo(): CloudUserInfo | null {
-		return this.userInfo
-	}
-
-	/**
-	 * Get the stored organization ID from credentials
-	 *
-	 * @returns The stored organization ID, null for personal accounts or if no credentials exist
-	 */
-	public getStoredOrganizationId(): string | null {
-		return this.credentials?.organizationId || null
-	}
-
-	private async clerkSignIn(ticket: string): Promise<AuthCredentials> {
-		const formData = new URLSearchParams()
-		formData.append("strategy", "ticket")
-		formData.append("ticket", ticket)
-
-		const response = await fetch(`${getClerkBaseUrl()}/v1/client/sign_ins`, {
-			method: "POST",
-			headers: {
-				"Content-Type": "application/x-www-form-urlencoded",
-				"User-Agent": this.userAgent(),
-			},
-			body: formData.toString(),
-			signal: AbortSignal.timeout(10000),
-		})
-
-		if (!response.ok) {
-			throw new Error(`HTTP ${response.status}: ${response.statusText}`)
-		}
-
-		const {
-			response: { created_session_id: sessionId },
-		} = clerkSignInResponseSchema.parse(await response.json())
-
-		// 3. Extract the client token from the Authorization header.
-		const clientToken = response.headers.get("authorization")
-
-		if (!clientToken) {
-			throw new Error("No authorization header found in the response")
-		}
-
-		return authCredentialsSchema.parse({ clientToken, sessionId })
-	}
-
-	private async clerkCreateSessionToken(): Promise<string> {
-		const formData = new URLSearchParams()
-		formData.append("_is_native", "1")
-
-		// Handle 3 cases for organization_id:
-		// 1. Have an org id: organization_id=THE_ORG_ID
-		// 2. Have a personal account: organization_id= (empty string)
-		// 3. Don't know if you have an org id (old style credentials): don't send organization_id param at all
-		const organizationId = this.getStoredOrganizationId()
-		if (this.credentials?.organizationId !== undefined) {
-			// We have organization context info (either org id or personal account)
-			formData.append("organization_id", organizationId || "")
-		}
-		// If organizationId is undefined, don't send the param at all (old credentials)
-
-		const response = await fetch(`${getClerkBaseUrl()}/v1/client/sessions/${this.credentials!.sessionId}/tokens`, {
-			method: "POST",
-			headers: {
-				"Content-Type": "application/x-www-form-urlencoded",
-				Authorization: `Bearer ${this.credentials!.clientToken}`,
-				"User-Agent": this.userAgent(),
-			},
-			body: formData.toString(),
-			signal: AbortSignal.timeout(10000),
-		})
-
-		if (response.status === 401 || response.status === 404) {
-			throw new InvalidClientTokenError()
-		} else if (!response.ok) {
-			throw new Error(`HTTP ${response.status}: ${response.statusText}`)
-		}
-
-		const data = clerkCreateSessionTokenResponseSchema.parse(await response.json())
-
-		return data.jwt
-	}
-
-	private async clerkMe(): Promise<CloudUserInfo> {
-		const response = await fetch(`${getClerkBaseUrl()}/v1/me`, {
-			headers: {
-				Authorization: `Bearer ${this.credentials!.clientToken}`,
-				"User-Agent": this.userAgent(),
-			},
-			signal: AbortSignal.timeout(10000),
-		})
-
-		if (!response.ok) {
-			throw new Error(`HTTP ${response.status}: ${response.statusText}`)
-		}
-
-		const payload = await response.json()
-		const { response: userData } = clerkMeResponseSchema.parse(payload)
-
-		const userInfo: CloudUserInfo = {
-			id: userData.id,
-			picture: userData.image_url,
-		}
-
-		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
-
-		if (primaryEmailAddressId && emailAddresses) {
-			userInfo.email = emailAddresses.find(
-				(email: { id: string }) => primaryEmailAddressId === email.id,
-			)?.email_address
-		}
-
-		// Fetch organization info if user is in organization context
-		try {
-			const storedOrgId = this.getStoredOrganizationId()
-
-			if (this.credentials?.organizationId !== undefined) {
-				// We have organization context info
-				if (storedOrgId !== null) {
-					// User is in organization context - fetch user's memberships and filter
-					const orgMemberships = await this.clerkGetOrganizationMemberships()
-					const userMembership = this.findOrganizationMembership(orgMemberships, storedOrgId)
-
-					if (userMembership) {
-						this.setUserOrganizationInfo(userInfo, userMembership)
-
-						this.log("[auth] User in organization context:", {
-							id: userMembership.organization.id,
-							name: userMembership.organization.name,
-							role: userMembership.role,
-						})
-					} else {
-						this.log("[auth] Warning: User not found in stored organization:", storedOrgId)
-					}
-				} else {
-					this.log("[auth] User in personal account context - not setting organization info")
-				}
-			} else {
-				// Old credentials without organization context - fetch organization info to determine context
-				const orgMemberships = await this.clerkGetOrganizationMemberships()
-				const primaryOrgMembership = this.findPrimaryOrganizationMembership(orgMemberships)
-
-				if (primaryOrgMembership) {
-					this.setUserOrganizationInfo(userInfo, primaryOrgMembership)
-
-					this.log("[auth] Legacy credentials: Found organization membership:", {
-						id: primaryOrgMembership.organization.id,
-						name: primaryOrgMembership.organization.name,
-						role: primaryOrgMembership.role,
-					})
-				} else {
-					this.log("[auth] Legacy credentials: No organization memberships found")
-				}
-			}
-		} catch (error) {
-			this.log("[auth] Failed to fetch organization info:", error)
-			// Don't throw - organization info is optional
-		}
-
-		return userInfo
-	}
-
-	private findOrganizationMembership(
-		memberships: CloudOrganizationMembership[],
-		organizationId: string,
-	): CloudOrganizationMembership | undefined {
-		return memberships?.find((membership) => membership.organization.id === organizationId)
-	}
-
-	private findPrimaryOrganizationMembership(
-		memberships: CloudOrganizationMembership[],
-	): CloudOrganizationMembership | undefined {
-		return memberships && memberships.length > 0 ? memberships[0] : undefined
-	}
-
-	private setUserOrganizationInfo(userInfo: CloudUserInfo, membership: CloudOrganizationMembership): void {
-		userInfo.organizationId = membership.organization.id
-		userInfo.organizationName = membership.organization.name
-		userInfo.organizationRole = membership.role
-		userInfo.organizationImageUrl = membership.organization.image_url
-	}
-
-	private async clerkGetOrganizationMemberships(): Promise<CloudOrganizationMembership[]> {
-		const response = await fetch(`${getClerkBaseUrl()}/v1/me/organization_memberships`, {
-			headers: {
-				Authorization: `Bearer ${this.credentials!.clientToken}`,
-				"User-Agent": this.userAgent(),
-			},
-			signal: AbortSignal.timeout(10000),
-		})
-
-		return clerkOrganizationMembershipsSchema.parse(await response.json()).response
-	}
-
-	private async clerkLogout(credentials: AuthCredentials): Promise<void> {
-		const formData = new URLSearchParams()
-		formData.append("_is_native", "1")
-
-		const response = await fetch(`${getClerkBaseUrl()}/v1/client/sessions/${credentials.sessionId}/remove`, {
-			method: "POST",
-			headers: {
-				"Content-Type": "application/x-www-form-urlencoded",
-				Authorization: `Bearer ${credentials.clientToken}`,
-				"User-Agent": this.userAgent(),
-			},
-			body: formData.toString(),
-			signal: AbortSignal.timeout(10000),
-		})
-
-		if (!response.ok) {
-			throw new Error(`HTTP ${response.status}: ${response.statusText}`)
-		}
-	}
-
-	private userAgent(): string {
-		return getUserAgent(this.context)
-	}
-}

+ 0 - 3
packages/cloud/src/auth/index.ts

@@ -1,3 +0,0 @@
-export type { AuthService, AuthServiceEvents, AuthState } from "./AuthService"
-export { WebAuthService } from "./WebAuthService"
-export { StaticTokenAuthService } from "./StaticTokenAuthService"

+ 0 - 5
packages/cloud/src/config.ts

@@ -1,5 +0,0 @@
-export const PRODUCTION_CLERK_BASE_URL = "https://clerk.roocode.com"
-export const PRODUCTION_ROO_CODE_API_URL = "https://app.roocode.com"
-
-export const getClerkBaseUrl = () => process.env.CLERK_BASE_URL || PRODUCTION_CLERK_BASE_URL
-export const getRooCodeApiUrl = () => process.env.ROO_CODE_API_URL || PRODUCTION_ROO_CODE_API_URL

+ 0 - 42
packages/cloud/src/errors.ts

@@ -1,42 +0,0 @@
-export class CloudAPIError extends Error {
-	constructor(
-		message: string,
-		public statusCode?: number,
-		public responseBody?: unknown,
-	) {
-		super(message)
-		this.name = "CloudAPIError"
-		Object.setPrototypeOf(this, CloudAPIError.prototype)
-	}
-}
-
-export class TaskNotFoundError extends CloudAPIError {
-	constructor(taskId?: string) {
-		super(taskId ? `Task '${taskId}' not found` : "Task not found", 404)
-		this.name = "TaskNotFoundError"
-		Object.setPrototypeOf(this, TaskNotFoundError.prototype)
-	}
-}
-
-export class AuthenticationError extends CloudAPIError {
-	constructor(message = "Authentication required") {
-		super(message, 401)
-		this.name = "AuthenticationError"
-		Object.setPrototypeOf(this, AuthenticationError.prototype)
-	}
-}
-
-export class NetworkError extends CloudAPIError {
-	constructor(message = "Network error occurred") {
-		super(message)
-		this.name = "NetworkError"
-		Object.setPrototypeOf(this, NetworkError.prototype)
-	}
-}
-
-export class InvalidClientTokenError extends Error {
-	constructor() {
-		super("Invalid/Expired client token")
-		Object.setPrototypeOf(this, InvalidClientTokenError.prototype)
-	}
-}

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

@@ -1,4 +0,0 @@
-export * from "./config"
-
-export * from "./CloudAPI"
-export * from "./CloudService"

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

@@ -1,4 +0,0 @@
-import { AuthServiceEvents } from "./auth"
-import { SettingsServiceEvents } from "./CloudSettingsService"
-
-export type CloudServiceEvents = AuthServiceEvents & SettingsServiceEvents

+ 0 - 10
packages/cloud/src/utils.ts

@@ -1,10 +0,0 @@
-import * as vscode from "vscode"
-
-/**
- * Get the User-Agent string for API requests
- * @param context Optional extension context for more accurate version detection
- * @returns User-Agent string in format "Roo-Code {version}"
- */
-export function getUserAgent(context?: vscode.ExtensionContext): string {
-	return `Roo-Code ${context?.extension?.packageJSON?.version || "unknown"}`
-}

+ 0 - 5
packages/cloud/tsconfig.json

@@ -1,5 +0,0 @@
-{
-	"extends": "@roo-code/config-typescript/vscode-library.json",
-	"include": ["src"],
-	"exclude": ["node_modules"]
-}

+ 0 - 14
packages/cloud/vitest.config.ts

@@ -1,14 +0,0 @@
-import { defineConfig } from "vitest/config"
-
-export default defineConfig({
-	test: {
-		globals: true,
-		environment: "node",
-		watch: false,
-	},
-	resolve: {
-		alias: {
-			vscode: new URL("./src/__mocks__/vscode.ts", import.meta.url).pathname,
-		},
-	},
-})

+ 0 - 2
packages/evals/Dockerfile.runner

@@ -84,7 +84,6 @@ WORKDIR /roo/repo
 RUN mkdir -p \
   scripts \
   packages/build \
-  packages/cloud \
   packages/config-eslint \
   packages/config-typescript \
   packages/evals \
@@ -99,7 +98,6 @@ COPY ./pnpm-lock.yaml                          ./
 COPY ./pnpm-workspace.yaml                     ./
 COPY ./scripts/bootstrap.mjs                   ./scripts/
 COPY ./packages/build/package.json             ./packages/build/
-COPY ./packages/cloud/package.json             ./packages/cloud/
 COPY ./packages/config-eslint/package.json     ./packages/config-eslint/
 COPY ./packages/config-typescript/package.json ./packages/config-typescript/
 COPY ./packages/evals/package.json             ./packages/evals/

+ 137 - 82
pnpm-lock.yaml

@@ -353,34 +353,6 @@ importers:
         specifier: ^3.2.3
         version: 3.2.4(@types/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
 
-  packages/cloud:
-    dependencies:
-      '@roo-code/telemetry':
-        specifier: workspace:^
-        version: link:../telemetry
-      '@roo-code/types':
-        specifier: workspace:^
-        version: link:../types
-      zod:
-        specifier: ^3.25.61
-        version: 3.25.61
-    devDependencies:
-      '@roo-code/config-eslint':
-        specifier: workspace:^
-        version: link:../config-eslint
-      '@roo-code/config-typescript':
-        specifier: workspace:^
-        version: link:../config-typescript
-      '@types/node':
-        specifier: 20.x
-        version: 20.17.57
-      '@types/vscode':
-        specifier: ^1.84.0
-        version: 1.100.0
-      vitest:
-        specifier: ^3.2.3
-        version: 3.2.4(@types/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
-
   packages/config-eslint:
     devDependencies:
       '@eslint/js':
@@ -591,8 +563,8 @@ importers:
         specifier: ^1.14.0
         version: 1.14.0([email protected])
       '@roo-code/cloud':
-        specifier: workspace:^
-        version: link:../packages/cloud
+        specifier: ^0.5.0
+        version: 0.5.0
       '@roo-code/ipc':
         specifier: workspace:^
         version: link:../packages/ipc
@@ -685,7 +657,7 @@ importers:
         version: 12.0.0
       openai:
         specifier: ^5.0.0
-        version: 5.5.1([email protected].2)([email protected])
+        version: 5.5.1([email protected].3)([email protected])
       os-name:
         specifier: ^6.0.0
         version: 6.1.0
@@ -1447,6 +1419,10 @@ packages:
     resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
     engines: {node: '>=6.9.0'}
 
+  '@babel/[email protected]':
+    resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==}
+    engines: {node: '>=6.9.0'}
+
   '@babel/[email protected]':
     resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
     engines: {node: '>=6.9.0'}
@@ -1957,6 +1933,9 @@ packages:
     cpu: [x64]
     os: [win32]
 
+  '@ioredis/[email protected]':
+    resolution: {integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==}
+
   '@isaacs/[email protected]':
     resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
     engines: {node: '>=12'}
@@ -2008,16 +1987,16 @@ packages:
   '@libsql/[email protected]':
     resolution: {integrity: sha512-TskygwF+ToZeWhPPT0WennyGrP3tmkKraaKopT2YwUjqD6DWDRm6SG5iy0VqnaO+HC9FNBCDX0oQPODU3gqqPQ==}
 
-  '@libsql/[email protected].9':
-    resolution: {integrity: sha512-4OVdeAmuaCUq5hYT8NNn0nxlO9AcA/eTjXfUZ+QK8MT3Dz7Z76m73x7KxjU6I64WyXX98dauVH2b9XM+d84npw==}
+  '@libsql/[email protected].10':
+    resolution: {integrity: sha512-fAMD+GnGQNdZ9zxeNC8AiExpKnou/97GJWkiDDZbTRHj3c9dvF1y4jsRQ0WE72m/CqTdbMGyU98yL0SJ9hQVeg==}
 
-  '@libsql/[email protected]3':
-    resolution: {integrity: sha512-ASz/EAMLDLx3oq9PVvZ4zBXXHbz2TxtxUwX2xpTRFR4V4uSHAN07+jpLu3aK5HUBLuv58z7+GjaL5w/cyjR28Q==}
+  '@libsql/[email protected]7':
+    resolution: {integrity: sha512-WTYG2skZsUnZmfZ2v7WFj7s3/5s2PfrYBZOWBKOnxHA8g4XCDc/4bFDaqob9Q2e88+GC7cWeJ8VNkVBFpD2Xxg==}
     cpu: [arm64]
     os: [darwin]
 
-  '@libsql/[email protected]3':
-    resolution: {integrity: sha512-kzglniv1difkq8opusSXM7u9H0WoEPeKxw0ixIfcGfvlCVMJ+t9UNtXmyNHW68ljdllje6a4C6c94iPmIYafYA==}
+  '@libsql/[email protected]7':
+    resolution: {integrity: sha512-ab0RlTR4KYrxgjNrZhAhY/10GibKoq6G0W4oi0kdm+eYiAv/Ip8GDMpSaZdAcoKA4T+iKR/ehczKHnMEB8MFxA==}
     cpu: [x64]
     os: [darwin]
 
@@ -2031,38 +2010,38 @@ packages:
   '@libsql/[email protected]':
     resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==}
 
-  '@libsql/[email protected]3':
-    resolution: {integrity: sha512-UEW+VZN2r0mFkfztKOS7cqfS8IemuekbjUXbXCwULHtusww2QNCXvM5KU9eJCNE419SZCb0qaEWYytcfka8qeA==}
+  '@libsql/[email protected]7':
+    resolution: {integrity: sha512-PcASh4k47RqC+kMWAbLUKf1y6Do0q8vnUGi0yhKY4ghJcimMExViBimjbjYRSa+WIb/zh3QxNoXOhQAXx3tiuw==}
     cpu: [arm]
     os: [linux]
 
-  '@libsql/[email protected]3':
-    resolution: {integrity: sha512-NMDgLqryYBv4Sr3WoO/m++XDjR5KLlw9r/JK4Ym6A1XBv2bxQQNhH0Lxx3bjLW8qqhBD4+0xfms4d2cOlexPyA==}
+  '@libsql/[email protected]7':
+    resolution: {integrity: sha512-vxOkSLG9Wspit+SNle84nuIzMtr2G2qaxFzW7BhsZBjlZ8+kErf9RXcT2YJQdJYxmBYRbsOrc91gg0jLEQVCqg==}
     cpu: [arm]
     os: [linux]
 
-  '@libsql/[email protected]3':
-    resolution: {integrity: sha512-/wCxVdrwl1ee6D6LEjwl+w4SxuLm5UL9Kb1LD5n0bBGs0q+49ChdPPh7tp175iRgkcrTgl23emymvt1yj3KxVQ==}
+  '@libsql/[email protected]7':
+    resolution: {integrity: sha512-L8jnaN01TxjBJlDuDTX2W2BKzBkAOhcnKfCOf3xzvvygblxnDOK0whkYwIXeTfwtd/rr4jN/d6dZD/bcHiDxEQ==}
     cpu: [arm64]
     os: [linux]
 
-  '@libsql/[email protected]3':
-    resolution: {integrity: sha512-xnVAbZIanUgX57XqeI5sNaDnVilp0Di5syCLSEo+bRyBobe/1IAeehNZpyVbCy91U2N6rH1C/mZU7jicVI9x+A==}
+  '@libsql/[email protected]7':
+    resolution: {integrity: sha512-HfFD7TzQtmmTwyQsuiHhWZdMRtdNpKJ1p4tbMMTMRECk+971NFHrj69D64cc2ClVTAmn7fA9XibKPil7WN/Q7w==}
     cpu: [arm64]
     os: [linux]
 
-  '@libsql/[email protected]3':
-    resolution: {integrity: sha512-/mfMRxcQAI9f8t7tU3QZyh25lXgXKzgin9B9TOSnchD73PWtsVhlyfA6qOCfjQl5kr4sHscdXD5Yb3KIoUgrpQ==}
+  '@libsql/[email protected]7':
+    resolution: {integrity: sha512-5l3XxWqUPVFrtX0xnZaXwqsXs0BFbP4w6ahRFTPSdXU50YBfUOajFznJRB6bJTMsCvraDSD0IkHhjSNfrE1CuQ==}
     cpu: [x64]
     os: [linux]
 
-  '@libsql/[email protected]3':
-    resolution: {integrity: sha512-rdefPTpQCVwUjIQYbDLMv3qpd5MdrT0IeD0UZPGqhT9AWU8nJSQoj2lfyIDAWEz7PPOVCY4jHuEn7FS2sw9kRA==}
+  '@libsql/[email protected]7':
+    resolution: {integrity: sha512-FvSpWlwc+dIeYIFYlsSv+UdQ/NiZWr+SstwVji+QZ//8NnvzwWQU9cgP+Vpps6Qiq4jyYQm9chJhTYOVT9Y3BA==}
     cpu: [x64]
     os: [linux]
 
-  '@libsql/[email protected]3':
-    resolution: {integrity: sha512-aNcmDrD1Ws+dNZIv9ECbxBQumqB9MlSVEykwfXJpqv/593nABb8Ttg5nAGUPtnADyaGDTrGvPPP81d/KsKho4Q==}
+  '@libsql/[email protected]7':
+    resolution: {integrity: sha512-f5bGH8+3A5sn6Lrqg8FsQ09a1pYXPnKGXGTFiAYlfQXVst1tUTxDTugnuWcJYKXyzDe/T7ccxyIZXeSmPOhq8A==}
     cpu: [x64]
     os: [win32]
 
@@ -3086,6 +3065,12 @@ packages:
     cpu: [x64]
     os: [win32]
 
+  '@roo-code/[email protected]':
+    resolution: {integrity: sha512-4u6Ce2Rmr5a9nxhjGUMRRWUWhZc63EmF/UJ/+Az5/1JARMOp0kHN5Pwqz2QAgfD137+TFSBKQORpiN0GXrdt2w==}
+
+  '@roo-code/[email protected]':
+    resolution: {integrity: sha512-3xbW4pYaCgWuHF5qOsiXpIcd281dlFTe1zboUGgcUUsB414Hu3pQI86PdgJxVGtZgxtaca0eHTQ2Sqjqq8nPlA==}
+
   '@sec-ant/[email protected]':
     resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
 
@@ -3886,8 +3871,8 @@ packages:
   '@types/[email protected]':
     resolution: {integrity: sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA==}
 
-  '@types/[email protected].4':
-    resolution: {integrity: sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==}
+  '@types/[email protected].9':
+    resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==}
 
   '@types/[email protected]':
     resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==}
@@ -5105,6 +5090,10 @@ packages:
     resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
     engines: {node: '>=0.4.0'}
 
+  [email protected]:
+    resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
+    engines: {node: '>=0.10'}
+
   [email protected]:
     resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
     engines: {node: '>= 0.8'}
@@ -6278,6 +6267,10 @@ packages:
     resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
     engines: {node: '>=12'}
 
+  [email protected]:
+    resolution: {integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==}
+    engines: {node: '>=12.22.0'}
+
   [email protected]:
     resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
     engines: {node: '>= 12'}
@@ -6745,8 +6738,8 @@ packages:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
 
-  [email protected]3:
-    resolution: {integrity: sha512-5Bwoa/CqzgkTwySgqHA5TsaUDRrdLIbdM4egdPcaAnqO3aC+qAgS6BwdzuZwARA5digXwiskogZ8H7Yy4XfdOg==}
+  [email protected]7:
+    resolution: {integrity: sha512-RRlj5XQI9+Wq+/5UY8EnugSWfRmHEw4hn3DKlPrkUgZONsge1PwTtHcpStP6MSNi8ohcbsRgEHJaymA33a8cBw==}
     cpu: [x64, arm64, wasm32, arm]
     os: [darwin, linux, win32]
 
@@ -6946,6 +6939,9 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
 
+  [email protected]:
+    resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
+
   [email protected]:
     resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
 
@@ -8269,6 +8265,14 @@ packages:
     resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
     engines: {node: '>=8'}
 
+  [email protected]:
+    resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
+    engines: {node: '>=4'}
+
+  [email protected]:
+    resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
+    engines: {node: '>=4'}
+
   [email protected]:
     resolution: {integrity: sha512-x7vpciikEY7nptGzQrE5I+/pvwFZJDadPk/uEoyGSg/pZ2m/CX2n5EhSgUh+S5T7Gz3uKM6YzWcXEu3ioAsdFQ==}
     engines: {node: '>= 18'}
@@ -8682,6 +8686,9 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==}
 
+  [email protected]:
+    resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
+
   [email protected]:
     resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
     engines: {node: '>= 0.8'}
@@ -9781,6 +9788,9 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-fzfJgUw78LTNnHujj9re1Ov/JJQkRZZGDMcYqSx7Hp4rPOkKywaFHq0S6GoHeXs0wGNE/sIOutkXgnwzrVOGCQ==}
 
+  [email protected]:
+    resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+
   [email protected]:
     resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
 
@@ -10535,6 +10545,8 @@ snapshots:
 
   '@babel/[email protected]': {}
 
+  '@babel/[email protected]': {}
+
   '@babel/[email protected]':
     dependencies:
       '@babel/code-frame': 7.27.1
@@ -11076,6 +11088,8 @@ snapshots:
   '@img/[email protected]':
     optional: true
 
+  '@ioredis/[email protected]': {}
+
   '@isaacs/[email protected]':
     dependencies:
       string-width: 5.1.2
@@ -11135,25 +11149,25 @@ snapshots:
 
   '@libsql/[email protected]':
     dependencies:
-      '@libsql/core': 0.15.9
+      '@libsql/core': 0.15.10
       '@libsql/hrana-client': 0.7.0
       js-base64: 3.7.7
-      libsql: 0.5.13
+      libsql: 0.5.17
       promise-limit: 2.7.0
     transitivePeerDependencies:
       - bufferutil
       - utf-8-validate
     optional: true
 
-  '@libsql/[email protected].9':
+  '@libsql/[email protected].10':
     dependencies:
       js-base64: 3.7.7
     optional: true
 
-  '@libsql/[email protected]3':
+  '@libsql/[email protected]7':
     optional: true
 
-  '@libsql/[email protected]3':
+  '@libsql/[email protected]7':
     optional: true
 
   '@libsql/[email protected]':
@@ -11179,25 +11193,25 @@ snapshots:
       - utf-8-validate
     optional: true
 
-  '@libsql/[email protected]3':
+  '@libsql/[email protected]7':
     optional: true
 
-  '@libsql/[email protected]3':
+  '@libsql/[email protected]7':
     optional: true
 
-  '@libsql/[email protected]3':
+  '@libsql/[email protected]7':
     optional: true
 
-  '@libsql/[email protected]3':
+  '@libsql/[email protected]7':
     optional: true
 
-  '@libsql/[email protected]3':
+  '@libsql/[email protected]7':
     optional: true
 
-  '@libsql/[email protected]3':
+  '@libsql/[email protected]7':
     optional: true
 
-  '@libsql/[email protected]3':
+  '@libsql/[email protected]7':
     optional: true
 
   '@lmstudio/[email protected]':
@@ -12177,6 +12191,19 @@ snapshots:
   '@rollup/[email protected]':
     optional: true
 
+  '@roo-code/[email protected]':
+    dependencies:
+      '@roo-code/types': 1.44.0
+      ioredis: 5.7.0
+      p-wait-for: 5.0.2
+      zod: 3.25.76
+    transitivePeerDependencies:
+      - supports-color
+
+  '@roo-code/[email protected]':
+    dependencies:
+      zod: 3.25.76
+
   '@sec-ant/[email protected]': {}
 
   '@sevinf/[email protected]': {}
@@ -12876,7 +12903,7 @@ snapshots:
   '@testing-library/[email protected]':
     dependencies:
       '@babel/code-frame': 7.27.1
-      '@babel/runtime': 7.27.6
+      '@babel/runtime': 7.28.2
       '@types/aria-query': 5.0.4
       aria-query: 5.3.0
       chalk: 4.1.2
@@ -13164,7 +13191,7 @@ snapshots:
     dependencies:
       undici-types: 6.21.0
 
-  '@types/[email protected].4':
+  '@types/[email protected].9':
     dependencies:
       undici-types: 6.21.0
     optional: true
@@ -13232,7 +13259,7 @@ snapshots:
 
   '@types/[email protected]':
     dependencies:
-      '@types/node': 20.19.4
+      '@types/node': 20.19.9
     optional: true
 
   '@types/[email protected]': {}
@@ -14557,6 +14584,8 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]: {}
@@ -15936,6 +15965,20 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]:
+    dependencies:
+      '@ioredis/commands': 1.3.0
+      cluster-key-slot: 1.1.2
+      debug: 4.4.1([email protected])
+      denque: 2.1.0
+      lodash.defaults: 4.2.0
+      lodash.isarguments: 3.1.0
+      redis-errors: 1.2.0
+      redis-parser: 3.0.0
+      standard-as-callback: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+
   [email protected]:
     dependencies:
       jsbn: 1.1.0
@@ -16426,20 +16469,20 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
-  [email protected]3:
+  [email protected]7:
     dependencies:
       '@neon-rs/load': 0.0.4
       detect-libc: 2.0.2
     optionalDependencies:
-      '@libsql/darwin-arm64': 0.5.13
-      '@libsql/darwin-x64': 0.5.13
-      '@libsql/linux-arm-gnueabihf': 0.5.13
-      '@libsql/linux-arm-musleabihf': 0.5.13
-      '@libsql/linux-arm64-gnu': 0.5.13
-      '@libsql/linux-arm64-musl': 0.5.13
-      '@libsql/linux-x64-gnu': 0.5.13
-      '@libsql/linux-x64-musl': 0.5.13
-      '@libsql/win32-x64-msvc': 0.5.13
+      '@libsql/darwin-arm64': 0.5.17
+      '@libsql/darwin-x64': 0.5.17
+      '@libsql/linux-arm-gnueabihf': 0.5.17
+      '@libsql/linux-arm-musleabihf': 0.5.17
+      '@libsql/linux-arm64-gnu': 0.5.17
+      '@libsql/linux-arm64-musl': 0.5.17
+      '@libsql/linux-x64-gnu': 0.5.17
+      '@libsql/linux-x64-musl': 0.5.17
+      '@libsql/win32-x64-msvc': 0.5.17
     optional: true
 
   [email protected]:
@@ -16604,6 +16647,8 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]: {}
@@ -17520,9 +17565,9 @@ snapshots:
       is-inside-container: 1.0.0
       is-wsl: 3.1.0
 
-  [email protected]([email protected].2)([email protected]):
+  [email protected]([email protected].3)([email protected]):
     optionalDependencies:
-      ws: 8.18.2
+      ws: 8.18.3
       zod: 3.25.61
 
   [email protected]: {}
@@ -18272,6 +18317,12 @@ snapshots:
       indent-string: 4.0.0
       strip-indent: 3.0.0
 
+  [email protected]: {}
+
+  [email protected]:
+    dependencies:
+      redis-errors: 1.2.0
+
   [email protected]:
     dependencies:
       '@redis/bloom': 5.5.5(@redis/[email protected])
@@ -18825,6 +18876,8 @@ snapshots:
       stack-generator: 2.0.10
       stacktrace-gps: 3.1.2
 
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]: {}
@@ -20142,4 +20195,6 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]: {}
+
   [email protected]: {}

+ 13 - 1
src/extension.ts

@@ -76,12 +76,25 @@ export async function activate(context: vscode.ExtensionContext) {
 
 	// Initialize Roo Code Cloud service.
 	const cloudService = await CloudService.createInstance(context, cloudLogger)
+
+	try {
+		if (cloudService.telemetryClient) {
+			TelemetryService.instance.register(cloudService.telemetryClient)
+		}
+	} catch (error) {
+		outputChannel.appendLine(
+			`[CloudService] Failed to register TelemetryClient: ${error instanceof Error ? error.message : String(error)}`,
+		)
+	}
+
 	const postStateListener = () => {
 		ClineProvider.getVisibleInstance()?.postStateToWebview()
 	}
+
 	cloudService.on("auth-state-changed", postStateListener)
 	cloudService.on("user-info", postStateListener)
 	cloudService.on("settings-updated", postStateListener)
+
 	// Add to subscriptions for proper cleanup on deactivate
 	context.subscriptions.push(cloudService)
 
@@ -200,7 +213,6 @@ export async function activate(context: vscode.ExtensionContext) {
 			{ path: context.extensionPath, name: "extension" },
 			{ path: path.join(context.extensionPath, "../packages/types"), name: "types" },
 			{ path: path.join(context.extensionPath, "../packages/telemetry"), name: "telemetry" },
-			{ path: path.join(context.extensionPath, "../packages/cloud"), name: "cloud" },
 		]
 
 		console.log(

+ 1 - 1
src/package.json

@@ -420,7 +420,7 @@
 		"@mistralai/mistralai": "^1.3.6",
 		"@modelcontextprotocol/sdk": "^1.9.0",
 		"@qdrant/js-client-rest": "^1.14.0",
-		"@roo-code/cloud": "workspace:^",
+		"@roo-code/cloud": "^0.5.0",
 		"@roo-code/ipc": "workspace:^",
 		"@roo-code/telemetry": "workspace:^",
 		"@roo-code/types": "workspace:^",