diff --git a/.gitignore b/.gitignore index c9ce41b..6c8f94e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,10 @@ __pycache__/ venv/ # Runtime data (main agent) -# 注意:运行态数据默认已迁出源码树到 ~/.agents//。 +# 注意:运行态数据默认已迁出源码树到 ~/.astrion/astrion//。 # 以下条目用于忽略「通过具体目录变量指回源码树」或历史遗留的运行态目录。 +.astrion/ +.agents/ logs/ data/ project/ diff --git a/AGENTS.md b/AGENTS.md index 06ab334..d7ccfab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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/` +2. **数据根目录环境变量**:`ASTRION_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.astrion/astrion`) +3. **兜底默认**:`~/.astrion/astrion/` 具体目录变量支持相对路径(相对仓库根目录展开)、绝对路径与 `~`。 > 注意:`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//`(`data/`、`users/`、`logs/`、`api/`),不在源码树内;详见 §1.5。`.gitignore` 仍忽略源码树内的 `logs/`、`data/`、`users/`、`api/`、`project/` 等,以防通过具体变量指回源码树或历史遗留产生污染。分享前需脱敏。 +- 运行态数据默认在 `~/.astrion/astrion//`(`data/`、`users/`、`logs/`、`api/`),不在源码树内;详见 §1.5。`.gitignore` 仍忽略源码树内的 `logs/`、`data/`、`users/`、`api/`、`project/` 等,以防通过具体变量指回源码树或历史遗留产生污染。分享前需脱敏。 - **`_experiments/` 用途**:归档本地实验残留与历史文档(调试记录、旧变更日志、模型测试脚本、翻译资料、旧子智能体文档等)。该目录**不纳入 git**(已在 `.gitignore`)。需要保留但不属于当前主线、又不想直接删的零散文件,统一放这里,不要散落在根目录。 - 不要把本地构建产物(如 `static/dist/`、`node_modules/`)纳入提交。 diff --git a/CLAUDE.md b/CLAUDE.md index 00cfb3f..26da6b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -137,12 +137,12 @@ npm --prefix cli run build # 查看容器状态 docker ps -a | grep agent-term -# 查看调试日志(运行态日志默认在 ~/.agents/agents//logs/) -tail -f ~/.agents/agents/host/logs/debug_stream.log # host 模式 -tail -f ~/.agents/agents/web/logs/debug_stream.log # web/docker 模式 +# 查看调试日志(运行态日志默认在 ~/.astrion/astrion//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/`(对齐 `~/.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/`(对齐 `~/.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/` +2. 数据根目录变量 `ASTRION_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.astrion/astrion`) +3. 兜底 `~/.astrion/astrion/` **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/`,复制+备份+可回滚+幂等,`logs/` 丢弃不迁。脚本复用 config 路径解析(模式由 `.env` 决定),执行前先 `--dry-run` 确认目标。 +- `scripts/migrate_runtime_data.py`:源码树 → `~/.astrion/astrion/`,复制+备份+可回滚+幂等,`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//`。详见 Configuration 节「数据目录与路径变量」。 +> 历史说明:`data/`、`users/`、`logs/`、`project/` 等过去曾在源码树内,现已默认迁至 `~/.astrion/astrion//`。详见 Configuration 节「数据目录与路径变量」。 ## Development Tips diff --git a/README.md b/README.md index 3b77ba6..665902f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - 新 CLI 端已开始重写,位于 `cli/`,技术栈为 React 19 + Ink 6 + TypeScript。 - 当前 CLI 不是独立后端;它会直接连接本地 `8091` Web API,并复用现有会话、任务、权限、工作区等接口。 - CLI 已实现“启动即连接后端并新建会话”的流程,但仍处于持续打磨阶段,输入法、光标、时间线裁剪等终端细节仍在迭代。 -- 运行态数据(对话、用户库、日志等)默认存放在用户主目录的 `~/.agents/agents//` 下,不再落在源码树内;源码树内的 `data/`、`logs/`、`users/` 等仅作为 `.gitignore` 防污染保留。 +- 运行态数据(对话、用户库、日志等)默认存放在用户主目录的 `~/.astrion/astrion//` 下,不再落在源码树内;源码树内的 `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`: 部署级配置目录(默认 `/config/`,host/web 共享) +- `ASTRION_DATA_ROOT`: 运行态数据根目录(默认 `~/.astrion/astrion`,host/web 两个模式共享该根) +- `DEPLOY_CONFIG_DIR`: 部署级配置目录(默认 `/config/`,host/web 共享) ### 限制配置 - `MAX_ITERATIONS_PER_TASK`: 单任务最大迭代次数 @@ -534,15 +534,15 @@ python -m server.app --path ./project --port 8091 --debug --thinking-mode ## 数据存储 -运行态数据默认存放在用户主目录的 `~/.agents/agents//` 下(按 `TERMINAL_SANDBOX_MODE` 自动分流): +运行态数据默认存放在用户主目录的 `~/.astrion/astrion//` 下(按 `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/` +2. **数据根目录环境变量**:`ASTRION_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.astrion/astrion`) +3. **兜底默认**:`~/.astrion/astrion/` ### 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//logs/`: +运行态日志默认位于 `~/.astrion/astrion//logs/`: -- 主日志: `~/.agents/agents//logs/main.log` -- 错误日志: `~/.agents/agents//logs/errors/` -- 任务日志: `~/.agents/agents//logs/tasks/` -- Web 服务器: `~/.agents/agents//logs/web_server.log` -- 自动审批智能体调试记录: `~/.agents/agents//logs/approval_agent/` +- 主日志: `~/.astrion/astrion//logs/main.log` +- 错误日志: `~/.astrion/astrion//logs/errors/` +- 任务日志: `~/.astrion/astrion//logs/tasks/` +- Web 服务器: `~/.astrion/astrion//logs/web_server.log` +- 自动审批智能体调试记录: `~/.astrion/astrion//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//`,支持 host/web 双路径读取 +- 运行态数据默认迁移到 `~/.astrion/astrion//`,支持 host/web 双路径读取 - 子智能体改为主进程内 `asyncio.Task` 实现,工具调用复用主进程链路 - 新增宿主机权限模式(readonly/approval/auto_approval/unrestricted)与执行环境(sandbox/direct)双层控制 - 新增日志轮转、API dump 可控、部署级配置外置等运维能力 diff --git a/config/__init__.py b/config/__init__.py index ee0e7fd..2cdd9e6 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -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: diff --git a/config/paths.py b/config/paths.py index b7b8ab9..a3d45e7 100644 --- a/config/paths.py +++ b/config/paths.py @@ -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/ ← 部署级 JSON,host/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", diff --git a/config/security.py b/config/security.py index 0705fab..b3b2ee2 100644 --- a/config/security.py +++ b/config/security.py @@ -9,7 +9,7 @@ from .paths import resolve_deploy_config def _load_forbidden_commands() -> list[str]: """从独立 JSON 文件加载命令拦截关键词。 - 属于部署级配置(部署者可调宽松/严苛),优先读 ~/.agents//config, + 属于部署级配置(部署者可调宽松/严苛),优先读 ~/.astrion//config, 回退源码树种子。 """ fallback = [ diff --git a/config/sub_agent.py b/config/sub_agent.py index c52893c..9ae3e6c 100644 --- a/config/sub_agent.py +++ b/config/sub_agent.py @@ -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//data)。 +# 子智能体任务目录与状态文件属于运行态数据,跟随 DATA_DIR(默认 ~/.astrion//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,不迁入运行态根目录。 diff --git a/core/main_terminal_parts/context/memory.py b/core/main_terminal_parts/context/memory.py index bfb6563..3c8a396 100644 --- a/core/main_terminal_parts/context/memory.py +++ b/core/main_terminal_parts/context/memory.py @@ -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 = "" diff --git a/core/main_terminal_parts/tools_definition/context_tools.py b/core/main_terminal_parts/tools_definition/context_tools.py index bd2fc2a..db6327d 100644 --- a/core/main_terminal_parts/tools_definition/context_tools.py +++ b/core/main_terminal_parts/tools_definition/context_tools.py @@ -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"] diff --git a/core/main_terminal_parts/tools_definition/file_tools.py b/core/main_terminal_parts/tools_definition/file_tools.py index 72abf57..8d05a4b 100644 --- a/core/main_terminal_parts/tools_definition/file_tools.py +++ b/core/main_terminal_parts/tools_definition/file_tools.py @@ -123,7 +123,7 @@ class ToolsDefinitionFileToolsMixin: "type": "function", "function": { "name": "read_skill", - "description": "按 skill 名称读取 .agents/skills//SKILL.md 内容;内部等价于 read_file 的 read 模式,并返回解析后的 path。", + "description": "按 skill 名称读取 .astrion/skills//SKILL.md 内容;内部等价于 read_file 的 read 模式,并返回解析后的 path。", "parameters": { "type": "object", "properties": self._inject_intent({ diff --git a/core/main_terminal_parts/tools_execution.py b/core/main_terminal_parts/tools_execution.py index ebd82cb..5826ac7 100644 --- a/core/main_terminal_parts/tools_execution.py +++ b/core/main_terminal_parts/tools_execution.py @@ -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 = { diff --git a/core/main_terminal_parts/tools_read.py b/core/main_terminal_parts/tools_read.py index 60e5e8e..6b409b1 100644 --- a/core/main_terminal_parts/tools_read.py +++ b/core/main_terminal_parts/tools_read.py @@ -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", diff --git a/docs/config_refactor_plan.md b/docs/config_refactor_plan.md index ee05afc..be6acc5 100644 --- a/docs/config_refactor_plan.md +++ b/docs/config_refactor_plan.md @@ -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") # 运行时获取,支持热更新 优先级(高→低): 运行时 Set(config_manager.set()) → 环境变量覆盖 -→ 项目级配置(/.agents/config.json) -→ 全局配置(~/.agents/agents/settings.json) +→ 项目级配置(/.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. 运行时 Set(config_manager.set(),内存中) 2. 环境变量覆盖(os.environ) - 3. 项目级配置(/.agents/config.json) - 4. 全局配置(~/.agents//config/settings.json) + 3. 项目级配置(/.astrion/config.json) + 4. 全局配置(~/.astrion/astrion//config/settings.json) 5. 默认值(硬编码) ``` @@ -442,7 +442,7 @@ class ConfigManager: ```python # 当用户选择了工作区后,自动加载项目级配置 config_manager.load_project_config(workspace_path) -# → 读取 /.agents/config.json +# → 读取 /.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`) | -| 项目配置 | `/opencode.json(c)` 向上查找 | `/.agents/config.json` | +| 全局配置路径 | `~/.config/opencode/config.json` | `{data_root}/settings.json`(默认 `~/.astrion/astrion/settings.json`) | +| 项目配置 | `/opencode.json(c)` 向上查找 | `/.astrion/config.json` | | 配置 Service | `Config.Service` (Effect-TS) | `ConfigManager` (Python 单例) | | 变量引用 | `{env:VAR}` / `{file:path}` | 同 | | 运行时修改 | `Config.update()` / `Config.updateGlobal()` | `config_manager.set()` | diff --git a/modules/api_user_manager.py b/modules/api_user_manager.py index 60306bb..c9fe6d5 100644 --- a/modules/api_user_manager.py +++ b/modules/api_user_manager.py @@ -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// # 单个工作区 project/ - .agents/user_upload/ + .astrion/user_upload/ data/ conversations/ backups/ diff --git a/modules/approval_agent.py b/modules/approval_agent.py index 7d5e269..fbeef81 100644 --- a/modules/approval_agent.py +++ b/modules/approval_agent.py @@ -12,7 +12,7 @@ import httpx from config import resolve_deploy_config, LOGS_DIR -# 部署级配置(审批智能体 url/key/model,含密钥引用)→ 优先 ~/.agents//config +# 部署级配置(审批智能体 url/key/model,含密钥引用)→ 优先 ~/.astrion//config CONFIG_PATH = Path(resolve_deploy_config("auto_approval.json")) DEFAULT_MAX_ROUNDS = 3 DEFAULT_TIMEOUT_SECONDS = 60 diff --git a/modules/goal_review_agent.py b/modules/goal_review_agent.py index 604e1b6..3c1fa58 100644 --- a/modules/goal_review_agent.py +++ b/modules/goal_review_agent.py @@ -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//config +# 部署级配置(目标审核智能体 url/key/model,含密钥引用)→ 优先 ~/.astrion//config CONFIG_PATH = Path(resolve_deploy_config("goal_review.json")) PROMPT_NAME = "goal_review_agent.txt" DEFAULT_MAX_ROUNDS = 3 diff --git a/modules/host_sandbox_policy.py b/modules/host_sandbox_policy.py index 029a4d0..224570a 100644 --- a/modules/host_sandbox_policy.py +++ b/modules/host_sandbox_policy.py @@ -37,7 +37,7 @@ DEFAULT_MACOS_DENY_READ_REGEXES = [ ] _LOCK = threading.Lock() -# 部署级配置(机器特定可读写路径,会被运行时写回)→ ~/.agents//config +# 部署级配置(机器特定可读写路径,会被运行时写回)→ ~/.astrion//config _POLICY_PATH = Path(deploy_config_path("host_sandbox_policy.json")) diff --git a/modules/skills_manager.py b/modules/skills_manager.py index 1fd85e3..af4b453 100644 --- a/modules/skills_manager.py +++ b/modules/skills_manager.py @@ -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 diff --git a/modules/versioning_manager.py b/modules/versioning_manager.py index ff3b43e..f0e5b4d 100644 --- a/modules/versioning_manager.py +++ b/modules/versioning_manager.py @@ -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" diff --git a/scripts/cleanup_misplaced_web.sh b/scripts/cleanup_misplaced_web.sh index cc969ca..1b32d9c 100644 --- a/scripts/cleanup_misplaced_web.sh +++ b/scripts/cleanup_misplaced_web.sh @@ -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 diff --git a/scripts/migrate_runtime_data.py b/scripts/migrate_runtime_data.py index 2510549..56bb99d 100644 --- a/scripts/migrate_runtime_data.py +++ b/scripts/migrate_runtime_data.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""运行态数据迁移脚本:源码树 -> 运行态根目录(~/.agents/)。 +"""运行态数据迁移脚本:源码树 -> 运行态根目录(~/.astrion/astrion/)。 把历史遗留在源码树内的运行态数据迁移到新的运行态根目录。目标位置直接复用 ``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//config/``, + 从源码树 ``config/`` 复制到部署配置目录 ``~/.astrion/astrion//config/``, 同样先备份、不覆盖已存在目标。docker_risk_markers、skill_hints 属于 程序能力,保留源码树,不迁。 @@ -57,7 +57,7 @@ def _migrate_targets() -> dict[str, Path]: } -# 部署级配置文件:源码树 config/ -> 部署配置目录 ~/.agents//config/。 +# 部署级配置文件:源码树 config/ -> 部署配置目录 ~/.astrion/astrion//config/。 # 仅迁移部署者自定义类(含密钥/机器特定/可调策略);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/)") + parser = argparse.ArgumentParser(description="迁移运行态数据到运行态根目录(~/.astrion/astrion/)") parser.add_argument("--dry-run", action="store_true", help="仅打印将要执行的操作,不实际写入") args = parser.parse_args() diff --git a/scripts/setup.py b/scripts/setup.py index e7c816f..f5075d6 100644 --- a/scripts/setup.py +++ b/scripts/setup.py @@ -51,17 +51,17 @@ ENV_EXAMPLE_PATH = REPO_ROOT / ".env.example" def _deploy_config_dir(mode: str, custom_data: str) -> Path: - """推导部署级配置目录(~/.agents//config)。 + """推导部署级配置目录(~/.astrion/astrion/config)。 必须与 config/paths.py 的解析保持一致: - 自定义数据根(向导里填的)优先,作为运行态根目录; - - 否则默认 ~/.agents/; + - 否则默认 ~/.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_HOME,web -> 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//config/custom_models.json)--- + # --- 5. 模型配置(写入部署级配置目录 ~/.astrion/astrion/config/custom_models.json)--- print("\n[5/5] 模型配置") - print(" 配置一个可用模型(写入 ~/.agents//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//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) diff --git a/server/app_legacy.py b/server/app_legacy.py index a971c9e..a97ac4e 100644 --- a/server/app_legacy.py +++ b/server/app_legacy.py @@ -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: diff --git a/server/chat/approval.py b/server/chat/approval.py index 26ccd60..75f48c0 100644 --- a/server/chat/approval.py +++ b/server/chat/approval.py @@ -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 diff --git a/server/chat/files.py b/server/chat/files.py index d7faa7c..06e1a24 100644 --- a/server/chat/files.py +++ b/server/chat/files.py @@ -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: diff --git a/server/chat/misc.py b/server/chat/misc.py index c909759..9e2c65b 100644 --- a/server/chat/misc.py +++ b/server/chat/misc.py @@ -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 diff --git a/server/chat/permission.py b/server/chat/permission.py index e446c0a..4119833 100644 --- a/server/chat/permission.py +++ b/server/chat/permission.py @@ -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]: diff --git a/server/chat/settings.py b/server/chat/settings.py index e9fcd96..bff2326 100644 --- a/server/chat/settings.py +++ b/server/chat/settings.py @@ -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: diff --git a/server/chat/terminal.py b/server/chat/terminal.py index de1f99a..5cec289 100644 --- a/server/chat/terminal.py +++ b/server/chat/terminal.py @@ -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 diff --git a/server/conversation.py b/server/conversation.py index 2421faf..44826b9 100644 --- a/server/conversation.py +++ b/server/conversation.py @@ -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 } }) diff --git a/server/tasks/skills.py b/server/tasks/skills.py index 718a542..5eaa9cd 100644 --- a/server/tasks/skills.py +++ b/server/tasks/skills.py @@ -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 文件不存在") diff --git a/static/src/app/methods/message/shared.ts b/static/src/app/methods/message/shared.ts index 08c9f99..d74a241 100644 --- a/static/src/app/methods/message/shared.ts +++ b/static/src/app/methods/message/shared.ts @@ -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 = []; diff --git a/static/src/components/chat/ChatArea.vue b/static/src/components/chat/ChatArea.vue index 0df1c66..b21e8aa 100644 --- a/static/src/components/chat/ChatArea.vue +++ b/static/src/components/chat/ChatArea.vue @@ -913,7 +913,7 @@ const escapeUserHtml = (value: string): string => .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 SUB_AGENT_DONE_LABEL_RE = /^子智能体\d+\s*任务完成$/; const SUB_AGENT_DONE_PREFIX_RE = /^(?:✅\s*)?子智能体\s*#?\s*(\d+)\s*任务摘要[::]/; diff --git a/static/src/components/personalization/PersonalizationDrawer.vue b/static/src/components/personalization/PersonalizationDrawer.vue index 85e868b..4358b6f 100644 --- a/static/src/components/personalization/PersonalizationDrawer.vue +++ b/static/src/components/personalization/PersonalizationDrawer.vue @@ -1460,7 +1460,7 @@
可用 Skills勾选后会注入 system prompt,并同步到工作区的 skills/ 目录勾选后会注入 system prompt,并同步到工作区的 .astrion/skills/ 目录
diff --git a/test/test_config_paths_resolution.py b/test/test_config_paths_resolution.py index c48403d..b5c63a7 100644 --- a/test/test_config_paths_resolution.py +++ b/test/test_config_paths_resolution.py @@ -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//config。""" + """部署级配置目录与 host_workspaces.json 锚定到运行态根 ~/.astrion/astrion/config(host/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): diff --git a/test/test_skills_manager.py b/test/test_skills_manager.py index 237ae61..7a8d4a2 100644 --- a/test/test_skills_manager.py +++ b/test/test_skills_manager.py @@ -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__": diff --git a/test/test_tool_result_formatter.py b/test/test_tool_result_formatter.py index 7a71f4a..c053e92 100644 --- a/test/test_tool_result_formatter.py +++ b/test/test_tool_result_formatter.py @@ -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) diff --git a/utils/api_client/base_mixin.py b/utils/api_client/base_mixin.py index ea82875..7419029 100644 --- a/utils/api_client/base_mixin.py +++ b/utils/api_client/base_mixin.py @@ -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//logs/api_requests) + # 请求体落盘目录(跟随 LOGS_DIR,默认 ~/.astrion//logs/api_requests) self.request_dump_dir = Path(LOGS_DIR) / "api_requests" self.debug_log_path = Path(LOGS_DIR) / "api_debug.log" diff --git a/utils/context_manager/message_mixin.py b/utils/context_manager/message_mixin.py index a4bc4d9..791b213 100644 --- a/utils/context_manager/message_mixin.py +++ b/utils/context_manager/message_mixin.py @@ -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" diff --git a/utils/context_manager/project_mixin.py b/utils/context_manager/project_mixin.py index 3325f1d..8056eed 100644 --- a/utils/context_manager/project_mixin.py +++ b/utils/context_manager/project_mixin.py @@ -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: