refactor(paths): 统一迁移运行态数据路径到 Astrion 命名空间

- 工作区内部路径 .agents/ -> .astrion/
- 默认运行态数据根 ~/.agents/agents/ -> ~/.astrion/astrion/
- 环境变量 AGENTS_DATA_ROOT -> ASTRION_DATA_ROOT(无兼容)
- 同步更新代码、测试、文档与脚本中的路径引用
- 新增 .gitignore 忽略 .astrion/ 与 .agents/ 运行态目录
This commit is contained in:
JOJO 2026-07-10 00:35:15 +08:00
parent 6a2cb820bc
commit e3c897947f
41 changed files with 182 additions and 154 deletions

4
.gitignore vendored
View File

@ -12,8 +12,10 @@ __pycache__/
venv/
# Runtime data (main agent)
# 注意:运行态数据默认已迁出源码树到 ~/.agents/<mode>/。
# 注意:运行态数据默认已迁出源码树到 ~/.astrion/astrion/<mode>/。
# 以下条目用于忽略「通过具体目录变量指回源码树」或历史遗留的运行态目录。
.astrion/
.agents/
logs/
data/
project/

View File

@ -47,28 +47,28 @@
## 1.5) 运行态数据目录与路径变量2026-06 更新)
> 设计目标:运行态数据(对话、用户工作区、日志等)默认存放在用户主目录的 `~/.agents/agents/` 下,对齐 `~/.claude`、`~/.codex` 惯例,**不落在源码树内**。配置实现见 `config/paths.py`
> 设计目标:运行态数据(对话、用户工作区、日志等)默认存放在用户主目录的 `~/.astrion/astrion/` 下,对齐 `~/.claude`、`~/.codex` 惯例,**不落在源码树内**。配置实现见 `config/paths.py`
### 1.5.1 默认位置与模式分流
运行态数据根目录(`data_root`)默认为 `~/.agents/agents`,可通过 `AGENTS_DATA_ROOT` 整体搬迁。在该根目录下,按运行模式自动分流(由 `TERMINAL_SANDBOX_MODE` 决定):
运行态数据根目录(`data_root`)默认为 `~/.astrion/astrion`,可通过 `ASTRION_DATA_ROOT` 整体搬迁。在该根目录下,按运行模式自动分流(由 `TERMINAL_SANDBOX_MODE` 决定):
- 宿主机模式(`TERMINAL_SANDBOX_MODE=host`)→ `~/.agents/agents/host`
- 其它模式(默认 docker / web`~/.agents/agents/web`
- 宿主机模式(`TERMINAL_SANDBOX_MODE=host`)→ `~/.astrion/astrion/host`
- 其它模式(默认 docker / web`~/.astrion/astrion/web`
数据根目录下还包含:`settings.json`(唯一配置文件)、`config/`部署级配置host/web 共享)。每个模式目录下包含:`data/`对话、用户库、记忆、sub_agents.json、sub_agent_tasks 等)、`users/`web 多用户工作区)、`api/users/`API 用户工作区)、`logs/`(日志)。
### 1.5.2 路径解析优先级(从高到低)
1. **具体目录环境变量**`DATA_DIR` / `LOGS_DIR` / `USER_SPACE_DIR` / `API_USER_SPACE_DIR`(单独覆盖某个目录,最高优先级)
2. **数据根目录环境变量**`AGENTS_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.agents/agents`
3. **兜底默认**`~/.agents/agents/<mode>`
2. **数据根目录环境变量**`ASTRION_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.astrion/astrion`
3. **兜底默认**`~/.astrion/astrion/<mode>`
具体目录变量支持相对路径(相对仓库根目录展开)、绝对路径与 `~`
> 注意:`config/*.json` 分两类:
> - **程序能力**`docker_risk_markers.json`、`skill_hints.json`):是程序行为的一部分,随版本演进,仍锚定源码树。`prompts/`、`agentskills/` 同理。
> - **部署级配置**`custom_models`、`host_workspaces`、`auto_approval`、`goal_review`、`forbidden_commands`、`host_sandbox_policy`):因部署/机器而异或含密钥,外置到 `~/.agents/agents/config/`(即 `DEPLOY_CONFIG_DIR`可单独用该环境变量覆盖host/web 共享)。读取走 `config.resolve_deploy_config(name)`,回退链:部署目录 → 源码树 `.json` → 源码树 `.json.example`,因此开发环境不必先跑 setup 也能用源码树种子。含密钥/机器特定的 5 个(除 `forbidden_commands`)不纳入 git仓库仅留 `.example`
> - **部署级配置**`custom_models`、`host_workspaces`、`auto_approval`、`goal_review`、`forbidden_commands`、`host_sandbox_policy`):因部署/机器而异或含密钥,外置到 `~/.astrion/astrion/config/`(即 `DEPLOY_CONFIG_DIR`可单独用该环境变量覆盖host/web 共享)。读取走 `config.resolve_deploy_config(name)`,回退链:部署目录 → 源码树 `.json` → 源码树 `.json.example`,因此开发环境不必先跑 setup 也能用源码树种子。含密钥/机器特定的 5 个(除 `forbidden_commands`)不纳入 git仓库仅留 `.example`
### 1.5.3 Host / Web 双路径机制2026-06 新增)
@ -252,7 +252,7 @@ AI 执行以下流程时,每一步都要向用户说明在做什么:
## 7) 安全与仓库卫生
- 严禁提交真实密钥(`.env`、token、cookie、用户隐私
- 运行态数据默认在 `~/.agents/agents/<mode>/``data/`、`users/`、`logs/`、`api/`),不在源码树内;详见 §1.5。`.gitignore` 仍忽略源码树内的 `logs/`、`data/`、`users/`、`api/`、`project/` 等,以防通过具体变量指回源码树或历史遗留产生污染。分享前需脱敏。
- 运行态数据默认在 `~/.astrion/astrion/<mode>/``data/`、`users/`、`logs/`、`api/`),不在源码树内;详见 §1.5。`.gitignore` 仍忽略源码树内的 `logs/`、`data/`、`users/`、`api/`、`project/` 等,以防通过具体变量指回源码树或历史遗留产生污染。分享前需脱敏。
- **`_experiments/` 用途**:归档本地实验残留与历史文档(调试记录、旧变更日志、模型测试脚本、翻译资料、旧子智能体文档等)。该目录**不纳入 git**(已在 `.gitignore`)。需要保留但不属于当前主线、又不想直接删的零散文件,统一放这里,不要散落在根目录。
- 不要把本地构建产物(如 `static/dist/`、`node_modules/`)纳入提交。

View File

