Files
xiaoli-cli/clawli_server.py
T
Xiaoli Dev e22652fbbb feat: 增强编码能力 - 新增代码编辑器、代码搜索、Git集成
- 新增 plugins/code_editor.py: 精准代码编辑(搜索替换、批量编辑、diff对比)
- 新增 plugins/code_search.py: 代码搜索与理解(跨文件搜索、符号提取、依赖分析)
- 新增 plugins/git_tools.py: Git 版本控制(status/diff/log/commit/branch)
- 新增 core/ 模块: 配置管理、引擎管理、对话处理、显示输出、代码沙箱
- 修复 requirements.txt: 使用正确的 pip 包名(Pillow、opencv-python 等)
- 更新 unified_tool_manager.py: 兼容 Plugin 和 Liugin 两种类名
- 更新系统提示词: 增强编程工作流指导

本次更新由 mimo 自动化执行,与开发者无关。
2026-04-25 10:55:27 +08:00

259 lines
8.4 KiB
Python

"""
Clawli WebSocket Server - 独立进程 WebSocket 服务器
用于 PC-手机端通信
"""
import multiprocessing
from multiprocessing import Process, Queue
from typing import Optional, List
import threading
import time
import json
import base64
import os
import asyncio
class ClawliWebSocketServer:
"""Clawli WebSocket 服务器管理类"""
def __init__(self):
self.process: Optional[Process] = None
self.input_queue: Optional[Queue] = None
self.output_queue: Optional[Queue] = None
self.running = False
self.host = "0.0.0.0"
self.port = 9079
self.password = ""
def start(self, host: str = "0.0.0.0", port: int = 9079, password: str = "") -> bool:
"""启动 WebSocket 服务器进程"""
if self.running:
print("[Clawli] 服务器已在运行")
return True
self.host = host
self.port = int(port)
self.password = password
self.input_queue = Queue()
self.output_queue = Queue()
self.process = Process(
target=_ws_server_main,
args=(self.host, self.port, self.password, self.input_queue, self.output_queue)
)
self.process.daemon = True
self.process.start()
self.running = True
print(f"[Clawli] WebSocket 服务器已启动: ws://{host}:{self.port}")
return True
def stop(self):
"""停止服务器"""
if not self.running:
return
self.running = False
if self.process:
self.process.terminate()
self.process.join(timeout=2)
if self.process.is_alive():
self.process.kill()
self.process = None
self.input_queue = None
self.output_queue = None
print("[Clawli] WebSocket 服务器已停止")
def send_message(self, content: str, msg_type: str = "text", **kwargs) -> tuple:
"""发送消息到手机"""
if not self.running or not self.input_queue:
print(f"[Clawli] 发送失败: 服务器未运行")
return False, "服务器未运行"
try:
msg = {
"type": msg_type,
"content": content,
"timestamp": time.time(),
**kwargs
}
self.input_queue.put(msg)
print(f"[Clawli] 消息已放入队列: type={msg_type}, len={len(content)}")
return True, "消息已发送"
except Exception as e:
print(f"[Clawli] 发送异常: {e}")
return False, f"发送失败: {e}"
def send_image(self, image_path: str = None, image_data: str = None,
filename: str = "", caption: str = "") -> tuple:
"""发送图片到手机"""
if not self.running or not self.input_queue:
return False, "服务器未运行"
try:
if image_path and os.path.exists(image_path):
with open(image_path, 'rb') as f:
image_bytes = f.read()
image_data = base64.b64encode(image_bytes).decode('utf-8')
filename = filename or os.path.basename(image_path)
if not image_data:
return False, "无图片数据"
msg = {
"type": "image",
"image": image_data,
"filename": filename,
"caption": caption,
"timestamp": time.time()
}
self.input_queue.put(msg)
print(f"[Clawli] 图片已放入队列: {filename}")
return True, "图片已发送"
except Exception as e:
return False, f"发送失败: {e}"
def send_tool_status(self, tool_name: str, args: str, status: str, result: str = ""):
"""发送工具状态"""
return self.send_message(
content=result,
msg_type="tool_status",
tool=tool_name,
args=args,
status=status,
result=result
)
def get_messages(self) -> List[dict]:
"""获取从手机收到的消息"""
messages = []
if self.output_queue:
while True:
try:
msg = self.output_queue.get(timeout=0.01)
messages.append(msg)
except:
break
return messages
def is_running(self) -> bool:
return self.running and self.process is not None and self.process.is_alive()
def _ws_server_main(host: str, port: int, password: str,
input_queue: Queue, output_queue: Queue):
"""WebSocket 服务器主进程"""
import websockets
from websockets.server import serve
port = int(port)
running = [True] # 用列表包装以便在闭包中修改
async def handler(websocket):
"""WebSocket 连接处理器"""
# 认证
try:
auth_msg = await asyncio.wait_for(websocket.recv(), timeout=5.0)
auth_data = json.loads(auth_msg)
if auth_data.get("type") != "auth" or auth_data.get("password") != password:
await websocket.send(json.dumps({"type": "auth_failed", "message": "密码错误"}))
return
await websocket.send(json.dumps({"type": "auth_success"}))
print(f"[Clawli WS] 客户端认证成功")
except Exception as e:
print(f"[Clawli WS] 认证失败: {e}")
return
print(f"[Clawli WS] 开始处理消息循环")
try:
# 接收任务
async def receiver():
async for message in websocket:
try:
data = json.loads(message)
output_queue.put(data)
print(f"[Clawli WS] 收到: {data.get('type', 'unknown')}")
except Exception as e:
print(f"[Clawli WS] 解析失败: {e}")
# 发送任务
async def sender():
while running[0]:
try:
# 从队列获取消息(非阻塞)
msg = await asyncio.get_event_loop().run_in_executor(
None, lambda: input_queue.get(timeout=0.2)
)
await websocket.send(json.dumps(msg, ensure_ascii=False))
print(f"[Clawli WS] 发送: {msg.get('type', 'unknown')}")
except:
pass # 超时,继续循环
# 并行运行两个任务
await asyncio.gather(receiver(), sender())
except websockets.exceptions.ConnectionClosed:
print("[Clawli WS] 客户端断开")
except Exception as e:
print(f"[Clawli WS] 连接错误: {e}")
async def main():
print(f"[Clawli WS] 服务器启动于 ws://{host}:{port}")
async with serve(handler, host, port):
await asyncio.Future()
try:
asyncio.run(main())
except KeyboardInterrupt:
running[0] = False
print("[Clawli WS] 服务器关闭")
# 全局实例
_server_instance = ClawliWebSocketServer()
def start_server(host: str = "0.0.0.0", port: int = 9079, password: str = "") -> bool:
return _server_instance.start(host, port, password)
def stop_server():
_server_instance.stop()
def send_message(content: str, msg_type: str = "text", **kwargs) -> tuple:
return _server_instance.send_message(content, msg_type, **kwargs)
def send_image(image_path: str = None, image_data: str = None,
filename: str = "", caption: str = "") -> tuple:
return _server_instance.send_image(image_path, image_data, filename, caption)
def send_tool_status(tool_name: str, args: str, status: str, result: str = ""):
return _server_instance.send_tool_status(tool_name, args, status, result)
def get_messages() -> List[dict]:
return _server_instance.get_messages()
def is_running() -> bool:
return _server_instance.is_running()
if __name__ == "__main__":
start_server("0.0.0.0", 9079, "test")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
stop_server()