- Split server/chat.py, server/status.py, server/tasks.py into sub-packages
- Split utils/context_manager.py, utils/conversation_manager.py into mixin packages
- Split modules/file_manager.py, persistent_terminal.py, terminal_ops.py, mcp_client_manager.py into packages
- Split core/main_terminal_parts/context.py into base + mixins
- Split static/src/app/methods/{ui,message,upload,taskPolling,conversation} into sub-modules
- Fix flattened method exports, dynamic import paths, missing cross-module imports
- Add missing permission/network/execution mode constants and _PERMISSION_MODE_LABEL
- Update AGENTS.md, CLAUDE.md and project memories to reflect new structure
154 lines
6.3 KiB
Python
154 lines
6.3 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 ConversationMixin:
|
|
"""MainTerminalContextMixin conversation 能力 mixin。"""
|
|
|
|
def _build_recent_conversations_message(self, limit: int = 10) -> Optional[str]:
|
|
"""构建最近对话提示(仅当前工作区)。"""
|
|
try:
|
|
manager = getattr(self.context_manager, "conversation_manager", None)
|
|
if not manager:
|
|
return None
|
|
current_id = getattr(self.context_manager, "current_conversation_id", None)
|
|
items = manager.get_recent_conversation_summaries(
|
|
limit=limit,
|
|
exclude_conversation_id=current_id,
|
|
first_message_max_chars=100,
|
|
)
|
|
if not items:
|
|
return None
|
|
lines: List[str] = []
|
|
for index, item in enumerate(items, start=1):
|
|
title = str(item.get("title") or "未命名对话").strip()
|
|
conv_id = str(item.get("id") or "").strip()
|
|
first_message = str(item.get("first_user_message") or "").strip() or "(无首条用户消息)"
|
|
updated_at = str(item.get("updated_at") or "").strip()
|
|
lines.append(f"{index}. [{conv_id}] 标题:{title}")
|
|
lines.append(f" 首条用户消息:{first_message}")
|
|
if updated_at:
|
|
lines.append(f" 更新时间:{updated_at}")
|
|
recent_text = "\n".join(lines)
|
|
template = self.load_prompt("recent_conversations").strip()
|
|
if template and "{recent_conversations}" in template:
|
|
return template.format(recent_conversations=recent_text)
|
|
if template:
|
|
return f"{template}\n\n{recent_text}"
|
|
return f"## 最近对话(仅当前工作区)\n\n{recent_text}"
|
|
except Exception:
|
|
return None
|
|
|
|
def _tool_calls_followed_by_tools(self, conversation: List[Dict], start_idx: int, tool_calls: List[Dict]) -> bool:
|
|
"""判断指定助手消息的工具调用是否拥有后续的工具响应。"""
|
|
if not tool_calls:
|
|
return False
|
|
expected_ids = [tc.get("id") for tc in tool_calls if tc.get("id")]
|
|
if not expected_ids:
|
|
return False
|
|
matched: Set[str] = set()
|
|
idx = start_idx + 1
|
|
total = len(conversation)
|
|
while idx < total and len(matched) < len(expected_ids):
|
|
next_conv = conversation[idx]
|
|
role = next_conv.get("role")
|
|
if role == "tool":
|
|
call_id = next_conv.get("tool_call_id")
|
|
if call_id in expected_ids:
|
|
matched.add(call_id)
|
|
else:
|
|
break
|
|
elif role in ("assistant", "user"):
|
|
break
|
|
idx += 1
|
|
return len(matched) == len(expected_ids)
|