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