3030from typing import Any
3131
3232import click
33- from loguru import logger
3433
34+ from ._internal .sdk_logger import logger
3535from .errors import AstrBotError
3636from .runtime .bootstrap import run_plugin_worker , run_supervisor , run_websocket_server
3737from .runtime .loader import load_plugin , load_plugin_spec , validate_plugin_spec
@@ -1144,6 +1144,39 @@ def _build_plugin(plugin_dir: Path, output_dir: Path | None) -> None:
11441144 click .echo (f"artifact: { archive_path } " )
11451145
11461146
1147+ def _run_websocket_worker_entrypoint (
1148+ * ,
1149+ worker_id : str | None ,
1150+ plugin_dirs : tuple [Path , ...],
1151+ host : str ,
1152+ port : int ,
1153+ path : str ,
1154+ tls_ca_file : Path ,
1155+ tls_cert_file : Path ,
1156+ tls_key_file : Path ,
1157+ ) -> None :
1158+ resolved_plugin_dirs = list (plugin_dirs ) if plugin_dirs else [Path .cwd ()]
1159+ _run_async_entrypoint (
1160+ run_websocket_server (
1161+ worker_id = worker_id ,
1162+ plugin_dirs = resolved_plugin_dirs ,
1163+ host = host ,
1164+ port = port ,
1165+ path = path ,
1166+ tls_ca_file = tls_ca_file ,
1167+ tls_cert_file = tls_cert_file ,
1168+ tls_key_file = tls_key_file ,
1169+ ),
1170+ log_message = f"启动 WebSocket Worker,端口:{ port } " ,
1171+ context = {
1172+ "worker_id" : worker_id ,
1173+ "plugin_dirs" : resolved_plugin_dirs ,
1174+ "port" : port ,
1175+ "path" : path ,
1176+ },
1177+ )
1178+
1179+
11471180@click .group ()
11481181@click .option ("-v" , "--verbose" , is_flag = True , help = "Enable verbose output" )
11491182@click .pass_context
@@ -1161,20 +1194,37 @@ def cli(ctx, verbose: bool) -> None:
11611194 type = click .Path (file_okay = False , dir_okay = True , path_type = Path ),
11621195 help = "Directory containing plugin folders" ,
11631196)
1197+ @click .option (
1198+ "--workers-manifest" ,
1199+ default = None ,
1200+ type = click .Path (file_okay = True , dir_okay = False , path_type = Path ),
1201+ help = "Supervisor manifest describing remote websocket workers" ,
1202+ )
11641203@click .option (
11651204 "--protocol-stdout" ,
11661205 default = None ,
11671206 type = str ,
11681207 help = "Redirect runtime protocol stdout to console, silent, or a file path" ,
11691208)
1170- def run (plugins_dir : Path , protocol_stdout : str | None ) -> None :
1209+ def run (
1210+ plugins_dir : Path ,
1211+ workers_manifest : Path | None ,
1212+ protocol_stdout : str | None ,
1213+ ) -> None :
11711214 """Start the plugin supervisor over stdio."""
11721215 transport_stdout , opened_stdout = _resolve_protocol_stdout (protocol_stdout )
11731216 try :
11741217 _run_async_entrypoint (
1175- run_supervisor (plugins_dir = plugins_dir , stdout = transport_stdout ),
1218+ run_supervisor (
1219+ plugins_dir = plugins_dir ,
1220+ stdout = transport_stdout ,
1221+ workers_manifest = workers_manifest ,
1222+ ),
11761223 log_message = f"启动插件主管进程,插件目录:{ plugins_dir } " ,
1177- context = {"plugins_dir" : plugins_dir },
1224+ context = {
1225+ "plugins_dir" : plugins_dir ,
1226+ "workers_manifest" : workers_manifest ,
1227+ },
11781228 )
11791229 finally :
11801230 if opened_stdout is not None :
@@ -1362,12 +1412,101 @@ def worker(
13621412 opened_stdout .close ()
13631413
13641414
1415+ @cli .command ("serve-worker" )
1416+ @click .option ("--worker-id" , default = None , type = str , help = "Stable websocket worker id" )
1417+ @click .option (
1418+ "--plugin-dir" ,
1419+ "plugin_dirs" ,
1420+ multiple = True ,
1421+ type = click .Path (file_okay = False , dir_okay = True , path_type = Path ),
1422+ help = "Plugin directory to serve; repeat to host multiple plugins in one worker" ,
1423+ )
1424+ @click .option ("--host" , default = "127.0.0.1" , show_default = True )
1425+ @click .option ("--port" , default = 8765 , type = int , show_default = True )
1426+ @click .option ("--path" , default = "/" , show_default = True )
1427+ @click .option (
1428+ "--tls-ca-file" ,
1429+ required = True ,
1430+ type = click .Path (file_okay = True , dir_okay = False , exists = True , path_type = Path ),
1431+ )
1432+ @click .option (
1433+ "--tls-cert-file" ,
1434+ required = True ,
1435+ type = click .Path (file_okay = True , dir_okay = False , exists = True , path_type = Path ),
1436+ )
1437+ @click .option (
1438+ "--tls-key-file" ,
1439+ required = True ,
1440+ type = click .Path (file_okay = True , dir_okay = False , exists = True , path_type = Path ),
1441+ )
1442+ def serve_worker (
1443+ worker_id : str | None ,
1444+ plugin_dirs : tuple [Path , ...],
1445+ host : str ,
1446+ port : int ,
1447+ path : str ,
1448+ tls_ca_file : Path ,
1449+ tls_cert_file : Path ,
1450+ tls_key_file : Path ,
1451+ ) -> None :
1452+ """Serve one or more plugins as a standalone websocket worker."""
1453+ _run_websocket_worker_entrypoint (
1454+ worker_id = worker_id ,
1455+ plugin_dirs = plugin_dirs ,
1456+ host = host ,
1457+ port = port ,
1458+ path = path ,
1459+ tls_ca_file = tls_ca_file ,
1460+ tls_cert_file = tls_cert_file ,
1461+ tls_key_file = tls_key_file ,
1462+ )
1463+
1464+
13651465@cli .command (hidden = True )
1366- @click .option ("--port" , default = 8765 , type = int , help = "WebSocket server port" )
1367- def websocket (port : int ) -> None :
1368- """WebSocket runtime entrypoint kept for standalone bridge scenarios."""
1369- _run_async_entrypoint (
1370- run_websocket_server (port = port ),
1371- log_message = f"启动 WebSocket 服务器,端口:{ port } " ,
1372- context = {"port" : port },
1466+ @click .option ("--worker-id" , default = None , type = str )
1467+ @click .option (
1468+ "--plugin-dir" ,
1469+ "plugin_dirs" ,
1470+ multiple = True ,
1471+ type = click .Path (file_okay = False , dir_okay = True , path_type = Path ),
1472+ )
1473+ @click .option ("--host" , default = "127.0.0.1" , show_default = True )
1474+ @click .option ("--port" , default = 8765 , type = int , show_default = True )
1475+ @click .option ("--path" , default = "/" , show_default = True )
1476+ @click .option (
1477+ "--tls-ca-file" ,
1478+ required = True ,
1479+ type = click .Path (file_okay = True , dir_okay = False , exists = True , path_type = Path ),
1480+ )
1481+ @click .option (
1482+ "--tls-cert-file" ,
1483+ required = True ,
1484+ type = click .Path (file_okay = True , dir_okay = False , exists = True , path_type = Path ),
1485+ )
1486+ @click .option (
1487+ "--tls-key-file" ,
1488+ required = True ,
1489+ type = click .Path (file_okay = True , dir_okay = False , exists = True , path_type = Path ),
1490+ )
1491+ def websocket (
1492+ worker_id : str | None ,
1493+ plugin_dirs : tuple [Path , ...],
1494+ host : str ,
1495+ port : int ,
1496+ path : str ,
1497+ tls_ca_file : Path ,
1498+ tls_cert_file : Path ,
1499+ tls_key_file : Path ,
1500+ ) -> None :
1501+ """Deprecated websocket runtime wrapper for standalone worker scenarios."""
1502+ logger .warning ("'astr websocket' is deprecated; use 'astr serve-worker' instead" )
1503+ _run_websocket_worker_entrypoint (
1504+ worker_id = worker_id ,
1505+ plugin_dirs = plugin_dirs ,
1506+ host = host ,
1507+ port = port ,
1508+ path = path ,
1509+ tls_ca_file = tls_ca_file ,
1510+ tls_cert_file = tls_cert_file ,
1511+ tls_key_file = tls_key_file ,
13731512 )
0 commit comments