- 工作区内部路径 .agents/ -> .astrion/ - 默认运行态数据根 ~/.agents/agents/ -> ~/.astrion/astrion/ - 环境变量 AGENTS_DATA_ROOT -> ASTRION_DATA_ROOT(无兼容) - 同步更新代码、测试、文档与脚本中的路径引用 - 新增 .gitignore 忽略 .astrion/ 与 .agents/ 运行态目录
18 KiB
18 KiB
配置系统改造计划
版本: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_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(默认 ~/.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:分层配置优先级
优先级(高→低):
运行时 Set(config_manager.set())
→ 环境变量覆盖
→ 项目级配置(<workspace>/.astrion/config.json)
→ 全局配置(~/.astrion/astrion/settings.json)
→ 默认值
模式 3:统一 ConfigManager 单例
ConfigManager
├── load() 从 JSON 文件加载
├── get(key) 获取配置值
├── set(key, value) 运行时修改 + 持久化
├── reload() 重新加载(配置热更新)
├── subscribe() 变更监听
└── _resolve_path() 路径解析
模式 4:Flag 系统
# 所有 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 核心原则
- 启动后再配置:除最低限度启动参数(端口等)外,所有配置可在运行时修改
- 单一配置源:所有模块统一通过
ConfigManager获取配置 - JSON 为主、.env 退役:配置文件统一为
{data_root}/settings.json。.env 仅作为开发环境快捷方式(可选),不再参与正式配置流程 - 元配置与普通配置分离:
data_root等必须在加载配置前确定的参数走 CLI/环境变量,其余走settings.json - 向后兼容:分阶段改造,每阶段可独立上线
- 最小改动:先统一读取入口,再逐步改造消费端
四、分阶段改造计划
阶段 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/astriondata_root设置后,host/web 子目录在其下创建- 模式子目录:
{data_root}/{sandbox_mode}/,如~/.astrion/astrion/host/data/
1.5 验证标准
- 删除项目根目录
.env后,程序仍能正常启动(使用默认值) - 所有现有功能不受影响
grep -rn 'parents\[1\].*\.env'结果中只剩config/manager.py和scripts/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. 运行时 Set(config_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_path、setup_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.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 完成后即可合并,不影响阶段 2-4 的进度
- 向后兼容:阶段 1 保留
from config import DATA_DIR语法,通过__getattr__代理 - 回滚安全:配置文件备份机制(
settings.json.bak_<timestamp>) - 渐进式清理:先统一入口,再逐个模块迁移,不搞大爆炸重构
七、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.app → config_manager.initialize() → 服务启动 |