refactor: 拆分 ai_cli.py 为模块化 xcli_core 包
将 4130 行的单文件拆分为 13 个职责清晰的模块: - constants.py: 常量 + 可选依赖导入 - sandbox.py: 安全代码沙箱 (AST检查器 + 子进程隔离) - config.py: 配置管理 + 日志 - plugin_manager.py: LiuginManager 插件管理器 - cli_base.py: BaseAICLI 基类 (初始化/引擎/插件/命令) - cli_clawli.py: ClawliMixin 远程模式 - cli_tools.py: ToolMixin 工具调用 - cli_code_exec.py: CodeExecMixin 代码沙箱执行 - cli_display.py: DisplayMixin 显示/输出/图片/帮助 - cli_history.py: HistoryMixin 聊天记录 - cli_core.py: AICLI 主类 + 对话循环 + main() - tui.py: Textual TUI 界面 - __init__.py: 包导出汇总 ai_cli.py 保留为入口文件 (11行), 根目录整洁。 原逻辑完全不变, 仅做结构拆分。
This commit is contained in:
1 parent
7efb6dfe8b
commit
04e2d26809
14 files changed
+3798
-4125
No files matched your search
@@ -0,0 +1,206 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import subprocess
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from colorama import Fore, Style
|
||||
|
||||
from .config import logger
|
||||
from .sandbox import (
|
||||
_sandbox_checker, _make_sandbox_worker, _sandbox_work_dir,
|
||||
_HAS_RESOURCE, _SAFE_WHITELIST,
|
||||
)
|
||||
|
||||
try:
|
||||
import resource as _resource_mod
|
||||
except ImportError:
|
||||
_resource_mod = None
|
||||
|
||||
|
||||
class CodeExecMixin:
|
||||
"""代码沙箱执行相关方法"""
|
||||
|
||||
def request_code_execution(self, liugin_name, code, timeout=None, variables=None, context=None):
|
||||
"""
|
||||
插件代码执行请求接口
|
||||
"""
|
||||
if not self.code_execution_enabled:
|
||||
return {
|
||||
'success': False,
|
||||
'result': '代码执行功能已禁用',
|
||||
'stdout': '',
|
||||
'stderr': '',
|
||||
'execution_time': 0,
|
||||
'memory_usage': 0
|
||||
}
|
||||
|
||||
if not self._is_code_safe(code):
|
||||
return {
|
||||
'success': False,
|
||||
'result': '代码包含潜在危险操作,已阻止执行',
|
||||
'stdout': '',
|
||||
'stderr': '',
|
||||
'execution_time': 0,
|
||||
'memory_usage': 0
|
||||
}
|
||||
|
||||
exec_timeout = timeout if timeout is not None else self.code_execution_timeout
|
||||
|
||||
execution_id = f"{liugin_name}_{int(time.time())}"
|
||||
execution_record = {
|
||||
'id': execution_id,
|
||||
'liugin_name': liugin_name,
|
||||
'code': code,
|
||||
'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'context': context
|
||||
}
|
||||
|
||||
try:
|
||||
result = self._execute_code_safely(code, exec_timeout, variables)
|
||||
|
||||
execution_record.update({
|
||||
'success': result['success'],
|
||||
'execution_time': result['execution_time'],
|
||||
'memory_usage': result['memory_usage']
|
||||
})
|
||||
|
||||
self.code_execution_history.append(execution_record)
|
||||
if len(self.code_execution_history) > 100:
|
||||
self.code_execution_history.pop(0)
|
||||
|
||||
if result['success']:
|
||||
logger.info(f"代码执行成功 - 插件: {liugin_name}, 耗时: {result['execution_time']:.3f}s")
|
||||
else:
|
||||
logger.warning(f"代码执行失败 - 插件: {liugin_name}, 错误: {result['result']}")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"代码执行异常: {str(e)}"
|
||||
logger.error(f"{error_msg} - 插件: {liugin_name}")
|
||||
return {
|
||||
'success': False,
|
||||
'result': error_msg,
|
||||
'stdout': '',
|
||||
'stderr': error_msg,
|
||||
'execution_time': 0,
|
||||
'memory_usage': 0
|
||||
}
|
||||
|
||||
def _is_code_safe(self, code):
|
||||
"""检查代码是否安全(使用 v2 AST+正则 沙箱检查器)"""
|
||||
safe, violations = _sandbox_checker.check(code)
|
||||
if not safe:
|
||||
logger.warning(f"代码安全检查失败: {violations}")
|
||||
return safe
|
||||
|
||||
def _execute_code_safely(self, code, timeout, variables=None):
|
||||
"""
|
||||
在安全沙箱中执行代码(v2 子进程隔离)
|
||||
"""
|
||||
safe, violations = _sandbox_checker.check(code)
|
||||
if not safe:
|
||||
return {
|
||||
'success': False,
|
||||
'result': '代码包含潜在危险操作,已阻止执行',
|
||||
'stdout': '', 'stderr': '; '.join(violations[:5]),
|
||||
'execution_time': 0, 'memory_usage': 0
|
||||
}
|
||||
|
||||
vars_json = json.dumps(variables or {}, ensure_ascii=False, default=str)
|
||||
wl = getattr(self, 'code_execution_whitelist', None) or _SAFE_WHITELIST
|
||||
whitelist_json = json.dumps(wl)
|
||||
script = _make_sandbox_worker(code, vars_json, whitelist_json, _sandbox_work_dir)
|
||||
|
||||
safe_env = {
|
||||
'PATH': '/usr/bin:/bin',
|
||||
'LANG': 'en_US.UTF-8', 'LC_ALL': 'en_US.UTF-8',
|
||||
'HOME': _sandbox_work_dir,
|
||||
'TMPDIR': _sandbox_work_dir, 'TEMP': _sandbox_work_dir, 'TMP': _sandbox_work_dir,
|
||||
'PYTHONNOUSERSITE': '1', 'PYTHONDONTWRITEBYTECODE': '1',
|
||||
}
|
||||
for key in ('TERM', 'COLUMNS', 'LINES', 'SHELL'):
|
||||
val = os.environ.get(key)
|
||||
if val:
|
||||
safe_env[key] = val
|
||||
|
||||
start_time = time.time()
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[sys.executable, '-c', script],
|
||||
capture_output=True, timeout=timeout,
|
||||
cwd=_sandbox_work_dir, env=safe_env,
|
||||
preexec_fn=CodeExecMixin._sandbox_set_limits if _HAS_RESOURCE else None,
|
||||
)
|
||||
elapsed = time.time() - start_time
|
||||
stdout = proc.stdout.decode('utf-8', errors='replace')[:1000000]
|
||||
stderr = proc.stderr.decode('utf-8', errors='replace')[:1000000]
|
||||
|
||||
try:
|
||||
data = json.loads(stdout)
|
||||
return {
|
||||
'success': data.get('success', False),
|
||||
'result': data.get('result', ''),
|
||||
'stdout': data.get('stdout', ''),
|
||||
'stderr': data.get('stderr', ''),
|
||||
'execution_time': elapsed,
|
||||
'memory_usage': 0
|
||||
}
|
||||
except json.JSONDecodeError:
|
||||
return {
|
||||
'success': proc.returncode == 0,
|
||||
'result': stdout[:2000] if stdout else 'no output',
|
||||
'stdout': stdout, 'stderr': stderr,
|
||||
'execution_time': elapsed, 'memory_usage': 0
|
||||
}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
'success': False,
|
||||
'result': f'代码执行超时(超过{timeout}秒)',
|
||||
'stdout': '', 'stderr': '',
|
||||
'execution_time': timeout, 'memory_usage': 0
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'result': f'代码执行异常: {str(e)}',
|
||||
'stdout': '', 'stderr': traceback.format_exc(),
|
||||
'execution_time': time.time() - start_time, 'memory_usage': 0
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _sandbox_set_limits():
|
||||
"""子进程资源限制"""
|
||||
if not _HAS_RESOURCE or not _resource_mod:
|
||||
return
|
||||
try:
|
||||
mem = 256 * 1024 * 1024 # 256MB
|
||||
_resource_mod.setrlimit(_resource_mod.RLIMIT_AS, (mem, mem))
|
||||
_resource_mod.setrlimit(_resource_mod.RLIMIT_CPU, (30, 35))
|
||||
_resource_mod.setrlimit(_resource_mod.RLIMIT_NPROC, (0, 0))
|
||||
_resource_mod.setrlimit(_resource_mod.RLIMIT_FSIZE, (50*1024*1024, 50*1024*1024))
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
|
||||
def get_code_execution_history(self, limit=10):
|
||||
"""获取代码执行历史"""
|
||||
return self.code_execution_history[-limit:]
|
||||
|
||||
def clear_code_execution_history(self):
|
||||
"""清空代码执行历史"""
|
||||
self.code_execution_history = []
|
||||
logger.info("代码执行历史已清空")
|
||||
|
||||
def set_code_execution_timeout(self, timeout):
|
||||
"""设置代码执行超时时间"""
|
||||
if timeout > 0:
|
||||
self.code_execution_timeout = timeout
|
||||
logger.info(f"代码执行超时时间已设置为 {timeout} 秒")
|
||||
|
||||
def set_code_execution_whitelist(self, modules):
|
||||
"""设置允许导入的模块白名单"""
|
||||
self.code_execution_whitelist = modules
|
||||
logger.info(f"代码执行模块白名单已更新: {modules}")
|
||||
Reference in new issue
Block a user