Skip to content

Commit 7de562e

Browse files
committed
fix: resolve marketplace timeout issues and display installed MCPs (#4817)
- Separate marketplace data fetching from main state updates - Add lazy loading for marketplace items to prevent UI freezes - Fix marketplaceInstalledMetadata not being displayed correctly - Add dedicated fetchMarketplaceData mechanism triggered only when needed - Ensure marketplace timeouts don't block core functionality This prevents the 'timeout of 10000ms exceeded' error and UI freezes in restricted network environments by fetching marketplace data asynchronously only when the marketplace tab is accessed.
1 parent b31250d commit 7de562e

File tree

7 files changed

+88
-11
lines changed

7 files changed

+88
-11
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,33 @@ export class ClineProvider
12541254
}
12551255
}
12561256

1257+
/**
1258+
* Fetches marketplace data on demand to avoid blocking main state updates
1259+
*/
1260+
async fetchMarketplaceData() {
1261+
try {
1262+
const [marketplaceItems, marketplaceInstalledMetadata] = await Promise.all([
1263+
this.marketplaceManager.getCurrentItems().catch(() => []),
1264+
this.marketplaceManager.getInstallationMetadata().catch(() => ({ project: {}, global: {} })),
1265+
])
1266+
1267+
// Send marketplace data separately
1268+
this.postMessageToWebview({
1269+
type: "marketplaceData",
1270+
marketplaceItems: marketplaceItems || [],
1271+
marketplaceInstalledMetadata: marketplaceInstalledMetadata || { project: {}, global: {} },
1272+
})
1273+
} catch (error) {
1274+
console.error("Failed to fetch marketplace data:", error)
1275+
// Send empty data on error to prevent UI from hanging
1276+
this.postMessageToWebview({
1277+
type: "marketplaceData",
1278+
marketplaceItems: [],
1279+
marketplaceInstalledMetadata: { project: {}, global: {} },
1280+
})
1281+
}
1282+
}
1283+
12571284
/**
12581285
* Checks if there is a file-based system prompt override for the given mode
12591286
*/
@@ -1342,13 +1369,10 @@ export class ClineProvider
13421369
const allowedCommands = vscode.workspace.getConfiguration(Package.name).get<string[]>("allowedCommands") || []
13431370
const cwd = this.cwd
13441371

1345-
// Fetch marketplace data
1372+
// Initialize marketplace data as empty - will be fetched on demand
13461373
let marketplaceItems: any[] = []
13471374
let marketplaceInstalledMetadata: any = { project: {}, global: {} }
13481375

1349-
marketplaceItems = (await this.marketplaceManager.getCurrentItems()) || []
1350-
marketplaceInstalledMetadata = await this.marketplaceManager.getInstallationMetadata()
1351-
13521376
// Check if there's a system prompt override for the current mode
13531377
const currentMode = mode ?? defaultModeSlug
13541378
const hasSystemPromptOverride = await this.hasFileBasedSystemPromptOverride(currentMode)

src/core/webview/webviewMessageHandler.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,12 @@ export const webviewMessageHandler = async (
15081508
break
15091509
}
15101510

