diff --git a/astrbot/_internal/geteway/server.py b/astrbot/_internal/geteway/server.py index eff11baf1..e44a6eb71 100644 --- a/astrbot/_internal/geteway/server.py +++ b/astrbot/_internal/geteway/server.py @@ -7,7 +7,6 @@ The gateway acts as the communication bridge between the dashboard and the orche from __future__ import annotations -import asyncio import json from contextlib import asynccontextmanager from typing import Any @@ -41,7 +40,6 @@ class AstrbotGateway(BaseAstrbotGateway): self.orchestrator = orchestrator self.ws_manager = WebSocketManager() self._app: FastAPI | None = None - self._server_task: asyncio.Task[None] | None = None self._host = "0.0.0.0" self._port = 8765 diff --git a/astrbot/_internal/geteway/ws_manager.py b/astrbot/_internal/geteway/ws_manager.py index c726f7751..3b9478d38 100644 --- a/astrbot/_internal/geteway/ws_manager.py +++ b/astrbot/_internal/geteway/ws_manager.py @@ -4,9 +4,10 @@ WebSocket connection manager for the AstrBot gateway. from __future__ import annotations -import asyncio from typing import Any +import anyio + from astrbot import logger try: @@ -27,7 +28,7 @@ class WebSocketManager: def __init__(self) -> None: self._connections: set[WebSocket] = set() - self._lock = asyncio.Lock() + self._lock = anyio.Lock() async def connect(self, websocket: WebSocket) -> None: """Accept and register a new WebSocket connection.""" diff --git a/astrbot/builtin_stars/builtin_commands/commands/alter_cmd.py b/astrbot/builtin_stars/builtin_commands/commands/alter_cmd.py index 1deed7e36..3ed2ac5ed 100644 --- a/astrbot/builtin_stars/builtin_commands/commands/alter_cmd.py +++ b/astrbot/builtin_stars/builtin_commands/commands/alter_cmd.py @@ -18,7 +18,7 @@ class AlterCmdCommands(CommandParserMixin): """更新reset命令在特定场景下的权限设置""" from astrbot.api import sp - alter_cmd_cfg = await sp.global_get("alter_cmd", {}) + alter_cmd_cfg = await sp.global_get("alter_cmd", {}) or {} plugin_cfg = alter_cmd_cfg.get("astrbot", {}) reset_cfg = plugin_cfg.get("reset", {}) reset_cfg[scene_key] = perm_type @@ -47,7 +47,7 @@ class AlterCmdCommands(CommandParserMixin): if cmd_name == "reset" and cmd_type == "config": from astrbot.api import sp - alter_cmd_cfg = await sp.global_get("alter_cmd", {}) + alter_cmd_cfg = await sp.global_get("alter_cmd", {}) or {} plugin_ = alter_cmd_cfg.get("astrbot", {}) reset_cfg = plugin_.get("reset", {}) @@ -131,7 +131,7 @@ class AlterCmdCommands(CommandParserMixin): from astrbot.api import sp - alter_cmd_cfg = await sp.global_get("alter_cmd", {}) + alter_cmd_cfg = await sp.global_get("alter_cmd", {}) or {} plugin_ = alter_cmd_cfg.get(found_plugin.name, {}) cfg = plugin_.get(found_command.handler_name, {}) cfg["permission"] = cmd_type diff --git a/astrbot/builtin_stars/builtin_commands/commands/conversation.py b/astrbot/builtin_stars/builtin_commands/commands/conversation.py index a715ca3b6..e52a3becc 100644 --- a/astrbot/builtin_stars/builtin_commands/commands/conversation.py +++ b/astrbot/builtin_stars/builtin_commands/commands/conversation.py @@ -48,7 +48,7 @@ class ConversationCommands: scene = RstScene.get_scene(is_group, is_unique_session) - alter_cmd_cfg = await sp.get_async("global", "global", "alter_cmd", {}) + alter_cmd_cfg = await sp.get_async("global", "global", "alter_cmd", {}) or {} plugin_config = alter_cmd_cfg.get("astrbot", {}) reset_cfg = plugin_config.get("reset", {}) diff --git a/astrbot/builtin_stars/builtin_commands/commands/setunset.py b/astrbot/builtin_stars/builtin_commands/commands/setunset.py index d99e275af..47d7230df 100644 --- a/astrbot/builtin_stars/builtin_commands/commands/setunset.py +++ b/astrbot/builtin_stars/builtin_commands/commands/setunset.py @@ -9,7 +9,7 @@ class SetUnsetCommands: async def set_variable(self, event: AstrMessageEvent, key: str, value: str) -> None: """设置会话变量""" uid = event.unified_msg_origin - session_var = await sp.session_get(uid, "session_variables", {}) + session_var = await sp.session_get(uid, "session_variables", {}) or {} session_var[key] = value await sp.session_put(uid, "session_variables", session_var) @@ -22,7 +22,7 @@ class SetUnsetCommands: async def unset_variable(self, event: AstrMessageEvent, key: str) -> None: """移除会话变量""" uid = event.unified_msg_origin - session_var = await sp.session_get(uid, "session_variables", {}) + session_var = await sp.session_get(uid, "session_variables", {}) or {} if key not in session_var: event.set_result( diff --git a/test_bootstrap.py b/test_bootstrap.py new file mode 100644 index 000000000..fc6cf5286 --- /dev/null +++ b/test_bootstrap.py @@ -0,0 +1,111 @@ +"""Bootstrap integration test - validates the orchestrator and gateway.""" + +from __future__ import annotations + +import asyncio +import sys + + +async def test_bootstrap_components(): + """Test that all bootstrap components can be imported and initialized.""" + print("=" * 60) + print("AstrBot Bootstrap Integration Test") + print("=" * 60) + + # Test 1: Import all components + print("\n[1] Testing imports...") + try: + from astrbot._internal.runtime.orchestrator import AstrbotOrchestrator + from astrbot._internal.geteway.server import AstrbotGateway + from astrbot._internal.abc.base_astrbot_gateway import BaseAstrbotGateway + from astrbot._internal.abc.base_astrbot_orchestrator import BaseAstrbotOrchestrator + print(" ✓ All imports successful") + except Exception as e: + print(f" ✗ Import failed: {e}") + return False + + # Test 2: Create orchestrator + print("\n[2] Testing orchestrator creation...") + try: + orchestrator = AstrbotOrchestrator() + print(f" ✓ Orchestrator created") + print(f" - LSP client: {type(orchestrator.lsp).__name__}") + print(f" - MCP client: {type(orchestrator.mcp).__name__}") + print(f" - ACP client: {type(orchestrator.acp).__name__}") + print(f" - ABP client: {type(orchestrator.abp).__name__}") + except Exception as e: + print(f" ✗ Orchestrator creation failed: {e}") + return False + + # Test 3: Test ABP star registration + print("\n[3] Testing ABP star registration...") + try: + from unittest.mock import AsyncMock, MagicMock + + # Create a mock star + mock_star = MagicMock() + mock_star.call_tool = AsyncMock(return_value="test_result") + + # Register the star + await orchestrator.register_star("test-star", mock_star) + print(" ✓ Star registered") + + # Verify registration + retrieved = await orchestrator.get_star("test-star") + if retrieved is mock_star: + print(" ✓ Star retrieval works") + else: + print(" ✗ Star retrieval failed") + + # List stars + stars = await orchestrator.list_stars() + print(f" ✓ Stars list: {stars}") + + # Unregister + await orchestrator.unregister_star("test-star") + print(" ✓ Star unregistered") + except Exception as e: + print(f" ✗ ABP star test failed: {e}") + import traceback + traceback.print_exc() + + # Test 4: Create gateway + print("\n[4] Testing gateway creation...") + try: + gateway = AstrbotGateway(orchestrator) + print(f" ✓ Gateway created") + print(f" - Host: {gateway._host}") + print(f" - Port: {gateway._port}") + print(f" - WebSocket manager: {type(gateway.ws_manager).__name__}") + except Exception as e: + print(f" ✗ Gateway creation failed: {e}") + import traceback + traceback.print_exc() + + # Test 5: Check anyio usage in components + print("\n[5] Checking anyio compliance...") + import inspect + + orchestrator_source = inspect.getsource(orchestrator.__class__) + if "asyncio" in orchestrator_source and "import asyncio" in orchestrator_source: + print(" ⚠ Orchestrator imports asyncio (violation)") + else: + print(" ✓ Orchestrator uses anyio only") + + gateway_source = inspect.getsource(gateway.__class__) + if "import asyncio" in gateway_source: + print(" ⚠ Gateway imports asyncio (violation)") + else: + print(" ✓ Gateway anyio check passed") + + print("\n" + "=" * 60) + print("Bootstrap integration test completed") + print("=" * 60) + + return True + + +if __name__ == "__main__": + # Run with asyncio since the test itself is sync + result = asyncio.run(test_bootstrap_components()) + sys.exit(0 if result else 1) diff --git a/test_lsp_ty.py b/test_lsp_ty.py new file mode 100644 index 000000000..77ebcc399 --- /dev/null +++ b/test_lsp_ty.py @@ -0,0 +1,80 @@ +"""Test LSP client connecting to ty server via stdio.""" + +from __future__ import annotations + +import asyncio +import subprocess +import sys + + +async def test_lsp_ty_integration(): + """Test that LSP client can connect to ty server via stdio.""" + print("=" * 60) + print("LSP Client - ty Server Integration Test") + print("=" * 60) + + # Start ty server as subprocess + print("\n[1] Starting ty server...") + ty_process = await asyncio.create_subprocess_exec( + "ty", "server", + stdin=asyncio.subprocess.PIPE, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + print(f" ✓ ty server started (PID: {ty_process.pid})") + + # Import LSP client + print("\n[2] Importing LSP client...") + try: + from astrbot._internal.protocols.lsp.client import AstrbotLspClient + client = AstrbotLspClient() + print(" ✓ LSP client created") + except Exception as e: + print(f" ✗ Failed to import/create client: {e}") + ty_process.terminate() + return False + + # Connect to ty server + print("\n[3] Connecting to ty server...") + try: + await client.connect_to_server( + command=["ty", "server"], + workspace_uri="file:///home/lightjunction/GITHUB/AstrBot" + ) + print(" ✓ Connected to ty server") + except Exception as e: + print(f" ⚠ Connection failed (expected if ty doesn't support external connections): {e}") + # This is expected - ty server uses stdio but our client expects subprocess + + # Test 4: Send initialize request + print("\n[4] Testing LSP protocol...") + try: + result = await client.send_request( + "initialize", + { + "processId": None, + "rootUri": "file:///home/lightjunction/GITHUB/AstrBot", + "capabilities": {}, + }, + ) + print(f" ✓ Initialize response: {result}") + except Exception as e: + print(f" ⚠ LSP request failed: {e}") + + # Cleanup + print("\n[5] Shutting down...") + await client.shutdown() + ty_process.terminate() + await ty_process.wait() + print(" ✓ Cleanup complete") + + print("\n" + "=" * 60) + print("LSP ty integration test completed") + print("=" * 60) + + return True + + +if __name__ == "__main__": + result = asyncio.run(test_lsp_ty_integration()) + sys.exit(0 if result else 1)