agent-Specialization/modules/multi_agent/sub_agent_context.py
JOJO 493ac160eb fix(windows): 平台适配与稳定性修复(进程终止/编码/原子写/事件竞态/子智能体)
- 进程终止:Windows 用 CTRL_BREAK + taskkill /F /T 替代不存在的 killpg/SIGKILL,
  超时/取消全路径有界;_is_pid_alive 改 ctypes(原 os.kill(pid,0) 在 Windows 会真杀进程);
  reader 任务 finally 收口,消除 Task was destroyed but it is pending / unclosed transport
- 子智能体调度链:_run_coro 超时 60s + 失败 cancel/close 防幽灵协程;create 改先调度后提交、
  失败回滚;state 归属守卫 + 新建 120s 宽限期防误标已终止;_ensure_event_loop 加锁;
  子智能体 todo 改 per-agent 隔离存储,不再串到主智能体前端
- 子智能体执行环境(execution_env_text.py):创建/切换/恢复三时点注入环境说明,
  Windows 下 sandbox=WSL2 bash、direct=cmd;inject_notification 纯通知不触发新一轮工作,
  tool 序列中延迟到安全点 flush;传统子智能体按既定语义不通知
- 编码:git 侧边栏 subprocess 显式 utf-8 + errors=replace(修中文 Windows GBK
  _readerthread 崩溃导致侧边栏空白);check_environment/install_package 用 self.python_cmd
- 安全/路径:permission.py Windows deny 列表生效 + 驱动器根拦截;路径比较统一 normcase;
  /tmp 白名单平台分流;正斜杠判断归一化;os-release 平台守卫;PowerShell -ExecutionPolicy Bypass
- 稳定性:任务事件轮询持锁快照(修 deque mutated during iteration);
  原子写新增 replace_with_retry(修 WinError 5/32 瞬时持锁);
  共享文件兜底链 symlink→os.link 硬链接→copy2
- 部署:新增 _bootstrap.bat / setup.bat / start.bat Windows 启动脚本
2026-07-31 11:33:57 +08:00

248 lines
9.2 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.

