From 4622f2e86a72e595e454d35edf50a93b2632eb8c Mon Sep 17 00:00:00 2001 From: huanghuoguoguo <60681390+huanghuoguoguo@users.noreply.github.com> Date: Sat, 13 Jun 2026 15:32:05 +0800 Subject: [PATCH] fix(plugin): preserve marketplace package metadata --- src/langbot/pkg/plugin/connector.py | 28 +++++++++++++++---- .../unit_tests/plugin/test_connector_pure.py | 24 ++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/langbot/pkg/plugin/connector.py b/src/langbot/pkg/plugin/connector.py index 12413e49..d1f0e9d1 100644 --- a/src/langbot/pkg/plugin/connector.py +++ b/src/langbot/pkg/plugin/connector.py @@ -197,10 +197,11 @@ class PluginRuntimeConnector(ManagedRuntimeConnector): self, file_bytes: bytes, task_context: taskmgr.TaskContext | None, - ) -> tuple[str | None, str | None]: + ) -> tuple[str | None, str | None, str | None]: """Extract plugin identity and dependency metadata from a plugin package.""" plugin_author = None plugin_name = None + plugin_version = None try: with zipfile.ZipFile(io.BytesIO(file_bytes)) as zf: @@ -209,6 +210,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector): metadata = manifest.get('metadata', {}) plugin_author = metadata.get('author') plugin_name = metadata.get('name') + plugin_version = metadata.get('version') except Exception: pass @@ -227,7 +229,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector): except Exception: pass - return plugin_author, plugin_name + return plugin_author, plugin_name, plugin_version async def _install_mcp_from_marketplace( self, @@ -369,6 +371,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector): ): plugin_author = install_info.get('plugin_author') plugin_name = install_info.get('plugin_name') + plugin_file_transferred = False if install_source == PluginInstallSource.MARKETPLACE: # Handle marketplace plugin/mcp/skill installation @@ -463,9 +466,18 @@ class PluginRuntimeConnector(ManagedRuntimeConnector): ) file_bytes = download_resp.content - self._inspect_plugin_package(file_bytes, task_context) + plugin_author, plugin_name, plugin_version = self._inspect_plugin_package( + file_bytes, + task_context, + ) + if task_context is not None and plugin_author and plugin_name: + task_context.metadata['plugin_name'] = f'{plugin_author}/{plugin_name}' + if task_context is not None and plugin_version: + task_context.metadata['plugin_version'] = plugin_version file_key = await self.handler.send_file(file_bytes, 'lbpkg') install_info['plugin_file_key'] = file_key + install_source = PluginInstallSource.LOCAL + plugin_file_transferred = True self.ap.logger.info(f'Transfered file {file_key} to plugin runtime') # Continue to install via runtime else: @@ -481,12 +493,14 @@ class PluginRuntimeConnector(ManagedRuntimeConnector): mcp_resp.raise_for_status() raise Exception(f'Failed to get MCP {plugin_author}/{plugin_name}') - if install_source == PluginInstallSource.LOCAL: + if install_source == PluginInstallSource.LOCAL and not plugin_file_transferred: # transfer file before install file_bytes = install_info['plugin_file'] - plugin_author, plugin_name = self._inspect_plugin_package(file_bytes, task_context) + plugin_author, plugin_name, plugin_version = self._inspect_plugin_package(file_bytes, task_context) if task_context is not None and plugin_author and plugin_name: task_context.metadata['plugin_name'] = f'{plugin_author}/{plugin_name}' + if task_context is not None and plugin_version: + task_context.metadata['plugin_version'] = plugin_version file_key = await self.handler.send_file(file_bytes, 'lbpkg') install_info['plugin_file_key'] = file_key del install_info['plugin_file'] @@ -523,9 +537,11 @@ class PluginRuntimeConnector(ManagedRuntimeConnector): task_context.metadata['download_speed'] = downloaded / elapsed if elapsed > 0 else 0 file_bytes = b''.join(chunks) - plugin_author, plugin_name = self._inspect_plugin_package(file_bytes, task_context) + plugin_author, plugin_name, plugin_version = self._inspect_plugin_package(file_bytes, task_context) if task_context is not None and plugin_author and plugin_name: task_context.metadata['plugin_name'] = f'{plugin_author}/{plugin_name}' + if task_context is not None and plugin_version: + task_context.metadata['plugin_version'] = plugin_version file_key = await self.handler.send_file(file_bytes, 'lbpkg') install_info['plugin_file_key'] = file_key self.ap.logger.info(f'Transfered file {file_key} to plugin runtime') diff --git a/tests/unit_tests/plugin/test_connector_pure.py b/tests/unit_tests/plugin/test_connector_pure.py index 13ba29b5..2b4515fd 100644 --- a/tests/unit_tests/plugin/test_connector_pure.py +++ b/tests/unit_tests/plugin/test_connector_pure.py @@ -49,6 +49,30 @@ class TestExtractDepsMetadata: assert 'flask' in task_context.metadata['deps_list'] assert 'numpy' in task_context.metadata['deps_list'] + def test_extract_plugin_identity_includes_version(self): + """Extract plugin identity and version from manifest.yaml.""" + connector = self._create_connector() + + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zf: + zf.writestr( + 'manifest.yaml', + '\n'.join( + [ + 'metadata:', + ' author: langbot-team', + ' name: LangRAG', + ' version: 0.1.8', + ] + ), + ) + + assert connector._inspect_plugin_package(zip_buffer.getvalue(), None) == ( + 'langbot-team', + 'LangRAG', + '0.1.8', + ) + def test_extract_deps_empty_requirements(self): """Handle empty requirements.txt.""" connector = self._create_connector()