agent-Specialization/config/paths.py
JOJO b8a51c1c63 refactor(config): 移除硬编码模型残留,部署级配置外置到 ~/.agents
## 模型逻辑清理
早期把模型 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>
2026-06-01 16:59:34 +08:00

181 lines
7.1 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.

"""项目路径与目录配置。
运行态数据(对话、用户工作区、日志等)默认存放在用户主目录下的
``~/.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>/configHOST_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",
]