From 2bc8559c2a35b31061a4f9c66aa7aacbeb937d1a Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 1 Jun 2026 19:01:57 +0800 Subject: [PATCH] feat: push marketplace URL to runtime; fix market client base race - On connecting to the plugin runtime, push the configured space.url via the new SET_RUNTIME_CONFIG action so the runtime downloads plugins from the same Space, instead of relying on its own CLOUD_SERVICE_URL env/default. Wrapped in try/except so an older SDK without the action degrades gracefully. - web: the plugin market fetched recommendation lists (and listings) via the sync cloud client before its baseURL was resolved from system info, so it hit the default space.langbot.app. Await getCloudServiceClient() before the initial fetches and for the recommendation list. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/langbot/pkg/plugin/connector.py | 10 +++++++++ src/langbot/pkg/plugin/handler.py | 10 +++++++++ .../plugin-market/PluginMarketComponent.tsx | 21 +++++++++++++------ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/langbot/pkg/plugin/connector.py b/src/langbot/pkg/plugin/connector.py index 888eff14..5a59f1ed 100644 --- a/src/langbot/pkg/plugin/connector.py +++ b/src/langbot/pkg/plugin/connector.py @@ -103,6 +103,16 @@ class PluginRuntimeConnector(ManagedRuntimeConnector): self.handler_task = asyncio.create_task(self.handler.run()) _ = await self.handler.ping() + # Push the configured marketplace (Space) URL to the runtime so it + # downloads plugins from the same Space LangBot is bound to, rather + # than relying on the runtime's own env/default. + space_url = self.ap.instance_config.data.get('space', {}).get('url', '').rstrip('/') + if space_url: + try: + await self.handler.set_runtime_config(cloud_service_url=space_url) + self.ap.logger.info(f'Pushed marketplace URL to plugin runtime: {space_url}') + except Exception as e: + self.ap.logger.warning(f'Failed to push runtime config: {e}') self.ap.logger.info('Connected to plugin runtime.') await self.handler_task diff --git a/src/langbot/pkg/plugin/handler.py b/src/langbot/pkg/plugin/handler.py index 60922003..f5a8511e 100644 --- a/src/langbot/pkg/plugin/handler.py +++ b/src/langbot/pkg/plugin/handler.py @@ -779,6 +779,16 @@ class RuntimeConnectionHandler(handler.Handler): timeout=10, ) + async def set_runtime_config(self, cloud_service_url: str) -> dict[str, Any]: + """Push runtime configuration (e.g. marketplace URL) to the runtime.""" + return await self.call_action( + LangBotToRuntimeAction.SET_RUNTIME_CONFIG, + { + 'cloud_service_url': cloud_service_url, + }, + timeout=10, + ) + async def install_plugin( self, install_source: str, install_info: dict[str, Any] ) -> typing.AsyncGenerator[dict[str, Any], None]: diff --git a/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx b/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx index d14d2e0a..1f3efeea 100644 --- a/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx +++ b/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx @@ -29,7 +29,10 @@ import PluginMarketCardComponent from './plugin-market-card/PluginMarketCardComp import { PluginMarketCardVO } from './plugin-market-card/PluginMarketCardVO'; import { RecommendationLists } from './RecommendationLists'; import type { RecommendationList } from './RecommendationLists'; -import { getCloudServiceClientSync } from '@/app/infra/http'; +import { + getCloudServiceClient, + getCloudServiceClientSync, +} from '@/app/infra/http'; import { useTranslation } from 'react-i18next'; import { PluginV4, PluginV4Status } from '@/app/infra/entities/plugin'; import { extractI18nObject } from '@/i18n/I18nProvider'; @@ -253,17 +256,23 @@ function MarketPageContent({ // 初始加载 useEffect(() => { - fetchPlugins(1, false, true); - fetchAvailableTags(); - fetchRecommendationLists(); + // Resolve the cloud service base URL (from system info) before any + // marketplace fetch — otherwise the sync client may still hold the default + // URL and hit space.langbot.app instead of the configured instance. + (async () => { + await getCloudServiceClient(); + fetchPlugins(1, false, true); + fetchAvailableTags(); + fetchRecommendationLists(); + })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 获取推荐列表(精选,混合插件/MCP/Skill) const fetchRecommendationLists = async () => { try { - const { lists } = - await getCloudServiceClientSync().getRecommendationLists(); + const client = await getCloudServiceClient(); + const { lists } = await client.getRecommendationLists(); setRecommendationLists(lists || []); } catch (error) { console.error('Failed to fetch recommendation lists:', error);