add interval to fetch and set remote config (#6813)

* add interval to fetch and set remote config

* clear the remote config if the user goes from an org to a private account
This commit is contained in:
celestial-vault
2025-10-13 15:51:52 -07:00
committed by GitHub
parent cdffc002eb
commit 98c84cdc8f
3 changed files with 74 additions and 14 deletions
+35 -5
View File
@@ -42,6 +42,7 @@ import {
GlobalFileNames,
writeMcpMarketplaceCatalogToCache,
} from "../storage/disk"
import { fetchRemoteConfig } from "../storage/remote-config/fetch"
import { PersistenceErrorEvent, StateManager } from "../storage/StateManager"
import { Task } from "../task"
import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceCatalog"
@@ -67,6 +68,9 @@ export class Controller {
// NEW: Add workspace manager (optional initially)
private workspaceManager?: WorkspaceRootManager
// Timer for periodic remote config fetching
private remoteConfigTimer?: NodeJS.Timeout
// Public getter for workspace manager with lazy initialization - To get workspaces when task isn't initialized (Used by file mentions)
async ensureWorkspaceManager(): Promise<WorkspaceRootManager | undefined> {
if (!this.workspaceManager) {
@@ -87,15 +91,28 @@ export class Controller {
return this.workspaceManager
}
/**
* Starts the periodic remote config fetching timer
* Fetches immediately and then every 30 seconds
*/
private startRemoteConfigTimer() {
// Initial fetch
fetchRemoteConfig(this).catch((error) => {
console.error("Failed to fetch remote config:", error)
})
// Set up 30-second interval
this.remoteConfigTimer = setInterval(() => {
fetchRemoteConfig(this).catch((error) => {
console.error("Failed to fetch remote config:", error)
})
}, 30000) // 30 seconds
}
constructor(readonly context: vscode.ExtensionContext) {
PromptRegistry.getInstance() // Ensure prompts and tools are registered
HostProvider.get().logToChannel("ClineProvider instantiated")
this.stateManager = StateManager.get()
this.authService = AuthService.getInstance(this)
this.ocaAuthService = OcaAuthService.initialize(this)
this.accountService = ClineAccountService.getInstance()
this.authService.restoreRefreshTokenAndRetrieveAuthInfo()
StateManager.get().registerCallbacks({
onPersistenceError: async ({ error }: PersistenceErrorEvent) => {
console.error("[Controller] Cache persistence failed, recovering:", error)
@@ -118,6 +135,13 @@ export class Controller {
await this.postStateToWebview()
},
})
this.authService = AuthService.getInstance(this)
this.ocaAuthService = OcaAuthService.initialize(this)
this.accountService = ClineAccountService.getInstance()
this.authService.restoreRefreshTokenAndRetrieveAuthInfo().then(() => {
this.startRemoteConfigTimer()
})
this.mcpHub = new McpHub(
() => ensureMcpServersDirectoryExists(),
@@ -138,6 +162,12 @@ export class Controller {
- https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
*/
async dispose() {
// Clear the remote config timer
if (this.remoteConfigTimer) {
clearInterval(this.remoteConfigTimer)
this.remoteConfigTimer = undefined
}
await this.clearTask()
this.mcpHub.dispose()
+12
View File
@@ -315,6 +315,18 @@ export async function writeRemoteConfigToCache(organizationId: string, config: R
}
}
export async function deleteRemoteConfigFromCache(organizationId: string): Promise<void> {
try {
const remoteConfigFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.remoteConfig(organizationId))
const fileExists = await fileExistsAtPath(remoteConfigFilePath)
if (fileExists) {
await fs.unlink(remoteConfigFilePath)
}
} catch (error) {
console.error("Failed to delete remote config from cache:", error)
}
}
/**
* Gets the paths to the workspace's .clinerules/hooks directories to search for
* hooks. A workspace may not use hooks, and the resulting array will be empty. A
+27 -9
View File
@@ -1,9 +1,11 @@
import axios, { AxiosRequestConfig, AxiosResponse } from "axios"
import { Controller } from "@/core/controller"
import { clineEnvConfig } from "../../../config"
import { AuthService } from "../../../services/auth/AuthService"
import { CLINE_API_ENDPOINT } from "../../../shared/cline/api"
import { RemoteConfig, RemoteConfigSchema } from "../../../shared/remote-config/schema"
import { readRemoteConfigFromCache, writeRemoteConfigToCache } from "../disk"
import { deleteRemoteConfigFromCache, readRemoteConfigFromCache, writeRemoteConfigToCache } from "../disk"
import { StateManager } from "../StateManager"
import { applyRemoteConfig } from "./utils"
/**
@@ -13,12 +15,15 @@ import { applyRemoteConfig } from "./utils"
* @returns Promise resolving to the RemoteConfig object, or undefined if no active organization exists
* @throws Error if both API fetch and cache retrieval fail (when an organization exists)
*/
export async function fetchRemoteConfig(): Promise<RemoteConfig | undefined> {
export async function fetchRemoteConfig(controller: Controller): Promise<RemoteConfig | undefined> {
const authService = AuthService.getInstance()
// Get the active organization ID
const organizationId = authService.getActiveOrganizationId()
if (!organizationId) {
// Clear the in-memory cache of the remote config settings in case it was previously set with an organization that has remote config
StateManager.get().clearRemoteConfig()
return undefined
}
@@ -74,6 +79,12 @@ export async function fetchRemoteConfig(): Promise<RemoteConfig | undefined> {
// Check if config is enabled
if (!configData.enabled) {
// Clear the remote config from the on-disk cache if it exists
await deleteRemoteConfigFromCache(organizationId)
// Clear the in-memory cache of the remote config settings in case it was previously set
StateManager.get().clearRemoteConfig()
return undefined
}
@@ -89,6 +100,8 @@ export async function fetchRemoteConfig(): Promise<RemoteConfig | undefined> {
// Apply config to StateManager
applyRemoteConfig(validatedConfig)
controller.postStateToWebview()
return validatedConfig
} catch (error) {
console.error("Failed to fetch remote config from API:", error)
@@ -96,16 +109,21 @@ export async function fetchRemoteConfig(): Promise<RemoteConfig | undefined> {
// Try to fall back to cached config
const cachedConfig = await readRemoteConfigFromCache(organizationId)
if (cachedConfig) {
// Validate cached config against schema
const validatedCachedConfig = RemoteConfigSchema.parse(cachedConfig)
// Apply config to StateManager
applyRemoteConfig(validatedCachedConfig)
return validatedCachedConfig
try {
// Validate cached config against schema
const validatedCachedConfig = RemoteConfigSchema.parse(cachedConfig)
// Apply config to StateManager
applyRemoteConfig(validatedCachedConfig)
return validatedCachedConfig
} catch (validationError) {
// Cache validation failed - log and fall through
console.error("Cached config validation failed:", validationError)
}
}
// Both API and cache failed
// Both API and cache failed (or cache was invalid)
throw new Error(
`Failed to fetch remote config: ${error instanceof Error ? error.message : "Unknown error"}. No cached config available.`,
`Failed to fetch remote config: ${error instanceof Error ? error.message : "Unknown error"}. No valid cached config available.`,
)
}
}