@ -137,12 +137,12 @@ npm --prefix cli run build
# 查看容器状态
docker ps -a | grep agent-term
# 查看调试日志(运行态日志默认在 ~/.agents/agents/<mode>/logs/
tail -f ~/.agents/agents/host/logs/debug_stream.log # host 模式
tail -f ~/.agents/agents/web/logs/debug_stream.log # web/docker 模式
# 查看调试日志(运行态日志默认在 ~/.astrion/astrion/<mode>/logs/
tail -f ~/.astrion/astrion/host/logs/debug_stream.log # host 模式
tail -f ~/.astrion/astrion/web/logs/debug_stream.log # web/docker 模式
# 查看容器统计
tail -f ~/.agents/agents/web/logs/container_stats.log
tail -f ~/.astrion/astrion/web/logs/container_stats.log
```
## Configuration
@ -168,12 +168,12 @@ tail -f ~/.agents/agents/web/logs/container_stats.log
**数据目录与路径变量** (`config/paths.py`2026-06 更新):
运行态数据默认存放在 `~/.agents/agents/<mode>`(对齐 `~/.claude`、`~/.codex`**不落在源码树**。数据根目录(`data_root`)默认为 `~/.agents/agents`,可通过 `AGENTS_DATA_ROOT` 整体搬迁;模式由 `TERMINAL_SANDBOX_MODE` 决定:`host` → `~/.agents/agents/host`,其它(默认 docker/web`~/.agents/agents/web`。数据根目录下还包含 `settings.json`(唯一配置文件)和 `config/`部署级配置host/web 共享)。每个模式目录下含 `data/`对话、用户库、记忆、sub_agents.json、sub_agent_tasks、`users/`、`api/users/`、`logs/`。
运行态数据默认存放在 `~/.astrion/astrion/<mode>`(对齐 `~/.claude`、`~/.codex`**不落在源码树**。数据根目录(`data_root`)默认为 `~/.astrion/astrion`,可通过 `ASTRION_DATA_ROOT` 整体搬迁;模式由 `TERMINAL_SANDBOX_MODE` 决定:`host` → `~/.astrion/astrion/host`,其它(默认 docker/web`~/.astrion/astrion/web`。数据根目录下还包含 `settings.json`(唯一配置文件)和 `config/`部署级配置host/web 共享)。每个模式目录下含 `data/`对话、用户库、记忆、sub_agents.json、sub_agent_tasks、`users/`、`api/users/`、`logs/`。
路径解析优先级(高→低):
1. 具体目录变量 `DATA_DIR` / `LOGS_DIR` / `USER_SPACE_DIR` / `API_USER_SPACE_DIR`(单独覆盖某目录)
2. 数据根目录变量 `AGENTS_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.agents/agents`
3. 兜底 `~/.agents/agents/<mode>`
2. 数据根目录变量 `ASTRION_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.astrion/astrion`
3. 兜底 `~/.astrion/astrion/<mode>`
**Host / Web 双路径机制2026-06**:
- `paths.py` 新增 `IS_HOST_MODE`、`WEB_DATA_DIR`、`WEB_USER_SPACE_DIR` 三个固定变量
@ -181,14 +181,14 @@ tail -f ~/.agents/agents/web/logs/container_stats.log
- 已有 web 工作区直接复用(保持旧数据可访问),新工作区写入 host/
- web 模式:仅读取 web/ 数据,无变化
> `config/*.json` 分两类:**程序能力**`docker_risk_markers.json`、`skill_hints.json`)与 `prompts/`、`agentskills/` 一样锚定源码树;**部署级配置**`custom_models`、`host_workspaces`、`auto_approval`、`goal_review`、`forbidden_commands`、`host_sandbox_policy`)外置到 `~/.agents/agents/config/``DEPLOY_CONFIG_DIR`host/web 共享),读取走 `config.resolve_deploy_config(name)`,回退链:部署目录 → 源码树 `.json` → 源码树 `.json.example`。含密钥/机器特定的 5 个不进 git仓库仅留 `.example`
> `config/*.json` 分两类:**程序能力**`docker_risk_markers.json`、`skill_hints.json`)与 `prompts/`、`agentskills/` 一样锚定源码树;**部署级配置**`custom_models`、`host_workspaces`、`auto_approval`、`goal_review`、`forbidden_commands`、`host_sandbox_policy`)外置到 `~/.astrion/astrion/config/``DEPLOY_CONFIG_DIR`host/web 共享),读取走 `config.resolve_deploy_config(name)`,回退链:部署目录 → 源码树 `.json` → 源码树 `.json.example`。含密钥/机器特定的 5 个不进 git仓库仅留 `.example`
**日志策略**:
- API 请求体 dump 默认**关闭**`AGENT_API_DUMP_ENABLED=1` 开启。
- 混合轮转(`utils/log_rotation.py`):追加型单文件按大小轮转(默认 20MB×3 份dump 目录按份保留(默认 30 个)。阈值变量:`AGENT_LOG_ROTATE_MAX_BYTES` / `AGENT_LOG_ROTATE_BACKUPS` / `AGENT_DUMP_KEEP`
**数据迁移**:
- `scripts/migrate_runtime_data.py`:源码树 → `~/.agents/agents/<mode>`,复制+备份+可回滚+幂等,`logs/` 丢弃不迁。脚本复用 config 路径解析(模式由 `.env` 决定),执行前先 `--dry-run` 确认目标。
- `scripts/migrate_runtime_data.py`:源码树 → `~/.astrion/astrion/<mode>`,复制+备份+可回滚+幂等,`logs/` 丢弃不迁。脚本复用 config 路径解析(模式由 `.env` 决定),执行前先 `--dry-run` 确认目标。
## Important Implementation Details
@ -346,7 +346,7 @@ agents/
└── _experiments/ # 本地实验残留与历史文档归档gitignored不进仓库
运行态数据(不在源码树):
~/.agents/agents/ # 数据根目录(可通过 AGENTS_DATA_ROOT 覆盖)
~/.astrion/astrion/ # 数据根目录(可通过 ASTRION_DATA_ROOT 覆盖)
├── settings.json # 唯一配置文件
├── config/ # 部署级配置host/web 共享)
├── host/ # mode = host由 TERMINAL_SANDBOX_MODE 决定
@ -359,7 +359,7 @@ agents/
└── logs/ # 日志文件
```
> 历史说明:`data/`、`users/`、`logs/`、`project/` 等过去曾在源码树内,现已默认迁至 `~/.agents/agents/<mode>/`。详见 Configuration 节「数据目录与路径变量」。
> 历史说明:`data/`、`users/`、`logs/`、`project/` 等过去曾在源码树内,现已默认迁至 `~/.astrion/astrion/<mode>/`。详见 Configuration 节「数据目录与路径变量」。
## Development Tips

View File

@ -8,7 +8,7 @@
- 新 CLI 端已开始重写,位于 `cli/`,技术栈为 React 19 + Ink 6 + TypeScript。
- 当前 CLI 不是独立后端;它会直接连接本地 `8091` Web API并复用现有会话、任务、权限、工作区等接口。
- CLI 已实现“启动即连接后端并新建会话”的流程,但仍处于持续打磨阶段,输入法、光标、时间线裁剪等终端细节仍在迭代。
- 运行态数据(对话、用户库、日志等)默认存放在用户主目录的 `~/.agents/agents/<mode>/` 下,不再落在源码树内;源码树内的 `data/`、`logs/`、`users/` 等仅作为 `.gitignore` 防污染保留。
- 运行态数据(对话、用户库、日志等)默认存放在用户主目录的 `~/.astrion/astrion/<mode>/` 下,不再落在源码树内;源码树内的 `data/`、`logs/`、`users/` 等仅作为 `.gitignore` 防污染保留。
## CLI 快速开始
@ -488,7 +488,7 @@ python -m server.app --path ./project --port 8091 --debug --thinking-mode
主要配置项(通过环境变量或 `.env` 文件):
### API 配置
- `config/custom_models.json`: 动态模型注册文件(生产环境建议放到 `~/.agents/agents/config/custom_models.json`host/web 共享)
- `config/custom_models.json`: 动态模型注册文件(生产环境建议放到 `~/.astrion/astrion/config/custom_models.json`host/web 共享)
- `AGENT_DEFAULT_MODEL`: 可选,指定默认模型 key未设置时使用第一个可见动态模型
- 模型配置中的 `url` / `apikey` 支持 `${ENV_NAME}`、`env:ENV_NAME`、`$ENV_NAME` 等环境变量引用
@ -501,8 +501,8 @@ python -m server.app --path ./project --port 8091 --debug --thinking-mode
- `LOGS_DIR`: 日志目录
- `USER_SPACE_DIR`: Web 用户工作区目录
- `API_USER_SPACE_DIR`: API 用户工作区目录
- `AGENTS_DATA_ROOT`: 运行态数据根目录(默认 `~/.agents/agents`host/web 两个模式共享该根)
- `DEPLOY_CONFIG_DIR`: 部署级配置目录(默认 `<AGENTS_DATA_ROOT>/config/`host/web 共享)
- `ASTRION_DATA_ROOT`: 运行态数据根目录(默认 `~/.astrion/astrion`host/web 两个模式共享该根)
- `DEPLOY_CONFIG_DIR`: 部署级配置目录(默认 `<ASTRION_DATA_ROOT>/config/`host/web 共享)
### 限制配置
- `MAX_ITERATIONS_PER_TASK`: 单任务最大迭代次数
@ -534,15 +534,15 @@ python -m server.app --path ./project --port 8091 --debug --thinking-mode
## 数据存储
运行态数据默认存放在用户主目录的 `~/.agents/agents/<mode>/` 下(按 `TERMINAL_SANDBOX_MODE` 自动分流):
运行态数据默认存放在用户主目录的 `~/.astrion/astrion/<mode>/` 下(按 `TERMINAL_SANDBOX_MODE` 自动分流):
- **宿主机模式**`TERMINAL_SANDBOX_MODE=host`)→ `~/.agents/agents/host`
- **其它模式**(默认 docker / web`~/.agents/agents/web`
- **宿主机模式**`TERMINAL_SANDBOX_MODE=host`)→ `~/.astrion/astrion/host`
- **其它模式**(默认 docker / web`~/.astrion/astrion/web`
### 目录结构
```
~/.agents/agents/ # 数据根目录(可通过 AGENTS_DATA_ROOT 覆盖)
~/.astrion/astrion/ # 数据根目录(可通过 ASTRION_DATA_ROOT 覆盖)
├── settings.json # 唯一配置文件
├── config/ # 部署级配置custom_models/host_workspaces/auto_approval 等host/web 共享)
├── host/ # 宿主机模式运行态
@ -578,8 +578,8 @@ python -m server.app --path ./project --port 8091 --debug --thinking-mode
### 路径解析优先级(从高到低)
1. **具体目录环境变量**`DATA_DIR` / `LOGS_DIR` / `USER_SPACE_DIR` / `API_USER_SPACE_DIR`(单独覆盖某个目录)
2. **数据根目录环境变量**`AGENTS_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.agents/agents`
3. **兜底默认**`~/.agents/agents/<mode>`
2. **数据根目录环境变量**`ASTRION_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.astrion/astrion`
3. **兜底默认**`~/.astrion/astrion/<mode>`
### Host / Web 双路径机制
@ -625,7 +625,7 @@ python -m unittest test.test_server_refactor_smoke
### 添加新模型
1. 在 `config/custom_models.json`(生产环境建议放到 `~/.agents/agents/config/custom_models.json`)注册模型配置
1. 在 `config/custom_models.json`(生产环境建议放到 `~/.astrion/astrion/config/custom_models.json`)注册模型配置
2. 在 `utils/api_client/` 中实现或复用 API 调用逻辑
3. 通过环境变量或 `.env` 注入密钥等敏感信息
@ -656,13 +656,13 @@ python -m unittest test.test_server_refactor_smoke
### 日志位置
运行态日志默认位于 `~/.agents/agents/<mode>/logs/`
运行态日志默认位于 `~/.astrion/astrion/<mode>/logs/`
- 主日志: `~/.agents/agents/<mode>/logs/main.log`
- 错误日志: `~/.agents/agents/<mode>/logs/errors/`
- 任务日志: `~/.agents/agents/<mode>/logs/tasks/`
- Web 服务器: `~/.agents/agents/<mode>/logs/web_server.log`
- 自动审批智能体调试记录: `~/.agents/agents/<mode>/logs/approval_agent/`
- 主日志: `~/.astrion/astrion/<mode>/logs/main.log`
- 错误日志: `~/.astrion/astrion/<mode>/logs/errors/`
- 任务日志: `~/.astrion/astrion/<mode>/logs/tasks/`
- Web 服务器: `~/.astrion/astrion/<mode>/logs/web_server.log`
- 自动审批智能体调试记录: `~/.astrion/astrion/<mode>/logs/approval_agent/`
## 版本信息
@ -670,7 +670,7 @@ python -m unittest test.test_server_refactor_smoke
主要更新2026-06
- 后端接口拆分为 `server/chat/`、`server/status/`、`server/tasks/` 子包
- 运行态数据默认迁移到 `~/.agents/agents/<mode>/`,支持 host/web 双路径读取
- 运行态数据默认迁移到 `~/.astrion/astrion/<mode>/`,支持 host/web 双路径读取
- 子智能体改为主进程内 `asyncio.Task` 实现,工具调用复用主进程链路
- 新增宿主机权限模式readonly/approval/auto_approval/unrestricted与执行环境sandbox/direct双层控制
- 新增日志轮转、API dump 可控、部署级配置外置等运维能力

View File

@ -18,7 +18,7 @@ def _load_dotenv():
# 1) 仓库根 .env开发便利不覆盖已有环境变量
if getattr(sys, 'frozen', False):
env_path = Path.home() / '.agents' / 'agents' / '.env'
env_path = Path.home() / '.astrion' / 'astrion' / '.env'
else:
env_path = Path(__file__).resolve().parents[1] / '.env'
if env_path.exists():
@ -41,7 +41,7 @@ def _load_dotenv():
pass
# 2) settings.json数据根下的统一配置
data_root = os.environ.get("AGENTS_DATA_ROOT", str(Path.home() / ".agents" / "agents"))
data_root = os.environ.get("ASTRION_DATA_ROOT", str(Path.home() / ".astrion" / "astrion"))
settings_path = Path(data_root) / "settings.json"
if settings_path.exists():
try:

View File

@ -1,10 +1,10 @@
"""项目路径与目录配置。
运行态数据存放在 ``~/.agents/agents/``
运行态数据存放在 ``~/.astrion/astrion/``
::
~/.agents/agents/ data_root默认可通过 AGENTS_DATA_ROOT 覆盖
~/.astrion/astrion/ data_root默认可通过 ASTRION_DATA_ROOT 覆盖
settings.json 唯一配置文件
config/ 部署级 JSONhost/web 模式共享
host/ host 模式单用户
@ -19,8 +19,8 @@
1. 具体目录环境变量``DATA_DIR`` / ``LOGS_DIR`` / ``USER_SPACE_DIR`` /
``API_USER_SPACE_DIR``单独覆盖某个目录最高优先级
2. ``AGENTS_DATA_ROOT`` 整体搬迁数据根目录
3. 兜底默认值 ``~/.agents/agents``
2. ``ASTRION_DATA_ROOT`` 整体搬迁数据根目录
3. 兜底默认值 ``~/.astrion/astrion``
注意``config/*.json`` 分两类不再一概锚定源码树
@ -79,9 +79,9 @@ def _runtime_mode() -> str:
# ── 数据根目录data_root──
# 优先级AGENTS_DATA_ROOT 环境变量 > 默认 ~/.agents/agents
_DEFAULT_DATA_ROOT = Path("~/.agents/agents")
RUNTIME_ROOT = str(_norm(Path(_env("AGENTS_DATA_ROOT") or _DEFAULT_DATA_ROOT)))
# 优先级ASTRION_DATA_ROOT 环境变量 > 默认 ~/.astrion/astrion
_DEFAULT_DATA_ROOT = Path("~/.astrion/astrion")
RUNTIME_ROOT = str(_norm(Path(_env("ASTRION_DATA_ROOT") or _DEFAULT_DATA_ROOT)))
_MODE = _runtime_mode()
@ -161,7 +161,9 @@ HOST_PROJECT_PATH = _resolve_repo_path(os.environ.get("HOST_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"
WORKSPACE_SKILLS_DIRNAME = ".astrion/skills"
WORKSPACE_MEMORY_DIRNAME = ".astrion/memory"
WORKSPACE_REVIEW_DIRNAME = ".astrion/review"
HOST_WORKSPACES_FILE = _resolve_repo_path(
os.environ.get("HOST_WORKSPACES_FILE", ""),
deploy_config_path("host_workspaces.json"),
@ -185,6 +187,8 @@ __all__ = [
"LOGS_DIR",
"AGENT_SKILLS_DIR",
"WORKSPACE_SKILLS_DIRNAME",
"WORKSPACE_MEMORY_DIRNAME",
"WORKSPACE_REVIEW_DIRNAME",
"IS_HOST_MODE",
"USER_SPACE_DIR",
"WEB_DATA_DIR",

View File

@ -9,7 +9,7 @@ from .paths import resolve_deploy_config
def _load_forbidden_commands() -> list[str]:
"""从独立 JSON 文件加载命令拦截关键词。
属于部署级配置部署者可调宽松/严苛优先读 ~/.agents/<mode>/config
属于部署级配置部署者可调宽松/严苛优先读 ~/.astrion/<mode>/config
回退源码树种子
"""
fallback = [

View File

@ -15,7 +15,7 @@ SUB_AGENT_DEFAULT_TIMEOUT = int(os.environ.get("SUB_AGENT_DEFAULT_TIMEOUT", "180
SUB_AGENT_STATUS_POLL_INTERVAL = float(os.environ.get("SUB_AGENT_STATUS_POLL_INTERVAL", "2.0"))
# 存储与并发限制
# 子智能体任务目录与状态文件属于运行态数据,跟随 DATA_DIR默认 ~/.agents/<mode>/data
# 子智能体任务目录与状态文件属于运行态数据,跟随 DATA_DIR默认 ~/.astrion/<mode>/data
SUB_AGENT_TASKS_BASE_DIR = _resolve_repo_path(os.environ.get("SUB_AGENT_TASKS_BASE_DIR", ""), f"{DATA_DIR}/sub_agent_tasks")
SUB_AGENT_STATE_FILE = _resolve_repo_path(os.environ.get("SUB_AGENT_STATE_FILE", ""), f"{DATA_DIR}/sub_agents.json")
# 子智能体产出结果有意落在工作区内,跟随 DEFAULT_PROJECT_PATH不迁入运行态根目录。

View File

@ -18,6 +18,7 @@ try:
TERMINAL_SANDBOX_MEMORY,
PROJECT_MAX_STORAGE_MB,
CUSTOM_TOOLS_ENABLED,
WORKSPACE_MEMORY_DIRNAME,
)
except ImportError:
import sys
@ -38,6 +39,7 @@ except ImportError:
TERMINAL_SANDBOX_MEMORY,
PROJECT_MAX_STORAGE_MB,
CUSTOM_TOOLS_ENABLED,
WORKSPACE_MEMORY_DIRNAME,
)
from modules.file_manager import FileManager
@ -95,9 +97,9 @@ class MemoryMixin:
"""MainTerminalContextMixin memory 能力 mixin。"""
def _scan_project_memories(self):
"""扫描 .agents/memory/*.md 并解析 frontmatter 中的 name 和 description"""
"""扫描 .astrion/memory/*.md 并解析 frontmatter 中的 name 和 description"""
try:
memory_dir = Path(self.project_path) / ".agents" / "memory"
memory_dir = Path(self.project_path) / WORKSPACE_MEMORY_DIRNAME
except Exception:
return []
if not memory_dir.exists() or not memory_dir.is_dir():
@ -145,9 +147,9 @@ class MemoryMixin:
for m in project_memories:
desc = m.get("description", "")
if desc:
lines.append(f".agents/memory/{m['file']}{desc}")
lines.append(f".astrion/memory/{m['file']}{desc}")
else:
lines.append(f".agents/memory/{m['file']}")
lines.append(f".astrion/memory/{m['file']}")
project_memory_list = "\n".join(lines)
else:
project_memory_list = ""

View File

@ -113,13 +113,13 @@ class ToolsDefinitionContextToolsMixin:
"type": "function",
"function": {
"name": "recall_project_memory",
"description": "读取指定的项目记忆文件,返回完整内容(含 frontmatter。项目记忆存储在 .agents/memory/ 目录下。",
"description": "读取指定的项目记忆文件,返回完整内容(含 frontmatter。项目记忆存储在 .astrion/memory/ 目录下。",
"parameters": {
"type": "object",
"properties": self._inject_intent({
"name": {
"type": "string",
"description": "记忆名称,对应 .agents/memory/{name}.md 文件名"
"description": "记忆名称,对应 .astrion/memory/{name}.md 文件名"
}
}),
"required": ["name"]
@ -137,7 +137,7 @@ class ToolsDefinitionContextToolsMixin:
"properties": self._inject_intent({
"name": {
"type": "string",
"description": "记忆名称,同时作为文件名 .agents/memory/{name}.md。建议英文+下划线,如 docker_compose"
"description": "记忆名称,同时作为文件名 .astrion/memory/{name}.md。建议英文+下划线,如 docker_compose"
},
"description": {
"type": "string",
@ -181,7 +181,7 @@ class ToolsDefinitionContextToolsMixin:
"type": "function",
"function": {
"name": "conversation_review",
"description": "按 id 回顾当前工作区内的历史对话。mode=read 时直接返回回顾内容;若内容超过 50000 字符,将自动保存到 .agents/review/ 并提示分段或查找阅读。mode=save 时保存 Markdown 文件到 .agents/review/ 并返回路径。若 id 不属于当前工作区,将返回不存在或不属于当前工作区。",
"description": "按 id 回顾当前工作区内的历史对话。mode=read 时直接返回回顾内容;若内容超过 50000 字符,将自动保存到 .astrion/review/ 并提示分段或查找阅读。mode=save 时保存 Markdown 文件到 .astrion/review/ 并返回路径。若 id 不属于当前工作区,将返回不存在或不属于当前工作区。",
"parameters": {
"type": "object",
"properties": self._inject_intent({
@ -189,7 +189,7 @@ class ToolsDefinitionContextToolsMixin:
"mode": {
"type": "string",
"enum": ["read", "save"],
"description": "必要参数。read=直接返回回顾内容save=保存为 .agents/review/ 下的 Markdown 文件并返回路径。"
"description": "必要参数。read=直接返回回顾内容save=保存为 .astrion/review/ 下的 Markdown 文件并返回路径。"
}
}),
"required": ["conversation_id", "mode"]

View File

@ -123,7 +123,7 @@ class ToolsDefinitionFileToolsMixin:
"type": "function",
"function": {
"name": "read_skill",
"description": "按 skill 名称读取 .agents/skills/<name>/SKILL.md 内容;内部等价于 read_file 的 read 模式,并返回解析后的 path。",
"description": "按 skill 名称读取 .astrion/skills/<name>/SKILL.md 内容;内部等价于 read_file 的 read 模式,并返回解析后的 path。",
"parameters": {
"type": "object",
"properties": self._inject_intent({

View File

@ -26,6 +26,9 @@ try:
READONLY_RUN_COMMAND_ALLOWED,
READONLY_RUN_COMMAND_ALLOWED_GIT_SUBCOMMANDS,
READONLY_RUN_COMMAND_BLOCKED_TOKENS,
WORKSPACE_SKILLS_DIRNAME,
WORKSPACE_MEMORY_DIRNAME,
WORKSPACE_REVIEW_DIRNAME,
)
except ImportError:
import sys
@ -49,6 +52,9 @@ except ImportError:
READONLY_RUN_COMMAND_ALLOWED,
READONLY_RUN_COMMAND_ALLOWED_GIT_SUBCOMMANDS,
READONLY_RUN_COMMAND_BLOCKED_TOKENS,
WORKSPACE_SKILLS_DIRNAME,
WORKSPACE_MEMORY_DIRNAME,
WORKSPACE_REVIEW_DIRNAME,
)
from modules.file_manager import FileManager
@ -536,13 +542,13 @@ class MainTerminalToolsExecutionMixin:
return result
def _handle_update_project_memory(self, name: str, description: str, content: str) -> Dict[str, Any]:
"""处理 update_project_memory写入 .agents/memory/{name}.md"""
"""处理 update_project_memory写入 .astrion/memory/{name}.md"""
safe_name = str(name).strip()
if not safe_name or "/" in safe_name or "\\" in safe_name:
return {"success": False, "error": f"记忆名称不合法: {name}"}
# 确保 .agents/memory/ 目录存在
memory_dir = Path(self.project_path) / ".agents" / "memory"
# 确保 .astrion/memory/ 目录存在
memory_dir = Path(self.project_path) / WORKSPACE_MEMORY_DIRNAME
try:
memory_dir.mkdir(parents=True, exist_ok=True)
except Exception as exc:
@ -572,11 +578,11 @@ class MainTerminalToolsExecutionMixin:
normalized = self._normalize_tool_path(result.get("path") or arguments.get("path"))
if not normalized:
return
if normalized.endswith(".agents/skills/terminal-guide/skill.md"):
if normalized.endswith(f"{WORKSPACE_SKILLS_DIRNAME}/terminal-guide/skill.md"):
self.context_manager._set_meta_flag(self._skill_meta_key("terminal-guide"), True)
if normalized.endswith(".agents/skills/sub-agent-guide/skill.md"):
if normalized.endswith(f"{WORKSPACE_SKILLS_DIRNAME}/sub-agent-guide/skill.md"):
self.context_manager._set_meta_flag(self._skill_meta_key("sub-agent-guide"), True)
if normalized.endswith(".agents/skills/run-command-guide/skill.md"):
if normalized.endswith(f"{WORKSPACE_SKILLS_DIRNAME}/run-command-guide/skill.md"):
self.context_manager._set_meta_flag(self._skill_meta_key("run-command-guide"), True)
def _get_visited_read_files(self) -> Set[str]:
@ -759,7 +765,7 @@ class MainTerminalToolsExecutionMixin:
)
if already_read:
return None
required_path = f".agents/skills/{skill_id}/SKILL.md"
required_path = f"{WORKSPACE_SKILLS_DIRNAME}/{skill_id}/SKILL.md"
return {
"success": False,
"error": f"调用 {tool_name} 前必须先阅读 {required_path}",
@ -1672,12 +1678,12 @@ class MainTerminalToolsExecutionMixin:
def save_review_file() -> str:
safe_title = _sanitize_filename_component(title)
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
review_dir = Path(self.project_path) / ".agents" / "review"
review_dir = Path(self.project_path) / WORKSPACE_REVIEW_DIRNAME
review_dir.mkdir(parents=True, exist_ok=True)
filename = f"review_{safe_title}_{timestamp}.md"
target = review_dir / filename
target.write_text(content, encoding="utf-8")
return f".agents/review/{filename}"
return f"{WORKSPACE_REVIEW_DIRNAME}/{filename}"
if review_mode == "read" and char_count <= 50000:
result = {

View File

@ -19,6 +19,8 @@ try:
TERMINAL_SANDBOX_MEMORY,
PROJECT_MAX_STORAGE_MB,
CUSTOM_TOOLS_ENABLED,
WORKSPACE_SKILLS_DIRNAME,
WORKSPACE_MEMORY_DIRNAME,
)
except ImportError:
import sys
@ -39,6 +41,8 @@ except ImportError:
TERMINAL_SANDBOX_MEMORY,
PROJECT_MAX_STORAGE_MB,
CUSTOM_TOOLS_ENABLED,
WORKSPACE_SKILLS_DIRNAME,
WORKSPACE_MEMORY_DIRNAME,
)
from modules.file_manager import FileManager
@ -92,8 +96,9 @@ class MainTerminalToolsReadMixin:
if not raw:
return ""
normalized = raw.replace("\\", "/").strip("/")
if normalized.lower().startswith(".agents/skills/"):
normalized = normalized[len(".agents/skills/"):]
prefix = f"{WORKSPACE_SKILLS_DIRNAME}/"
if normalized.lower().startswith(prefix):
normalized = normalized[len(prefix):]
if normalized.lower().endswith("/skill.md"):
normalized = normalized[: -len("/SKILL.md")]
return normalized.strip()
@ -147,7 +152,7 @@ class MainTerminalToolsReadMixin:
skill_id = resolved["skill_id"]
read_args = {
"path": f".agents/skills/{skill_id}/SKILL.md",
"path": f"{WORKSPACE_SKILLS_DIRNAME}/{skill_id}/SKILL.md",
"type": "read",
}
result = self._handle_read_tool(read_args)
@ -159,11 +164,11 @@ class MainTerminalToolsReadMixin:
return result
def _handle_recall_project_memory(self, name: str) -> Dict:
"""处理 recall_project_memory读取 .agents/memory/{name}.md"""
"""处理 recall_project_memory读取 .astrion/memory/{name}.md"""
safe_name = str(name).strip()
if not safe_name or "/" in safe_name or "\\" in safe_name:
return {"success": False, "error": f"记忆名称不合法: {name}"}
file_path = f".agents/memory/{safe_name}.md"
file_path = f"{WORKSPACE_MEMORY_DIRNAME}/{safe_name}.md"
read_args = {
"path": file_path,
"type": "read",

View File

@ -9,7 +9,7 @@
### 目录树
```
~/.agents/agents/ ← 数据根目录data_root
~/.astrion/astrion/ ← 数据根目录data_root
├── settings.json ← 唯一配置文件
├── config/ ← 部署级 JSON两个模式共享
│ ├── custom_models.json
@ -37,7 +37,7 @@
| 元配置项 | 设定方式 | 默认值 |
|---------|---------|--------|
| `data_root` | CLI `--data-root` / 环境变量 `AGENTS_DATA_ROOT` | `~/.agents/agents` |
| `data_root` | CLI `--data-root` / 环境变量 `ASTRION_DATA_ROOT` | `~/.astrion/astrion` |
**普通配置**(启动后加载,在 `settings.json` 里):
@ -53,7 +53,7 @@
### 启动流程
```
1. 解析 CLI args / 环境变量 → 确定 data_root默认 ~/.agents/agents
1. 解析 CLI args / 环境变量 → 确定 data_root默认 ~/.astrion/astrion
2. 在 {data_root}/ 下创建目录结构(若不存在)
3. 加载 {data_root}/settings.json
4. 根据 settings.json 中的 sandbox_mode 确定数据子目录host/ 或 web/
@ -71,7 +71,7 @@
```
OpenCode: ~/.config/opencode/config.json + ~/.local/share/opencode/
我们: {data_root}/settings.json + {data_root}/{mode}/
其中 data_root 默认 ~/.agents/agents
其中 data_root 默认 ~/.astrion/astrion
```
---
@ -100,7 +100,7 @@ OpenCode: ~/.config/opencode/config.json + ~/.local/share/opencode/
└──────────────────────────────────────────────────────────┘
当前目录结构问题web 模式数据也落在 host/ 下,分流名存实亡):
~/.agents/
~/.astrion/
├── host/ ← 实际数据全在这里(因为 .env 写死 host
│ ├── .env ← ❌ 配置居然放在数据目录里
│ ├── config/
@ -177,8 +177,8 @@ data_dir = config_manager.get("DATA_DIR") # 运行时获取,支持热更新
优先级(高→低):
运行时 Setconfig_manager.set()
→ 环境变量覆盖
→ 项目级配置(<workspace>/.agents/config.json
→ 全局配置(~/.agents/agents/settings.json
→ 项目级配置(<workspace>/.astrion/config.json
→ 全局配置(~/.astrion/astrion/settings.json
→ 默认值
```
@ -212,7 +212,7 @@ class Flags:
```
用户启动
→ python -m server.app [--data-root ~/.agents/agents]
→ python -m server.app [--data-root ~/.astrion/astrion]
→ 解析 data_root元配置CLI > 环境变量 > 默认值)
→ ConfigManager.initialize(data_root)
→ 加载 {data_root}/settings.json
@ -313,7 +313,7 @@ def __getattr__(name):
#### 1.4 配置文件格式
```jsonc
// ~/.agents/agents/settings.json唯一配置不分 host/web
// ~/.astrion/astrion/settings.json唯一配置不分 host/web
{
"terminal": {
"sandbox_mode": "host", // "host" | "web"
@ -337,9 +337,9 @@ def __getattr__(name):
```
路径解析规则:
- `data_root` 未设置时,默认 `~/.agents/agents`
- `data_root` 未设置时,默认 `~/.astrion/astrion`
- `data_root` 设置后host/web 子目录在其下创建
- 模式子目录:`{data_root}/{sandbox_mode}/`,如 `~/.agents/agents/host/data/`
- 模式子目录:`{data_root}/{sandbox_mode}/`,如 `~/.astrion/astrion/host/data/`
#### 1.5 验证标准
@ -414,8 +414,8 @@ class ConfigManager:
优先级(高→低):
1. 运行时 Setconfig_manager.set(),内存中)
2. 环境变量覆盖os.environ
3. 项目级配置(<workspace>/.agents/config.json
4. 全局配置(~/.agents/<mode>/config/settings.json
3. 项目级配置(<workspace>/.astrion/config.json
4. 全局配置(~/.astrion/astrion/<mode>/config/settings.json
5. 默认值(硬编码)
```
@ -442,7 +442,7 @@ class ConfigManager:
```python
# 当用户选择了工作区后,自动加载项目级配置
config_manager.load_project_config(workspace_path)
# → 读取 <workspace_path>/.agents/config.json
# → 读取 <workspace_path>/.astrion/config.json
# → 浅层覆盖全局配置
```
@ -511,8 +511,8 @@ python -m server.app [--port 8091] [--host 127.0.0.1]
| 概念 | OpenCode | 我们的目标 |
|------|---------|-----------|
| 全局配置路径 | `~/.config/opencode/config.json` | `{data_root}/settings.json`(默认 `~/.agents/agents/settings.json` |
| 项目配置 | `<cwd>/opencode.json(c)` 向上查找 | `<workspace>/.agents/config.json` |
| 全局配置路径 | `~/.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()` |

View File

@ -46,7 +46,7 @@ class ApiUserWorkspace:
project_path: Path
data_dir: Path # 会话/备份等落盘到这里(每个工作区独立)
logs_dir: Path
uploads_dir: Path # project/.agents/user_upload
uploads_dir: Path # project/.astrion/user_upload
quarantine_dir: Path # 上传隔离区(按用户/工作区划分)
shared_dir: Path # 用户级共享目录prompts/personalization
prompts_dir: Path # 实际使用的 prompts 目录(指向 shared_dir/prompt
@ -103,7 +103,7 @@ class ApiUserManager:
personalization/
workspaces/<ws>/ # 单个工作区
project/
.agents/user_upload/
.astrion/user_upload/
data/
conversations/
backups/

View File

@ -12,7 +12,7 @@ import httpx
from config import resolve_deploy_config, LOGS_DIR
# 部署级配置(审批智能体 url/key/model含密钥引用→ 优先 ~/.agents/<mode>/config
# 部署级配置(审批智能体 url/key/model含密钥引用→ 优先 ~/.astrion/<mode>/config
CONFIG_PATH = Path(resolve_deploy_config("auto_approval.json"))
DEFAULT_MAX_ROUNDS = 3
DEFAULT_TIMEOUT_SECONDS = 60

View File

@ -26,7 +26,7 @@ import httpx
from config import PROMPTS_DIR, resolve_deploy_config, LOGS_DIR
from modules.goal_state_manager import REVIEW_MODE_ACTIVE, REVIEW_MODE_READONLY
# 部署级配置(目标审核智能体 url/key/model含密钥引用→ 优先 ~/.agents/<mode>/config
# 部署级配置(目标审核智能体 url/key/model含密钥引用→ 优先 ~/.astrion/<mode>/config
CONFIG_PATH = Path(resolve_deploy_config("goal_review.json"))
PROMPT_NAME = "goal_review_agent.txt"
DEFAULT_MAX_ROUNDS = 3

View File

@ -37,7 +37,7 @@ DEFAULT_MACOS_DENY_READ_REGEXES = [
]
_LOCK = threading.Lock()
# 部署级配置(机器特定可读写路径,会被运行时写回)→ ~/.agents/<mode>/config
# 部署级配置(机器特定可读写路径,会被运行时写回)→ ~/.astrion/<mode>/config
_POLICY_PATH = Path(deploy_config_path("host_sandbox_policy.json"))

View File

@ -317,9 +317,9 @@ def build_skills_list(
continue
description = (meta.get("description") or "").strip()
if description:
lines.append(f".agents/skills/{skill_id}{description}")
lines.append(f".astrion/skills/{skill_id}{description}")
else:
lines.append(f".agents/skills/{skill_id}")
lines.append(f".astrion/skills/{skill_id}")
return lines

View File

@ -32,7 +32,7 @@ class ConversationVersioningManager:
"""Manage hidden git snapshots bound to a conversation."""
TRACKING_MODE_WORKSPACE_AND_CONVERSATION = "workspace_and_conversation"
TRACKING_MODE_CONVERSATION_ONLY = "conversation_only"
SYSTEM_AUTO_DIRS = (".agents/compact_result", ".agents/skills", ".agents/user_upload")
SYSTEM_AUTO_DIRS = (".astrion/compact_result", ".astrion/skills", ".astrion/user_upload")
# 空 Git tree 对象的固定哈希,用于 tree-to-tree diff 的基准点
EMPTY_TREE_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"

View File

@ -1,10 +1,13 @@
#!/usr/bin/env bash
# 清理误迁到 ~/.agents/web 的运行态数据副本。
# 清理误迁到 ~/.agents/web 的历史运行态数据副本(已废弃路径)
#
# 背景:首次迁移时脚本未加载 .env、误判为 web 模式,把数据复制到了
# ~/.agents/web/。源数据仍在源码树web 只是程序读不到的错位副本。
# 本机终端有完整权限,可安全删除。
#
# 注意:当前项目运行态数据根目录已改为 ~/.astrion/astrion/,本脚本仅用于
# 清理旧路径 ~/.agents/web 的历史遗留,不应用于新路径。
#
# 用法bash scripts/cleanup_misplaced_web.sh
set -euo pipefail

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""运行态数据迁移脚本:源码树 -> 运行态根目录(~/.agents/<mode>)。
"""运行态数据迁移脚本:源码树 -> 运行态根目录(~/.astrion/astrion/<mode>)。
把历史遗留在源码树内的运行态数据迁移到新的运行态根目录目标位置直接复用
``config`` 模块的解析结果``config.DATA_DIR`` / ``USER_SPACE_DIR`` / ...
@ -16,7 +16,7 @@
保守策略全程复制而非移动旧目录保留便于回滚
- 部署级配置文件custom_models / host_workspaces / auto_approval / goal_review /
forbidden_commands / host_sandbox_policy
从源码树 ``config/`` 复制到部署配置目录 ``~/.agents/<mode>/config/``
从源码树 ``config/`` 复制到部署配置目录 ``~/.astrion/astrion/<mode>/config/``
同样先备份不覆盖已存在目标docker_risk_markersskill_hints 属于
程序能力保留源码树不迁
@ -57,7 +57,7 @@ def _migrate_targets() -> dict[str, Path]:
}
# 部署级配置文件:源码树 config/<name> -> 部署配置目录 ~/.agents/<mode>/config/<name>。
# 部署级配置文件:源码树 config/<name> -> 部署配置目录 ~/.astrion/astrion/<mode>/config/<name>。
# 仅迁移部署者自定义类(含密钥/机器特定/可调策略docker_risk_markers、skill_hints
# 属于程序能力,保留源码树,不迁。
_DEPLOY_CONFIG_FILES = (
@ -152,7 +152,7 @@ def _copy_file(src: Path, dst: Path, *, dry_run: bool) -> None:
def main() -> int:
parser = argparse.ArgumentParser(description="迁移运行态数据到运行态根目录(~/.agents/<mode>")
parser = argparse.ArgumentParser(description="迁移运行态数据到运行态根目录(~/.astrion/astrion/<mode>")
parser.add_argument("--dry-run", action="store_true", help="仅打印将要执行的操作,不实际写入")
args = parser.parse_args()

View File

@ -51,17 +51,17 @@ ENV_EXAMPLE_PATH = REPO_ROOT / ".env.example"
def _deploy_config_dir(mode: str, custom_data: str) -> Path:
"""推导部署级配置目录(~/.agents/<mode>/config
"""推导部署级配置目录(~/.astrion/astrion/config
必须与 config/paths.py 的解析保持一致
- 自定义数据根向导里填的优先作为运行态根目录
- 否则默认 ~/.agents/<mode>
- 否则默认 ~/.astrion/astrion
- 配置目录 = 运行态根 / config
setup 刻意不 import config避免在 .env 写好前过早锁定路径
因此这里用向导现场收集到的 mode / custom_data 自行推导
"""
root = Path(custom_data).expanduser() if custom_data else (Path("~/.agents").expanduser() / mode)
root = Path(custom_data).expanduser() if custom_data else (Path("~/.astrion").expanduser() / "astrion")
return (root / "config").resolve()
@ -237,12 +237,12 @@ def run_wizard(force: bool = False) -> int:
# --- 3. 数据目录(可选覆盖)---
print("\n[3/5] 数据目录")
default_data = f"~/.agents/{mode}"
default_data = "~/.astrion/astrion"
print(f" 运行态数据(对话/用户/记忆/日志)默认存放在 {default_data}")
custom_data = _ask("自定义数据根目录(留空用默认)", "")
if custom_data:
# 模式根变量host -> AGENTS_HOST_HOMEweb -> AGENTS_WEB_HOME
values["AGENTS_HOST_HOME" if mode == "host" else "AGENTS_WEB_HOME"] = custom_data
# 数据根变量ASTRION_DATA_ROOT
values["ASTRION_DATA_ROOT"] = custom_data
# --- 4. 管理员账户 ---
print("\n[4/5] 管理员账户")
@ -251,9 +251,9 @@ def run_wizard(force: bool = False) -> int:
values["AGENT_ADMIN_USERNAME"] = admin_user
values["AGENT_ADMIN_PASSWORD_HASH"] = generate_password_hash(admin_pw)
# --- 5. 模型配置(写入部署级配置目录 ~/.agents/<mode>/config/custom_models.json---
# --- 5. 模型配置(写入部署级配置目录 ~/.astrion/astrion/config/custom_models.json---
print("\n[5/5] 模型配置")
print(" 配置一个可用模型(写入 ~/.agents/<mode>/config/custom_models.json")
print(" 配置一个可用模型(写入 ~/.astrion/astrion/config/custom_models.json")
print(" 启动后还可在后台页面继续增删模型。")
model_name = _ask_required("模型显示名(界面上看到的名字)", "默认模型")
model_url = _ask_required("API 地址Base URL", "")
@ -283,7 +283,7 @@ def run_wizard(force: bool = False) -> int:
ENV_PATH.chmod(0o600) # .env 含密钥收紧权限Windows 上静默忽略)
except (OSError, NotImplementedError):
pass
# 模型配置写到部署级配置目录(~/.agents/<mode>/config而非源码树
# 模型配置写到部署级配置目录(~/.astrion/astrion/config而非源码树
custom_models_path = _deploy_config_dir(mode, custom_data) / "custom_models.json"
models_backup = _write_custom_models(custom_models_path, model_entry)

View File

@ -784,7 +784,7 @@ CHUNK_BACKEND_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "chunk_backend.
CHUNK_FRONTEND_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "chunk_frontend.log"
STREAMING_DEBUG_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "streaming_debug.log"
GOAL_MODE_DEBUG_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "goal_mode_debug.log"
UPLOAD_FOLDER_NAME = ".agents/user_upload"
UPLOAD_FOLDER_NAME = ".astrion/user_upload"
def is_logged_in() -> bool:

View File

@ -45,7 +45,7 @@ from server.extensions import socketio
from server.monitor import get_cached_monitor_snapshot
from server.files import sanitize_filename_preserve_unicode
UPLOAD_FOLDER_NAME = ".agents/user_upload"
UPLOAD_FOLDER_NAME = ".astrion/user_upload"
@chat_bp.route('/api/user-questions/pending', methods=['GET'])
@api_login_required
@with_terminal

View File

@ -46,7 +46,7 @@ from server.extensions import socketio
from server.monitor import get_cached_monitor_snapshot
from server.files import sanitize_filename_preserve_unicode
UPLOAD_FOLDER_NAME = ".agents/user_upload"
UPLOAD_FOLDER_NAME = ".astrion/user_upload"
def _derive_filename_from_upload(uploaded_file) -> str:

View File

@ -45,7 +45,7 @@ from server.extensions import socketio
from server.monitor import get_cached_monitor_snapshot
from server.files import sanitize_filename_preserve_unicode
UPLOAD_FOLDER_NAME = ".agents/user_upload"
UPLOAD_FOLDER_NAME = ".astrion/user_upload"
@chat_bp.route('/api/memory', methods=['GET'])
@api_login_required
@with_terminal

View File

@ -57,7 +57,7 @@ from server.files import sanitize_filename_preserve_unicode
import os
import re
UPLOAD_FOLDER_NAME = ".agents/user_upload"
UPLOAD_FOLDER_NAME = ".astrion/user_upload"
def _path_conflicts_with_deny_list(path: str) -> Optional[str]:

View File

@ -45,7 +45,7 @@ from server.extensions import socketio
from server.monitor import get_cached_monitor_snapshot
from server.files import sanitize_filename_preserve_unicode
UPLOAD_FOLDER_NAME = ".agents/user_upload"
UPLOAD_FOLDER_NAME = ".astrion/user_upload"
def _dispatch_runtime_mode_notice(terminal: WebTerminal, username: str, text: str, source: str = "notify") -> None:
notice = str(text or "").strip()
if not notice:

View File

@ -45,7 +45,7 @@ from server.extensions import socketio
from server.monitor import get_cached_monitor_snapshot
from server.files import sanitize_filename_preserve_unicode
UPLOAD_FOLDER_NAME = ".agents/user_upload"
UPLOAD_FOLDER_NAME = ".astrion/user_upload"
@chat_bp.route('/api/terminals')
@api_login_required
@with_terminal

View File

@ -1936,7 +1936,7 @@ def review_conversation(conversation_id, terminal: WebTerminal, workspace: UserW
return jsonify({
"success": True,
"data": {
"path": f".agents/review/{filename}",
"path": f".astrion/review/{filename}",
"char_count": char_count
}
})

View File

@ -59,7 +59,7 @@ def _resolve_workspace_skill_path(workspace, raw_path: str) -> Path:
target.relative_to(skills_dir)
except ValueError as exc:
debug_log(f"[SkillsAPI] resolve skill path outside skills_dir: target={target!r} skills_dir={skills_dir!r}")
raise ValueError("skill 路径必须位于当前工作区 .agents/skills/ 内") from exc
raise ValueError("skill 路径必须位于当前工作区 .astrion/skills/ 内") from exc
if not target.is_file():
debug_log(f"[SkillsAPI] resolve skill file not found: {target!r}")
raise ValueError("skill 文件不存在")

View File

@ -3,7 +3,7 @@
import { debugLog } from '../common';
import { useModelStore } from '../../../stores/model';
export const SKILL_MARKDOWN_LINK_RE = /\[\$([^\]\n]+)\]\(([^)\n]*\/\.agents\/skills\/[^)\n]+\/SKILL\.md)\)/g;
export const SKILL_MARKDOWN_LINK_RE = /\[\$([^\]\n]+)\]\(([^)\n]*\/\.astrion\/skills\/[^)\n]+\/SKILL\.md)\)/g;
export function extractSkillRefsFromMessage(message = '') {
const refs = [];

View File

@ -913,7 +913,7 @@ const escapeUserHtml = (value: string): string =>
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
const USER_SKILL_LINK_RE = /\[\$([^\]\n]+)\]\(([^)\n]*\/\.agents\/skills\/[^)\n]+\/SKILL\.md)\)/g;
const USER_SKILL_LINK_RE = /\[\$([^\]\n]+)\]\(([^)\n]*\/\.astrion\/skills\/[^)\n]+\/SKILL\.md)\)/g;
const USER_FILE_LINK_RE = /\[([^\]\n]+)\]\(file:\/\/([^)\n]+)\)/g;
const SUB_AGENT_DONE_LABEL_RE = /^子智能体\d+\s*任务完成$/;
const SUB_AGENT_DONE_PREFIX_RE = /^(?:✅\s*)?子智能体\s*#?\s*(\d+)\s*任务摘要[:]/;

View File

@ -1460,7 +1460,7 @@
<div class="settings-group-title">
<span class="settings-row-title">可用 Skills</span
><span class="settings-row-desc"
>勾选后会注入 system prompt并同步到工作区的 skills/ 目录</span
>勾选后会注入 system prompt并同步到工作区的 .astrion/skills/ 目录</span
>
</div>
<div class="settings-check-grid">

View File

@ -35,7 +35,7 @@ print(json.dumps({
env["PYTHONPATH"] = str(self.repo_root)
# 清掉可能从外部 .env / 环境继承的覆盖项,保证用例可控。
for key in (
"TERMINAL_SANDBOX_MODE", "AGENTS_HOST_HOME", "AGENTS_WEB_HOME",
"TERMINAL_SANDBOX_MODE", "ASTRION_DATA_ROOT",
"DATA_DIR", "LOGS_DIR", "USER_SPACE_DIR", "API_USER_SPACE_DIR",
"DEFAULT_PROJECT_PATH", "HOST_WORKSPACES_FILE", "PROMPTS_DIR",
"DEPLOY_CONFIG_DIR",
@ -57,12 +57,13 @@ print(json.dumps({
return json.loads(lines[-1])
def test_runtime_dirs_default_to_web_home(self):
"""默认(非 host模式下运行态目录锚定到 ~/.agents/web。"""
"""默认(非 host模式下运行态根为 ~/.astrion/astrion模式子目录为 web。"""
with tempfile.TemporaryDirectory() as td:
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "docker"})
web_home = Path("~/.agents/web").expanduser().resolve()
self.assertEqual(data["RUNTIME_ROOT"], str(web_home))
runtime_root = Path("~/.astrion/astrion").expanduser().resolve()
web_home = runtime_root / "web"
self.assertEqual(data["RUNTIME_ROOT"], str(runtime_root))
self.assertEqual(data["DATA_DIR"], str(web_home / "data"))
self.assertEqual(data["LOGS_DIR"], str(web_home / "logs"))
self.assertEqual(data["USER_SPACE_DIR"], str(web_home / "users"))
@ -72,12 +73,13 @@ print(json.dumps({
self.assertEqual(data["SUB_AGENT_TASKS_BASE_DIR"], str(web_home / "data" / "sub_agent_tasks"))
def test_runtime_dirs_switch_to_host_home(self):
"""host 模式下,运行态目录锚定到 ~/.agents/host。"""
"""host 模式下,运行态根为 ~/.astrion/astrion模式子目录为 host。"""
with tempfile.TemporaryDirectory() as td:
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "host"})
host_home = Path("~/.agents/host").expanduser().resolve()
self.assertEqual(data["RUNTIME_ROOT"], str(host_home))
runtime_root = Path("~/.astrion/astrion").expanduser().resolve()
host_home = runtime_root / "host"
self.assertEqual(data["RUNTIME_ROOT"], str(runtime_root))
self.assertEqual(data["DATA_DIR"], str(host_home / "data"))
self.assertEqual(data["LOGS_DIR"], str(host_home / "logs"))
@ -95,47 +97,47 @@ print(json.dumps({
)
def test_deploy_config_anchors_to_runtime_root(self):
"""部署级配置目录与 host_workspaces.json 锚定到运行态根 ~/.agents/<mode>/config"""
"""部署级配置目录与 host_workspaces.json 锚定到运行态根 ~/.astrion/astrion/confighost/web 共享)"""
with tempfile.TemporaryDirectory() as td:
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "web"})
web_config = Path("~/.agents/web/config").expanduser().resolve()
self.assertEqual(data["DEPLOY_CONFIG_DIR"], str(web_config))
runtime_config = Path("~/.astrion/astrion/config").expanduser().resolve()
self.assertEqual(data["DEPLOY_CONFIG_DIR"], str(runtime_config))
self.assertEqual(
data["HOST_WORKSPACES_FILE"], str(web_config / "host_workspaces.json")
data["HOST_WORKSPACES_FILE"], str(runtime_config / "host_workspaces.json")
)
def test_deploy_config_follows_mode_home(self):
"""模式根变量覆盖时,部署配置目录跟随该根目录。"""
def test_deploy_config_follows_data_root(self):
"""数据根变量覆盖时,部署配置目录跟随该根目录。"""
with tempfile.TemporaryDirectory() as td:
custom = Path(td) / "web_root"
data = self._load_paths(
cwd=Path(td),
extra_env={
"TERMINAL_SANDBOX_MODE": "web",
"AGENTS_WEB_HOME": str(custom),
"ASTRION_DATA_ROOT": str(custom),
},
)
self.assertEqual(data["DEPLOY_CONFIG_DIR"], str((custom / "config").resolve()))
def test_mode_home_env_overrides_root(self):
"""模式根变量AGENTS_HOST_HOME / AGENTS_WEB_HOME)整体搬迁运行态目录。"""
def test_data_root_env_overrides_root(self):
"""数据根变量ASTRION_DATA_ROOT)整体搬迁运行态目录。"""
with tempfile.TemporaryDirectory() as td:
custom = Path(td) / "host_root"
data = self._load_paths(
cwd=Path(td),
extra_env={
"TERMINAL_SANDBOX_MODE": "host",
"AGENTS_HOST_HOME": str(custom),
"ASTRION_DATA_ROOT": str(custom),
},
)
self.assertEqual(data["RUNTIME_ROOT"], str(custom.resolve()))
self.assertEqual(data["DATA_DIR"], str((custom / "data").resolve()))
self.assertEqual(data["LOGS_DIR"], str((custom / "logs").resolve()))
self.assertEqual(data["DATA_DIR"], str((custom / "host" / "data").resolve()))
self.assertEqual(data["LOGS_DIR"], str((custom / "host" / "logs").resolve()))
def test_specific_dir_env_has_highest_priority(self):
"""具体目录变量优先级高于模式根变量。"""
"""具体目录变量优先级高于数据根变量。"""
with tempfile.TemporaryDirectory() as td:
root = Path(td) / "root"
logs = Path(td) / "separate_logs"
@ -143,13 +145,13 @@ print(json.dumps({
cwd=Path(td),
extra_env={
"TERMINAL_SANDBOX_MODE": "web",
"AGENTS_WEB_HOME": str(root),
"ASTRION_DATA_ROOT": str(root),
"LOGS_DIR": str(logs),
},
)
# DATA 跟随根变量LOGS 被单独覆盖
self.assertEqual(data["DATA_DIR"], str((root / "data").resolve()))
# DATA 跟随根变量下的模式子目录LOGS 被单独覆盖
self.assertEqual(data["DATA_DIR"], str((root / "web" / "data").resolve()))
self.assertEqual(data["LOGS_DIR"], str(logs.resolve()))
def test_relative_specific_dir_env_anchors_to_repo_root(self):

View File

@ -1,9 +1,13 @@
from __future__ import annotations
import os
import tempfile
import unittest
from pathlib import Path
# 本模块测试按 web 模式布局推断 skills 目录,需在导入 config 前设定模式。
os.environ["TERMINAL_SANDBOX_MODE"] = "web"
from modules.skills_manager import (
archive_skill_directory,
get_skills_catalog,
@ -90,7 +94,7 @@ class SkillsManagerTest(unittest.TestCase):
private_dir=private_root,
)
self.assertTrue(synced.get("success"))
self.assertTrue((project / ".agents" / "skills" / "private-skill" / "SKILL.md").exists())
self.assertTrue((project / ".astrion" / "skills" / "private-skill" / "SKILL.md").exists())
if __name__ == "__main__":

View File

@ -8,19 +8,19 @@ class ToolResultFormatterTest(unittest.TestCase):
result = {
"success": True,
"type": "read",
"path": ".agents/skills/frontend-design/SKILL.md",
"path": ".astrion/skills/frontend-design/SKILL.md",
"max_chars": 50000,
"truncated": False,
"content": "# Skill\nFull content",
"line_start": 1,
"line_end": 2,
"total_lines": 2,
"message": "已读取 .agents/skills/frontend-design/SKILL.md 的内容(行 1~2",
"message": "已读取 .astrion/skills/frontend-design/SKILL.md 的内容(行 1~2",
}
formatted = format_tool_result_for_context("read_skill", result)
self.assertIn("读取 .agents/skills/frontend-design/SKILL.md 行 1~2", formatted)
self.assertIn("读取 .astrion/skills/frontend-design/SKILL.md 行 1~2", formatted)
self.assertIn("```\n# Skill\nFull content\n```", formatted)
self.assertNotEqual(result["message"], formatted)

View File

@ -77,7 +77,7 @@ class DeepSeekClientBaseMixin:
self.last_call_used_thinking = False # 最近一次调用是否使用思考模型
# 最近一次API错误详情
self.last_error_info: Optional[Dict[str, Any]] = None
# 请求体落盘目录(跟随 LOGS_DIR默认 ~/.agents/<mode>/logs/api_requests
# 请求体落盘目录(跟随 LOGS_DIR默认 ~/.astrion/<mode>/logs/api_requests
self.request_dump_dir = Path(LOGS_DIR) / "api_requests"
self.debug_log_path = Path(LOGS_DIR) / "api_debug.log"

View File

@ -81,7 +81,7 @@ class MessageMixin:
"""ContextManager message mixin 能力 mixin。"""
def _build_recent_uploads_block(self, max_items: int = MAX_RECENT_UPLOADS) -> str:
"""扫描工作区 .agents/user_upload 目录,按修改时间倒序生成最近上传文件列表。"""
"""扫描工作区 .astrion/user_upload 目录,按修改时间倒序生成最近上传文件列表。"""
try:
project_path = Path(getattr(self, "project_path", ".") or ".").expanduser().resolve()
uploads_dir = project_path / ".agents" / "user_upload"

View File

@ -222,7 +222,7 @@ class ProjectMixin:
for internal_name in ("skills", "user_upload", "compact_result", "review"):
internal_dir = agents_dir / internal_name
if internal_dir.exists() and internal_dir.is_dir():
add_entry(internal_dir, display_path=f".agents/{internal_name}")
add_entry(internal_dir, display_path=f".astrion/{internal_name}")
return structure
def get_project_structure(self) -> Dict: