From 3ca724d18ebb0567908a2b8d6d8bad4d2edfa96f Mon Sep 17 00:00:00 2001 From: Hyu Date: Sun, 26 Jul 2026 17:02:51 +0800 Subject: [PATCH] fix: use per-bot admins for command events (#2359) * fix: use per-bot admins for command events * style: format rerank provider changes --------- Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com> --- src/langbot/pkg/api/http/service/provider.py | 4 +--- .../pkg/pipeline/process/handlers/command.py | 11 ++++++++++- src/langbot/pkg/provider/modelmgr/modelmgr.py | 4 +--- .../pipeline/test_command_handler.py | 18 ++++++++++++++---- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/langbot/pkg/api/http/service/provider.py b/src/langbot/pkg/api/http/service/provider.py index 6caa54883..39df7f4a9 100644 --- a/src/langbot/pkg/api/http/service/provider.py +++ b/src/langbot/pkg/api/http/service/provider.py @@ -234,9 +234,7 @@ class ModelProviderService: embedding_models = await self.ap.embedding_models_service.get_embedding_models_by_provider(provider_uuid) rerank_service = getattr(self.ap, 'rerank_models_service', None) rerank_models = ( - await rerank_service.get_rerank_models_by_provider(provider_uuid) - if rerank_service is not None - else [] + await rerank_service.get_rerank_models_by_provider(provider_uuid) if rerank_service is not None else [] ) existing_llm_names = {model['name'] for model in llm_models} existing_embedding_names = {model['name'] for model in embedding_models} diff --git a/src/langbot/pkg/pipeline/process/handlers/command.py b/src/langbot/pkg/pipeline/process/handlers/command.py index 09fa5379b..03a2ba9e6 100644 --- a/src/langbot/pkg/pipeline/process/handlers/command.py +++ b/src/langbot/pkg/pipeline/process/handlers/command.py @@ -1,10 +1,12 @@ from __future__ import annotations import typing +import sqlalchemy from .. import handler from ... import entities from ... import plugin_diagnostics +from ....entity.persistence.bot import BotAdmin import langbot_plugin.api.entities.builtin.provider.message as provider_message import langbot_plugin.api.entities.builtin.provider.session as provider_session import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query @@ -24,7 +26,14 @@ class CommandHandler(handler.MessageHandler): privilege = 1 - if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.instance_config.data['admins']: + admins = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(BotAdmin).where( + BotAdmin.bot_uuid == (query.bot_uuid or ''), + BotAdmin.launcher_type == query.launcher_type.value, + BotAdmin.launcher_id == str(query.launcher_id), + ) + ) + if admins.first() is not None: privilege = 2 spt = command_text.split(' ') diff --git a/src/langbot/pkg/provider/modelmgr/modelmgr.py b/src/langbot/pkg/provider/modelmgr/modelmgr.py index 501090b4c..1431cf0b2 100644 --- a/src/langbot/pkg/provider/modelmgr/modelmgr.py +++ b/src/langbot/pkg/provider/modelmgr/modelmgr.py @@ -193,9 +193,7 @@ class ModelManager: existing_embedding_models = { m['uuid']: m for m in await self.ap.embedding_models_service.get_embedding_models() } - existing_rerank_models = { - m['uuid']: m for m in await self.ap.rerank_models_service.get_rerank_models() - } + existing_rerank_models = {m['uuid']: m for m in await self.ap.rerank_models_service.get_rerank_models()} created = 0 updated = 0 diff --git a/tests/unit_tests/pipeline/test_command_handler.py b/tests/unit_tests/pipeline/test_command_handler.py index 00bd5b681..bc5d39481 100644 --- a/tests/unit_tests/pipeline/test_command_handler.py +++ b/tests/unit_tests/pipeline/test_command_handler.py @@ -158,17 +158,21 @@ class TestCommandHandlerReal: @pytest.mark.asyncio async def test_admin_privilege_check(self, fake_app, mock_event_ctx, mock_execute_factory): - """Admin users get privilege level 2.""" + """A per-bot admin from the database is marked as admin in command events.""" from langbot_plugin.api.entities.builtin.provider.session import LauncherTypes command = get_command_handler() - fake_app.instance_config.data = {'admins': ['person_12345']} + admin_result = Mock() + admin_result.first.return_value = Mock() + fake_app.persistence_mgr.execute_async = AsyncMock(return_value=admin_result) + fake_app.instance_config.data = {} fake_app.plugin_connector.emit_event = AsyncMock(return_value=mock_event_ctx) fake_app.cmd_mgr.execute = mock_execute_factory() handler = command.CommandHandler(fake_app) query = command_query('status') + query.bot_uuid = 'bot-1' query.launcher_type = LauncherTypes.PERSON query.launcher_id = 12345 @@ -176,23 +180,28 @@ class TestCommandHandlerReal: async for result in handler.handle(query): results.append(result) + fake_app.persistence_mgr.execute_async.assert_awaited_once() call_args = fake_app.plugin_connector.emit_event.call_args event = call_args[0][0] assert event.is_admin is True @pytest.mark.asyncio async def test_non_admin_privilege_check(self, fake_app, mock_event_ctx, mock_execute_factory): - """Non-admin users get privilege level 1.""" + """A launcher absent from the per-bot admin table is not an admin.""" from langbot_plugin.api.entities.builtin.provider.session import LauncherTypes command = get_command_handler() - fake_app.instance_config.data = {'admins': ['person_12345']} + admin_result = Mock() + admin_result.first.return_value = None + fake_app.persistence_mgr.execute_async = AsyncMock(return_value=admin_result) + fake_app.instance_config.data = {} fake_app.plugin_connector.emit_event = AsyncMock(return_value=mock_event_ctx) fake_app.cmd_mgr.execute = mock_execute_factory() handler = command.CommandHandler(fake_app) query = command_query('status') + query.bot_uuid = 'bot-1' query.launcher_type = LauncherTypes.PERSON query.launcher_id = 67890 @@ -200,6 +209,7 @@ class TestCommandHandlerReal: async for result in handler.handle(query): results.append(result) + fake_app.persistence_mgr.execute_async.assert_awaited_once() call_args = fake_app.plugin_connector.emit_event.call_args event = call_args[0][0] assert event.is_admin is False