Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 42 additions & 10 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
type TerminalActionPromptType,
type HistoryItem,
type CloudUserInfo,
type MarketplaceItem,
requestyDefaultModelId,
openRouterDefaultModelId,
glamaDefaultModelId,
Expand All @@ -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"
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?:
Expand Down Expand Up @@ -136,6 +143,8 @@ export interface ExtensionMessage {
userInfo?: CloudUserInfo
organizationAllowList?: OrganizationAllowList
tab?: string
marketplaceItems?: MarketplaceItem[]
marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
}

export type ExtensionState = Pick<
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export interface WebviewMessage {
| "cancelMarketplaceInstall"
| "removeInstalledMarketplaceItem"
| "marketplaceInstallResult"
| "fetchMarketplaceData"
| "switchTab"
text?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
Expand Down
10 changes: 3 additions & 7 deletions webview-ui/src/components/marketplace/MarketplaceView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: "",
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
27 changes: 26 additions & 1 deletion webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 })),
Expand All @@ -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": {
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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,
Expand Down
Loading