235 lines
10 KiB
Python
235 lines
10 KiB
Python
import asyncio
|
||
import json
|
||
from pathlib import Path
|
||
from typing import Any, Dict, List, Optional, Set
|
||
|
||
try:
|
||
from config import (
|
||
OUTPUT_FORMATS, DATA_DIR, PROMPTS_DIR, NEED_CONFIRMATION,
|
||
MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE,
|
||
MAX_READ_FILE_CHARS, READ_TOOL_DEFAULT_MAX_CHARS,
|
||
READ_TOOL_DEFAULT_CONTEXT_BEFORE, READ_TOOL_DEFAULT_CONTEXT_AFTER,
|
||
READ_TOOL_MAX_CONTEXT_BEFORE, READ_TOOL_MAX_CONTEXT_AFTER,
|
||
READ_TOOL_DEFAULT_MAX_MATCHES, READ_TOOL_MAX_MATCHES,
|
||
READ_TOOL_MAX_FILE_SIZE,
|
||
TERMINAL_SANDBOX_MOUNT_PATH,
|
||
TERMINAL_SANDBOX_MODE,
|
||
TERMINAL_SANDBOX_CPUS,
|
||
TERMINAL_SANDBOX_MEMORY,
|
||
PROJECT_MAX_STORAGE_MB,
|
||
CUSTOM_TOOLS_ENABLED,
|
||
)
|
||
except ImportError:
|
||
import sys
|
||
project_root = Path(__file__).resolve().parents[2]
|
||
if str(project_root) not in sys.path:
|
||
sys.path.insert(0, str(project_root))
|
||
from config import (
|
||
OUTPUT_FORMATS, DATA_DIR, PROMPTS_DIR, NEED_CONFIRMATION,
|
||
MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE,
|
||
MAX_READ_FILE_CHARS, READ_TOOL_DEFAULT_MAX_CHARS,
|
||
READ_TOOL_DEFAULT_CONTEXT_BEFORE, READ_TOOL_DEFAULT_CONTEXT_AFTER,
|
||
READ_TOOL_MAX_CONTEXT_BEFORE, READ_TOOL_MAX_CONTEXT_AFTER,
|
||
READ_TOOL_DEFAULT_MAX_MATCHES, READ_TOOL_MAX_MATCHES,
|
||
READ_TOOL_MAX_FILE_SIZE,
|
||
TERMINAL_SANDBOX_MOUNT_PATH,
|
||
TERMINAL_SANDBOX_MODE,
|
||
TERMINAL_SANDBOX_CPUS,
|
||
TERMINAL_SANDBOX_MEMORY,
|
||
PROJECT_MAX_STORAGE_MB,
|
||
CUSTOM_TOOLS_ENABLED,
|
||
)
|
||
|
||
from modules.file_manager import FileManager
|
||
from modules.search_engine import SearchEngine
|
||
from modules.terminal_ops import TerminalOperator
|
||
from modules.memory_manager import MemoryManager
|
||
from modules.terminal_manager import TerminalManager
|
||
from modules.todo_manager import TodoManager
|
||
from modules.sub_agent import SubAgentManager
|
||
from modules.webpage_extractor import extract_webpage_content, tavily_extract
|
||
from modules.ocr_client import OCRClient
|
||
from modules.easter_egg_manager import EasterEggManager
|
||
from modules.personalization_manager import (
|
||
load_personalization_config,
|
||
build_personalization_prompt,
|
||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
|
||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
|
||
RECENT_CONVERSATIONS_PROMPT_LIMIT_DEFAULT,
|
||
)
|
||
from modules.skills_manager import (
|
||
get_skills_catalog,
|
||
build_skills_list,
|
||
merge_enabled_skills,
|
||
build_skills_prompt,
|
||
infer_private_skills_dir,
|
||
)
|
||
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
||
from modules.custom_tool_executor import CustomToolExecutor
|
||
|
||
try:
|
||
from config.limits import THINKING_FAST_INTERVAL
|
||
except ImportError:
|
||
THINKING_FAST_INTERVAL = 10
|
||
|
||
from modules.container_monitor import collect_stats, inspect_state
|
||
from core.tool_config import TOOL_CATEGORIES
|
||
from utils.api_client import DeepSeekClient
|
||
from utils.context_manager import ContextManager, AUTO_SHALLOW_PLACEHOLDER
|
||
from utils.host_workspace_debug import write_host_workspace_debug
|
||
from utils.tool_result_formatter import format_tool_result_for_context
|
||
from utils.logger import setup_logger
|
||
from config.model_profiles import (
|
||
get_model_profile,
|
||
get_model_prompt_replacements,
|
||
get_model_context_window,
|
||
model_supports_image,
|
||
model_supports_video,
|
||
)
|
||
|
||
logger = setup_logger(__name__)
|
||
DISABLE_LENGTH_CHECK = True
|
||
|
||
|
||
class ModeMixin:
|
||
"""MainTerminalContextMixin mode 能力 mixin。"""
|
||
|
||
# 权限模式说明(代码中存储的简要版本)
|
||
_PERMISSION_MODE_DESC = {
|
||
"unrestricted": "当前处于无限制模式,所有工具均可直接使用,无需额外批准。",
|
||
"readonly": "当前处于只读模式,仅能执行不会修改工作区的读取类操作。",
|
||
"approval": "当前处于批准模式,修改工作区的操作需要用户批准后方可执行。",
|
||
"auto_approval": "当前处于自动审核模式,工作区内文件编辑可直接执行,高风险操作由后台审批智能体自动审核。",
|
||
}
|
||
|
||
_PERMISSION_MODE_LABEL = {
|
||
"unrestricted": "无限制",
|
||
"readonly": "只读",
|
||
"approval": "批准",
|
||
"auto_approval": "自动审核",
|
||
}
|
||
|
||
_EXECUTION_MODE_LABEL = {
|
||
"sandbox": "沙箱",
|
||
"direct": "完全访问权限",
|
||
}
|
||
|
||
def _build_permission_mode_message(self) -> Optional[str]:
|
||
"""根据当前权限模式构建权限说明消息(从模板读取并替换占位符)"""
|
||
template = self.load_prompt("permission_mode")
|
||
if not template:
|
||
return None
|
||
|
||
mode = self.get_permission_mode()
|
||
|
||
# 根据模式生成详细规则
|
||
if mode == "unrestricted":
|
||
detailed_rules = (
|
||
"- 所有命令和工具均可直接使用,无需用户批准\n"
|
||
"- run_command:支持任意命令,包括管道、重定向、子shell等\n"
|
||
"- 文件操作:read_file / write_file / edit_file 可直接使用;其他文件管理请通过 run_command 执行;需要 Python 时先探测并选择合适解释器"
|
||
)
|
||
elif mode == "readonly":
|
||
detailed_rules = (
|
||
"- run_command:统一在系统只读沙箱中执行\n"
|
||
"- 若命令触发写入,系统会直接拒绝并返回权限错误\n"
|
||
"- 文件操作:仅允许 read_file、view_image、view_video 等读取类工具\n"
|
||
"- 禁止:write_file、edit_file 及会修改工作区的命令操作"
|
||
)
|
||
elif mode == "approval":
|
||
detailed_rules = (
|
||
"- run_command:先在系统只读沙箱执行;仅当出现权限拒绝时才触发审批\n"
|
||
"- 审批通过后:仅当前这一次命令会重试为可写沙箱执行\n"
|
||
"- 需要用户批准:terminal_input、write_file、edit_file、save_webpage 及其他会修改工作区的操作\n"
|
||
"- 被拒绝或超时:本次操作不会执行写入"
|
||
)
|
||
elif mode == "auto_approval":
|
||
detailed_rules = (
|
||
"- write_file / edit_file:若路径在当前工作区内,直接执行;工作区外路径会进入审批流程\n"
|
||
"- run_command:先在系统只读沙箱执行;仅当出现权限拒绝时才触发自动审批\n"
|
||
"- 自动审批由后台审批智能体执行,默认只判断危险性与越权风险,不判断任务必要性\n"
|
||
"- 自动审批拒绝:本次工具调用会返回“拒绝+理由”,主循环继续;人工拒绝可随时接管\n"
|
||
"- 可随时人工接管:用户在审批面板点击同意/拒绝/切换无限制后,自动审批会立即停止并以人工决策为准"
|
||
)
|
||
else:
|
||
detailed_rules = ""
|
||
|
||
return template.format(
|
||
permission_mode=mode,
|
||
permission_mode_label=self._PERMISSION_MODE_LABEL.get(mode, mode),
|
||
mode_description=self._PERMISSION_MODE_DESC.get(mode, ""),
|
||
detailed_rules=detailed_rules,
|
||
)
|
||
|
||
def _build_execution_mode_message(self) -> Optional[str]:
|
||
"""根据当前执行环境模式构建提示消息。"""
|
||
# 仅宿主机模式注入;Docker 模式不需要该提示。
|
||
try:
|
||
if not getattr(self, "_is_host_mode", lambda: False)():
|
||
return None
|
||
except Exception:
|
||
return None
|
||
template = self.load_prompt("execution_mode")
|
||
if not template:
|
||
return None
|
||
state = {}
|
||
if hasattr(self, "get_execution_mode_state"):
|
||
try:
|
||
state = self.get_execution_mode_state() or {}
|
||
except Exception:
|
||
state = {}
|
||
mode = str(state.get("mode") or "sandbox").strip().lower()
|
||
mode_label = self._EXECUTION_MODE_LABEL.get(mode, mode)
|
||
if mode == "sandbox":
|
||
rules = (
|
||
"- 所有命令默认在系统 OS 沙箱中执行\n"
|
||
"- 若操作受系统权限限制:先提供 1 个安全替代方案并执行;若仍无法满足目标,明确请求用户切换执行环境为“完全访问权限”后重试\n"
|
||
"- 不要通过复杂绕过手段反复尝试规避沙箱限制;若任务本质需要更高权限,应直接说明并等待用户确认"
|
||
)
|
||
net = getattr(self, "host_network_permission", "restricted")
|
||
if net == "restricted":
|
||
network_rules = "- 网络:受限(仅允许 localhost,外部网络不可达)"
|
||
elif net == "full":
|
||
network_rules = "- 网络:完全开放"
|
||
else:
|
||
network_rules = ""
|
||
else:
|
||
rules = (
|
||
"- 当前为宿主机直接执行模式(完全访问权限)\n"
|
||
"- 仅在必须时执行高权限操作,保持最小化命令范围\n"
|
||
"- 涉及删除/覆盖/系统级变更前,先说明风险再执行"
|
||
)
|
||
network_rules = ""
|
||
return template.format(
|
||
execution_mode=mode,
|
||
execution_mode_label=mode_label,
|
||
rules=rules,
|
||
network_rules=network_rules,
|
||
)
|
||
|
||
def _get_or_init_frozen_mode_prompt(self, key: str, builder) -> Optional[str]:
|
||
return self._get_or_init_frozen_prompt(key, builder)
|
||
|
||
def _get_or_init_frozen_prompt(self, key: str, builder) -> Optional[str]:
|
||
cm = getattr(self, "context_manager", None)
|
||
meta = getattr(cm, "conversation_metadata", {}) if cm else {}
|
||
cached = meta.get(key) if isinstance(meta, dict) else None
|
||
if isinstance(cached, str) and cached:
|
||
return cached
|
||
|
||
built = builder() or ""
|
||
conv_id = getattr(cm, "current_conversation_id", None) if cm else None
|
||
if cm and conv_id:
|
||
try:
|
||
target_manager = (
|
||
cm._get_conversation_manager_for_id(conv_id)
|
||
if hasattr(cm, "_get_conversation_manager_for_id")
|
||
else cm.conversation_manager
|
||
)
|
||
target_manager.update_conversation_metadata(conv_id, {key: built})
|
||
if isinstance(cm.conversation_metadata, dict):
|
||
cm.conversation_metadata[key] = built
|
||
except Exception:
|
||
pass
|
||
return built
|