From f960da96d575938de5a47dc2a77fd0eccc800ca9 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 | 23 +++++++++++++----- .../unit_tests/plugin/test_connector_pure.py | 24 +++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/langbot/pkg/plugin/connector.py b/src/langbot/pkg/plugin/connector.py index 94e45ca8..506abd6c 100644 --- a/src/langbot/pkg/plugin/connector.py +++ b/src/langbot/pkg/plugin/connector.py @@ -206,10 +206,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: @@ -218,6 +219,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 @@ -236,7 +238,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, @@ -378,6 +380,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 @@ -472,14 +475,18 @@ class PluginRuntimeConnector(ManagedRuntimeConnector): ) file_bytes = download_resp.content - plugin_author, plugin_name = self._inspect_plugin_package( + 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: @@ -495,12 +502,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'] @@ -537,9 +546,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()