# 配置系统改造计划 > 版本:v2.0 | 日期:2026-06-13 | 基于 OpenCode 架构分析 + 当前代码现状 --- ## 〇、最终目录结构(已确认) ### 目录树 ``` ~/.agents/agents/ ← 数据根目录(data_root) ├── settings.json ← 唯一配置文件 ├── config/ ← 部署级 JSON,两个模式共享 │ ├── custom_models.json │ ├── host_workspaces.json │ ├── host_sandbox_policy.json │ ├── forbidden_commands.json │ └── ... ├── host/ ← host 模式(单用户) │ ├── data/ ← 对话/记忆/用户数据库 │ └── logs/ └── web/ ← web 模式(多用户) ├── users/ ← 每个用户独立工作区 │ ├── alice/ │ │ └── data/ ← alice 的对话/记忆 │ └── bob/ │ └── data/ └── logs/ ← web 服务级日志 ``` > **注意**:host 模式没有 `users/`(单用户直接走 `data/`);web 模式根下没有 `data/`(数据分散在各 `users//` 下)。 ### 元配置 vs 普通配置 **元配置**(启动前必须知道,不在 `settings.json` 里): | 元配置项 | 设定方式 | 默认值 | |---------|---------|--------| | `data_root` | CLI `--data-root` / 环境变量 `AGENTS_DATA_ROOT` | `~/.agents/agents` | **普通配置**(启动后加载,在 `settings.json` 里): | 分类 | 示例 | |------|------| | 运行模式 | `terminal.sandbox_mode`(host/web) | | 服务 | `server.port`、`server.host` | | 模型 | `models.default_model_key` | | 终端 | `terminal.execution_mode_default`、沙箱参数 | | 管理员 | `admin.username`、`admin.password_hash` | | Flags | `flags.api_dump_enabled` 等 | ### 启动流程 ``` 1. 解析 CLI args / 环境变量 → 确定 data_root(默认 ~/.agents/agents) 2. 在 {data_root}/ 下创建目录结构(若不存在) 3. 加载 {data_root}/settings.json 4. 根据 settings.json 中的 sandbox_mode 确定数据子目录(host/ 或 web/) 5. 初始化子目录下的 data/ users/ logs/ 6. 加载 {data_root}/config/ 下的部署级 JSON 7. 启动 Web 服务 ``` 关键约束: - `data_root` 是元配置,**不在** `settings.json` 里(否则鸡生蛋) - `config/` 在根目录,两个模式**共享**(custom_models 不应该切换模式就丢失) - 首次启动时若 `settings.json` 不存在,使用硬编码默认值 + 自动弹出初始化向导 与 OpenCode 对照: ``` OpenCode: ~/.config/opencode/config.json + ~/.local/share/opencode/ 我们: {data_root}/settings.json + {data_root}/{mode}/ 其中 data_root 默认 ~/.agents/agents ``` --- ## 一、现状诊断 ### 1.1 架构问题全景图 ``` 当前启动流程: ./start.sh → _bootstrap.sh(创建 venv、pip install、npm ci & build) → 若 .env 不存在 → python -m scripts.setup(交互式向导,写出 .env) → exec python -m server.app → import config(触达 _load_dotenv(),锁定一切 ↓) ┌──────────────────────────────────────────────────────────┐ │ import config 瞬间,以下全部固化为模块级常量: │ │ │ │ config/paths.py RUNTIME_ROOT, DATA_DIR, LOGS_DIR, │ │ USER_SPACE_DIR, DEFAULT_PROJECT_PATH │ │ config/server.py WEB_SERVER_PORT, WEB_SERVER_HOST │ │ config/terminal.py TERMINAL_SANDBOX_MODE, ... │ │ config/auth.py ADMIN_USERNAME, ADMIN_PASSWORD_HASH │ │ config/model_profiles.py 模型列表 │ └──────────────────────────────────────────────────────────┘ 当前目录结构(问题:web 模式数据也落在 host/ 下,分流名存实亡): ~/.agents/ ├── host/ ← 实际数据全在这里(因为 .env 写死 host) │ ├── .env ← ❌ 配置居然放在数据目录里 │ ├── config/ │ ├── data/ │ ├── users/ │ └── logs/ └── web/ ← 几乎空,从未被真正使用 ``` ### 1.2 .env 被 6 处独立读取 | # | 文件 | 读取方式 | 问题 | |---|------|---------|------| | 1 | `config/__init__.py:_load_dotenv()` | 主加载器,读入 `os.environ` | 唯一的"正规"入口,但逻辑是 .env 不覆盖已有环境变量 | | 2 | `config/auth.py:_dotenv_cache()` | 直接读 `.env` 文件,有独立缓存 | **优先级反转**:优先 .env 再回退 os.environ | | 3 | `config/model_profiles.py:_env_optional()` | 先 os.environ,回退读 `.env` | 与主加载器逻辑重复 | | 4 | `utils/api_client.py:_resolve_env_value()` | 同上 | 同上 | | 5 | `server/chat_flow_helpers.py:_env_optional()` | 同上 | 同上 | | 6 | `modules/balance_client.py:_read_dotenv()` | 直接读 `.env`,有独立缓存 | 第 3 份 .env 解析实现 | **核心问题**:没有统一的配置来源,6 个模块各有一套 .env 解析逻辑,优先级不一致。 ### 1.3 历史遗留代码 | 文件 | 问题 | |------|------| | `web_server.py` | 已标记 deprecated,但仍存在,且是 `main.py` 的实际调用路径 | | `main.py` | CLI 模式代码是死代码(`setup_run_mode()` 硬编码 `self.web_mode = True`) | | `server/app.py` | 完全转发 `app_legacy.py`,"逐步替换"从未实现 | | `server/app_legacy.py` | 1409 行巨型文件,包含了从路由到工具执行的几乎所有逻辑 | ### 1.4 初始化脚本 ``` setup.sh → _bootstrap.sh(共享引导)→ scripts/setup.py(交互向导) start.sh → _bootstrap.sh(共享引导)→ python -m server.app ``` - `scripts/setup.py` 刻意不 `import config`(怕过早锁定路径),说明架构本身有问题 - 每次启动都走 `ensure_python_env` / `ensure_node_env` --- ## 二、OpenCode 架构参考 ### 2.1 核心设计原则 | 原则 | OpenCode 做法 | |------|-------------| | **配置存储** | JSON(C) 文件 + 环境变量,**无 .env 文件** | | **加载时机** | 运行时 Effect Service,惰性按需加载 | | **路径标准** | XDG 标准(`~/.config/opencode`、`~/.local/share/opencode`) | | **配置层级** | 9 层优先级叠加(全局→项目→远程→托管→MDM) | | **变量引用** | `{env:VAR}` / `{file:path}` 语法 | | **运行时修改** | `Config.update()` / `Config.updateGlobal()` 写回 JSON | | **依赖管理** | Effect-TS Layer 依赖注入,服务间松耦合 | ### 2.2 可借鉴的关键模式 **模式 1:运行时 Service 而非模块常量** ```python # ❌ 当前做法 from config import DATA_DIR # import 时固化 # ✅ 目标做法 from config.manager import config_manager data_dir = config_manager.get("DATA_DIR") # 运行时获取,支持热更新 ``` **模式 2:分层配置优先级** ``` 优先级(高→低): 运行时 Set(config_manager.set()) → 环境变量覆盖 → 项目级配置(/.agents/config.json) → 全局配置(~/.agents/agents/settings.json) → 默认值 ``` **模式 3:统一 ConfigManager 单例** ``` ConfigManager ├── load() 从 JSON 文件加载 ├── get(key) 获取配置值 ├── set(key, value) 运行时修改 + 持久化 ├── reload() 重新加载(配置热更新) ├── subscribe() 变更监听 └── _resolve_path() 路径解析 ``` **模式 4:Flag 系统** ```python # 所有 feature flag 集中定义 class Flags: DISABLE_AUTOCOMPACT = _flag("AGENT_DISABLE_AUTOCOMPACT") ENABLE_API_DUMP = _flag("AGENT_API_DUMP_ENABLED") # ... ``` --- ## 三、改造目标 ### 3.1 最终状态 ``` 用户启动 → python -m server.app [--data-root ~/.agents/agents] → 解析 data_root(元配置,CLI > 环境变量 > 默认值) → ConfigManager.initialize(data_root) → 加载 {data_root}/settings.json → 解析 sandbox_mode → 确定数据子目录 {data_root}/{mode}/ → 加载 {data_root}/config/ 下的部署级 JSON → Web 服务启动 → 管理后台 UI 可实时修改 settings.json 中的配置 → 配置变更自动通知相关组件 ``` ### 3.2 核心原则 1. **启动后再配置**:除最低限度启动参数(端口等)外,所有配置可在运行时修改 2. **单一配置源**:所有模块统一通过 `ConfigManager` 获取配置 3. **JSON 为主、.env 退役**:配置文件统一为 `{data_root}/settings.json`。.env 仅作为开发环境快捷方式(可选),不再参与正式配置流程 4. **元配置与普通配置分离**:`data_root` 等必须在加载配置前确定的参数走 CLI/环境变量,其余走 `settings.json` 4. **向后兼容**:分阶段改造,每阶段可独立上线 5. **最小改动**:先统一读取入口,再逐步改造消费端 --- ## 四、分阶段改造计划 ### 阶段 1:统一配置源(预计改动文件数:~8) **目标**:消除 6 处独立 .env 读取,所有配置从单一入口获取。 #### 1.1 新建 `config/manager.py` ```python """ ConfigManager — 统一配置管理器(单例) 职责: - 从 JSON 配置文件加载配置 - .env 仅作为"种子",ConfigManager 加载后统一接管 - 所有模块通过 ConfigManager 获取配置,不再直接读 .env 或 os.environ """ class ConfigManager: _instance = None def __init__(self): self._data: dict = {} self._seeds: dict = {} # 从 .env 一次性读入的种子值 self._callbacks: list = [] # ── 初始化 ── def initialize(self, mode: str | None = None): """启动时调用一次。加载 .env 种子 → 合并 JSON 配置 → 应用默认值。""" self._load_env_seeds() self._load_json_config(mode) self._apply_defaults() # ── 读取 ── def get(self, key: str, default=None): """获取配置值。优先级:运行时 Set > 环境变量 > JSON 配置 > 默认值。""" def get_path(self, key: str) -> str: """获取路径类配置(自动展开 ~、转绝对路径)。""" # ── 写入 ── def set(self, key: str, value): """运行时修改配置(持久化到 JSON + 通知订阅者)。""" # ── 热更新 ── def reload(self): """重新加载 JSON 配置(保留运行时 Set 的覆盖)。""" def on_change(self, callback): """注册配置变更回调。""" ``` #### 1.2 统一改造点 | 当前位置 | 改造方式 | |---------|---------| | `config/__init__.py:_load_dotenv()` | 替换为 `config_manager.initialize()` | | `config/auth.py:_dotenv_cache()` | 改为 `config_manager.get("AGENT_ADMIN_USERNAME")` | | `config/model_profiles.py:_env_optional()` | 改为 `config_manager.get(...)`,移除 .env 回退 | | `utils/api_client.py:_resolve_env_value()` | 同上 | | `server/chat_flow_helpers.py:_env_optional()` | 同上 | | `modules/balance_client.py:_read_dotenv()` | 同上 | | `config/terminal.py` | 常量改为从 `config_manager` 获取 | | `config/server.py` | 同上(端口等可保留环境变量覆盖,但统一入口) | #### 1.3 兼容性策略 ```python # config/__init__.py 保持向后兼容 from config.manager import config_manager # 旧的 from config import DATA_DIR 仍然可用(通过模块级 __getattr__ 代理) def __getattr__(name): return config_manager.get(name) ``` #### 1.4 配置文件格式 ```jsonc // ~/.agents/agents/settings.json(唯一配置,不分 host/web) { "terminal": { "sandbox_mode": "host", // "host" | "web" "execution_mode_default": "sandbox", "direct_ttl_seconds": 600 }, "server": { "port": 8091, "host": "127.0.0.1" }, "paths": { "project_path": null // null = ./project(相对于仓库根目录) }, "models": { "default_model_key": null }, "flags": { "api_dump_enabled": false } } ``` 路径解析规则: - `data_root` 未设置时,默认 `~/.agents/agents` - `data_root` 设置后,host/web 子目录在其下创建 - 模式子目录:`{data_root}/{sandbox_mode}/`,如 `~/.agents/agents/host/data/` #### 1.5 验证标准 - [ ] 删除项目根目录 `.env` 后,程序仍能正常启动(使用默认值) - [ ] 所有现有功能不受影响 - [ ] `grep -rn 'parents\[1\].*\.env'` 结果中只剩 `config/manager.py` 和 `scripts/setup.py` --- ### 阶段 2:运行时配置修改(预计改动文件数:~10) **目标**:管理后台 UI 可修改配置,无需重启。 #### 2.1 ConfigManager 增强 ```python class ConfigManager: def set(self, key: str, value): """运行时修改配置""" self._data[key] = value self._persist() # 写回 JSON self._notify(key, value) # 通知订阅者 def subscribe(self, key: str, callback): """订阅特定配置项的变更""" def get_all(self) -> dict: """获取完整配置(供管理后台展示)""" ``` #### 2.2 前端管理后台新增"系统设置"页面 ``` / 管理后台 ├── 用户管理 ├── 模型配置 (已有) ├── 系统设置 (新增) │ ├── 运行模式(host/web) │ ├── 数据目录 │ ├── Web 服务(端口/地址) │ ├── 终端沙箱配置 │ └── 管理员账户 └── ... ``` #### 2.3 不可运行时修改的配置 以下配置修改后需要手动重启才能生效,管理后台应有明确提示: | 配置项 | 原因 | |--------|------| | `server.port` | Flask/SocketIO 已在监听端口 | | `server.host` | 同上 | | `terminal.sandbox_mode` | 影响终端管理器初始化 | | `data_root` | 元配置,启动后数据目录已锁定 | #### 2.4 验证标准 - [ ] 在管理后台修改模型配置后,新对话立即生效(无需重启) - [ ] 修改数据目录路径后,新数据写入新位置 - [ ] 修改 Web 端口时,前端提示"需要重启" --- ### 阶段 3:分层配置 + 项目级覆盖(预计改动文件数:~6) **目标**:支持全局配置 + 项目级配置 + 环境变量覆盖。 #### 3.1 配置优先级 ``` 优先级(高→低): 1. 运行时 Set(config_manager.set(),内存中) 2. 环境变量覆盖(os.environ) 3. 项目级配置(/.agents/config.json) 4. 全局配置(~/.agents//config/settings.json) 5. 默认值(硬编码) ``` #### 3.2 变量引用语法 ```jsonc // 配置文件中支持变量引用 { "model": { "base_url": "{env:AGENT_API_BASE_URL}", "api_key": "{env:AGENT_API_KEY}" }, "prompt": { "custom": "{file:~/my_prompt.txt}" } } ``` - `{env:VAR}` — 引用环境变量 - `{file:path}` — 引用文件内容(`~/` 展开为用户主目录) #### 3.3 项目级配置 ```python # 当用户选择了工作区后,自动加载项目级配置 config_manager.load_project_config(workspace_path) # → 读取 /.agents/config.json # → 浅层覆盖全局配置 ``` #### 3.4 验证标准 - [ ] 不同工作区可以有不同配置(如不同默认模型) - [ ] `{env:VAR}` 语法正常工作 - [ ] 配置文件缺失时回退到上一级,不报错 --- ### 阶段 4:清理历史遗留(预计改动文件数:~5) **目标**:移除屎山代码,精简入口。 #### 4.1 清理清单 | 文件 | 操作 | |------|------| | `web_server.py` | 删除(已是兼容入口,无实际逻辑) | | `main.py` | 删除 CLI 模式死代码(`setup_project_path`、`setup_run_mode` 等交互式部分),精简为纯 Web 启动 | | `server/app.py` | 合并 `app_legacy.py` 的核心逻辑,"转发"改为"所有权" | | `server/app_legacy.py` | 拆分为多个模块(chat/、tasks/、conversation/ 已有蓝图,继续拆分) | #### 4.2 标准化启动 ```bash # 唯一的启动方式 python -m server.app [--port 8091] [--host 127.0.0.1] # 或保留 start.sh 作为便利脚本 ./start.sh ``` 移除 `setup.sh` → `start.sh` 的双脚本依赖,合并为一个入口。 #### 4.3 验证标准 - [ ] `python -m server.app` 正常工作 - [ ] 旧的 `python web_server.py` 报错提示使用新入口 - [ ] `python main.py` 仍可工作但输出 deprecation warning --- ## 五、文件变更清单总览 | 阶段 | 新增文件 | 修改文件 | 删除文件 | |------|---------|---------|---------| | 1 | `config/manager.py` | `config/__init__.py`, `config/auth.py`, `config/model_profiles.py`, `config/paths.py`, `config/terminal.py`, `config/server.py`, `utils/api_client.py`, `server/chat_flow_helpers.py`, `modules/balance_client.py` | — | | 2 | `static/src/components/admin/SystemSettings.vue`(前端) | `config/manager.py`(增强), `server/` 新增 `/api/admin/settings` 路由 | — | | 3 | — | `config/manager.py`(增强分层加载、变量解析) | — | | 4 | — | `main.py`(精简), `server/app.py`(接管) | `web_server.py` | --- ## 六、风险控制 1. **每阶段独立可上线**:阶段 1 完成后即可合并,不影响阶段 2-4 的进度 2. **向后兼容**:阶段 1 保留 `from config import DATA_DIR` 语法,通过 `__getattr__` 代理 3. **回滚安全**:配置文件备份机制(`settings.json.bak_`) 4. **渐进式清理**:先统一入口,再逐个模块迁移,不搞大爆炸重构 --- ## 七、OpenCode vs 我们的对照速查表 | 概念 | OpenCode | 我们的目标 | |------|---------|-----------| | 全局配置路径 | `~/.config/opencode/config.json` | `{data_root}/settings.json`(默认 `~/.agents/agents/settings.json`) | | 项目配置 | `/opencode.json(c)` 向上查找 | `/.agents/config.json` | | 配置 Service | `Config.Service` (Effect-TS) | `ConfigManager` (Python 单例) | | 变量引用 | `{env:VAR}` / `{file:path}` | 同 | | 运行时修改 | `Config.update()` / `Config.updateGlobal()` | `config_manager.set()` | | Flag 系统 | `Flag.OPENCODE_*` 集中定义 | `config/flags.py` 集中定义 | | 分层加载 | 9 层 mergeDeep | 4 层优先级 | | 路径标准 | XDG (`xdg-basedir`) | `{data_root}/` + `{mode}/`,data_root 由 CLI/环境变量指定 | | 初始化入口 | CLI handler → Runtime → Bootstrap → Config.get() | `server.app` → `config_manager.initialize()` → 服务启动 |