## 模型逻辑清理 早期把模型 API 端点/密钥/模型 ID 硬编码的残留(AGENT_API_* / THINKING_* / TITLE_* 三件套)已彻底移除。模型配置统一由 config/custom_models.json (经 model_profiles 解析为档案)描述,运行时通过 apply_profile 注入; 没有任何可用模型时按既定行为报错。 - config/api.py: 删 9 个三件套符号,仅保留 DEFAULT_RESPONSE_MAX_TOKENS - utils/api_client.py: client 改为空配置起步,移除三件套 import - server/chat_flow_helpers.py: 删死 import(TITLE_* 符号) ## 部署级配置外置(按"决策权归谁"分类) config/*.json 不再一概锚定源码树: - 程序能力(docker_risk_markers / skill_hints)留源码树,随版本演进 - 部署者自定义(custom_models / host_workspaces / auto_approval / goal_review / forbidden_commands / host_sandbox_policy)外置到 ~/.agents/<mode>/config/ 新增统一解析机制(config/paths.py): - DEPLOY_CONFIG_DIR(默认 ~/.agents/<mode>/config,可用环境变量覆盖) - resolve_deploy_config():只读,回退链 部署目录→源码树.json→源码树.example (开发环境不必先跑 setup 也能用源码树种子) - deploy_config_path():写回用,稳定指向部署目录 6 个加载点改用上述解析;顺带修复 approval_agent / goal_review_agent 的 DEBUG_TRANSCRIPT_DIR 仍指旧源码树 logs/ 的漏迁问题。 ## 安全与运维 - 含密钥/机器特定的 5 个文件停止 git 追踪(git rm --cached,本地保留) 并加入 .gitignore,仓库仅留 .example 种子;forbidden_commands 保留 追踪作默认黑名单 - scripts/setup.py: 模型配置写到部署目录(用与 paths 一致的独立推导, 不 import config 以免过早锁定路径) - scripts/migrate_runtime_data.py: 新增 config/*.json → 部署目录迁移 (备份 + 不覆盖已存在) ## 关联:P1 配置收敛 + P2 首启向导 - config/server.py: Web 端口/监听地址/debug/NODE_BIN/PYTHON_BIN 单一事实源 - 消灭 8091 多处重复定义(state.py 死代码、app_legacy、main.py 各读各的) - 修复 sub_agent_manager 命令数组硬编码 "node" → NODE_BIN(便携包内置 Node 的前提) - scripts/setup.py: 终端首启向导(模式/端口/管理员/密钥/模型) ## 测试 test_config_paths_resolution 更新以反映新行为(host_workspaces 锚定部署 目录、新增 DEPLOY_CONFIG_DIR 用例);全部离线用例通过。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
181 lines
7.1 KiB
Python
181 lines
7.1 KiB
Python
"""项目路径与目录配置。
|
||
|
||
运行态数据(对话、用户工作区、日志等)默认存放在用户主目录下的
|
||
``~/.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/<mode>``。
|
||
|
||
注意:``config/*.json`` 分两类,不再一概锚定源码树:
|
||
|
||
- **开发者配置 / 程序能力**(``docker_risk_markers.json`` / ``skill_hints.json``):
|
||
是程序行为的一部分,随版本演进,仍锚定源码树。
|
||
- **部署者自定义配置**(``custom_models`` / ``host_workspaces`` /
|
||
``auto_approval`` / ``goal_review`` / ``forbidden_commands`` /
|
||
``host_sandbox_policy``):因部署/机器而异、或含密钥,外置到
|
||
``~/.agents/<mode>/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>。
|
||
_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/<mode> 下,可被各自的环境变量覆盖)──
|
||
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/<mode>/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/<mode>/config`` 下)。
|
||
|
||
用于会被运行时写回的文件(如 host_workspaces / host_sandbox_policy),
|
||
始终指向部署目录,不回退源码树。
|
||
"""
|
||
return str(Path(DEPLOY_CONFIG_DIR) / filename)
|
||
|
||
|
||
def resolve_deploy_config(filename: str) -> str:
|
||
"""解析部署级配置的只读路径,回退链(高→低):
|
||
|
||
1. ``~/.agents/<mode>/config/<filename>``(部署者真实配置)
|
||
2. 源码树 ``config/<filename>``(仓库内种子)
|
||
3. 源码树 ``config/<filename>.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/<mode>/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",
|
||
]
|