"""多智能体子智能体动态上下文构建。
在子智能体创建时,为其系统提示词注入工作区相关信息:
- 执行环境(宿主机/docker + 沙箱/直接)
- AGENTS.md 项目规范
- 工作区信息(项目名称、运行环境、当前时间,不含 file_tree / recent_uploads
- .astrion/ 目录结构说明skills / memory / review / user_upload
- 可用 skill 列表
- 项目记忆索引
所有信息在子智能体创建那一刻快照写入 system_prompt.txt后续冻结不更新。
"""
from __future__ import annotations
import platform
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional
try:
from config import (
PROMPTS_DIR,
WORKSPACE_SKILLS_DIRNAME,
WORKSPACE_MEMORY_DIRNAME,
WORKSPACE_REVIEW_DIRNAME,
TERMINAL_SANDBOX_MODE,
)
from config.paths import IS_HOST_MODE
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 (
PROMPTS_DIR,
WORKSPACE_SKILLS_DIRNAME,
WORKSPACE_MEMORY_DIRNAME,
WORKSPACE_REVIEW_DIRNAME,
TERMINAL_SANDBOX_MODE,
)
from config.paths import IS_HOST_MODE
def _load_agents_md(workspace_path: str) -> Optional[str]:
"""加载工作区根目录的 AGENTS.md 文件内容。"""
try:
project_path = Path(workspace_path)
if not project_path.exists():
return None
agents_md_files = list(project_path.rglob("AGENTS.md"))
if not agents_md_files:
return None
latest_file = max(agents_md_files, key=lambda p: p.stat().st_mtime)
content = latest_file.read_text(encoding="utf-8")
return content.strip() if content else None
except Exception:
return None
def _build_execution_env_section(workspace_path: str, *, sandbox_mode: str = "") -> str:
"""构建执行环境信息段。
宿主机模式复用子智能体共享环境文本平台细分Windows 下 sandbox=WSL2 bash、
direct=cmdmac/Linux 同为 POSIX shell创建时快照切换由运行期通知补充。
Docker 模式:保持容器语义描述。
"""
mode = (sandbox_mode or TERMINAL_SANDBOX_MODE or "").lower()
if IS_HOST_MODE:
from modules.execution_env_text import build_sub_agent_env_section
return build_sub_agent_env_section(workspace_path, mode)
return (
"## 执行环境\n\n"
"- 运行环境Docker 容器模式\n"
"- 执行方式:容器内执行\n\n"
"### 当前规则\n"
"- 所有命令在 Docker 容器内执行\n"
"- 工作区挂载在容器内 /workspace 路径下\n"
"- 网络可能受限,仅允许 localhost\n"
)
def _build_workspace_section(workspace_path: str) -> str:
"""构建工作区信息段(精简版,不含 file_tree 和 recent_uploads"""
project_path = Path(workspace_path)
project_name = project_path.name or "未命名项目"
system_info = f"{platform.system()} {platform.release()}"
current_time = datetime.now().strftime("%Y年%m月%d%H点24小时制")
if IS_HOST_MODE:
resource_limit = "宿主机模式无限制"
else:
resource_limit = "容器模式,受 Docker 资源限制"
return (
f"## 工作区信息\n\n"
f"- 项目名称:{project_name}\n"
f"- 运行环境:{system_info}\n"
f"- 资源限制:{resource_limit}\n"
f"- 当前时间:{current_time}\n"
)
def _build_astrion_dir_section(workspace_path: str) -> str:
"""构建 .astrion/ 目录结构说明段。"""
return (
f"## 工作区数据目录(.astrion/\n\n"
f"工作区内的 `.astrion/` 目录存储以下内容:\n"
f"- `{WORKSPACE_SKILLS_DIRNAME}/`可用技能skill目录每个 skill 含 SKILL.md 说明\n"
f"- `{WORKSPACE_MEMORY_DIRNAME}/`:项目记忆,以 Markdown 文件形式存放项目约定/决策/调试坑\n"
f"- `{WORKSPACE_REVIEW_DIRNAME}/`:对话回顾文件\n"
f"- `user_upload/`:用户上传的文件\n\n"
f"用户上传的文件位于 `.astrion/user_upload/` 目录下。"
f"项目记忆以文件形式存放在 `{WORKSPACE_MEMORY_DIRNAME}/` 下。"
f"技能skill存放在 `{WORKSPACE_SKILLS_DIRNAME}/` 下。\n"
)
def _build_agents_md_section(workspace_path: str) -> str:
"""构建 AGENTS.md 项目规范段。"""
content = _load_agents_md(workspace_path)
if not content:
return ""
return (
f"## AGENTS.md 项目规范\n\n"
f"> 以下是工作区根目录的 AGENTS.md 文件内容,请在回答时参考这些项目规范:\n\n"
f"{content}\n\n"
f"---\n"
f"注意:以上规范来自工作区根目录的 AGENTS.md 文件,若与未来代码冲突,以实际代码为准。\n"
)
def _build_skills_section(workspace_path: str, data_dir: str = "") -> str:
"""构建可用 skill 列表段。"""
try:
from modules.skills_manager import (
get_skills_catalog,
build_skills_list,
infer_private_skills_dir,
)
private_dir = infer_private_skills_dir(data_dir) if data_dir else None
catalog = get_skills_catalog(private_dir=private_dir)
# 子智能体不区分 enabled/disabled列出全部可用 skill
skills_list = build_skills_list(catalog, enabled=None)
if not skills_list:
return ""
return (
f"## 可用 AgentSkill\n\n"
f"agent skills 系统已启用,以下是可用的 skills含简要说明\n\n"
f"{skills_list}\n\n"
f"使用技能时,优先用 read_skill 通过技能名读取 SKILL.md"
f"需要阅读 skill 目录中的其他文件时再使用 read_file\n"
)
except Exception:
return ""
def _build_project_memory_section(workspace_path: str) -> str:
"""构建项目记忆索引段(只列索引,不展开内容)。"""
try:
memory_dir = Path(workspace_path) / WORKSPACE_MEMORY_DIRNAME
if not memory_dir.exists() or not memory_dir.is_dir():
return ""
entries: List[str] = []
for md_file in sorted(memory_dir.glob("*.md")):
try:
text = md_file.read_text(encoding="utf-8")
name = None
description = None
if text.startswith("---"):
end = text.find("---", 3)
if end > 0:
frontmatter = text[3:end]
for line in frontmatter.strip().split("\n"):
ls = line.strip()
if ls.startswith("name:"):
name = ls.split(":", 1)[1].strip()
elif ls.startswith("description:"):
description = ls.split(":", 1)[1].strip()
file_name = md_file.name
display_name = name or md_file.stem
desc = description or ""
if desc:
entries.append(f".astrion/memory/{file_name}{desc}")
else:
entries.append(f".astrion/memory/{file_name}:当需要{display_name}时,应该索引本记忆")
except Exception:
pass
if not entries:
return ""
return (
f"## 项目记忆\n\n"
f"项目记忆以文件形式存放在 `.astrion/memory/` 下。\n"
f"开始涉及项目约定、历史决策或已知问题的任务前,先检查项目记忆索引,"
f"不确定时先用 search_project_memory 检索正文,定位后用 recall_project_memory 读取。\n\n"
f"### 项目记忆索引\n\n"
+ "\n".join(entries)
+ "\n"
)
except Exception:
return ""
def build_sub_agent_dynamic_context(
workspace_path: str,
*,
data_dir: str = "",
sandbox_mode: str = "",
) -> str:
"""构建子智能体的动态上下文信息(拼接到 system prompt 末尾)。
所有信息在子智能体创建那一刻快照,后续冻结不更新。
Args:
workspace_path: 工作区路径
data_dir: 数据目录路径(用于推断 private skills 目录)
sandbox_mode: 沙箱模式sandbox/direct/host/docker
"""
sections: List[str] = []
# 1. 执行环境
sections.append(_build_execution_env_section(workspace_path, sandbox_mode=sandbox_mode))
# 2. 工作区信息(精简版)
sections.append(_build_workspace_section(workspace_path))
# 3. .astrion/ 目录说明
sections.append(_build_astrion_dir_section(workspace_path))
# 4. AGENTS.md
agents_md = _build_agents_md_section(workspace_path)
if agents_md:
sections.append(agents_md)
# 5. 可用 skill
skills = _build_skills_section(workspace_path, data_dir=data_dir)
if skills:
sections.append(skills)
# 6. 项目记忆索引
memory = _build_project_memory_section(workspace_path)
if memory:
sections.append(memory)
return "\n".join(sections)