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) <noreply@anthropic.com>
This commit is contained in:
Junyan Qin
2026-06-01 19:01:57 +08:00
parent 62bdd1b3df
commit 2bc8559c2a
3 changed files with 35 additions and 6 deletions

View File

@@ -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

View File

@@ -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]:

View File

@@ -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);