Skip to content

Commit 9a5f6ce

Browse files
authored
fix: resolve marketplace timeout issues and display installed MCPs (#4817) (#4941)
1 parent d1eecb9 commit 9a5f6ce

File tree

9 files changed

+137
-22
lines changed

9 files changed

+137
-22
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
type TerminalActionPromptType,
2424
type HistoryItem,
2525
type CloudUserInfo,
26+
type MarketplaceItem,
2627
requestyDefaultModelId,
2728
openRouterDefaultModelId,
2829
glamaDefaultModelId,
@@ -37,7 +38,7 @@ import { Package } from "../../shared/package"
3738
import { findLast } from "../../shared/array"
3839
import { supportPrompt } from "../../shared/support-prompt"
3940
import { GlobalFileNames } from "../../shared/globalFileNames"
40-
import { ExtensionMessage } from "../../shared/ExtensionMessage"
41+
import { ExtensionMessage, MarketplaceInstalledMetadata } from "../../shared/ExtensionMessage"
4142
import { Mode, defaultModeSlug } from "../../shared/modes"
4243
import { experimentDefault, experiments, EXPERIMENT_IDS } from "../../shared/experiments"
4344
import { formatLanguage } from "../../shared/language"
@@ -1254,6 +1255,46 @@ export class ClineProvider
12541255
}
12551256
}
12561257

1258+
/**
1259+
* Fetches marketplace data on demand to avoid blocking main state updates
1260+
*/
1261+
async fetchMarketplaceData() {
1262+
try {
1263+
const [marketplaceItems, marketplaceInstalledMetadata] = await Promise.all([
1264+
this.marketplaceManager.getCurrentItems().catch((error) => {
1265+
console.error("Failed to fetch marketplace items:", error)
1266+
return [] as MarketplaceItem[]
1267+
}),
1268+
this.marketplaceManager.getInstallationMetadata().catch((error) => {
1269+
console.error("Failed to fetch installation metadata:", error)
1270+
return { project: {}, global: {} } as MarketplaceInstalledMetadata
1271+
}),
1272+
])
1273+
1274+
// Send marketplace data separately
1275+
this.postMessageToWebview({
1276+
type: "marketplaceData",
1277+
marketplaceItems: marketplaceItems || [],
1278+
marketplaceInstalledMetadata: marketplaceInstalledMetadata || { project: {}, global: {} },
1279+
})
1280+
} catch (error) {
1281+
console.error("Failed to fetch marketplace data:", error)
1282+
// Send empty data on error to prevent UI from hanging
1283+
this.postMessageToWebview({
1284+
type: "marketplaceData",
1285+
marketplaceItems: [],
1286+
marketplaceInstalledMetadata: { project: {}, global: {} },
1287+
})
1288+
1289+
// Show user-friendly error notification for network issues
1290+
if (error instanceof Error && error.message.includes("timeout")) {
1291+
vscode.window.showWarningMessage(
1292+
"Marketplace data could not be loaded due to network restrictions. Core functionality remains available.",
1293+
)
1294+
}
1295+
}
1296+
}
1297+
12571298
/**
12581299
* Checks if there is a file-based system prompt override for the given mode
12591300
*/
@@ -1342,21 +1383,12 @@ export class ClineProvider
13421383
const allowedCommands = vscode.workspace.getConfiguration(Package.name).get<string[]>("allowedCommands") || []
13431384
const cwd = this.cwd
13441385

1345-
// Fetch marketplace data
1346-
let marketplaceItems: any[] = []
1347-
let marketplaceInstalledMetadata: any = { project: {}, global: {} }
1348-
1349-
marketplaceItems = (await this.marketplaceManager.getCurrentItems()) || []
1350-
marketplaceInstalledMetadata = await this.marketplaceManager.getInstallationMetadata()
1351-
13521386
// Check if there's a system prompt override for the current mode
13531387
const currentMode = mode ?? defaultModeSlug
13541388
const hasSystemPromptOverride = await this.hasFileBasedSystemPromptOverride(currentMode)
13551389

