agent-Specialization/core/main_terminal_parts/context/prompt.py
JOJO 6fb5d10542 feat(context): CLAUDE.md 注入开关(默认关)+ .agents/skills 行业目录扫描(含同名冲突报错)
- personalization 新增 claude_md_auto_inject(默认关)与 agents_skills_scan_enabled(默认开),配置存 personalization.json 按工作区隔离
- prompt.py 指令文件加载器泛化,messages.py 新增 CLAUDE.md 冻结注入段(模板 claude_md_inject.txt),子目录只列路径通知
- skills_manager catalog 新增工作区 .agents/skills/ 根(原地读取不同步复制),build_skills_list 按真实路径前缀展示并附来源/冲突说明
- read_skill 支持 .agents/skills 路径解析;与 .astrion/skills 同名重复时报错并引导 read_file 按具体路径读取
- 多智能体 sub_agent_context 同步 CLAUDE.md 开关;顺带修复其 skills 段参数名写错被 except 吞掉恒为空、以及 f-string 打印列表 repr 的存量 bug
- 前端个人空间新增两个开关
2026-08-26 12:14:49 +08:00

176 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
import json
import time
from datetime import datetime
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
from modules.container_monitor import collect_stats, inspect_state
from core.tool_config import TOOL_CATEGORIES
from utils.api_client import APIClient
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
# 扫描一级子目录 AGENTS.md 时跳过的通用非源码目录(版本控制/依赖/构建产物/工具数据等,不针对本项目特化)
_AGENTS_MD_SKIP_DIRS = {
".git", ".hg", ".svn",
"node_modules", "bower_components",
".venv", "venv", "__pycache__",
"dist", "build", ".next", ".nuxt", ".output",
".astrion", ".claude", ".cursor", ".idea", ".vscode",
}
class PromptMixin:
"""MainTerminalContextMixin prompt 能力 mixin。"""
def _load_root_md_content(self, filename: str) -> Optional[str]:
"""加载工作区根目录的指定指令文件内容(仅根目录,子目录的不注入)。"""
try:
project_path = Path(self.project_path)
root_file = project_path / filename
if not root_file.is_file():
return None
content = root_file.read_text(encoding='utf-8')
return content.strip() if content else None
except Exception as exc:
logger.warning(f"[{filename}] 读取失败: {exc}")
return None
def _load_root_md_updated_at(self, filename: str) -> str:
"""返回根目录指定指令文件的最后修改时间文案如「最后修改2026-08-12 17:54失败返回空串"""
try:
root_file = Path(self.project_path) / filename
if not root_file.is_file():
return ""
mtime = root_file.stat().st_mtime
return f"(最后修改:{datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M')}"
except Exception:
return ""
def _load_sub_md_paths(self, filename: str, max_notice: int = 20, timeout_seconds: float = 30.0) -> tuple:
"""扫描工作区一级子目录中的指定指令文件,返回 (相对路径列表, 实际总数)。
- 仅扫描根目录与一级子目录不递归跳过通用非源码目录_AGENTS_MD_SKIP_DIRS
- 列表最多 max_notice 个(超出截断,总数仍在第二项返回)。
- 扫描超过 timeout_seconds 秒时停止,返回已扫描到的结果。
"""
found: List[str] = []
total = 0
try:
project_path = Path(self.project_path)
if not project_path.is_dir():
return found, total
start = time.monotonic()
for sub in project_path.iterdir():
if time.monotonic() - start >= timeout_seconds:
logger.warning(f"[{filename}] 子目录扫描超过 {timeout_seconds}s返回已扫描到的 {len(found)}")
break
if not sub.is_dir() or sub.name in _AGENTS_MD_SKIP_DIRS:
continue
candidate = sub / filename
if candidate.is_file():
total += 1
if len(found) < max_notice:
found.append(candidate.relative_to(project_path).as_posix())
except Exception as exc:
logger.warning(f"[{filename}] 子目录扫描失败: {exc}")
return found, total
def _load_agents_md_content(self) -> Optional[str]:
"""加载工作区根目录的 AGENTS.md 文件内容(仅根目录,子目录的不注入)。"""
return self._load_root_md_content("AGENTS.md")
def _load_agents_md_updated_at(self) -> str:
"""返回根目录 AGENTS.md 的最后修改时间文案如「最后修改2026-08-12 17:54失败返回空串"""
return self._load_root_md_updated_at("AGENTS.md")
def _load_sub_agents_md_paths(self, max_notice: int = 20, timeout_seconds: float = 30.0) -> tuple:
"""扫描工作区一级子目录中的 AGENTS.md返回 (相对路径列表, 实际总数)。"""
return self._load_sub_md_paths("AGENTS.md", max_notice=max_notice, timeout_seconds=timeout_seconds)
def load_prompt(self, name: str) -> str:
"""加载提示模板"""
prompt_file = Path(PROMPTS_DIR) / f"{name}.txt"
if prompt_file.exists():
with open(prompt_file, 'r', encoding='utf-8') as f:
return f.read()
return "你是 Astrion一个智能助手。"