agent-Specialization/test/test_config_paths_resolution.py
JOJO eaa5e3bee9 refactor(runtime): 运行态数据迁出源码树到 ~/.agents,日志默认关并加轮转,清理实验残留
将运行态数据(对话/用户工作区/日志等)默认迁出源码树到 ~/.agents/<mode>,
对齐 ~/.claude、~/.codex 惯例;同时为日志加上开关与轮转,并归档/清理仓库内
长期堆积的实验残留与误产生文件。

## 路径变量重写(核心)
- 重写 config/paths.py:运行态根按模式分流,host -> ~/.agents/host,
  其它(默认 docker/web)-> ~/.agents/web。
- 三级解析优先级(高→低):
  1) 具体目录变量 DATA_DIR / LOGS_DIR / USER_SPACE_DIR / API_USER_SPACE_DIR
  2) 模式根变量 AGENTS_HOST_HOME / AGENTS_WEB_HOME
  3) 兜底 ~/.agents/<mode>
- 导出变量名保持不变,20+ 处消费方零改动;统一 config/sub_agent.py 重复的
  路径解析逻辑,将 sub_agent 任务目录/状态文件收编进 DATA_DIR;
  SUB_AGENT_PROJECT_RESULTS_DIR 仍随工作区(有意保留)。
- config/*.json、prompts/、agentskills/ 属配置/资源,仍锚定源码树。

## 日志止血
- 新增 utils/log_rotation.py:按大小轮转(默认 20MB×3 份)+ 按份清理
  (dump 默认保留最近 30 个),阈值可由 AGENT_LOG_ROTATE_MAX_BYTES /
  AGENT_LOG_ROTATE_BACKUPS / AGENT_DUMP_KEEP 覆盖。
- utils/api_client.py:API 请求体 dump 默认关闭(AGENT_API_DUMP_ENABLED 开启),
  收编两处写死的 logs/ 硬编码路径改用 LOGS_DIR;dump 关闭时 request_dump 安全置空。
- utils/host_workspace_debug.py、server/utils_common.py(chunk/conn_diag 等)、
  utils/logger.py(TaskLogger/ErrorLogger)统一接入轮转。

## 迁移与运维脚本
- scripts/migrate_runtime_data.py:源码树 -> 运行态根,复制+备份+校验+可回滚+幂等,
  logs 丢弃不迁;import config 复用程序同一套路径解析(模式由 .env 决定),
  覆盖 data/users/api 及 sub_agent/tasks(-> data/sub_agent_tasks)。
- scripts/cleanup_misplaced_web.sh:清理误迁到 ~/.agents/web 的副本(带 chflags 兜底)。

## 测试
- 重写 test/test_config_paths_resolution.py:覆盖默认分流、模式切换、模式根变量覆盖、
  具体目录变量最高优先级、相对路径锚定 repo root、源码树配置项不随迁移等 6 个用例。

## 仓库清理
- 删除旧版子智能体目录 sub_agent/(逻辑已统一在 easyagent/)。
- 删除误产生/垃圾文件:误敲命令生成的 "ystemctl status ..."、空文件 .zhouyanbo /
  testfile_from_ai、运行态日志/pid、test_playwright.png、test_system_message.py 等。
- 实验残留与历史文档(BUG_FIX/POLLING 变更日志、SUB_AGENT 文档、翻译资料、
  model_tests、compact_result、奇奇怪怪的bug、截图、goal_research)归档到
  本地 _experiments/,并从版本控制移除(已加入 .gitignore)。
- .gitignore:新增 api/、_experiments/,清理已失效的 sub_agent/* 条目与冗余项。

## 文档
- CLAUDE.md / AGENTS.md:补充数据目录与路径变量、日志策略、迁移流程,
  更新目录结构(移除 sub_agent、新增 _experiments/scripts),调试日志路径改为 ~/.agents/<mode>/logs/。
2026-06-01 13:17:32 +08:00

161 lines
6.9 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.

from __future__ import annotations
import json
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
class ConfigPathsResolutionTest(unittest.TestCase):
def setUp(self):
self.repo_root = Path(__file__).resolve().parents[1]
def _load_paths(self, *, cwd: Path, extra_env: dict | None = None) -> dict:
code = """
import json
import config
print(json.dumps({
"RUNTIME_ROOT": config.RUNTIME_ROOT,
"DEFAULT_PROJECT_PATH": config.DEFAULT_PROJECT_PATH,
"HOST_WORKSPACES_FILE": config.HOST_WORKSPACES_FILE,
"PROMPTS_DIR": config.PROMPTS_DIR,
"DATA_DIR": config.DATA_DIR,
"LOGS_DIR": config.LOGS_DIR,
"USER_SPACE_DIR": config.USER_SPACE_DIR,
"API_USER_SPACE_DIR": config.API_USER_SPACE_DIR,
"SUB_AGENT_STATE_FILE": config.SUB_AGENT_STATE_FILE,
"SUB_AGENT_TASKS_BASE_DIR": config.SUB_AGENT_TASKS_BASE_DIR,
}, ensure_ascii=False))
"""
env = dict(os.environ)
env["PYTHONPATH"] = str(self.repo_root)
# 清掉可能从外部 .env / 环境继承的覆盖项,保证用例可控。
for key in (
"TERMINAL_SANDBOX_MODE", "AGENTS_HOST_HOME", "AGENTS_WEB_HOME",
"DATA_DIR", "LOGS_DIR", "USER_SPACE_DIR", "API_USER_SPACE_DIR",
"DEFAULT_PROJECT_PATH", "HOST_WORKSPACES_FILE", "PROMPTS_DIR",
"SUB_AGENT_STATE_FILE", "SUB_AGENT_TASKS_BASE_DIR",
):
env.pop(key, None)
if extra_env:
env.update(extra_env)
completed = subprocess.run(
[sys.executable, "-c", code],
cwd=str(cwd),
env=env,
capture_output=True,
text=True,
check=True,
)
lines = [line.strip() for line in (completed.stdout or "").splitlines() if line.strip()]
self.assertTrue(lines, f"stdout is empty, stderr={completed.stderr}")
return json.loads(lines[-1])
def test_runtime_dirs_default_to_web_home(self):
"""默认(非 host模式下运行态目录锚定到 ~/.agents/web。"""
with tempfile.TemporaryDirectory() as td:
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "docker"})
web_home = Path("~/.agents/web").expanduser().resolve()
self.assertEqual(data["RUNTIME_ROOT"], str(web_home))
self.assertEqual(data["DATA_DIR"], str(web_home / "data"))
self.assertEqual(data["LOGS_DIR"], str(web_home / "logs"))
self.assertEqual(data["USER_SPACE_DIR"], str(web_home / "users"))
self.assertEqual(data["API_USER_SPACE_DIR"], str(web_home / "api" / "users"))
# 数据文件跟随 DATA_DIR
self.assertEqual(data["SUB_AGENT_STATE_FILE"], str(web_home / "data" / "sub_agents.json"))
self.assertEqual(data["SUB_AGENT_TASKS_BASE_DIR"], str(web_home / "data" / "sub_agent_tasks"))
def test_runtime_dirs_switch_to_host_home(self):
"""host 模式下,运行态目录锚定到 ~/.agents/host。"""
with tempfile.TemporaryDirectory() as td:
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "host"})
host_home = Path("~/.agents/host").expanduser().resolve()
self.assertEqual(data["RUNTIME_ROOT"], str(host_home))
self.assertEqual(data["DATA_DIR"], str(host_home / "data"))
self.assertEqual(data["LOGS_DIR"], str(host_home / "logs"))
def test_source_tree_paths_stay_anchored_to_repo_root(self):
"""配置类路径提示词、host_workspaces.json、默认工作区仍锚定源码树。"""
with tempfile.TemporaryDirectory() as td:
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "docker"})
for key in ("DEFAULT_PROJECT_PATH", "HOST_WORKSPACES_FILE", "PROMPTS_DIR"):
value = Path(data[key])
self.assertTrue(value.is_absolute(), f"{key} not absolute: {data[key]}")
self.assertTrue(
str(value).startswith(str(self.repo_root)),
f"{key} should anchor to repo root: {data[key]}",
)
def test_mode_home_env_overrides_root(self):
"""模式根变量AGENTS_HOST_HOME / AGENTS_WEB_HOME整体搬迁运行态目录。"""
with tempfile.TemporaryDirectory() as td:
custom = Path(td) / "host_root"
data = self._load_paths(
cwd=Path(td),
extra_env={
"TERMINAL_SANDBOX_MODE": "host",
"AGENTS_HOST_HOME": str(custom),
},
)
self.assertEqual(data["RUNTIME_ROOT"], str(custom.resolve()))
self.assertEqual(data["DATA_DIR"], str((custom / "data").resolve()))
self.assertEqual(data["LOGS_DIR"], str((custom / "logs").resolve()))
def test_specific_dir_env_has_highest_priority(self):
"""具体目录变量优先级高于模式根变量。"""
with tempfile.TemporaryDirectory() as td:
root = Path(td) / "root"
logs = Path(td) / "separate_logs"
data = self._load_paths(
cwd=Path(td),
extra_env={
"TERMINAL_SANDBOX_MODE": "web",
"AGENTS_WEB_HOME": str(root),
"LOGS_DIR": str(logs),
},
)
# DATA 跟随根变量LOGS 被单独覆盖
self.assertEqual(data["DATA_DIR"], str((root / "data").resolve()))
self.assertEqual(data["LOGS_DIR"], str(logs.resolve()))
def test_relative_specific_dir_env_anchors_to_repo_root(self):
"""具体目录变量给相对路径时,仍锚定仓库根目录。"""
with tempfile.TemporaryDirectory() as td:
data = self._load_paths(
cwd=Path(td),
extra_env={
"TERMINAL_SANDBOX_MODE": "docker",
"DATA_DIR": "./data_rel",
"LOGS_DIR": "./logs_rel",
"USER_SPACE_DIR": "./users_rel",
"DEFAULT_PROJECT_PATH": "./workspace_rel",
"HOST_WORKSPACES_FILE": "./config/host_workspaces_rel.json",
"SUB_AGENT_STATE_FILE": "./data/sub_agents_rel.json",
},
)
self.assertEqual(data["DATA_DIR"], str((self.repo_root / "data_rel").resolve()))
self.assertEqual(data["LOGS_DIR"], str((self.repo_root / "logs_rel").resolve()))
self.assertEqual(data["USER_SPACE_DIR"], str((self.repo_root / "users_rel").resolve()))
self.assertEqual(data["DEFAULT_PROJECT_PATH"], str((self.repo_root / "workspace_rel").resolve()))
self.assertEqual(
data["HOST_WORKSPACES_FILE"],
str((self.repo_root / "config" / "host_workspaces_rel.json").resolve()),
)
self.assertEqual(
data["SUB_AGENT_STATE_FILE"],
str((self.repo_root / "data" / "sub_agents_rel.json").resolve()),
)
if __name__ == "__main__":
unittest.main()