agent-Specialization/docs/config_refactor_plan.md
JOJO e3c897947f refactor(paths): 统一迁移运行态数据路径到 Astrion 命名空间
- 工作区内部路径 .agents/ -> .astrion/
- 默认运行态数据根 ~/.agents/agents/ -> ~/.astrion/astrion/
- 环境变量 AGENTS_DATA_ROOT -> ASTRION_DATA_ROOT(无兼容)
- 同步更新代码、测试、文档与脚本中的路径引用
- 新增 .gitignore 忽略 .astrion/ 与 .agents/ 运行态目录
2026-07-10 00:35:15 +08:00

18 KiB
Raw Blame History

配置系统改造计划

版本v2.0 | 日期2026-06-13 | 基于 OpenCode 架构分析 + 当前代码现状


〇、最终目录结构(已确认)

目录树

~/.astrion/astrion/              ← 数据根目录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/<name>/ 下)。

元配置 vs 普通配置

元配置(启动前必须知道,不在 settings.json 里):

元配置项 设定方式 默认值
data_root CLI --data-root / 环境变量 ASTRION_DATA_ROOT ~/.astrion/astrion

普通配置(启动后加载,在 settings.json 里):

分类 示例
运行模式 terminal.sandbox_modehost/web
服务 server.portserver.host
模型 models.default_model_key
终端 terminal.execution_mode_default、沙箱参数
管理员 admin.usernameadmin.password_hash
Flags flags.api_dump_enabled

启动流程

1. 解析 CLI args / 环境变量 → 确定 data_root默认 ~/.astrion/astrion
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 默认 ~/.astrion/astrion

一、现状诊断

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/ 下,分流名存实亡):
  ~/.astrion/
  ├── 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 而非模块常量

# ❌ 当前做法
from config import DATA_DIR  # import 时固化

# ✅ 目标做法
from config.manager import config_manager
data_dir = config_manager.get("DATA_DIR")  # 运行时获取,支持热更新

模式 2分层配置优先级

优先级(高→低):
  运行时 Setconfig_manager.set()
→ 环境变量覆盖
→ 项目级配置(<workspace>/.astrion/config.json
→ 全局配置(~/.astrion/astrion/settings.json
→ 默认值

模式 3统一 ConfigManager 单例

ConfigManager
  ├── load()           从 JSON 文件加载
  ├── get(key)         获取配置值
  ├── set(key, value)  运行时修改 + 持久化
  ├── reload()         重新加载(配置热更新)
  ├── subscribe()      变更监听
  └── _resolve_path()  路径解析

模式 4Flag 系统

# 所有 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 ~/.astrion/astrion]
  → 解析 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
  5. 向后兼容:分阶段改造,每阶段可独立上线
  6. 最小改动:先统一读取入口,再逐步改造消费端

四、分阶段改造计划

阶段 1统一配置源预计改动文件数~8

目标:消除 6 处独立 .env 读取,所有配置从单一入口获取。

1.1 新建 config/manager.py

"""
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 兼容性策略

# 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 配置文件格式

// ~/.astrion/astrion/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 未设置时,默认 ~/.astrion/astrion
  • data_root 设置后host/web 子目录在其下创建
  • 模式子目录:{data_root}/{sandbox_mode}/,如 ~/.astrion/astrion/host/data/

1.5 验证标准

  • 删除项目根目录 .env 后,程序仍能正常启动(使用默认值)
  • 所有现有功能不受影响
  • grep -rn 'parents\[1\].*\.env' 结果中只剩 config/manager.pyscripts/setup.py

阶段 2运行时配置修改预计改动文件数~10

目标:管理后台 UI 可修改配置,无需重启。

2.1 ConfigManager 增强

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. 运行时 Setconfig_manager.set(),内存中)
  2. 环境变量覆盖os.environ
  3. 项目级配置(<workspace>/.astrion/config.json
  4. 全局配置(~/.astrion/astrion/<mode>/config/settings.json
  5. 默认值(硬编码)

3.2 变量引用语法

// 配置文件中支持变量引用
{
  "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 项目级配置

# 当用户选择了工作区后,自动加载项目级配置
config_manager.load_project_config(workspace_path)
# → 读取 <workspace_path>/.astrion/config.json
# → 浅层覆盖全局配置

3.4 验证标准

  • 不同工作区可以有不同配置(如不同默认模型)
  • {env:VAR} 语法正常工作
  • 配置文件缺失时回退到上一级,不报错

阶段 4清理历史遗留预计改动文件数~5

目标:移除屎山代码,精简入口。

4.1 清理清单

文件 操作
web_server.py 删除(已是兼容入口,无实际逻辑)
main.py 删除 CLI 模式死代码(setup_project_pathsetup_run_mode 等交互式部分),精简为纯 Web 启动
server/app.py 合并 app_legacy.py 的核心逻辑,"转发"改为"所有权"
server/app_legacy.py 拆分为多个模块chat/、tasks/、conversation/ 已有蓝图,继续拆分)

4.2 标准化启动

# 唯一的启动方式
python -m server.app [--port 8091] [--host 127.0.0.1]

# 或保留 start.sh 作为便利脚本
./start.sh

移除 setup.shstart.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_<timestamp>
  4. 渐进式清理:先统一入口,再逐个模块迁移,不搞大爆炸重构

七、OpenCode vs 我们的对照速查表

概念 OpenCode 我们的目标
全局配置路径 ~/.config/opencode/config.json {data_root}/settings.json(默认 ~/.astrion/astrion/settings.json
项目配置 <cwd>/opencode.json(c) 向上查找 <workspace>/.astrion/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.appconfig_manager.initialize() → 服务启动