13561390
return {
13571391
version: this.context.extension?.packageJSON?.version ?? "",
1358-
marketplaceItems,
1359-
marketplaceInstalledMetadata,
13601392
apiConfiguration,
13611393
customInstructions,
13621394
alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,

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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ import { Mode } from "./modes"
1818
import { RouterModels } from "./api"
1919
import type { MarketplaceItem } from "@roo-code/types"
2020

21+
// Type for marketplace installed metadata
22+
export interface MarketplaceInstalledMetadata {
23+
project: Record<string, { type: string }>
24+
global: Record<string, { type: string }>
25+
}
26+
2127
// Indexing status types
2228
export interface IndexingStatus {
2329
systemStatus: string
@@ -90,6 +96,7 @@ export interface ExtensionMessage {
9096
| "indexCleared"
9197
| "codebaseIndexConfig"
9298
| "marketplaceInstallResult"
99+
| "marketplaceData"
93100
text?: string
94101
payload?: any // Add a generic payload for now, can refine later
95102
action?:
@@ -136,6 +143,8 @@ export interface ExtensionMessage {
136143
userInfo?: CloudUserInfo
137144
organizationAllowList?: OrganizationAllowList
138145
tab?: string
146+
marketplaceItems?: MarketplaceItem[]
147+
marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
139148
}
140149

141150
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: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,10 @@ 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
37+
// Note: isFetching is already true by default for initial load
3738
vscode.postMessage({
38-
type: "filterMarketplaceItems",
39-
filters: {
40-
type: "",
41-
search: "",
42-
tags: [],
43-
},
39+
type: "fetchMarketplaceData",
4440
})
4541
}
4642

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

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class MarketplaceViewStateManager {
5555
return {
5656
allItems: [],
5757
displayItems: [], // Always initialize as empty array, not undefined
58-
isFetching: false,
58+
isFetching: true, // Start with loading state for initial load
5959
activeTab: "mcp",
6060
filters: {
6161
type: "",
@@ -148,8 +148,12 @@ export class MarketplaceViewStateManager {
148148
public async transition(transition: ViewStateTransition): Promise<void> {
149149
switch (transition.type) {
150150
case "FETCH_ITEMS": {
151-
// Fetch functionality removed - data comes automatically from extension
152-
// No manual fetching needed since we removed caching
151+
// Set fetching state to show loading indicator
152+
this.state = {
153+
...this.state,
154+
isFetching: true,
155+
}
156+
this.notifyStateChange()
153157
break
154158
}
155159

@@ -225,12 +229,21 @@ export class MarketplaceViewStateManager {
225229
tags: filters.tags !== undefined ? filters.tags : this.state.filters.tags,
226230
}
227231

228-
// Update state
232+
// Update filters first
229233
this.state = {
230234
...this.state,
231235
filters: updatedFilters,
232236
}
233237

238+
// Apply filters to displayItems with the updated filters
239+
const newDisplayItems = this.filterItems(this.state.allItems)
240+
241+
// Update state with filtered items
242+
this.state = {
243+
...this.state,
244+
displayItems: newDisplayItems,
245+
}
246+
234247
// Send filter message
235248
vscode.postMessage({
236249
type: "filterMarketplaceItems",
@@ -341,5 +354,28 @@ export class MarketplaceViewStateManager {
341354
void this.transition({ type: "FETCH_ITEMS" })
342355
}
343356
}
357+
358+
// Handle marketplace data updates (fetched on demand)
359+
if (message.type === "marketplaceData") {
360+
const marketplaceItems = message.marketplaceItems
361+
362+
if (marketplaceItems !== undefined) {
363+
// Always use the marketplace items from the extension when they're provided
364+
// This ensures fresh data is always displayed
365+
const items = [...marketplaceItems]
366+
const newDisplayItems = this.isFilterActive() ? this.filterItems(items) : items
367+
368+
// Update state in a single operation
369+
this.state = {
370+
...this.state,
371+
isFetching: false,
372+
allItems: items,
373+
displayItems: newDisplayItems,
374+
}
375+
}
376+
377+
// Notify state change
378+
this.notifyStateChange()
379+
}
344380
}
345381
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ export const MarketplaceInstallModal: React.FC<MarketplaceInstallModalProps> = (
136136
// Installation succeeded - show success state
137137
setInstallationComplete(true)
138138
setValidationError(null)
139+
140+
// Request fresh marketplace data to update installed status
141+
vscode.postMessage({
142+
type: "fetchMarketplaceData",
143+
})
139144
} else {
140145
// Installation failed - show error
141146
setValidationError(message.error || "Installation failed")

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ export const MarketplaceItemCard: React.FC<MarketplaceItemCardProps> = ({ item,
9494
mpItem: item,
9595
mpInstallOptions: { target },
9696
})
97+
98+
// Request fresh marketplace data to update installed status
99+
vscode.postMessage({
100+
type: "fetchMarketplaceData",
101+
})
97102
}}>
98103
{t("marketplace:items.card.remove")}
99104
</Button>

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
ORGANIZATION_ALLOW_ALL,
1111
} from "@roo-code/types"
1212

13-
import { ExtensionMessage, ExtensionState } from "@roo/ExtensionMessage"
13+
import { ExtensionMessage, ExtensionState, MarketplaceInstalledMetadata } from "@roo/ExtensionMessage"
1414
import { findLastIndex } from "@roo/array"
1515
import { McpServer } from "@roo/mcp"
1616
import { checkExistKey } from "@roo/checkExistApiConfig"
@@ -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?: MarketplaceInstalledMetadata
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<MarketplaceInstalledMetadata>({
224+
project: {},
225+
global: {},
226+
})
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)