refactor(paths): 统一迁移运行态数据路径到 Astrion 命名空间
- 工作区内部路径 .agents/ -> .astrion/ - 默认运行态数据根 ~/.agents/agents/ -> ~/.astrion/astrion/ - 环境变量 AGENTS_DATA_ROOT -> ASTRION_DATA_ROOT(无兼容) - 同步更新代码、测试、文档与脚本中的路径引用 - 新增 .gitignore 忽略 .astrion/ 与 .agents/ 运行态目录
This commit is contained in:
parent
6a2cb820bc
commit
e3c897947f
4
.gitignore
vendored
4
.gitignore
vendored
@ -12,8 +12,10 @@ __pycache__/
|
|||||||
venv/
|
venv/
|
||||||
|
|
||||||
# Runtime data (main agent)
|
# Runtime data (main agent)
|
||||||
# 注意:运行态数据默认已迁出源码树到 ~/.agents/<mode>/。
|
# 注意:运行态数据默认已迁出源码树到 ~/.astrion/astrion/<mode>/。
|
||||||
# 以下条目用于忽略「通过具体目录变量指回源码树」或历史遗留的运行态目录。
|
# 以下条目用于忽略「通过具体目录变量指回源码树」或历史遗留的运行态目录。
|
||||||
|
.astrion/
|
||||||
|
.agents/
|
||||||
logs/
|
logs/
|
||||||
data/
|
data/
|
||||||
project/
|
project/
|
||||||
|
|||||||
16
AGENTS.md
16
AGENTS.md
@ -47,28 +47,28 @@
|
|||||||
|
|
||||||
## 1.5) 运行态数据目录与路径变量(2026-06 更新)
|
## 1.5) 运行态数据目录与路径变量(2026-06 更新)
|
||||||
|
|
||||||
> 设计目标:运行态数据(对话、用户工作区、日志等)默认存放在用户主目录的 `~/.agents/agents/` 下,对齐 `~/.claude`、`~/.codex` 惯例,**不落在源码树内**。配置实现见 `config/paths.py`。
|
> 设计目标:运行态数据(对话、用户工作区、日志等)默认存放在用户主目录的 `~/.astrion/astrion/` 下,对齐 `~/.claude`、`~/.codex` 惯例,**不落在源码树内**。配置实现见 `config/paths.py`。
|
||||||
|
|
||||||
### 1.5.1 默认位置与模式分流
|
### 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`
|
- 宿主机模式(`TERMINAL_SANDBOX_MODE=host`)→ `~/.astrion/astrion/host`
|
||||||
- 其它模式(默认 docker / web)→ `~/.agents/agents/web`
|
- 其它模式(默认 docker / web)→ `~/.astrion/astrion/web`
|
||||||
|
|
||||||
数据根目录下还包含:`settings.json`(唯一配置文件)、`config/`(部署级配置,host/web 共享)。每个模式目录下包含:`data/`(对话、用户库、记忆、sub_agents.json、sub_agent_tasks 等)、`users/`(web 多用户工作区)、`api/users/`(API 用户工作区)、`logs/`(日志)。
|
数据根目录下还包含:`settings.json`(唯一配置文件)、`config/`(部署级配置,host/web 共享)。每个模式目录下包含:`data/`(对话、用户库、记忆、sub_agents.json、sub_agent_tasks 等)、`users/`(web 多用户工作区)、`api/users/`(API 用户工作区)、`logs/`(日志)。
|
||||||
|
|
||||||
### 1.5.2 路径解析优先级(从高到低)
|
### 1.5.2 路径解析优先级(从高到低)
|
||||||
|
|
||||||
1. **具体目录环境变量**:`DATA_DIR` / `LOGS_DIR` / `USER_SPACE_DIR` / `API_USER_SPACE_DIR`(单独覆盖某个目录,最高优先级)
|
1. **具体目录环境变量**:`DATA_DIR` / `LOGS_DIR` / `USER_SPACE_DIR` / `API_USER_SPACE_DIR`(单独覆盖某个目录,最高优先级)
|
||||||
2. **数据根目录环境变量**:`AGENTS_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.agents/agents`)
|
2. **数据根目录环境变量**:`ASTRION_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.astrion/astrion`)
|
||||||
3. **兜底默认**:`~/.agents/agents/<mode>`
|
3. **兜底默认**:`~/.astrion/astrion/<mode>`
|
||||||
|
|
||||||
具体目录变量支持相对路径(相对仓库根目录展开)、绝对路径与 `~`。
|
具体目录变量支持相对路径(相对仓库根目录展开)、绝对路径与 `~`。
|
||||||
|
|
||||||
> 注意:`config/*.json` 分两类:
|
> 注意:`config/*.json` 分两类:
|
||||||
> - **程序能力**(`docker_risk_markers.json`、`skill_hints.json`):是程序行为的一部分,随版本演进,仍锚定源码树。`prompts/`、`agentskills/` 同理。
|
> - **程序能力**(`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 新增)
|
### 1.5.3 Host / Web 双路径机制(2026-06 新增)
|
||||||
|
|
||||||
@ -252,7 +252,7 @@ AI 执行以下流程时,每一步都要向用户说明在做什么:
|
|||||||
## 7) 安全与仓库卫生
|
## 7) 安全与仓库卫生
|
||||||
|
|
||||||
- 严禁提交真实密钥(`.env`、token、cookie、用户隐私)。
|
- 严禁提交真实密钥(`.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`)。需要保留但不属于当前主线、又不想直接删的零散文件,统一放这里,不要散落在根目录。
|
- **`_experiments/` 用途**:归档本地实验残留与历史文档(调试记录、旧变更日志、模型测试脚本、翻译资料、旧子智能体文档等)。该目录**不纳入 git**(已在 `.gitignore`)。需要保留但不属于当前主线、又不想直接删的零散文件,统一放这里,不要散落在根目录。
|
||||||
- 不要把本地构建产物(如 `static/dist/`、`node_modules/`)纳入提交。
|
- 不要把本地构建产物(如 `static/dist/`、`node_modules/`)纳入提交。
|
||||||
|
|
||||||
|
|||||||
22
CLAUDE.md
22
CLAUDE.md
@ -137,12 +137,12 @@ npm --prefix cli run build
|
|||||||
# 查看容器状态
|
# 查看容器状态
|
||||||
docker ps -a | grep agent-term
|
docker ps -a | grep agent-term
|
||||||
|
|
||||||
# 查看调试日志(运行态日志默认在 ~/.agents/agents/<mode>/logs/)
|
# 查看调试日志(运行态日志默认在 ~/.astrion/astrion/<mode>/logs/)
|
||||||
tail -f ~/.agents/agents/host/logs/debug_stream.log # host 模式
|
tail -f ~/.astrion/astrion/host/logs/debug_stream.log # host 模式
|
||||||
tail -f ~/.agents/agents/web/logs/debug_stream.log # web/docker 模式
|
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
|
## Configuration
|
||||||
@ -168,12 +168,12 @@ tail -f ~/.agents/agents/web/logs/container_stats.log
|
|||||||
|
|
||||||
**数据目录与路径变量** (`config/paths.py`,2026-06 更新):
|
**数据目录与路径变量** (`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`(单独覆盖某目录)
|
1. 具体目录变量 `DATA_DIR` / `LOGS_DIR` / `USER_SPACE_DIR` / `API_USER_SPACE_DIR`(单独覆盖某目录)
|
||||||
2. 数据根目录变量 `AGENTS_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.agents/agents`)
|
2. 数据根目录变量 `ASTRION_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.astrion/astrion`)
|
||||||
3. 兜底 `~/.agents/agents/<mode>`
|
3. 兜底 `~/.astrion/astrion/<mode>`
|
||||||
|
|
||||||
**Host / Web 双路径机制(2026-06)**:
|
**Host / Web 双路径机制(2026-06)**:
|
||||||
- `paths.py` 新增 `IS_HOST_MODE`、`WEB_DATA_DIR`、`WEB_USER_SPACE_DIR` 三个固定变量
|
- `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 工作区直接复用(保持旧数据可访问),新工作区写入 host/
|
||||||
- web 模式:仅读取 web/ 数据,无变化
|
- 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` 开启。
|
- 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`。
|
- 混合轮转(`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
|
## Important Implementation Details
|
||||||
|
|
||||||
@ -346,7 +346,7 @@ agents/
|
|||||||
└── _experiments/ # 本地实验残留与历史文档归档(gitignored,不进仓库)
|
└── _experiments/ # 本地实验残留与历史文档归档(gitignored,不进仓库)
|
||||||
|
|
||||||
运行态数据(不在源码树):
|
运行态数据(不在源码树):
|
||||||
~/.agents/agents/ # 数据根目录(可通过 AGENTS_DATA_ROOT 覆盖)
|
~/.astrion/astrion/ # 数据根目录(可通过 ASTRION_DATA_ROOT 覆盖)
|
||||||
├── settings.json # 唯一配置文件
|
├── settings.json # 唯一配置文件
|
||||||
├── config/ # 部署级配置(host/web 共享)
|
├── config/ # 部署级配置(host/web 共享)
|
||||||
├── host/ # mode = host,由 TERMINAL_SANDBOX_MODE 决定
|
├── host/ # mode = host,由 TERMINAL_SANDBOX_MODE 决定
|
||||||
@ -359,7 +359,7 @@ agents/
|
|||||||
└── logs/ # 日志文件
|
└── logs/ # 日志文件
|
||||||
```
|
```
|
||||||
|
|
||||||
> 历史说明:`data/`、`users/`、`logs/`、`project/` 等过去曾在源码树内,现已默认迁至 `~/.agents/agents/<mode>/`。详见 Configuration 节「数据目录与路径变量」。
|
> 历史说明:`data/`、`users/`、`logs/`、`project/` 等过去曾在源码树内,现已默认迁至 `~/.astrion/astrion/<mode>/`。详见 Configuration 节「数据目录与路径变量」。
|
||||||
|
|
||||||
## Development Tips
|
## Development Tips
|
||||||
|
|
||||||
|
|||||||
36
README.md
36
README.md
@ -8,7 +8,7 @@
|
|||||||
- 新 CLI 端已开始重写,位于 `cli/`,技术栈为 React 19 + Ink 6 + TypeScript。
|
- 新 CLI 端已开始重写,位于 `cli/`,技术栈为 React 19 + Ink 6 + TypeScript。
|
||||||
- 当前 CLI 不是独立后端;它会直接连接本地 `8091` Web API,并复用现有会话、任务、权限、工作区等接口。
|
- 当前 CLI 不是独立后端;它会直接连接本地 `8091` Web API,并复用现有会话、任务、权限、工作区等接口。
|
||||||
- CLI 已实现“启动即连接后端并新建会话”的流程,但仍处于持续打磨阶段,输入法、光标、时间线裁剪等终端细节仍在迭代。
|
- CLI 已实现“启动即连接后端并新建会话”的流程,但仍处于持续打磨阶段,输入法、光标、时间线裁剪等终端细节仍在迭代。
|
||||||
- 运行态数据(对话、用户库、日志等)默认存放在用户主目录的 `~/.agents/agents/<mode>/` 下,不再落在源码树内;源码树内的 `data/`、`logs/`、`users/` 等仅作为 `.gitignore` 防污染保留。
|
- 运行态数据(对话、用户库、日志等)默认存放在用户主目录的 `~/.astrion/astrion/<mode>/` 下,不再落在源码树内;源码树内的 `data/`、`logs/`、`users/` 等仅作为 `.gitignore` 防污染保留。
|
||||||
|
|
||||||
## CLI 快速开始
|
## CLI 快速开始
|
||||||
|
|
||||||
@ -488,7 +488,7 @@ python -m server.app --path ./project --port 8091 --debug --thinking-mode
|
|||||||
主要配置项(通过环境变量或 `.env` 文件):
|
主要配置项(通过环境变量或 `.env` 文件):
|
||||||
|
|
||||||
### API 配置
|
### 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;未设置时使用第一个可见动态模型
|
- `AGENT_DEFAULT_MODEL`: 可选,指定默认模型 key;未设置时使用第一个可见动态模型
|
||||||
- 模型配置中的 `url` / `apikey` 支持 `${ENV_NAME}`、`env:ENV_NAME`、`$ENV_NAME` 等环境变量引用
|
- 模型配置中的 `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`: 日志目录
|
- `LOGS_DIR`: 日志目录
|
||||||
- `USER_SPACE_DIR`: Web 用户工作区目录
|
- `USER_SPACE_DIR`: Web 用户工作区目录
|
||||||
- `API_USER_SPACE_DIR`: API 用户工作区目录
|
- `API_USER_SPACE_DIR`: API 用户工作区目录
|
||||||
- `AGENTS_DATA_ROOT`: 运行态数据根目录(默认 `~/.agents/agents`,host/web 两个模式共享该根)
|
- `ASTRION_DATA_ROOT`: 运行态数据根目录(默认 `~/.astrion/astrion`,host/web 两个模式共享该根)
|
||||||
- `DEPLOY_CONFIG_DIR`: 部署级配置目录(默认 `<AGENTS_DATA_ROOT>/config/`,host/web 共享)
|
- `DEPLOY_CONFIG_DIR`: 部署级配置目录(默认 `<ASTRION_DATA_ROOT>/config/`,host/web 共享)
|
||||||
|
|
||||||
### 限制配置
|
### 限制配置
|
||||||
- `MAX_ITERATIONS_PER_TASK`: 单任务最大迭代次数
|
- `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`
|
- **宿主机模式**(`TERMINAL_SANDBOX_MODE=host`)→ `~/.astrion/astrion/host`
|
||||||
- **其它模式**(默认 docker / web)→ `~/.agents/agents/web`
|
- **其它模式**(默认 docker / web)→ `~/.astrion/astrion/web`
|
||||||
|
|
||||||
### 目录结构
|
### 目录结构
|
||||||
|
|
||||||
```
|
```
|
||||||
~/.agents/agents/ # 数据根目录(可通过 AGENTS_DATA_ROOT 覆盖)
|
~/.astrion/astrion/ # 数据根目录(可通过 ASTRION_DATA_ROOT 覆盖)
|
||||||
├── settings.json # 唯一配置文件
|
├── settings.json # 唯一配置文件
|
||||||
├── config/ # 部署级配置(custom_models/host_workspaces/auto_approval 等,host/web 共享)
|
├── config/ # 部署级配置(custom_models/host_workspaces/auto_approval 等,host/web 共享)
|
||||||
├── host/ # 宿主机模式运行态
|
├── 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`(单独覆盖某个目录)
|
1. **具体目录环境变量**:`DATA_DIR` / `LOGS_DIR` / `USER_SPACE_DIR` / `API_USER_SPACE_DIR`(单独覆盖某个目录)
|
||||||
2. **数据根目录环境变量**:`AGENTS_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.agents/agents`)
|
2. **数据根目录环境变量**:`ASTRION_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.astrion/astrion`)
|
||||||
3. **兜底默认**:`~/.agents/agents/<mode>`
|
3. **兜底默认**:`~/.astrion/astrion/<mode>`
|
||||||
|
|
||||||
### Host / Web 双路径机制
|
### 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 调用逻辑
|
2. 在 `utils/api_client/` 中实现或复用 API 调用逻辑
|
||||||
3. 通过环境变量或 `.env` 注入密钥等敏感信息
|
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`
|
- 主日志: `~/.astrion/astrion/<mode>/logs/main.log`
|
||||||
- 错误日志: `~/.agents/agents/<mode>/logs/errors/`
|
- 错误日志: `~/.astrion/astrion/<mode>/logs/errors/`
|
||||||
- 任务日志: `~/.agents/agents/<mode>/logs/tasks/`
|
- 任务日志: `~/.astrion/astrion/<mode>/logs/tasks/`
|
||||||
- Web 服务器: `~/.agents/agents/<mode>/logs/web_server.log`
|
- Web 服务器: `~/.astrion/astrion/<mode>/logs/web_server.log`
|
||||||
- 自动审批智能体调试记录: `~/.agents/agents/<mode>/logs/approval_agent/`
|
- 自动审批智能体调试记录: `~/.astrion/astrion/<mode>/logs/approval_agent/`
|
||||||
|
|
||||||
## 版本信息
|
## 版本信息
|
||||||
|
|
||||||
@ -670,7 +670,7 @@ python -m unittest test.test_server_refactor_smoke
|
|||||||
|
|
||||||
主要更新(2026-06):
|
主要更新(2026-06):
|
||||||
- 后端接口拆分为 `server/chat/`、`server/status/`、`server/tasks/` 子包
|
- 后端接口拆分为 `server/chat/`、`server/status/`、`server/tasks/` 子包
|
||||||
- 运行态数据默认迁移到 `~/.agents/agents/<mode>/`,支持 host/web 双路径读取
|
- 运行态数据默认迁移到 `~/.astrion/astrion/<mode>/`,支持 host/web 双路径读取
|
||||||
- 子智能体改为主进程内 `asyncio.Task` 实现,工具调用复用主进程链路
|
- 子智能体改为主进程内 `asyncio.Task` 实现,工具调用复用主进程链路
|
||||||
- 新增宿主机权限模式(readonly/approval/auto_approval/unrestricted)与执行环境(sandbox/direct)双层控制
|
- 新增宿主机权限模式(readonly/approval/auto_approval/unrestricted)与执行环境(sandbox/direct)双层控制
|
||||||
- 新增日志轮转、API dump 可控、部署级配置外置等运维能力
|
- 新增日志轮转、API dump 可控、部署级配置外置等运维能力
|
||||||
|
|||||||
@ -18,7 +18,7 @@ def _load_dotenv():
|
|||||||
|
|
||||||
# 1) 仓库根 .env(开发便利,不覆盖已有环境变量)
|
# 1) 仓库根 .env(开发便利,不覆盖已有环境变量)
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
env_path = Path.home() / '.agents' / 'agents' / '.env'
|
env_path = Path.home() / '.astrion' / 'astrion' / '.env'
|
||||||
else:
|
else:
|
||||||
env_path = Path(__file__).resolve().parents[1] / '.env'
|
env_path = Path(__file__).resolve().parents[1] / '.env'
|
||||||
if env_path.exists():
|
if env_path.exists():
|
||||||
@ -41,7 +41,7 @@ def _load_dotenv():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# 2) settings.json(数据根下的统一配置)
|
# 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"
|
settings_path = Path(data_root) / "settings.json"
|
||||||
if settings_path.exists():
|
if settings_path.exists():
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
"""项目路径与目录配置。
|
"""项目路径与目录配置。
|
||||||
|
|
||||||
运行态数据存放在 ``~/.agents/agents/`` 下:
|
运行态数据存放在 ``~/.astrion/astrion/`` 下:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
~/.agents/agents/ ← data_root(默认,可通过 AGENTS_DATA_ROOT 覆盖)
|
~/.astrion/astrion/ ← data_root(默认,可通过 ASTRION_DATA_ROOT 覆盖)
|
||||||
├── settings.json ← 唯一配置文件
|
├── settings.json ← 唯一配置文件
|
||||||
├── config/ ← 部署级 JSON,host/web 模式共享
|
├── config/ ← 部署级 JSON,host/web 模式共享
|
||||||
├── host/ ← host 模式(单用户)
|
├── host/ ← host 模式(单用户)
|
||||||
@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
1. 具体目录环境变量(``DATA_DIR`` / ``LOGS_DIR`` / ``USER_SPACE_DIR`` /
|
1. 具体目录环境变量(``DATA_DIR`` / ``LOGS_DIR`` / ``USER_SPACE_DIR`` /
|
||||||
``API_USER_SPACE_DIR``)——单独覆盖某个目录,最高优先级;
|
``API_USER_SPACE_DIR``)——单独覆盖某个目录,最高优先级;
|
||||||
2. ``AGENTS_DATA_ROOT`` ——整体搬迁数据根目录;
|
2. ``ASTRION_DATA_ROOT`` ——整体搬迁数据根目录;
|
||||||
3. 兜底默认值 ``~/.agents/agents``。
|
3. 兜底默认值 ``~/.astrion/astrion``。
|
||||||
|
|
||||||
注意:``config/*.json`` 分两类,不再一概锚定源码树:
|
注意:``config/*.json`` 分两类,不再一概锚定源码树:
|
||||||
|
|
||||||
@ -79,9 +79,9 @@ def _runtime_mode() -> str:
|
|||||||
|
|
||||||
|
|
||||||
# ── 数据根目录(data_root)──
|
# ── 数据根目录(data_root)──
|
||||||
# 优先级:AGENTS_DATA_ROOT 环境变量 > 默认 ~/.agents/agents
|
# 优先级:ASTRION_DATA_ROOT 环境变量 > 默认 ~/.astrion/astrion
|
||||||
_DEFAULT_DATA_ROOT = Path("~/.agents/agents")
|
_DEFAULT_DATA_ROOT = Path("~/.astrion/astrion")
|
||||||
RUNTIME_ROOT = str(_norm(Path(_env("AGENTS_DATA_ROOT") or _DEFAULT_DATA_ROOT)))
|
RUNTIME_ROOT = str(_norm(Path(_env("ASTRION_DATA_ROOT") or _DEFAULT_DATA_ROOT)))
|
||||||
|
|
||||||
_MODE = _runtime_mode()
|
_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")
|
PROMPTS_DIR = _resolve_repo_path(os.environ.get("PROMPTS_DIR", ""), "./prompts")
|
||||||
AGENT_SKILLS_DIR = _resolve_repo_path(os.environ.get("AGENT_SKILLS_DIR", ""), "./agentskills")
|
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(
|
HOST_WORKSPACES_FILE = _resolve_repo_path(
|
||||||
os.environ.get("HOST_WORKSPACES_FILE", ""),
|
os.environ.get("HOST_WORKSPACES_FILE", ""),
|
||||||
deploy_config_path("host_workspaces.json"),
|
deploy_config_path("host_workspaces.json"),
|
||||||
@ -185,6 +187,8 @@ __all__ = [
|
|||||||
"LOGS_DIR",
|
"LOGS_DIR",
|
||||||
"AGENT_SKILLS_DIR",
|
"AGENT_SKILLS_DIR",
|
||||||
"WORKSPACE_SKILLS_DIRNAME",
|
"WORKSPACE_SKILLS_DIRNAME",
|
||||||
|
"WORKSPACE_MEMORY_DIRNAME",
|
||||||
|
"WORKSPACE_REVIEW_DIRNAME",
|
||||||
"IS_HOST_MODE",
|
"IS_HOST_MODE",
|
||||||
"USER_SPACE_DIR",
|
"USER_SPACE_DIR",
|
||||||
"WEB_DATA_DIR",
|
"WEB_DATA_DIR",
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from .paths import resolve_deploy_config
|
|||||||
def _load_forbidden_commands() -> list[str]:
|
def _load_forbidden_commands() -> list[str]:
|
||||||
"""从独立 JSON 文件加载命令拦截关键词。
|
"""从独立 JSON 文件加载命令拦截关键词。
|
||||||
|
|
||||||
属于部署级配置(部署者可调宽松/严苛),优先读 ~/.agents/<mode>/config,
|
属于部署级配置(部署者可调宽松/严苛),优先读 ~/.astrion/<mode>/config,
|
||||||
回退源码树种子。
|
回退源码树种子。
|
||||||
"""
|
"""
|
||||||
fallback = [
|
fallback = [
|
||||||
|
|||||||
@ -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"))
|
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_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")
|
SUB_AGENT_STATE_FILE = _resolve_repo_path(os.environ.get("SUB_AGENT_STATE_FILE", ""), f"{DATA_DIR}/sub_agents.json")
|
||||||
# 子智能体产出结果有意落在工作区内,跟随 DEFAULT_PROJECT_PATH,不迁入运行态根目录。
|
# 子智能体产出结果有意落在工作区内,跟随 DEFAULT_PROJECT_PATH,不迁入运行态根目录。
|
||||||
|
|||||||
@ -18,6 +18,7 @@ try:
|
|||||||
TERMINAL_SANDBOX_MEMORY,
|
TERMINAL_SANDBOX_MEMORY,
|
||||||
PROJECT_MAX_STORAGE_MB,
|
PROJECT_MAX_STORAGE_MB,
|
||||||
CUSTOM_TOOLS_ENABLED,
|
CUSTOM_TOOLS_ENABLED,
|
||||||
|
WORKSPACE_MEMORY_DIRNAME,
|
||||||
)
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import sys
|
import sys
|
||||||
@ -38,6 +39,7 @@ except ImportError:
|
|||||||
TERMINAL_SANDBOX_MEMORY,
|
TERMINAL_SANDBOX_MEMORY,
|
||||||
PROJECT_MAX_STORAGE_MB,
|
PROJECT_MAX_STORAGE_MB,
|
||||||
CUSTOM_TOOLS_ENABLED,
|
CUSTOM_TOOLS_ENABLED,
|
||||||
|
WORKSPACE_MEMORY_DIRNAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
from modules.file_manager import FileManager
|
from modules.file_manager import FileManager
|
||||||
@ -95,9 +97,9 @@ class MemoryMixin:
|
|||||||
"""MainTerminalContextMixin memory 能力 mixin。"""
|
"""MainTerminalContextMixin memory 能力 mixin。"""
|
||||||
|
|
||||||
def _scan_project_memories(self):
|
def _scan_project_memories(self):
|
||||||
"""扫描 .agents/memory/*.md 并解析 frontmatter 中的 name 和 description"""
|
"""扫描 .astrion/memory/*.md 并解析 frontmatter 中的 name 和 description"""
|
||||||
try:
|
try:
|
||||||
memory_dir = Path(self.project_path) / ".agents" / "memory"
|
memory_dir = Path(self.project_path) / WORKSPACE_MEMORY_DIRNAME
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
if not memory_dir.exists() or not memory_dir.is_dir():
|
if not memory_dir.exists() or not memory_dir.is_dir():
|
||||||
@ -145,9 +147,9 @@ class MemoryMixin:
|
|||||||
for m in project_memories:
|
for m in project_memories:
|
||||||
desc = m.get("description", "")
|
desc = m.get("description", "")
|
||||||
if desc:
|
if desc:
|
||||||
lines.append(f".agents/memory/{m['file']}:{desc}")
|
lines.append(f".astrion/memory/{m['file']}:{desc}")
|
||||||
else:
|
else:
|
||||||
lines.append(f".agents/memory/{m['file']}")
|
lines.append(f".astrion/memory/{m['file']}")
|
||||||
project_memory_list = "\n".join(lines)
|
project_memory_list = "\n".join(lines)
|
||||||
else:
|
else:
|
||||||
project_memory_list = ""
|
project_memory_list = ""
|
||||||
|
|||||||
@ -113,13 +113,13 @@ class ToolsDefinitionContextToolsMixin:
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "recall_project_memory",
|
"name": "recall_project_memory",
|
||||||
"description": "读取指定的项目记忆文件,返回完整内容(含 frontmatter)。项目记忆存储在 .agents/memory/ 目录下。",
|
"description": "读取指定的项目记忆文件,返回完整内容(含 frontmatter)。项目记忆存储在 .astrion/memory/ 目录下。",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": self._inject_intent({
|
"properties": self._inject_intent({
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "记忆名称,对应 .agents/memory/{name}.md 文件名"
|
"description": "记忆名称,对应 .astrion/memory/{name}.md 文件名"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
"required": ["name"]
|
"required": ["name"]
|
||||||
@ -137,7 +137,7 @@ class ToolsDefinitionContextToolsMixin:
|
|||||||
"properties": self._inject_intent({
|
"properties": self._inject_intent({
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "记忆名称,同时作为文件名 .agents/memory/{name}.md。建议英文+下划线,如 docker_compose"
|
"description": "记忆名称,同时作为文件名 .astrion/memory/{name}.md。建议英文+下划线,如 docker_compose"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -181,7 +181,7 @@ class ToolsDefinitionContextToolsMixin:
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "conversation_review",
|
"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": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": self._inject_intent({
|
"properties": self._inject_intent({
|
||||||
@ -189,7 +189,7 @@ class ToolsDefinitionContextToolsMixin:
|
|||||||
"mode": {
|
"mode": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["read", "save"],
|
"enum": ["read", "save"],
|
||||||
"description": "必要参数。read=直接返回回顾内容;save=保存为 .agents/review/ 下的 Markdown 文件并返回路径。"
|
"description": "必要参数。read=直接返回回顾内容;save=保存为 .astrion/review/ 下的 Markdown 文件并返回路径。"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
"required": ["conversation_id", "mode"]
|
"required": ["conversation_id", "mode"]
|
||||||
|
|||||||
@ -123,7 +123,7 @@ class ToolsDefinitionFileToolsMixin:
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "read_skill",
|
"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": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": self._inject_intent({
|
"properties": self._inject_intent({
|
||||||
|
|||||||
@ -26,6 +26,9 @@ try:
|
|||||||
READONLY_RUN_COMMAND_ALLOWED,
|
READONLY_RUN_COMMAND_ALLOWED,
|
||||||
READONLY_RUN_COMMAND_ALLOWED_GIT_SUBCOMMANDS,
|
READONLY_RUN_COMMAND_ALLOWED_GIT_SUBCOMMANDS,
|
||||||
READONLY_RUN_COMMAND_BLOCKED_TOKENS,
|
READONLY_RUN_COMMAND_BLOCKED_TOKENS,
|
||||||
|
WORKSPACE_SKILLS_DIRNAME,
|
||||||
|
WORKSPACE_MEMORY_DIRNAME,
|
||||||
|
WORKSPACE_REVIEW_DIRNAME,
|
||||||
)
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import sys
|
import sys
|
||||||
@ -49,6 +52,9 @@ except ImportError:
|
|||||||
READONLY_RUN_COMMAND_ALLOWED,
|
READONLY_RUN_COMMAND_ALLOWED,
|
||||||
READONLY_RUN_COMMAND_ALLOWED_GIT_SUBCOMMANDS,
|
READONLY_RUN_COMMAND_ALLOWED_GIT_SUBCOMMANDS,
|
||||||
READONLY_RUN_COMMAND_BLOCKED_TOKENS,
|
READONLY_RUN_COMMAND_BLOCKED_TOKENS,
|
||||||
|
WORKSPACE_SKILLS_DIRNAME,
|
||||||
|
WORKSPACE_MEMORY_DIRNAME,
|
||||||
|
WORKSPACE_REVIEW_DIRNAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
from modules.file_manager import FileManager
|
from modules.file_manager import FileManager
|
||||||
@ -536,13 +542,13 @@ class MainTerminalToolsExecutionMixin:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _handle_update_project_memory(self, name: str, description: str, content: str) -> Dict[str, Any]:
|
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()
|
safe_name = str(name).strip()
|
||||||
if not safe_name or "/" in safe_name or "\\" in safe_name:
|
if not safe_name or "/" in safe_name or "\\" in safe_name:
|
||||||
return {"success": False, "error": f"记忆名称不合法: {name}"}
|
return {"success": False, "error": f"记忆名称不合法: {name}"}
|
||||||
|
|
||||||
# 确保 .agents/memory/ 目录存在
|
# 确保 .astrion/memory/ 目录存在
|
||||||
memory_dir = Path(self.project_path) / ".agents" / "memory"
|
memory_dir = Path(self.project_path) / WORKSPACE_MEMORY_DIRNAME
|
||||||
try:
|
try:
|
||||||
memory_dir.mkdir(parents=True, exist_ok=True)
|
memory_dir.mkdir(parents=True, exist_ok=True)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@ -572,11 +578,11 @@ class MainTerminalToolsExecutionMixin:
|
|||||||
normalized = self._normalize_tool_path(result.get("path") or arguments.get("path"))
|
normalized = self._normalize_tool_path(result.get("path") or arguments.get("path"))
|
||||||
if not normalized:
|
if not normalized:
|
||||||
return
|
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)
|
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)
|
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)
|
self.context_manager._set_meta_flag(self._skill_meta_key("run-command-guide"), True)
|
||||||
|
|
||||||
def _get_visited_read_files(self) -> Set[str]:
|
def _get_visited_read_files(self) -> Set[str]:
|
||||||
@ -759,7 +765,7 @@ class MainTerminalToolsExecutionMixin:
|
|||||||
)
|
)
|
||||||
if already_read:
|
if already_read:
|
||||||
return None
|
return None
|
||||||
required_path = f".agents/skills/{skill_id}/SKILL.md"
|
required_path = f"{WORKSPACE_SKILLS_DIRNAME}/{skill_id}/SKILL.md"
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": f"调用 {tool_name} 前必须先阅读 {required_path}",
|
"error": f"调用 {tool_name} 前必须先阅读 {required_path}",
|
||||||
@ -1672,12 +1678,12 @@ class MainTerminalToolsExecutionMixin:
|
|||||||
def save_review_file() -> str:
|
def save_review_file() -> str:
|
||||||
safe_title = _sanitize_filename_component(title)
|
safe_title = _sanitize_filename_component(title)
|
||||||
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
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)
|
review_dir.mkdir(parents=True, exist_ok=True)
|
||||||
filename = f"review_{safe_title}_{timestamp}.md"
|
filename = f"review_{safe_title}_{timestamp}.md"
|
||||||
target = review_dir / filename
|
target = review_dir / filename
|
||||||
target.write_text(content, encoding="utf-8")
|
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:
|
if review_mode == "read" and char_count <= 50000:
|
||||||
result = {
|
result = {
|
||||||
|
|||||||
@ -19,6 +19,8 @@ try:
|
|||||||
TERMINAL_SANDBOX_MEMORY,
|
TERMINAL_SANDBOX_MEMORY,
|
||||||
PROJECT_MAX_STORAGE_MB,
|
PROJECT_MAX_STORAGE_MB,
|
||||||
CUSTOM_TOOLS_ENABLED,
|
CUSTOM_TOOLS_ENABLED,
|
||||||
|
WORKSPACE_SKILLS_DIRNAME,
|
||||||
|
WORKSPACE_MEMORY_DIRNAME,
|
||||||
)
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import sys
|
import sys
|
||||||
@ -39,6 +41,8 @@ except ImportError:
|
|||||||
TERMINAL_SANDBOX_MEMORY,
|
TERMINAL_SANDBOX_MEMORY,
|
||||||
PROJECT_MAX_STORAGE_MB,
|
PROJECT_MAX_STORAGE_MB,
|
||||||
CUSTOM_TOOLS_ENABLED,
|
CUSTOM_TOOLS_ENABLED,
|
||||||
|
WORKSPACE_SKILLS_DIRNAME,
|
||||||
|
WORKSPACE_MEMORY_DIRNAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
from modules.file_manager import FileManager
|
from modules.file_manager import FileManager
|
||||||
@ -92,8 +96,9 @@ class MainTerminalToolsReadMixin:
|
|||||||
if not raw:
|
if not raw:
|
||||||
return ""
|
return ""
|
||||||
normalized = raw.replace("\\", "/").strip("/")
|
normalized = raw.replace("\\", "/").strip("/")
|
||||||
if normalized.lower().startswith(".agents/skills/"):
|
prefix = f"{WORKSPACE_SKILLS_DIRNAME}/"
|
||||||
normalized = normalized[len(".agents/skills/"):]
|
if normalized.lower().startswith(prefix):
|
||||||
|
normalized = normalized[len(prefix):]
|
||||||
if normalized.lower().endswith("/skill.md"):
|
if normalized.lower().endswith("/skill.md"):
|
||||||
normalized = normalized[: -len("/SKILL.md")]
|
normalized = normalized[: -len("/SKILL.md")]
|
||||||
return normalized.strip()
|
return normalized.strip()
|
||||||
@ -147,7 +152,7 @@ class MainTerminalToolsReadMixin:
|
|||||||
|
|
||||||
skill_id = resolved["skill_id"]
|
skill_id = resolved["skill_id"]
|
||||||
read_args = {
|
read_args = {
|
||||||
"path": f".agents/skills/{skill_id}/SKILL.md",
|
"path": f"{WORKSPACE_SKILLS_DIRNAME}/{skill_id}/SKILL.md",
|
||||||
"type": "read",
|
"type": "read",
|
||||||
}
|
}
|
||||||
result = self._handle_read_tool(read_args)
|
result = self._handle_read_tool(read_args)
|
||||||
@ -159,11 +164,11 @@ class MainTerminalToolsReadMixin:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _handle_recall_project_memory(self, name: str) -> Dict:
|
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()
|
safe_name = str(name).strip()
|
||||||
if not safe_name or "/" in safe_name or "\\" in safe_name:
|
if not safe_name or "/" in safe_name or "\\" in safe_name:
|
||||||
return {"success": False, "error": f"记忆名称不合法: {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 = {
|
read_args = {
|
||||||
"path": file_path,
|
"path": file_path,
|
||||||
"type": "read",
|
"type": "read",
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
### 目录树
|
### 目录树
|
||||||
|
|
||||||
```
|
```
|
||||||
~/.agents/agents/ ← 数据根目录(data_root)
|
~/.astrion/astrion/ ← 数据根目录(data_root)
|
||||||
├── settings.json ← 唯一配置文件
|
├── settings.json ← 唯一配置文件
|
||||||
├── config/ ← 部署级 JSON,两个模式共享
|
├── config/ ← 部署级 JSON,两个模式共享
|
||||||
│ ├── custom_models.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` 里):
|
**普通配置**(启动后加载,在 `settings.json` 里):
|
||||||
|
|
||||||
@ -53,7 +53,7 @@
|
|||||||
### 启动流程
|
### 启动流程
|
||||||
|
|
||||||
```
|
```
|
||||||
1. 解析 CLI args / 环境变量 → 确定 data_root(默认 ~/.agents/agents)
|
1. 解析 CLI args / 环境变量 → 确定 data_root(默认 ~/.astrion/astrion)
|
||||||
2. 在 {data_root}/ 下创建目录结构(若不存在)
|
2. 在 {data_root}/ 下创建目录结构(若不存在)
|
||||||
3. 加载 {data_root}/settings.json
|
3. 加载 {data_root}/settings.json
|
||||||
4. 根据 settings.json 中的 sandbox_mode 确定数据子目录(host/ 或 web/)
|
4. 根据 settings.json 中的 sandbox_mode 确定数据子目录(host/ 或 web/)
|
||||||
@ -71,7 +71,7 @@
|
|||||||
```
|
```
|
||||||
OpenCode: ~/.config/opencode/config.json + ~/.local/share/opencode/
|
OpenCode: ~/.config/opencode/config.json + ~/.local/share/opencode/
|
||||||
我们: {data_root}/settings.json + {data_root}/{mode}/
|
我们: {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/ 下,分流名存实亡):
|
当前目录结构(问题:web 模式数据也落在 host/ 下,分流名存实亡):
|
||||||
~/.agents/
|
~/.astrion/
|
||||||
├── host/ ← 实际数据全在这里(因为 .env 写死 host)
|
├── host/ ← 实际数据全在这里(因为 .env 写死 host)
|
||||||
│ ├── .env ← ❌ 配置居然放在数据目录里
|
│ ├── .env ← ❌ 配置居然放在数据目录里
|
||||||
│ ├── config/
|
│ ├── config/
|
||||||
@ -177,8 +177,8 @@ data_dir = config_manager.get("DATA_DIR") # 运行时获取,支持热更新
|
|||||||
优先级(高→低):
|
优先级(高→低):
|
||||||
运行时 Set(config_manager.set())
|
运行时 Set(config_manager.set())
|
||||||
→ 环境变量覆盖
|
→ 环境变量覆盖
|
||||||
→ 项目级配置(<workspace>/.agents/config.json)
|
→ 项目级配置(<workspace>/.astrion/config.json)
|
||||||
→ 全局配置(~/.agents/agents/settings.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 > 环境变量 > 默认值)
|
→ 解析 data_root(元配置,CLI > 环境变量 > 默认值)
|
||||||
→ ConfigManager.initialize(data_root)
|
→ ConfigManager.initialize(data_root)
|
||||||
→ 加载 {data_root}/settings.json
|
→ 加载 {data_root}/settings.json
|
||||||
@ -313,7 +313,7 @@ def __getattr__(name):
|
|||||||
#### 1.4 配置文件格式
|
#### 1.4 配置文件格式
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
// ~/.agents/agents/settings.json(唯一配置,不分 host/web)
|
// ~/.astrion/astrion/settings.json(唯一配置,不分 host/web)
|
||||||
{
|
{
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"sandbox_mode": "host", // "host" | "web"
|
"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` 设置后,host/web 子目录在其下创建
|
||||||
- 模式子目录:`{data_root}/{sandbox_mode}/`,如 `~/.agents/agents/host/data/`
|
- 模式子目录:`{data_root}/{sandbox_mode}/`,如 `~/.astrion/astrion/host/data/`
|
||||||
|
|
||||||
#### 1.5 验证标准
|
#### 1.5 验证标准
|
||||||
|
|
||||||
@ -414,8 +414,8 @@ class ConfigManager:
|
|||||||
优先级(高→低):
|
优先级(高→低):
|
||||||
1. 运行时 Set(config_manager.set(),内存中)
|
1. 运行时 Set(config_manager.set(),内存中)
|
||||||
2. 环境变量覆盖(os.environ)
|
2. 环境变量覆盖(os.environ)
|
||||||
3. 项目级配置(<workspace>/.agents/config.json)
|
3. 项目级配置(<workspace>/.astrion/config.json)
|
||||||
4. 全局配置(~/.agents/<mode>/config/settings.json)
|
4. 全局配置(~/.astrion/astrion/<mode>/config/settings.json)
|
||||||
5. 默认值(硬编码)
|
5. 默认值(硬编码)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -442,7 +442,7 @@ class ConfigManager:
|
|||||||
```python
|
```python
|
||||||
# 当用户选择了工作区后,自动加载项目级配置
|
# 当用户选择了工作区后,自动加载项目级配置
|
||||||
config_manager.load_project_config(workspace_path)
|
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 | 我们的目标 |
|
| 概念 | OpenCode | 我们的目标 |
|
||||||
|------|---------|-----------|
|
|------|---------|-----------|
|
||||||
| 全局配置路径 | `~/.config/opencode/config.json` | `{data_root}/settings.json`(默认 `~/.agents/agents/settings.json`) |
|
| 全局配置路径 | `~/.config/opencode/config.json` | `{data_root}/settings.json`(默认 `~/.astrion/astrion/settings.json`) |
|
||||||
| 项目配置 | `<cwd>/opencode.json(c)` 向上查找 | `<workspace>/.agents/config.json` |
|
| 项目配置 | `<cwd>/opencode.json(c)` 向上查找 | `<workspace>/.astrion/config.json` |
|
||||||
| 配置 Service | `Config.Service` (Effect-TS) | `ConfigManager` (Python 单例) |
|
| 配置 Service | `Config.Service` (Effect-TS) | `ConfigManager` (Python 单例) |
|
||||||
| 变量引用 | `{env:VAR}` / `{file:path}` | 同 |
|
| 变量引用 | `{env:VAR}` / `{file:path}` | 同 |
|
||||||
| 运行时修改 | `Config.update()` / `Config.updateGlobal()` | `config_manager.set()` |
|
| 运行时修改 | `Config.update()` / `Config.updateGlobal()` | `config_manager.set()` |
|
||||||
|
|||||||
@ -46,7 +46,7 @@ class ApiUserWorkspace:
|
|||||||
project_path: Path
|
project_path: Path
|
||||||
data_dir: Path # 会话/备份等落盘到这里(每个工作区独立)
|
data_dir: Path # 会话/备份等落盘到这里(每个工作区独立)
|
||||||
logs_dir: Path
|
logs_dir: Path
|
||||||
uploads_dir: Path # project/.agents/user_upload
|
uploads_dir: Path # project/.astrion/user_upload
|
||||||
quarantine_dir: Path # 上传隔离区(按用户/工作区划分)
|
quarantine_dir: Path # 上传隔离区(按用户/工作区划分)
|
||||||
shared_dir: Path # 用户级共享目录(prompts/personalization)
|
shared_dir: Path # 用户级共享目录(prompts/personalization)
|
||||||
prompts_dir: Path # 实际使用的 prompts 目录(指向 shared_dir/prompt)
|
prompts_dir: Path # 实际使用的 prompts 目录(指向 shared_dir/prompt)
|
||||||
@ -103,7 +103,7 @@ class ApiUserManager:
|
|||||||
personalization/
|
personalization/
|
||||||
workspaces/<ws>/ # 单个工作区
|
workspaces/<ws>/ # 单个工作区
|
||||||
project/
|
project/
|
||||||
.agents/user_upload/
|
.astrion/user_upload/
|
||||||
data/
|
data/
|
||||||
conversations/
|
conversations/
|
||||||
backups/
|
backups/
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import httpx
|
|||||||
from config import resolve_deploy_config, LOGS_DIR
|
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"))
|
CONFIG_PATH = Path(resolve_deploy_config("auto_approval.json"))
|
||||||
DEFAULT_MAX_ROUNDS = 3
|
DEFAULT_MAX_ROUNDS = 3
|
||||||
DEFAULT_TIMEOUT_SECONDS = 60
|
DEFAULT_TIMEOUT_SECONDS = 60
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import httpx
|
|||||||
from config import PROMPTS_DIR, resolve_deploy_config, LOGS_DIR
|
from config import PROMPTS_DIR, resolve_deploy_config, LOGS_DIR
|
||||||
from modules.goal_state_manager import REVIEW_MODE_ACTIVE, REVIEW_MODE_READONLY
|
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"))
|
CONFIG_PATH = Path(resolve_deploy_config("goal_review.json"))
|
||||||
PROMPT_NAME = "goal_review_agent.txt"
|
PROMPT_NAME = "goal_review_agent.txt"
|
||||||
DEFAULT_MAX_ROUNDS = 3
|
DEFAULT_MAX_ROUNDS = 3
|
||||||
|
|||||||
@ -37,7 +37,7 @@ DEFAULT_MACOS_DENY_READ_REGEXES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
_LOCK = threading.Lock()
|
_LOCK = threading.Lock()
|
||||||
# 部署级配置(机器特定可读写路径,会被运行时写回)→ ~/.agents/<mode>/config
|
# 部署级配置(机器特定可读写路径,会被运行时写回)→ ~/.astrion/<mode>/config
|
||||||
_POLICY_PATH = Path(deploy_config_path("host_sandbox_policy.json"))
|
_POLICY_PATH = Path(deploy_config_path("host_sandbox_policy.json"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -317,9 +317,9 @@ def build_skills_list(
|
|||||||
continue
|
continue
|
||||||
description = (meta.get("description") or "").strip()
|
description = (meta.get("description") or "").strip()
|
||||||
if description:
|
if description:
|
||||||
lines.append(f".agents/skills/{skill_id}:{description}")
|
lines.append(f".astrion/skills/{skill_id}:{description}")
|
||||||
else:
|
else:
|
||||||
lines.append(f".agents/skills/{skill_id}")
|
lines.append(f".astrion/skills/{skill_id}")
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ class ConversationVersioningManager:
|
|||||||
"""Manage hidden git snapshots bound to a conversation."""
|
"""Manage hidden git snapshots bound to a conversation."""
|
||||||
TRACKING_MODE_WORKSPACE_AND_CONVERSATION = "workspace_and_conversation"
|
TRACKING_MODE_WORKSPACE_AND_CONVERSATION = "workspace_and_conversation"
|
||||||
TRACKING_MODE_CONVERSATION_ONLY = "conversation_only"
|
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 的基准点
|
# 空 Git tree 对象的固定哈希,用于 tree-to-tree diff 的基准点
|
||||||
EMPTY_TREE_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
EMPTY_TREE_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# 清理误迁到 ~/.agents/web 的运行态数据副本。
|
# 清理误迁到 ~/.agents/web 的历史运行态数据副本(已废弃路径)。
|
||||||
#
|
#
|
||||||
# 背景:首次迁移时脚本未加载 .env、误判为 web 模式,把数据复制到了
|
# 背景:首次迁移时脚本未加载 .env、误判为 web 模式,把数据复制到了
|
||||||
# ~/.agents/web/。源数据仍在源码树,web 只是程序读不到的错位副本。
|
# ~/.agents/web/。源数据仍在源码树,web 只是程序读不到的错位副本。
|
||||||
# 本机终端有完整权限,可安全删除。
|
# 本机终端有完整权限,可安全删除。
|
||||||
#
|
#
|
||||||
|
# 注意:当前项目运行态数据根目录已改为 ~/.astrion/astrion/,本脚本仅用于
|
||||||
|
# 清理旧路径 ~/.agents/web 的历史遗留,不应用于新路径。
|
||||||
|
#
|
||||||
# 用法:bash scripts/cleanup_misplaced_web.sh
|
# 用法:bash scripts/cleanup_misplaced_web.sh
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""运行态数据迁移脚本:源码树 -> 运行态根目录(~/.agents/<mode>)。
|
"""运行态数据迁移脚本:源码树 -> 运行态根目录(~/.astrion/astrion/<mode>)。
|
||||||
|
|
||||||
把历史遗留在源码树内的运行态数据迁移到新的运行态根目录。目标位置直接复用
|
把历史遗留在源码树内的运行态数据迁移到新的运行态根目录。目标位置直接复用
|
||||||
``config`` 模块的解析结果(``config.DATA_DIR`` / ``USER_SPACE_DIR`` / ... ),
|
``config`` 模块的解析结果(``config.DATA_DIR`` / ``USER_SPACE_DIR`` / ... ),
|
||||||
@ -16,7 +16,7 @@
|
|||||||
保守策略,全程复制而非移动,旧目录保留,便于回滚。
|
保守策略,全程复制而非移动,旧目录保留,便于回滚。
|
||||||
- 部署级配置文件(custom_models / host_workspaces / auto_approval / goal_review /
|
- 部署级配置文件(custom_models / host_workspaces / auto_approval / goal_review /
|
||||||
forbidden_commands / host_sandbox_policy)
|
forbidden_commands / host_sandbox_policy)
|
||||||
从源码树 ``config/`` 复制到部署配置目录 ``~/.agents/<mode>/config/``,
|
从源码树 ``config/`` 复制到部署配置目录 ``~/.astrion/astrion/<mode>/config/``,
|
||||||
同样先备份、不覆盖已存在目标。docker_risk_markers、skill_hints 属于
|
同样先备份、不覆盖已存在目标。docker_risk_markers、skill_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
|
# 仅迁移部署者自定义类(含密钥/机器特定/可调策略);docker_risk_markers、skill_hints
|
||||||
# 属于程序能力,保留源码树,不迁。
|
# 属于程序能力,保留源码树,不迁。
|
||||||
_DEPLOY_CONFIG_FILES = (
|
_DEPLOY_CONFIG_FILES = (
|
||||||
@ -152,7 +152,7 @@ def _copy_file(src: Path, dst: Path, *, dry_run: bool) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
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="仅打印将要执行的操作,不实际写入")
|
parser.add_argument("--dry-run", action="store_true", help="仅打印将要执行的操作,不实际写入")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|||||||
@ -51,17 +51,17 @@ ENV_EXAMPLE_PATH = REPO_ROOT / ".env.example"
|
|||||||
|
|
||||||
|
|
||||||
def _deploy_config_dir(mode: str, custom_data: str) -> Path:
|
def _deploy_config_dir(mode: str, custom_data: str) -> Path:
|
||||||
"""推导部署级配置目录(~/.agents/<mode>/config)。
|
"""推导部署级配置目录(~/.astrion/astrion/config)。
|
||||||
|
|
||||||
必须与 config/paths.py 的解析保持一致:
|
必须与 config/paths.py 的解析保持一致:
|
||||||
- 自定义数据根(向导里填的)优先,作为运行态根目录;
|
- 自定义数据根(向导里填的)优先,作为运行态根目录;
|
||||||
- 否则默认 ~/.agents/<mode>;
|
- 否则默认 ~/.astrion/astrion;
|
||||||
- 配置目录 = 运行态根 / config。
|
- 配置目录 = 运行态根 / config。
|
||||||
|
|
||||||
setup 刻意不 import config(避免在 .env 写好前过早锁定路径),
|
setup 刻意不 import config(避免在 .env 写好前过早锁定路径),
|
||||||
因此这里用向导现场收集到的 mode / custom_data 自行推导。
|
因此这里用向导现场收集到的 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()
|
return (root / "config").resolve()
|
||||||
|
|
||||||
|
|
||||||
@ -237,12 +237,12 @@ def run_wizard(force: bool = False) -> int:
|
|||||||
|
|
||||||
# --- 3. 数据目录(可选覆盖)---
|
# --- 3. 数据目录(可选覆盖)---
|
||||||
print("\n[3/5] 数据目录")
|
print("\n[3/5] 数据目录")
|
||||||
default_data = f"~/.agents/{mode}"
|
default_data = "~/.astrion/astrion"
|
||||||
print(f" 运行态数据(对话/用户/记忆/日志)默认存放在 {default_data}")
|
print(f" 运行态数据(对话/用户/记忆/日志)默认存放在 {default_data}")
|
||||||
custom_data = _ask("自定义数据根目录(留空用默认)", "")
|
custom_data = _ask("自定义数据根目录(留空用默认)", "")
|
||||||
if custom_data:
|
if custom_data:
|
||||||
# 模式根变量:host -> AGENTS_HOST_HOME,web -> AGENTS_WEB_HOME
|
# 数据根变量:ASTRION_DATA_ROOT
|
||||||
values["AGENTS_HOST_HOME" if mode == "host" else "AGENTS_WEB_HOME"] = custom_data
|
values["ASTRION_DATA_ROOT"] = custom_data
|
||||||
|
|
||||||
# --- 4. 管理员账户 ---
|
# --- 4. 管理员账户 ---
|
||||||
print("\n[4/5] 管理员账户")
|
print("\n[4/5] 管理员账户")
|
||||||
@ -251,9 +251,9 @@ def run_wizard(force: bool = False) -> int:
|
|||||||
values["AGENT_ADMIN_USERNAME"] = admin_user
|
values["AGENT_ADMIN_USERNAME"] = admin_user
|
||||||
values["AGENT_ADMIN_PASSWORD_HASH"] = generate_password_hash(admin_pw)
|
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("\n[5/5] 模型配置")
|
||||||
print(" 配置一个可用模型(写入 ~/.agents/<mode>/config/custom_models.json)。")
|
print(" 配置一个可用模型(写入 ~/.astrion/astrion/config/custom_models.json)。")
|
||||||
print(" 启动后还可在后台页面继续增删模型。")
|
print(" 启动后还可在后台页面继续增删模型。")
|
||||||
model_name = _ask_required("模型显示名(界面上看到的名字)", "默认模型")
|
model_name = _ask_required("模型显示名(界面上看到的名字)", "默认模型")
|
||||||
model_url = _ask_required("API 地址(Base URL)", "")
|
model_url = _ask_required("API 地址(Base URL)", "")
|
||||||
@ -283,7 +283,7 @@ def run_wizard(force: bool = False) -> int:
|
|||||||
ENV_PATH.chmod(0o600) # .env 含密钥,收紧权限(Windows 上静默忽略)
|
ENV_PATH.chmod(0o600) # .env 含密钥,收紧权限(Windows 上静默忽略)
|
||||||
except (OSError, NotImplementedError):
|
except (OSError, NotImplementedError):
|
||||||
pass
|
pass
|
||||||
# 模型配置写到部署级配置目录(~/.agents/<mode>/config),而非源码树
|
# 模型配置写到部署级配置目录(~/.astrion/astrion/config),而非源码树
|
||||||
custom_models_path = _deploy_config_dir(mode, custom_data) / "custom_models.json"
|
custom_models_path = _deploy_config_dir(mode, custom_data) / "custom_models.json"
|
||||||
models_backup = _write_custom_models(custom_models_path, model_entry)
|
models_backup = _write_custom_models(custom_models_path, model_entry)
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
CHUNK_FRONTEND_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "chunk_frontend.log"
|
||||||
STREAMING_DEBUG_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "streaming_debug.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"
|
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:
|
def is_logged_in() -> bool:
|
||||||
|
|||||||
@ -45,7 +45,7 @@ from server.extensions import socketio
|
|||||||
from server.monitor import get_cached_monitor_snapshot
|
from server.monitor import get_cached_monitor_snapshot
|
||||||
from server.files import sanitize_filename_preserve_unicode
|
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'])
|
@chat_bp.route('/api/user-questions/pending', methods=['GET'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
@with_terminal
|
@with_terminal
|
||||||
|
|||||||
@ -46,7 +46,7 @@ from server.extensions import socketio
|
|||||||
from server.monitor import get_cached_monitor_snapshot
|
from server.monitor import get_cached_monitor_snapshot
|
||||||
from server.files import sanitize_filename_preserve_unicode
|
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:
|
def _derive_filename_from_upload(uploaded_file) -> str:
|
||||||
|
|||||||
@ -45,7 +45,7 @@ from server.extensions import socketio
|
|||||||
from server.monitor import get_cached_monitor_snapshot
|
from server.monitor import get_cached_monitor_snapshot
|
||||||
from server.files import sanitize_filename_preserve_unicode
|
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'])
|
@chat_bp.route('/api/memory', methods=['GET'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
@with_terminal
|
@with_terminal
|
||||||
|
|||||||
@ -57,7 +57,7 @@ from server.files import sanitize_filename_preserve_unicode
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
UPLOAD_FOLDER_NAME = ".agents/user_upload"
|
UPLOAD_FOLDER_NAME = ".astrion/user_upload"
|
||||||
|
|
||||||
|
|
||||||
def _path_conflicts_with_deny_list(path: str) -> Optional[str]:
|
def _path_conflicts_with_deny_list(path: str) -> Optional[str]:
|
||||||
|
|||||||
@ -45,7 +45,7 @@ from server.extensions import socketio
|
|||||||
from server.monitor import get_cached_monitor_snapshot
|
from server.monitor import get_cached_monitor_snapshot
|
||||||
from server.files import sanitize_filename_preserve_unicode
|
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:
|
def _dispatch_runtime_mode_notice(terminal: WebTerminal, username: str, text: str, source: str = "notify") -> None:
|
||||||
notice = str(text or "").strip()
|
notice = str(text or "").strip()
|
||||||
if not notice:
|
if not notice:
|
||||||
|
|||||||
@ -45,7 +45,7 @@ from server.extensions import socketio
|
|||||||
from server.monitor import get_cached_monitor_snapshot
|
from server.monitor import get_cached_monitor_snapshot
|
||||||
from server.files import sanitize_filename_preserve_unicode
|
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')
|
@chat_bp.route('/api/terminals')
|
||||||
@api_login_required
|
@api_login_required
|
||||||
@with_terminal
|
@with_terminal
|
||||||
|
|||||||
@ -1936,7 +1936,7 @@ def review_conversation(conversation_id, terminal: WebTerminal, workspace: UserW
|
|||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"data": {
|
"data": {
|
||||||
"path": f".agents/review/{filename}",
|
"path": f".astrion/review/{filename}",
|
||||||
"char_count": char_count
|
"char_count": char_count
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -59,7 +59,7 @@ def _resolve_workspace_skill_path(workspace, raw_path: str) -> Path:
|
|||||||
target.relative_to(skills_dir)
|
target.relative_to(skills_dir)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
debug_log(f"[SkillsAPI] resolve skill path outside skills_dir: target={target!r} skills_dir={skills_dir!r}")
|
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():
|
if not target.is_file():
|
||||||
debug_log(f"[SkillsAPI] resolve skill file not found: {target!r}")
|
debug_log(f"[SkillsAPI] resolve skill file not found: {target!r}")
|
||||||
raise ValueError("skill 文件不存在")
|
raise ValueError("skill 文件不存在")
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { debugLog } from '../common';
|
import { debugLog } from '../common';
|
||||||
import { useModelStore } from '../../../stores/model';
|
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 = '') {
|
export function extractSkillRefsFromMessage(message = '') {
|
||||||
const refs = [];
|
const refs = [];
|
||||||
|
|||||||
@ -913,7 +913,7 @@ const escapeUserHtml = (value: string): string =>
|
|||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
|
|
||||||
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 USER_FILE_LINK_RE = /\[([^\]\n]+)\]\(file:\/\/([^)\n]+)\)/g;
|
||||||
const SUB_AGENT_DONE_LABEL_RE = /^子智能体\d+\s*任务完成$/;
|
const SUB_AGENT_DONE_LABEL_RE = /^子智能体\d+\s*任务完成$/;
|
||||||
const SUB_AGENT_DONE_PREFIX_RE = /^(?:✅\s*)?子智能体\s*#?\s*(\d+)\s*任务摘要[::]/;
|
const SUB_AGENT_DONE_PREFIX_RE = /^(?:✅\s*)?子智能体\s*#?\s*(\d+)\s*任务摘要[::]/;
|
||||||
|
|||||||
@ -1460,7 +1460,7 @@
|
|||||||
<div class="settings-group-title">
|
<div class="settings-group-title">
|
||||||
<span class="settings-row-title">可用 Skills</span
|
<span class="settings-row-title">可用 Skills</span
|
||||||
><span class="settings-row-desc"
|
><span class="settings-row-desc"
|
||||||
>勾选后会注入 system prompt,并同步到工作区的 skills/ 目录</span
|
>勾选后会注入 system prompt,并同步到工作区的 .astrion/skills/ 目录</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-check-grid">
|
<div class="settings-check-grid">
|
||||||
|
|||||||
@ -35,7 +35,7 @@ print(json.dumps({
|
|||||||
env["PYTHONPATH"] = str(self.repo_root)
|
env["PYTHONPATH"] = str(self.repo_root)
|
||||||
# 清掉可能从外部 .env / 环境继承的覆盖项,保证用例可控。
|
# 清掉可能从外部 .env / 环境继承的覆盖项,保证用例可控。
|
||||||
for key in (
|
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",
|
"DATA_DIR", "LOGS_DIR", "USER_SPACE_DIR", "API_USER_SPACE_DIR",
|
||||||
"DEFAULT_PROJECT_PATH", "HOST_WORKSPACES_FILE", "PROMPTS_DIR",
|
"DEFAULT_PROJECT_PATH", "HOST_WORKSPACES_FILE", "PROMPTS_DIR",
|
||||||
"DEPLOY_CONFIG_DIR",
|
"DEPLOY_CONFIG_DIR",
|
||||||
@ -57,12 +57,13 @@ print(json.dumps({
|
|||||||
return json.loads(lines[-1])
|
return json.loads(lines[-1])
|
||||||
|
|
||||||
def test_runtime_dirs_default_to_web_home(self):
|
def test_runtime_dirs_default_to_web_home(self):
|
||||||
"""默认(非 host)模式下,运行态目录锚定到 ~/.agents/web。"""
|
"""默认(非 host)模式下,运行态根为 ~/.astrion/astrion,模式子目录为 web。"""
|
||||||
with tempfile.TemporaryDirectory() as td:
|
with tempfile.TemporaryDirectory() as td:
|
||||||
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "docker"})
|
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "docker"})
|
||||||
|
|
||||||
web_home = Path("~/.agents/web").expanduser().resolve()
|
runtime_root = Path("~/.astrion/astrion").expanduser().resolve()
|
||||||
self.assertEqual(data["RUNTIME_ROOT"], str(web_home))
|
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["DATA_DIR"], str(web_home / "data"))
|
||||||
self.assertEqual(data["LOGS_DIR"], str(web_home / "logs"))
|
self.assertEqual(data["LOGS_DIR"], str(web_home / "logs"))
|
||||||
self.assertEqual(data["USER_SPACE_DIR"], str(web_home / "users"))
|
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"))
|
self.assertEqual(data["SUB_AGENT_TASKS_BASE_DIR"], str(web_home / "data" / "sub_agent_tasks"))
|
||||||
|
|
||||||
def test_runtime_dirs_switch_to_host_home(self):
|
def test_runtime_dirs_switch_to_host_home(self):
|
||||||
"""host 模式下,运行态目录锚定到 ~/.agents/host。"""
|
"""host 模式下,运行态根为 ~/.astrion/astrion,模式子目录为 host。"""
|
||||||
with tempfile.TemporaryDirectory() as td:
|
with tempfile.TemporaryDirectory() as td:
|
||||||
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "host"})
|
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "host"})
|
||||||
|
|
||||||
host_home = Path("~/.agents/host").expanduser().resolve()
|
runtime_root = Path("~/.astrion/astrion").expanduser().resolve()
|
||||||
self.assertEqual(data["RUNTIME_ROOT"], str(host_home))
|
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["DATA_DIR"], str(host_home / "data"))
|
||||||
self.assertEqual(data["LOGS_DIR"], str(host_home / "logs"))
|
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):
|
def test_deploy_config_anchors_to_runtime_root(self):
|
||||||
"""部署级配置目录与 host_workspaces.json 锚定到运行态根 ~/.agents/<mode>/config。"""
|
"""部署级配置目录与 host_workspaces.json 锚定到运行态根 ~/.astrion/astrion/config(host/web 共享)。"""
|
||||||
with tempfile.TemporaryDirectory() as td:
|
with tempfile.TemporaryDirectory() as td:
|
||||||
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "web"})
|
data = self._load_paths(cwd=Path(td), extra_env={"TERMINAL_SANDBOX_MODE": "web"})
|
||||||
|
|
||||||
web_config = Path("~/.agents/web/config").expanduser().resolve()
|
runtime_config = Path("~/.astrion/astrion/config").expanduser().resolve()
|
||||||
self.assertEqual(data["DEPLOY_CONFIG_DIR"], str(web_config))
|
self.assertEqual(data["DEPLOY_CONFIG_DIR"], str(runtime_config))
|
||||||
self.assertEqual(
|
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:
|
with tempfile.TemporaryDirectory() as td:
|
||||||
custom = Path(td) / "web_root"
|
custom = Path(td) / "web_root"
|
||||||
data = self._load_paths(
|
data = self._load_paths(
|
||||||
cwd=Path(td),
|
cwd=Path(td),
|
||||||
extra_env={
|
extra_env={
|
||||||
"TERMINAL_SANDBOX_MODE": "web",
|
"TERMINAL_SANDBOX_MODE": "web",
|
||||||
"AGENTS_WEB_HOME": str(custom),
|
"ASTRION_DATA_ROOT": str(custom),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertEqual(data["DEPLOY_CONFIG_DIR"], str((custom / "config").resolve()))
|
self.assertEqual(data["DEPLOY_CONFIG_DIR"], str((custom / "config").resolve()))
|
||||||
|
|
||||||
def test_mode_home_env_overrides_root(self):
|
def test_data_root_env_overrides_root(self):
|
||||||
"""模式根变量(AGENTS_HOST_HOME / AGENTS_WEB_HOME)整体搬迁运行态目录。"""
|
"""数据根变量(ASTRION_DATA_ROOT)整体搬迁运行态目录。"""
|
||||||
with tempfile.TemporaryDirectory() as td:
|
with tempfile.TemporaryDirectory() as td:
|
||||||
custom = Path(td) / "host_root"
|
custom = Path(td) / "host_root"
|
||||||
data = self._load_paths(
|
data = self._load_paths(
|
||||||
cwd=Path(td),
|
cwd=Path(td),
|
||||||
extra_env={
|
extra_env={
|
||||||
"TERMINAL_SANDBOX_MODE": "host",
|
"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["RUNTIME_ROOT"], str(custom.resolve()))
|
||||||
self.assertEqual(data["DATA_DIR"], str((custom / "data").resolve()))
|
self.assertEqual(data["DATA_DIR"], str((custom / "host" / "data").resolve()))
|
||||||
self.assertEqual(data["LOGS_DIR"], str((custom / "logs").resolve()))
|
self.assertEqual(data["LOGS_DIR"], str((custom / "host" / "logs").resolve()))
|
||||||
|
|
||||||
def test_specific_dir_env_has_highest_priority(self):
|
def test_specific_dir_env_has_highest_priority(self):
|
||||||
"""具体目录变量优先级高于模式根变量。"""
|
"""具体目录变量优先级高于数据根变量。"""
|
||||||
with tempfile.TemporaryDirectory() as td:
|
with tempfile.TemporaryDirectory() as td:
|
||||||
root = Path(td) / "root"
|
root = Path(td) / "root"
|
||||||
logs = Path(td) / "separate_logs"
|
logs = Path(td) / "separate_logs"
|
||||||
@ -143,13 +145,13 @@ print(json.dumps({
|
|||||||
cwd=Path(td),
|
cwd=Path(td),
|
||||||
extra_env={
|
extra_env={
|
||||||
"TERMINAL_SANDBOX_MODE": "web",
|
"TERMINAL_SANDBOX_MODE": "web",
|
||||||
"AGENTS_WEB_HOME": str(root),
|
"ASTRION_DATA_ROOT": str(root),
|
||||||
"LOGS_DIR": str(logs),
|
"LOGS_DIR": str(logs),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# DATA 跟随根变量,LOGS 被单独覆盖
|
# DATA 跟随根变量下的模式子目录,LOGS 被单独覆盖
|
||||||
self.assertEqual(data["DATA_DIR"], str((root / "data").resolve()))
|
self.assertEqual(data["DATA_DIR"], str((root / "web" / "data").resolve()))
|
||||||
self.assertEqual(data["LOGS_DIR"], str(logs.resolve()))
|
self.assertEqual(data["LOGS_DIR"], str(logs.resolve()))
|
||||||
|
|
||||||
def test_relative_specific_dir_env_anchors_to_repo_root(self):
|
def test_relative_specific_dir_env_anchors_to_repo_root(self):
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 本模块测试按 web 模式布局推断 skills 目录,需在导入 config 前设定模式。
|
||||||
|
os.environ["TERMINAL_SANDBOX_MODE"] = "web"
|
||||||
|
|
||||||
from modules.skills_manager import (
|
from modules.skills_manager import (
|
||||||
archive_skill_directory,
|
archive_skill_directory,
|
||||||
get_skills_catalog,
|
get_skills_catalog,
|
||||||
@ -90,7 +94,7 @@ class SkillsManagerTest(unittest.TestCase):
|
|||||||
private_dir=private_root,
|
private_dir=private_root,
|
||||||
)
|
)
|
||||||
self.assertTrue(synced.get("success"))
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -8,19 +8,19 @@ class ToolResultFormatterTest(unittest.TestCase):
|
|||||||
result = {
|
result = {
|
||||||
"success": True,
|
"success": True,
|
||||||
"type": "read",
|
"type": "read",
|
||||||
"path": ".agents/skills/frontend-design/SKILL.md",
|
"path": ".astrion/skills/frontend-design/SKILL.md",
|
||||||
"max_chars": 50000,
|
"max_chars": 50000,
|
||||||
"truncated": False,
|
"truncated": False,
|
||||||
"content": "# Skill\nFull content",
|
"content": "# Skill\nFull content",
|
||||||
"line_start": 1,
|
"line_start": 1,
|
||||||
"line_end": 2,
|
"line_end": 2,
|
||||||
"total_lines": 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)
|
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.assertIn("```\n# Skill\nFull content\n```", formatted)
|
||||||
self.assertNotEqual(result["message"], formatted)
|
self.assertNotEqual(result["message"], formatted)
|
||||||
|
|
||||||
|
|||||||
@ -77,7 +77,7 @@ class DeepSeekClientBaseMixin:
|
|||||||
self.last_call_used_thinking = False # 最近一次调用是否使用思考模型
|
self.last_call_used_thinking = False # 最近一次调用是否使用思考模型
|
||||||
# 最近一次API错误详情
|
# 最近一次API错误详情
|
||||||
self.last_error_info: Optional[Dict[str, Any]] = None
|
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.request_dump_dir = Path(LOGS_DIR) / "api_requests"
|
||||||
self.debug_log_path = Path(LOGS_DIR) / "api_debug.log"
|
self.debug_log_path = Path(LOGS_DIR) / "api_debug.log"
|
||||||
|
|
||||||
|
|||||||
@ -81,7 +81,7 @@ class MessageMixin:
|
|||||||
"""ContextManager message mixin 能力 mixin。"""
|
"""ContextManager message mixin 能力 mixin。"""
|
||||||
|
|
||||||
def _build_recent_uploads_block(self, max_items: int = MAX_RECENT_UPLOADS) -> str:
|
def _build_recent_uploads_block(self, max_items: int = MAX_RECENT_UPLOADS) -> str:
|
||||||
"""扫描工作区 .agents/user_upload 目录,按修改时间倒序生成最近上传文件列表。"""
|
"""扫描工作区 .astrion/user_upload 目录,按修改时间倒序生成最近上传文件列表。"""
|
||||||
try:
|
try:
|
||||||
project_path = Path(getattr(self, "project_path", ".") or ".").expanduser().resolve()
|
project_path = Path(getattr(self, "project_path", ".") or ".").expanduser().resolve()
|
||||||
uploads_dir = project_path / ".agents" / "user_upload"
|
uploads_dir = project_path / ".agents" / "user_upload"
|
||||||
|
|||||||
@ -222,7 +222,7 @@ class ProjectMixin:
|
|||||||
for internal_name in ("skills", "user_upload", "compact_result", "review"):
|
for internal_name in ("skills", "user_upload", "compact_result", "review"):
|
||||||
internal_dir = agents_dir / internal_name
|
internal_dir = agents_dir / internal_name
|
||||||
if internal_dir.exists() and internal_dir.is_dir():
|
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
|
return structure
|
||||||
|
|
||||||
def get_project_structure(self) -> Dict:
|
def get_project_structure(self) -> Dict:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user