fix: resolve marketplace timeout issues and display installed MCPs (#4817) (#4941)

This commit is contained in:
Daniel 2025-06-20 12:20:02 -05:00 committed by GitHub
parent d1eecb9084
commit 9a5f6ce7ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 137 additions and 22 deletions

View file

@ -23,6 +23,7 @@ import {
type TerminalActionPromptType,
type HistoryItem,
type CloudUserInfo,
type MarketplaceItem,
requestyDefaultModelId,
openRouterDefaultModelId,
glamaDefaultModelId,
@ -37,7 +38,7 @@ import { Package } from "../../shared/package"
import { findLast } from "../../shared/array"
import { supportPrompt } from "../../shared/support-prompt"
import { GlobalFileNames } from "../../shared/globalFileNames"
import { ExtensionMessage } from "../../shared/ExtensionMessage"
import { ExtensionMessage, MarketplaceInstalledMetadata } from "../../shared/ExtensionMessage"
import { Mode, defaultModeSlug } from "../../shared/modes"
import { experimentDefault, experiments, EXPERIMENT_IDS } from "../../shared/experiments"
import { formatLanguage } from "../../shared/language"
@ -1254,6 +1255,46 @@ export class ClineProvider
}
}
/**
* Fetches marketplace data on demand to avoid blocking main state updates
*/
async fetchMarketplaceData() {
try {
const [marketplaceItems, marketplaceInstalledMetadata] = await Promise.all([
this.marketplaceManager.getCurrentItems().catch((error) => {
console.error("Failed to fetch marketplace items:", error)
return [] as MarketplaceItem[]
}),
this.marketplaceManager.getInstallationMetadata().catch((error) => {
console.error("Failed to fetch installation metadata:", error)
return { project: {}, global: {} } as MarketplaceInstalledMetadata
}),
])
// Send marketplace data separately
this.postMessageToWebview({
type: "marketplaceData",
marketplaceItems: marketplaceItems || [],
marketplaceInstalledMetadata: marketplaceInstalledMetadata || { project: {}, global: {} },
})
} catch (error) {
console.error("Failed to fetch marketplace data:", error)
// Send empty data on error to prevent UI from hanging
this.postMessageToWebview({
type: "marketplaceData",
marketplaceItems: [],
marketplaceInstalledMetadata: { project: {}, global: {} },
})
// Show user-friendly error notification for network issues
if (error instanceof Error && error.message.includes("timeout")) {
vscode.window.showWarningMessage(
"Marketplace data could not be loaded due to network restrictions. Core functionality remains available.",
)
}
}
}
/**
* Checks if there is a file-based system prompt override for the given mode
*/
@ -1342,21 +1383,12 @@ export class ClineProvider
const allowedCommands = vscode.workspace.getConfiguration(Package.name).get<string[]>("allowedCommands") || []
const cwd = this.cwd
// Fetch marketplace data
let marketplaceItems: any[] = []
let marketplaceInstalledMetadata: any = { project: {}, global: {} }
marketplaceItems = (await this.marketplaceManager.getCurrentItems()) || []
marketplaceInstalledMetadata = await this.marketplaceManager.getInstallationMetadata()
// Check if there's a system prompt override for the current mode
const currentMode = mode ?? defaultModeSlug
const hasSystemPromptOverride = await this.hasFileBasedSystemPromptOverride(currentMode)
return {
version: this.context.extension?.packageJSON?.version ?? "",
marketplaceItems,
marketplaceInstalledMetadata,
apiConfiguration,
customInstructions,
alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,

View file

@ -1508,6 +1508,12 @@ export const webviewMessageHandler = async (
break
}
case "fetchMarketplaceData": {
// Fetch marketplace data on demand
await provider.fetchMarketplaceData()
break
}
case "installMarketplaceItem": {
if (marketplaceManager && message.mpItem && message.mpInstallOptions) {
try {

View file

@ -18,6 +18,12 @@ import { Mode } from "./modes"
import { RouterModels } from "./api"
import type { MarketplaceItem } from "@roo-code/types"
// Type for marketplace installed metadata
export interface MarketplaceInstalledMetadata {
project: Record<string, { type: string }>
global: Record<string, { type: string }>
}
// Indexing status types
export interface IndexingStatus {
systemStatus: string
@ -90,6 +96,7 @@ export interface ExtensionMessage {
| "indexCleared"
| "codebaseIndexConfig"
| "marketplaceInstallResult"
| "marketplaceData"
text?: string
payload?: any // Add a generic payload for now, can refine later
action?:
@ -136,6 +143,8 @@ export interface ExtensionMessage {
userInfo?: CloudUserInfo
organizationAllowList?: OrganizationAllowList
tab?: string
marketplaceItems?: MarketplaceItem[]
marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
}
export type ExtensionState = Pick<

View file

@ -167,6 +167,7 @@ export interface WebviewMessage {
| "cancelMarketplaceInstall"
| "removeInstalledMarketplaceItem"
| "marketplaceInstallResult"
| "fetchMarketplaceData"
| "switchTab"
text?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"

View file

@ -33,14 +33,10 @@ export function MarketplaceView({ stateManager, onDone }: MarketplaceViewProps)
// a filter message with empty filters, which will cause the extension to
// send back the full state including all marketplace items.
if (!hasReceivedInitialState && state.allItems.length === 0) {
// Send empty filter to trigger state update
// Fetch marketplace data on demand
// Note: isFetching is already true by default for initial load
vscode.postMessage({
type: "filterMarketplaceItems",
filters: {
type: "",
search: "",
tags: [],
},
type: "fetchMarketplaceData",
})
}

View file

@ -55,7 +55,7 @@ export class MarketplaceViewStateManager {
return {
allItems: [],
displayItems: [], // Always initialize as empty array, not undefined
isFetching: false,
isFetching: true, // Start with loading state for initial load
activeTab: "mcp",
filters: {
type: "",
@ -148,8 +148,12 @@ export class MarketplaceViewStateManager {
public async transition(transition: ViewStateTransition): Promise<void> {
switch (transition.type) {
case "FETCH_ITEMS": {
// Fetch functionality removed - data comes automatically from extension
// No manual fetching needed since we removed caching
// Set fetching state to show loading indicator
this.state = {
...this.state,
isFetching: true,
}
this.notifyStateChange()
break
}
@ -225,12 +229,21 @@ export class MarketplaceViewStateManager {
tags: filters.tags !== undefined ? filters.tags : this.state.filters.tags,
}
// Update state
// Update filters first
this.state = {
...this.state,
filters: updatedFilters,
}
// Apply filters to displayItems with the updated filters
const newDisplayItems = this.filterItems(this.state.allItems)
// Update state with filtered items
this.state = {
...this.state,
displayItems: newDisplayItems,
}
// Send filter message
vscode.postMessage({
type: "filterMarketplaceItems",
@ -341,5 +354,28 @@ export class MarketplaceViewStateManager {
void this.transition({ type: "FETCH_ITEMS" })
}
}
// Handle marketplace data updates (fetched on demand)
if (message.type === "marketplaceData") {
const marketplaceItems = message.marketplaceItems
if (marketplaceItems !== undefined) {
// Always use the marketplace items from the extension when they're provided
// This ensures fresh data is always displayed
const items = [...marketplaceItems]
const newDisplayItems = this.isFilterActive() ? this.filterItems(items) : items
// Update state in a single operation
this.state = {
...this.state,
isFetching: false,
allItems: items,
displayItems: newDisplayItems,
}
}
// Notify state change
this.notifyStateChange()
}
}
}

View file

@ -136,6 +136,11 @@ export const MarketplaceInstallModal: React.FC<MarketplaceInstallModalProps> = (
// Installation succeeded - show success state
setInstallationComplete(true)
setValidationError(null)
// Request fresh marketplace data to update installed status
vscode.postMessage({
type: "fetchMarketplaceData",
})
} else {
// Installation failed - show error
setValidationError(message.error || "Installation failed")

View file

@ -94,6 +94,11 @@ export const MarketplaceItemCard: React.FC<MarketplaceItemCardProps> = ({ item,
mpItem: item,
mpInstallOptions: { target },
})
// Request fresh marketplace data to update installed status
vscode.postMessage({
type: "fetchMarketplaceData",
})
}}>
{t("marketplace:items.card.remove")}
</Button>

View file

@ -10,7 +10,7 @@ import {
ORGANIZATION_ALLOW_ALL,
} from "@roo-code/types"
import { ExtensionMessage, ExtensionState } from "@roo/ExtensionMessage"
import { ExtensionMessage, ExtensionState, MarketplaceInstalledMetadata } from "@roo/ExtensionMessage"
import { findLastIndex } from "@roo/array"
import { McpServer } from "@roo/mcp"
import { checkExistKey } from "@roo/checkExistApiConfig"
@ -42,6 +42,8 @@ export interface ExtensionStateContextType extends ExtensionState {
setCondensingApiConfigId: (value: string) => void
customCondensingPrompt?: string
setCustomCondensingPrompt: (value: string) => void
marketplaceItems?: any[]
marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
setApiConfiguration: (config: ProviderSettings) => void
setCustomInstructions: (value?: string) => void
setAlwaysAllowReadOnly: (value: boolean) => void
@ -217,6 +219,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
const [currentCheckpoint, setCurrentCheckpoint] = useState<string>()
const [extensionRouterModels, setExtensionRouterModels] = useState<RouterModels | undefined>(undefined)
const [marketplaceItems, setMarketplaceItems] = useState<any[]>([])
const [marketplaceInstalledMetadata, setMarketplaceInstalledMetadata] = useState<MarketplaceInstalledMetadata>({
project: {},
global: {},
})
const setListApiConfigMeta = useCallback(
(value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
@ -242,6 +249,13 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setState((prevState) => mergeExtensionState(prevState, newState))
setShowWelcome(!checkExistKey(newState.apiConfiguration))
setDidHydrateState(true)
// Handle marketplace data if present in state message
if (newState.marketplaceItems !== undefined) {
setMarketplaceItems(newState.marketplaceItems)
}
if (newState.marketplaceInstalledMetadata !== undefined) {
setMarketplaceInstalledMetadata(newState.marketplaceInstalledMetadata)
}
break
}
case "theme": {
@ -288,6 +302,15 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setExtensionRouterModels(message.routerModels)
break
}
case "marketplaceData": {
if (message.marketplaceItems !== undefined) {
setMarketplaceItems(message.marketplaceItems)
}
if (message.marketplaceInstalledMetadata !== undefined) {
setMarketplaceInstalledMetadata(message.marketplaceInstalledMetadata)
}
break
}
}
},
[setListApiConfigMeta],
@ -320,6 +343,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
screenshotQuality: state.screenshotQuality,
routerModels: extensionRouterModels,
cloudIsAuthenticated: state.cloudIsAuthenticated ?? false,
marketplaceItems,
marketplaceInstalledMetadata,
setExperimentEnabled: (id, enabled) =>
setState((prevState) => ({ ...prevState, experiments: { ...prevState.experiments, [id]: enabled } })),
setApiConfiguration,