1511+
case "fetchMarketplaceData": {
1512+
// Fetch marketplace data on demand
1513+
await provider.fetchMarketplaceData()
1514+
break
1515+
}
1516+
15111517
case "installMarketplaceItem": {
15121518
if (marketplaceManager && message.mpItem && message.mpInstallOptions) {
15131519
try {

src/shared/ExtensionMessage.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export interface ExtensionMessage {
9090
| "indexCleared"
9191
| "codebaseIndexConfig"
9292
| "marketplaceInstallResult"
93+
| "marketplaceData"
9394
text?: string
9495
payload?: any // Add a generic payload for now, can refine later
9596
action?:
@@ -136,6 +137,8 @@ export interface ExtensionMessage {
136137
userInfo?: CloudUserInfo
137138
organizationAllowList?: OrganizationAllowList
138139
tab?: string
140+
marketplaceItems?: MarketplaceItem[]
141+
marketplaceInstalledMetadata?: { project: Record<string, any>; global: Record<string, any> }
139142
}
140143

141144
export type ExtensionState = Pick<

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export interface WebviewMessage {
167167
| "cancelMarketplaceInstall"
168168
| "removeInstalledMarketplaceItem"
169169
| "marketplaceInstallResult"
170+
| "fetchMarketplaceData"
170171
| "switchTab"
171172
text?: string
172173
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"

webview-ui/src/components/marketplace/MarketplaceView.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,9 @@ export function MarketplaceView({ stateManager, onDone }: MarketplaceViewProps)
3333
// a filter message with empty filters, which will cause the extension to
3434
// send back the full state including all marketplace items.
3535
if (!hasReceivedInitialState && state.allItems.length === 0) {
36-
// Send empty filter to trigger state update
36+
// Fetch marketplace data on demand
3737
vscode.postMessage({
38-
type: "filterMarketplaceItems",
39-
filters: {
40-
type: "",
41-
search: "",
42-
tags: [],
43-
},
38+
type: "fetchMarketplaceData",
4439
})
4540
}
4641

webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,5 +341,28 @@ export class MarketplaceViewStateManager {
341341
void this.transition({ type: "FETCH_ITEMS" })
342342
}
343343
}
344+
345+
// Handle marketplace data updates (fetched on demand)
346+
if (message.type === "marketplaceData") {
347+
const marketplaceItems = message.marketplaceItems
348+
349+
if (marketplaceItems !== undefined) {
350+
// Always use the marketplace items from the extension when they're provided
351+
// This ensures fresh data is always displayed
352+
const items = [...marketplaceItems]
353+
const newDisplayItems = this.isFilterActive() ? this.filterItems(items) : items
354+
355+
// Update state in a single operation
356+
this.state = {
357+
...this.state,
358+
isFetching: false,
359+
allItems: items,
360+
displayItems: newDisplayItems,
361+
}
362+
}
363+
364+
// Notify state change
365+
this.notifyStateChange()
366+
}
344367
}
345368
}

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export interface ExtensionStateContextType extends ExtensionState {
4242
setCondensingApiConfigId: (value: string) => void
4343
customCondensingPrompt?: string
4444
setCustomCondensingPrompt: (value: string) => void
45+
marketplaceItems?: any[]
46+
marketplaceInstalledMetadata?: { project: Record<string, any>; global: Record<string, any> }
4547
setApiConfiguration: (config: ProviderSettings) => void
4648
setCustomInstructions: (value?: string) => void
4749
setAlwaysAllowReadOnly: (value: boolean) => void
@@ -217,6 +219,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
217219
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
218220
const [currentCheckpoint, setCurrentCheckpoint] = useState<string>()
219221
const [extensionRouterModels, setExtensionRouterModels] = useState<RouterModels | undefined>(undefined)
222+
const [marketplaceItems, setMarketplaceItems] = useState<any[]>([])
223+
const [marketplaceInstalledMetadata, setMarketplaceInstalledMetadata] = useState<{
224+
project: Record<string, any>
225+
global: Record<string, any>
226+
}>({ project: {}, global: {} })
220227

221228
const setListApiConfigMeta = useCallback(
222229
(value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
@@ -242,6 +249,13 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
242249
setState((prevState) => mergeExtensionState(prevState, newState))
243250
setShowWelcome(!checkExistKey(newState.apiConfiguration))
244251
setDidHydrateState(true)
252+
// Handle marketplace data if present in state message
253+
if (newState.marketplaceItems !== undefined) {
254+
setMarketplaceItems(newState.marketplaceItems)
255+
}
256+
if (newState.marketplaceInstalledMetadata !== undefined) {
257+
setMarketplaceInstalledMetadata(newState.marketplaceInstalledMetadata)
258+
}
245259
break
246260
}
247261
case "theme": {
@@ -288,6 +302,15 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
288302
setExtensionRouterModels(message.routerModels)
289303
break
290304
}
305+
case "marketplaceData": {
306+
if (message.marketplaceItems !== undefined) {
307+
setMarketplaceItems(message.marketplaceItems)
308+
}
309+
if (message.marketplaceInstalledMetadata !== undefined) {
310+
setMarketplaceInstalledMetadata(message.marketplaceInstalledMetadata)
311+
}
312+
break
313+
}
291314
}
292315
},
293316
[setListApiConfigMeta],
@@ -320,6 +343,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
320343
screenshotQuality: state.screenshotQuality,
321344
routerModels: extensionRouterModels,
322345
cloudIsAuthenticated: state.cloudIsAuthenticated ?? false,
346+
marketplaceItems,
347+
marketplaceInstalledMetadata,
323348
setExperimentEnabled: (id, enabled) =>
324349
setState((prevState) => ({ ...prevState, experiments: { ...prevState.experiments, [id]: enabled } })),
325350
setApiConfiguration,

0 commit comments

Comments
 (0)