"""项目路径与目录配置。 运行态数据(对话、用户工作区、日志等)默认存放在用户主目录下的 ``~/.agents`` 中,并按运行模式分流: - 宿主机模式(``TERMINAL_SANDBOX_MODE=host``)-> ``~/.agents/host`` - 其它模式(默认 docker / web) -> ``~/.agents/web`` 对齐 ``~/.claude``、``~/.codex`` 的惯例,把运行态产物移出源码树。 路径解析优先级(从高到低): 1. 具体目录环境变量(``DATA_DIR`` / ``LOGS_DIR`` / ``USER_SPACE_DIR`` / ``API_USER_SPACE_DIR``)——单独覆盖某个目录,最高优先级; 2. 当前模式的根目录环境变量(``AGENTS_HOST_HOME`` 或 ``AGENTS_WEB_HOME``)—— 整体搬迁该模式下的全部运行态数据; 3. 兜底默认值 ``~/.agents/``。 注意:``config/*.json`` 分两类,不再一概锚定源码树: - **开发者配置 / 程序能力**(``docker_risk_markers.json`` / ``skill_hints.json``): 是程序行为的一部分,随版本演进,仍锚定源码树。 - **部署者自定义配置**(``custom_models`` / ``host_workspaces`` / ``auto_approval`` / ``goal_review`` / ``forbidden_commands`` / ``host_sandbox_policy``):因部署/机器而异、或含密钥,外置到 ``~/.agents//config/``(即 ``DEPLOY_CONFIG_DIR``)。读取走 ``resolve_deploy_config``,回退链为:部署目录 -> 源码树 ``.json`` -> 源码树 ``.json.example``。这样开发环境不必先跑 setup 也能用源码树种子。 """ import os from pathlib import Path _REPO_ROOT = Path(__file__).resolve().parents[1] def _norm(path: Path) -> Path: """展开 ``~``、转绝对路径并归一化。相对路径相对仓库根目录展开。""" path = path.expanduser() if not path.is_absolute(): path = _REPO_ROOT / path return path.resolve() def _env(name: str) -> str: return str(os.environ.get(name, "") or "").strip() def _resolve_repo_path(raw_value: str, default: str) -> str: """解析锚定在源码树的路径(配置文件、提示词等)。 相对路径相对仓库根目录展开,绝对路径与 ``~`` 原样支持。 """ candidate = str(raw_value or "").strip() or str(default) return str(_norm(Path(candidate))) def _runtime_mode() -> str: """运行模式:``host`` 或 ``web``。 仅当 ``TERMINAL_SANDBOX_MODE=host`` 时为 host 模式,其余(默认 docker)归为 web。 直接读环境变量,避免对 ``config.terminal`` 形成导入依赖。 """ return "host" if _env("TERMINAL_SANDBOX_MODE").lower() == "host" else "web" _MODE = _runtime_mode() # 当前模式的运行态根目录。 # 优先级:模式根变量(AGENTS_HOST_HOME / AGENTS_WEB_HOME)> 默认 ~/.agents/。 _MODE_HOME_ENV = "AGENTS_HOST_HOME" if _MODE == "host" else "AGENTS_WEB_HOME" _DEFAULT_RUNTIME_ROOT = Path("~/.agents") / _MODE RUNTIME_ROOT = str(_norm(Path(_env(_MODE_HOME_ENV) or _DEFAULT_RUNTIME_ROOT))) def _runtime_dir(env_name: str, subdir: str) -> str: """解析运行态子目录。 优先级:具体目录环境变量 > 当前模式根目录下的对应子目录。 具体目录变量支持相对路径(相对仓库根目录)、绝对路径与 ``~``。 """ raw = _env(env_name) if raw: return str(_norm(Path(raw))) return str(Path(RUNTIME_ROOT) / subdir) # ── 运行态目录(默认落在 ~/.agents/ 下,可被各自的环境变量覆盖)── DATA_DIR = _runtime_dir("DATA_DIR", "data") LOGS_DIR = _runtime_dir("LOGS_DIR", "logs") USER_SPACE_DIR = _runtime_dir("USER_SPACE_DIR", "users") # API 专用用户与工作区(与网页用户隔离) API_USER_SPACE_DIR = _runtime_dir("API_USER_SPACE_DIR", "api/users") # ── 部署级配置目录(部署者自定义的 config/*.json 外置到这里)── # 默认 ~/.agents//config,可用 DEPLOY_CONFIG_DIR 覆盖。 DEPLOY_CONFIG_DIR = _runtime_dir("DEPLOY_CONFIG_DIR", "config") # 源码树内的配置种子目录(首次运行/开发环境的默认内容来源)。 _SEED_CONFIG_DIR = _REPO_ROOT / "config" def deploy_config_path(filename: str) -> str: """部署级配置文件应写入/读取的位置(``~/.agents//config`` 下)。 用于会被运行时写回的文件(如 host_workspaces / host_sandbox_policy), 始终指向部署目录,不回退源码树。 """ return str(Path(DEPLOY_CONFIG_DIR) / filename) def resolve_deploy_config(filename: str) -> str: """解析部署级配置的只读路径,回退链(高→低): 1. ``~/.agents//config/``(部署者真实配置) 2. 源码树 ``config/``(仓库内种子) 3. 源码树 ``config/.example``(示例兜底) 都不存在时返回部署目录下的目标路径,由调用方处理「文件不存在」。 仅用于定位,不在此做拷贝(拷贝交给 setup / 迁移脚本)。 """ deployed = Path(DEPLOY_CONFIG_DIR) / filename if deployed.exists(): return str(deployed) seed = _SEED_CONFIG_DIR / filename if seed.exists(): return str(seed) example = _SEED_CONFIG_DIR / f"{filename}.example" if example.exists(): return str(example) return str(deployed) # ── 启动工作区 ── # 默认项目路径,可通过环境变量覆盖以指向宿主机任意目录。 DEFAULT_PROJECT_PATH = _resolve_repo_path(os.environ.get("DEFAULT_PROJECT_PATH", ""), "./project") # 兼容旧配置:若仍有模块读取 HOST_PROJECT_PATH,保留该键(实际宿主机路径选择改由 JSON 管理)。 HOST_PROJECT_PATH = _resolve_repo_path(os.environ.get("HOST_PROJECT_PATH", ""), DEFAULT_PROJECT_PATH) # ── 源码树内的配置与资源(不随运行态根目录迁移)── PROMPTS_DIR = _resolve_repo_path(os.environ.get("PROMPTS_DIR", ""), "./prompts") AGENT_SKILLS_DIR = _resolve_repo_path(os.environ.get("AGENT_SKILLS_DIR", ""), "./agentskills") WORKSPACE_SKILLS_DIRNAME = ".agents/skills" # 宿主机模式工作区配置文件(JSON)。属于部署级配置且会被运行时写回, # 因此指向部署目录 ~/.agents//config(HOST_WORKSPACES_FILE 显式设置时优先)。 HOST_WORKSPACES_FILE = _resolve_repo_path( os.environ.get("HOST_WORKSPACES_FILE", ""), deploy_config_path("host_workspaces.json"), ) # ── 基于 DATA_DIR 派生的数据文件 ── USERS_DB_FILE = f"{DATA_DIR}/users.json" INVITE_CODES_FILE = f"{DATA_DIR}/invite_codes.json" ADMIN_POLICY_FILE = f"{DATA_DIR}/admin_policy.json" API_USERS_DB_FILE = f"{DATA_DIR}/api_users.json" API_TOKENS_FILE = f"{DATA_DIR}/api_tokens.json" API_USAGE_FILE = f"{DATA_DIR}/api_usage.json" __all__ = [ "RUNTIME_ROOT", "DEFAULT_PROJECT_PATH", "HOST_WORKSPACES_FILE", "HOST_PROJECT_PATH", "PROMPTS_DIR", "DATA_DIR", "LOGS_DIR", "AGENT_SKILLS_DIR", "WORKSPACE_SKILLS_DIRNAME", "USER_SPACE_DIR", "USERS_DB_FILE", "INVITE_CODES_FILE", "ADMIN_POLICY_FILE", "API_USER_SPACE_DIR", "API_USERS_DB_FILE", "API_TOKENS_FILE", "API_USAGE_FILE", "DEPLOY_CONFIG_DIR", "deploy_config_path", "resolve_deploy_config", ]