agent-Specialization/AGENTS.md
JOJO 84c40e505e feat(android): 恢复 app 源码追踪——域名构建期注入脱敏 + 应用更名 Astrion
- HOME_URL 改为构建期注入(BuildConfig):-P 参数 > local.properties > 环境变量 > 占位符
- network_security_config.xml 删除冗余 domain-config(base-config 已全局禁明文)
- README 重写:移除真实域名,补充注入方式说明
- app_name 更名为 Astrion;链接外跳系统浏览器(1.0.42)+ 1.0.43 版本
- AGENTS.md §8 补充版本检测链路说明;.gitignore 解除 android-webview-app 排除

Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
2026-09-02 07:48:36 +08:00

592 lines
60 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Repository Guidelines (Code-Verified)
> Last verified against current codebase: 2026-06-25
这份文档基于当前仓库实际代码重写。若与未来代码冲突,以代码为准并及时更新本文档。
> 和用户交流时默认使用中文。
## 0) 项目名称
本项目(仓库 `agents`)的暂定产品名为 **Astrion**
- **构词**`Astr-`(希腊语 *astron*,星星/天体)+ `-ion`(粒子/状态后缀,如 photon、electron
- **意象**:星之子、星际粒子、来自宇宙尘埃的微小发光体。
- **寓意**Agent 像星际间传递信号的粒子,把人类意图传递到工具、文件、终端与子智能体之间;同时把零散任务像星尘聚合成恒星一样,聚合成可执行结果。
- **状态**:暂定名,后续若变更需同步更新 `AGENTS.md`(及本地 `claude.md`,未随仓库发布)。
- **中文参考**:阿斯特里恩;简称可考虑 **星子**
## 1) 当前项目结构(按代码现状)
- **后端入口**
- `main.py`: 统一启动入口(当前默认走 Web 模式 + thinking_mode=True
- `server/app.py`: 推荐的 Web 服务入口(封装并转发到 `server/app_legacy.py`
- `web_server.py`: 兼容入口,已标记 deprecated但仍可启动
- **后端核心目录**
- `server/`: Flask 业务主线chat/task/status 已拆分为子包:`server/chat/`、`server/status/`、`server/tasks/`REST 任务轮询为主Socket.IO 主要用于兼容与实时辅助通道)
- `core/`: 终端与工具编排(`main_terminal.py`、`web_terminal.py`、`main_terminal_parts/*`;其中 `main_terminal_parts/context/``main_terminal_parts/tools_definition` 已拆分为 base + mixin 子包)
- `modules/`: 可复用能力模块terminal/file/memory/sub_agent/upload_security/user 等;`file_manager`、`persistent_terminal`、`terminal_ops`、`mcp_client_manager` 已拆分为子包)
- `config/`: 配置拆分(`api.py`, `limits.py`, `terminal.py`, `paths.py` ...),由 `config/__init__.py` 聚合并加载 `.env`
- `utils/`: API client、日志、上下文与对话工具等公共函数`api_client`、`tool_result_formatter`、`context_manager`、`conversation_manager` 已拆分为子包;原入口文件保留为兼容入口)
- **前端目录**
- `static/src/`: Vue 3 + TS 前端
- `cli/src/`: React 19 + Ink 6 + TypeScript CLI 前端(正在重写中)
- 监控动画相关核心文件:
- `static/src/components/chat/monitor/MonitorDirector.ts`
- `static/src/stores/monitor.ts`
- `static/src/components/chat/monitor/*`
- **CLI 相关文档**(本地文档,未随仓库发布)
- `docs/cli_ui_display_spec.md`: CLI 显示层设计说明
- `docs/cli_slash_commands_spec.md`: CLI `/` 指令设计说明
- **其他子项目/资源**
- `android-webview-app/`: Android WebView 客户端工程
- `modules/sub_agent/`: 子智能体执行逻辑(主进程内 `asyncio.Task`,工具调用复用主进程链路)
- `easyagent/`: 旧版 Node.js 子智能体实现,暂时保留但已不再使用
- `_experiments/`: 本地实验残留与历史文档归档目录(**不纳入 git**,见 §7
## 1.5) 运行态数据目录与路径变量2026-06 更新)
> 设计目标:运行态数据(对话、用户工作区、日志等)默认存放在用户主目录的 `~/.astrion/astrion/` 下,对齐 `~/.claude`、`~/.codex` 惯例,**不落在源码树内**。配置实现见 `config/paths.py`。
### 1.5.1 默认位置与模式分流
运行态数据根目录(`data_root`)默认为 `~/.astrion/astrion`,可通过 `ASTRION_DATA_ROOT` 整体搬迁。在该根目录下,按运行模式自动分流(由 `TERMINAL_SANDBOX_MODE` 决定):
- 宿主机模式(`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. **数据根目录环境变量**`ASTRION_DATA_ROOT`(整体搬迁运行态数据根目录,默认 `~/.astrion/astrion`
3. **兜底默认**`~/.astrion/astrion/<mode>`
具体目录变量支持相对路径(相对仓库根目录展开)、绝对路径与 `~`
> 注意:`config/*.json` 分两类:
> - **程序能力**`docker_risk_markers.json`、`skill_hints.json`):是程序行为的一部分,随版本演进,仍锚定源码树。`prompts/`、`agentskills/` 同理。
> - **部署级配置**`custom_models`、`host_workspaces`、`auto_approval`、`goal_review`、`forbidden_commands`、`host_sandbox_policy`):因部署/机器而异或含密钥,外置到 `~/.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 新增)
`paths.py` 新增三个固定变量,支持 host 模式同时读取 web/ 数据:
- **`IS_HOST_MODE`**:当前是否为 host 模式(布尔值)
- **`WEB_DATA_DIR`**:固定指向 `<data_root>/web/data`(不受当前模式影响)
- **`WEB_USER_SPACE_DIR`**:固定指向 `<data_root>/web/users`(不受当前模式影响)
**host 模式**`modules/user_manager.py`
- 用户列表:同时加载 `host/data/users.json` + `web/data/users.json`web 用户不覆盖已有)
- 工作区列表:合并 host/ 和 web/ 两处
- 工作区定位:已有 web 工作区直接复用;新工作区写入 host/
- 写操作(创建/删除/重命名):始终走 host 路径
**web 模式**:只读取 web/ 数据,无变化。
> 设计意图host 模式 = host + web 数据都可用web 模式 = 仅 web 数据可用。
### 1.5.4 日志策略2026-06
- API 请求体落盘dump默认**关闭**,由 `AGENT_API_DUMP_ENABLED` 控制(`1/true/yes/on` 开启)。
- 日志混合轮转(实现见 `utils/log_rotation.py`
- 追加型单文件(`host_workspace_debug.log`、`chunk_*.log`、`conn_diag.log`、`api_debug.log`、TaskLogger/ErrorLogger按大小轮转默认单文件 20MB、保留 3 份。
- 按份计量目录(`api_requests/` 一请求一文件)只保留最近 N 个,默认 30。
- 阈值可覆盖:`AGENT_LOG_ROTATE_MAX_BYTES` / `AGENT_LOG_ROTATE_BACKUPS` / `AGENT_DUMP_KEEP`
### 1.5.5 数据迁移
- 迁移脚本:`scripts/migrate_runtime_data.py`(源码树 → 运行态根;复制+备份+可回滚+幂等,`logs/` 丢弃不迁)。
- 脚本 `import config` 复用程序同一套路径解析(含 `.env`**模式由 `.env` 决定**;执行前务必先 `--dry-run` 确认目标。
- 清理误迁副本:`scripts/cleanup_misplaced_web.sh`。
## 2) 当前可用启动/构建命令(已按代码核对)
### Python / 服务端
- 安装依赖:`pip install -r requirements.txt`
- 推荐启动 Web`python -m server.app`
- 可选参数:`python -m server.app --path ./project --port 8091 --debug --thinking-mode`
- 兼容启动方式:`python web_server.py`
- 统一入口:`python main.py`(当前实现会默认进入 Web 启动流程)
### Frontend根目录
- 安装依赖:`npm install`
- 构建:`npm run build --silent 2>&1 | tail -n 5`(默认只看最后 5 行,减少 Vite 构建资源列表等无关输出)
- 开发监听(当前脚本是 build watch`npm run dev`
- Lint`npm run lint`
### CLIReact / Ink
- 安装依赖:`npm --prefix cli install`
- 开发启动:`npm run cli` 或 `npm --prefix cli run dev`
- 构建:`npm run cli:build`
- 类型检查:`npm run cli:typecheck`
- 可执行命令名(构建后):`agents` / `agents-cli`
## 3) 测试现状(不要再写过时命令)
- 当前仓库内可见自动化冒烟:`test/test_server_refactor_smoke.py``unittest`
- 运行方式:`python -m pytest test/test_server_refactor_smoke.py -q`
- 注意2026-08-01 实测):`python -m unittest test.test_server_refactor_smoke` 在标准 Windows Python 上失败——stdlib 自带常规包 `test` 遮蔽本地 `test/` 目录(无 `__init__.py` 的命名空间包优先级更低),报 `No module named 'test.test_server_refactor_smoke'`。无 pytest 时可用:
`python -c "import sys; sys.path.insert(0, '.'); import unittest; suite = unittest.TestLoader().discover('test', pattern='test_server_refactor_smoke.py'); r = unittest.TextTestRunner().run(suite); sys.exit(0 if r.wasSuccessful() else 1)"`
- `test_system_message.py` 依赖外部 `MOONSHOT_API_KEY` 与网络,不属于离线稳定 CI 用例。
- 当前仓库未发现 `pytest.ini`/`pyproject.toml`/`tox.ini`;不要默认要求 `pytest` 作为唯一入口(仅作为冒烟测试的便捷运行器)。
- CLI 当前最小可复现验证:
- `npm --prefix cli run typecheck`
- `npm --prefix cli run build`
- 若改动 `server/chat/`、`server/status/`、`server/tasks/` 等后端接口适配,补充:
- `python3 -m py_compile server/chat/*.py server/status/*.py server/tasks/*.py`
- `python -m pytest test/test_server_refactor_smoke.py -q`
## 4) 代码修改约定(实用版)
- **根治优先原则**:修复问题时优先判断能否根治;能根治的,应告知用户根治方案与影响范围并询问是否执行,在一定范围内的重构是可接受的,尽可能避免打补丁式的临时修改。
- **精确读取原则**:禁止整文件通读。阅读/修改前先用搜索工具grep / 符号查找 / 关键字定位)锁定需要的行段,只精确提取并阅读相关片段,不要把整个文件一次性读进来。
- **文件编辑方式**:修改文件时优先使用 `apply_patch` 或其他原生文件编辑工具;尽量不要用 `bash`/`python` 脚本批量改文件,除非原生工具明显不适合。
- **后端改动优先级**:先改 `modules/`、`server/` 内对应模块,最后才动入口。
- **前端改动优先级**:按 `static/src` 现有分层改(`app/`、`stores/`、`components/`、`composables/`)。
- **CLI 改动优先级**:优先在 `cli/src/App.tsx`、`cli/src/components.tsx`、`cli/src/eventMapper.ts`、`cli/src/api.ts` 内做最小闭环修改。
- 涉及 monitor 动画/事件联动时,至少同步检查:
- `MonitorDirector.ts`(动画与场景执行)
- `stores/monitor.ts`(事件队列与状态机)
## 5) 风格与质量
### 文件长度与拆分
- **单文件最好不超过 1000 行**;超过时应优先考虑合理拆分,避免维护成本快速上升。
- **行数不是硬性指标**:文件是否拆分取决于**可维护性**,而不是必须小于某个固定行数。
- 当单一文件超过约 500 行时,应评估是否具备以下拆分价值:
- 业务逻辑过重、职责不单一;
- 存在清晰的边界(如按工具类型、按资源、按生命周期阶段);
- 拆分后能提高复用性、可读性或测试便利性。
- **不要为了拆分而拆分**。边界模糊、耦合紧密或改动频率低的文件,保持现状可能更划算。
- Vue SFC 因模板、样式、脚本耦合较深,拆分风险较高;优先抽离 composable 和子组件,而非直接切分 `.vue` 文件。
- Python4 空格、`snake_case` 函数、`PascalCase` 类,新增公共函数尽量补 type hints。
- Vue/TS保持现有代码风格不做无关风格清洗。
- CLI React/TS保留现有 Ink 渲染方式与光标修正逻辑,不要轻易重写输入框定位策略。
- 日志:优先复用现有 logger/日志路径,不引入大量临时 `print`
- 提交前至少做与改动相关的最小验证(命令输出或手工步骤要可复现)。
- 视觉验证默认由用户完成:除非用户特别说明,所有需要视觉确认的修改(尤其是动画/过渡效果),构建/lint 通过后交由用户亲自查看确认,不要用 Playwright 截图或浏览器自动化代替用户验收。
- 运行根目录前端构建时,默认使用 `npm run build --silent 2>&1 | tail -n 5`
### CLI 当前交互约束2026-05-15
- CLI 连接的是现有本地 Web API默认 `127.0.0.1:8091`),不是独立 agent runtime。
- 启动 CLI 时应清屏、连接本地服务、创建新会话,并将输入区固定在底部。
- 当前目录若不在工作区中,应先弹出“是否添加到工作区”的选择。
- 思考内容当前默认隐藏,只显示“思考中 / 思考完成”标题;相关折叠代码保留,后续可继续修。
- 不要在未获得用户要求的情况下运行交互式 TUI 压测或长时间模拟输入,以免刷屏占满上下文。
## 5.5) 前端设计风格统一规范(强制)
> 适用范围:所有前端 UI以 `static/src` Web 为主;`cli/src` 在视觉可类比处同样适用;以及未来新增的任何界面)。
> 约束级别:写新 UI 或改动 UI 时**必须遵守**;遇到存量违规应在最小改动允许范围内顺手修正。
> 配色基础设施:两层 token原始层 + 语义层)定义在 `static/src/styles/base/_tokens.scss``:root[data-theme='classic'|'light'|'dark']` + 首屏回退 `:root:not([data-theme])`),切换逻辑见 `static/src/utils/theme.ts``.stylelintrc.cjs` 三条规则做防回退栏杆,`build` 含 `stylelint` 步骤。
1. **禁止边缘光晕**:不使用任何 glow / 外发光 / 彩色光晕效果(大范围彩色 `box-shadow` 扩散、`filter: drop-shadow` 光圈、`::before/::after` 模糊光晕等)。已有的要移除。允许的只是中性、克制的投影——沿用 `--shadow-*` 既有 token不自造发光阴影。**仅在用户明确允许或要求时才可使用光晕效果。**
2. **禁止原生浏览器组件**:不直接使用浏览器默认外观的 `<select>`、`<input type=checkbox/radio/range/date/color>`、`alert/confirm/prompt`、原生右键菜单、原生 tooltip 等,一律换成与项目风格统一的自定义组件(下拉 / 开关 / 单选 / 滑块 / 模态 / Toast
3. **禁止圆角套娃**:不允许「圆角矩形套圆角矩形套圆角矩形」的多层嵌套卡片——典型垃圾审美,且极大占据有效显示面积。容器层级要扁平,优先用分隔线 / 留白 / 底色区分,不要多包一层带边框圆角的盒子。
4. **图标外框对齐**:同一组图标按钮的外框(点击热区 / 容器)必须统一对齐,不能一会居中、一会左对齐、一会右对齐;同组固定相同尺寸与对齐方式。
5. **图标视觉对齐而非理论对齐**:按图标视觉重心对齐而非几何边界盒;对重心偏移的图标(三角形 / 播放键 / 放大镜等)做微调,使其「看起来居中」。
6. **颜色遵循三模式切换 + 两层 Token**:所有颜色走原生三模式,禁止写死颜色字面量(裸 hex / `rgb()`/`hsl()` 字面色 / `var(--x, #hex)` 兜底),一律用**中性语义** CSS 变量(`--surface-*` / `--text-*` / `--border-*` / `--accent*` / `--state-*`)。三主题定位:**经典抄 Claude 亮色盘**(暖奶油 + 暖橙 primary `#cc785c`)、**浅色抄 ChatGPT 亮色盘**(冷白 + 灰 + 近黑 primary `#181818`)、**深色自研中性灰阶**。亮色表面靠灰阶拉层次(经典 `--surface-base #faf9f5` < `soft #f5f0e8` < `card #efe9de` < `muted #e8e0d2`不许全塌成白。primary 克制CTA-onlyhover/选中/运行态走中性灰。半透明 tint 用 `color-mix(in srgb, var(--token) N%, transparent)` 派生。`--claude-*`/`--theme-*` 是过时别名,新代码勿用。新增颜色必须三主题(含首屏回退)补齐。
7. **带文字容器固定高度**:所有含内部文字的选项 / 按钮 / 标签 / 容器固定高度,禁止因文字多少被撑大撑高;过长文字用省略号或内部滚动处理。
8. **窗口设最大尺寸 + 内部滚动**:所有弹窗 / 面板 / 列表设置 `max-height` / `max-width`,超出由内部容器滚动,不顶大整个窗口;滚动条要么隐藏,要么做样式适配,不暴露原生粗滚动条。
9. **实体面板禁止半透明**:实体 UI 容器(对话区 / 侧栏 / 下拉/二级菜单 / 抽屉 / 对话框本体 / 状态条 / tooltip / 输入栏)背景必须不透明,且不加 `backdrop-filter` 磨砂。唯一例外:遮罩层 scrim`--overlay-scrim``position:fixed;inset:0`)和刻意玻璃质感装饰保留半透明。
10. **主题变体必须与基础定义同文件(禁止跨文件补丁覆盖)**:组件的 dark/light/classic 主题变体写在该组件自己的样式文件里(文件内统一的 `body[data-theme='…'] { … }` 块),禁止把某组件的主题样式写到别的文件去覆盖。新增主题样式前先确认该组件基础定义所在文件;发现存量跨文件覆盖应顺手回迁并删除原覆盖。`!important` 只准用于遮盖第三方库样式,禁止用于压过项目自有定义(出现这种需求说明应该合并定义,而不是加 `!important`)。审计脚本:`python3 cache/style_audit.py` + `python3 cache/theme_diff.py`(本地脚本,未随仓库发布)。
11. **大面积色块禁止纯黑/近黑**:任何窗口的 hover、头部、背景等大面积色块禁止使用纯黑`#000`)或肉眼近黑(如 `#0a0a0a`/`#0f0f0f` 一档的颜色深色模式同样如此。深色下的层次靠中性灰阶拉开hover 用 `--hover-bg`(深色为白色微量 tint提亮而非压黑弹窗/面板头部优先用分隔线区分而非深色衬底(参考 `VersioningDialog.vue` 头部)。纯黑/近黑只允许用于小范围强调渲染(如行内代码衬底等小面积场景)。
## 5.6) 工具结果显示规范(强制)
> 适用范围:所有使用 `tool-result-meta` / `tool-result-content` 结构渲染的工具结果(如 `static/src/components/chat/actions/ToolAction.vue`、`toolRenderers.ts` 等)。
> 约束级别:新增或修改工具结果渲染时**必须遵守**。
1. **meta 部分只放参数**`tool-result-meta` 仅用于展示工具调用的参数、配置、状态标识等元信息。例如状态、ID、路径、任务描述、超时时间等。禁止在 meta 区域放置执行结果、统计摘要、输出内容等。
2. **content 部分只放结果**`tool-result-content` 仅用于展示工具执行后的结果、输出、统计、总结等。例如:文件内容、命令输出、搜索命中、执行统计(工作时间 / 调用次数 / 工具次数)、最终回复摘要等。禁止在 content 区域重复展示工具参数。
## 5.7) 调试输出规范(强制)
> 约束级别:需要临时加调试日志排查问题时**必须遵守**。
### 前端调试日志
- **统一筛选词(强制)**:所有临时 `console.log` 必须使用**同一个**筛选前缀(例如 `[route-debug]`),方便用户在浏览器控制台用该前缀一次性筛选。
- **同一次调试任务中严禁使用多个前缀**。如果涉及多个模块如状态栏、Git 摘要、用户问题),应共用同一个前缀(如 `[status-bar-debug]`),通过日志对象里的字段区分模块,而不是发明多个前缀增加用户筛选成本。
- 选择前缀时优先与本次排查的「用户可见现象」对齐,而不是与内部模块名对齐。
- **控制数量与时机**
- 禁止一次性输出上百上千条日志刷屏。
- 只在关键路径(入口、分支判断、状态变化、导航动作)打印,避免在循环、高频事件、每帧渲染中输出。
- 需要大量结构化数据时,优先用 `console.group` / `console.table` 或对象快照,而不是逐条打印。
- **用完即清**:问题定位后应及时删除或注释掉临时调试日志,不要长期留在代码里。
### 后端调试日志
- **必须写入文件**:后端调试信息禁止直接 `print` 到终端刷屏,必须写入日志文件。
- **复用现有 logger**:优先复用项目已有 logger`utils/logger.py`、`modules/multi_agent/debug_logger.py` 等),按模块落到 `~/.astrion/astrion/<mode>/logs/` 下。
- **关键状态转换必打**:多智能体、子智能体、任务轮询等复杂链路应在关键状态转换点写结构化日志,便于复现问题后按时间线追溯。
## 5.8) 前端多语言i18n文案规范强制2026-08 新增)
> 详细规范:`doc/frontend/i18n_spec.md`。约束级别与 §5.5 同级:写新 UI 或改动 UI 文案时必须遵守。
- **引擎**vue-i18n v10Composition 模式);文案唯一定义在 `static/src/locales/{zh-CN,en-US}/<namespace>.ts`,中文为源语言。
- **使用**:模板用 `$t('ns.key')`全局注入、响应式SFC script / 纯 TS 用 `import { t } from '@/locales'`(调用时求值、非响应式;响应式标签用 `useI18n()` 或读取 `currentLocale`)。
- **公共词**:高频通用词唯一来源是 `common` 命名空间,禁止在各域重复定义。
- **key 奇偶强校验**en-US 聚合器用 `DeepString<typeof zhCN>` 约束en 缺/多 key 直接 tsc 报错;新增命名空间须在 `zh-CN.ts` / `en-US.ts` 同步注册。
- **防回退栏杆**`npm run lint` 先跑 `scripts/i18n_audit.mjs`(剥离注释后查裸中文,独立命令 `lint:text`);存量文件列在 `scripts/i18n_baseline.txt`**迁移完一个文件就删一行**,删除后该文件永久受栏杆保护。
- **语言切换**:个人空间 → 外观 → 界面语言;默认 zh-CN不跟随浏览器持久化 key `agents_ui_locale`
- **边界**后端下发文字API error、通知、工具结果摘要不做多语言前端原样显示CLI 暂不纳入。
## 6) Git 工作流(开发 + Review
### 6.1 核心原则
- 默认直接在 `main` 上开发,不强制新建功能分支。
- 每个功能建议在 `main` 上保持一个清晰 commit历史干净可回滚。
- 使用 **Conventional Commits**`feat:` `fix:` `refactor:` `chore:` `docs:` `test:` `perf:`
- 个人项目,不设 PR 和 Review 流程;如用户临时要求隔离开发,再按需新建分支。
### 6.2 工作流(唯一步骤)
AI 执行以下流程时,每一步都要向用户说明在做什么:
```
0. 检查工作区状态
git status
如有未提交的改动:
- 若无关:提醒用户先 commit 或 discard
- 若有关git stash push -m "WIP: <简短描述>" 暂存
1. 确认在 main 并同步最新代码
git checkout main && git pull origin main
2. 开发并提交
git add <修改的文件>
git commit -m "feat(scope): 简短描述"
(多轮迭代可用 git commit --amend 保持一个 commit
3. 推送到远程(备份 + 方便切设备)
git push origin main
```
**重要约束**
- AI 必须在本地验证修改能正常运行,再建议用户提交。
- 修改完成后按改动规模汇报:大修改说明核心变更点与验证结果;小修改只说明核心变更点。
- 禁止 AI 在未经用户确认的情况下执行 `git commit``git push`
- commit / push 授权均为一次性:用户说“可以 commit/push”只代表允许这一次操作后续如需继续 commit / push必须由用户再次主动同意。
- 允许 commit 不等于允许 push两者授权相互独立获得 commit 许可后仍需单独征得 push 许可。
- 不需要每次都执行或输出 diff如用户要求 Review再在同一对话内使用 `git diff``git diff HEAD~1..HEAD`
### 6.3 常用命令速查
| 操作 | 命令 |
|------|------|
| 暂存未完成工作 | `git stash push -m "WIP: xxx"` |
| 修改最近 commit | `git commit --amend` |
| 同步 main | `git checkout main && git pull origin main` |
| 同对话内 Review diff | `git diff``git diff HEAD~1..HEAD` |
| 查看提交历史 | `git log --oneline -10` |
## 7) 安全与仓库卫生
- 严禁提交真实密钥(`.env`、token、cookie、用户隐私
- 运行态数据默认在 `~/.astrion/astrion/<mode>/``data/`、`users/`、`logs/`、`api/`),不在源码树内;详见 §1.5。`.gitignore` 仍忽略源码树内的 `logs/`、`data/`、`users/`、`api/`、`project/` 等,以防通过具体变量指回源码树或历史遗留产生污染。分享前需脱敏。
- **`_experiments/` 用途**:归档本地实验残留与历史文档(调试记录、旧变更日志、模型测试脚本、翻译资料、旧子智能体文档等)。该目录**不纳入 git**(已在 `.gitignore`)。需要保留但不属于当前主线、又不想直接删的零散文件,统一放这里,不要散落在根目录。
- 不要把本地构建产物(如 `static/dist/`、`node_modules/`)纳入提交。
## 8) Android App 发布联动要求(重要)
当修改前端并需要发布 Android WebView App 时,必须同步执行以下步骤(依据 `doc/android_app_release_and_update.md`,本地文档未随仓库发布):
1. 同时更新版本信息
- `android-webview-app/app/build.gradle.kts`:递增 `versionCode`,更新 `versionName`
2. 同时更新更新说明
- `android-webview-app/APP_CHANGELOG.md`:在顶部新增当前版本说明
3. 完成修改后提醒用户运行上传脚本
- 在发布流程中运行:`bash ./upload_android_apk.sh`(本地私有脚本,未随仓库发布)
- 脚本除上传 APK 外,还会同步 `app/build.gradle.kts``APP_CHANGELOG.md` 到服务器2026-09-02 修复)
> 禁止只改前端代码而不更新版本号/更新说明,否则会导致客户端更新提示与分发信息不一致。
> 服务端 `/api/app/version` 的版本号是从服务器文件系统上的 `build.gradle.kts` 解析的(实现见 `server/status/docker.py`**不是从 APK 包解析**——只传 APK 不同步元数据文件会导致 App 检测不到新版本。
## 9) 给 Agent 的硬性要求
- 先读当前代码再执行,不依赖历史文档记忆。
- 输出结果按改动规模决定:
- 大修改:必须包含核心变更点、验证结果(已执行命令/未执行原因)。
- 小修改:只需包含核心变更点。
- 不要求每次列出所有修改文件;仅在用户要求、改动复杂或有助于说明时列出。
- 不要求每次执行或展示 diff仅在用户要求 Review 或排查差异时使用。
- 如果发现本文档过时,直接更新 `AGENTS.md` 并在结果中说明依据。
## 10) 宿主机权限模式与沙箱执行机制2026-05 更新)
完整说明文档:`docs/host_sandbox_and_permission_model.md`(本地文档,未随仓库发布)
为避免误解,当前系统有两套独立但叠加的控制:
1. **权限模式Permission Mode**`readonly` / `approval` / `auto_approval` / `unrestricted`
2. **执行环境Execution Mode**`sandbox` / `direct`(仅宿主机模式可切换)
### 10.1 权限模式
- `readonly`
- `run_command` 可调用,但在只读沙箱执行;写入由系统拒绝
- 读取同样受限(各平台只读强制的可读边界见 §10.8);读不到属预期边界,不是故障
- `write_file` / `edit_file` 等写入类工具直接拒绝
- 持久终端可自由创建与输入但以只读身份运行docker 非特权 uid / 宿主机只读 profile写入 EPERM 由系统强制权限跨界切换受限档⇄unrestricted时现有终端直接销毁重开生效
- `approval`
- `run_command` 先走只读沙箱(可读边界见 §10.8,读越界也会触发权限拒绝)
- 若出现权限拒绝(例如 `Operation not permitted` / `Permission denied`),触发前端审批
- 审批通过后,仅该次命令以可写沙箱重试;工具结果返回“重试后的最终结果”
- **审批只授予该次命令的工作区内写权限,不放大读取**2026-08-30 方案一):可写沙箱读边界与只读同一白名单,读越界唯一途径是「路径授权」;重试后仍为文件权限拒绝时,工具结果附带 `read_scope_hint` 引导模型说明
- **执行环境锁定为沙箱**2026-08-30 起direct 下无沙箱兜底,启发式漏判的写命令会直接执行成功、审批承诺被架空,故批准/自动审核与 direct 硬互斥(锁定机制同 plan/只读,见 §10.6
- 持久终端以只读身份运行(同 readonly 条目):终端内写入永远 EPERM需审批的写入走 `run_command` 两段式通道
- `auto_approval`
- `write_file` / `edit_file`:工作区内路径直接执行,工作区外路径进入审批流程
- `run_command` 先走只读沙箱(可读边界见 §10.8);触发权限拒绝后由后台审批智能体自动审核
- 自动审核拒绝时,工具返回“被拒绝+理由”并继续主循环(不强制结束任务)
- 批准后的读取边界与 approval 相同(不放大)
- 执行环境同样锁定为沙箱(同 approval 条目)
- `unrestricted`
- 权限层不做拦截:工作区内读写自由、命令免批准;是否沙箱由执行环境决定
- 注意「无限制」只管工作区内:宿主机沙箱执行时读边界仍是白名单(见 §10.8授权范围外读取需路径授权direct 模式才真正无边界
### 10.2 执行环境
- `sandbox`(默认):使用 OS 沙箱执行macOS: `sandbox-exec`Linux: `bwrap + seccomp`Windows: `WSL2`
- `direct`:宿主机直接执行(高风险),**仅 `unrestricted` 权限可选**:受限权限档(只读/批准/自动审核)与 plan 模式下后端 `set_execution_mode` 硬锁拒绝 directi18n 键 `main_terminal.restricted_mode_locks_sandbox` / `plan_mode_locks_sandbox`);进入受限档时若已是 direct 会被联动压回沙箱(记录 `pre_readonly_execution_mode`,切回 unrestricted 时恢复);存量「受限档+direct」对话在加载时自愈矫正回沙箱
- 切换后一直生效无自动回退机制2026-07 已移除原 TTL 自动回退)
### 10.3 路径授权语义
- 前端“路径授权”支持两类路径:
- **可读可写**
- **仅可读**
- 语义:
- 可读集合 = 可读可写 + 仅可读
- 可写集合 = 可读可写
- 配置来源2026-08-30 收敛为两个):`config/host_sandbox_policy.json`UI 唯一读写目标)+ 真·环境变量 `HOST_SANDBOX_MACOS_WRITABLE_PATHS`部署通道与文件合并去重。settings.json 的 `terminal.macos_writable_paths` 映射已移除(历史值一次性失效,需用路径授权重新添加);.env 注入技术上仍生效,但不是受支持的通道,不推荐使用
### 10.4 维护约束
- 不要在提示词里暴露内部实现细节(例如“命令文本猜测”等)
- 文案应面向用户能力边界与操作建议,不描述内部判定算法
### 10.5 审核智能体配置与调试
- 审核智能体配置统一在个人空间「审核智能体」页设置2026-08 起),存于 personalization.json 的 `review_agents`
- 三个审核智能体:`auto_approval`(自动审批)/ `goal_review`(目标审核)/ `workflow_review`(工作流审核)
- 字段:`model`(子智能体模型库条目名,留空=模型库 default_model/ `thinking` / `timeout_seconds` / `max_rounds` / `max_command_timeout`
- 解析入口:`modules/review_agent_config.py::resolve_review_agent_config`(复用 `sub_agent_models.json` + `_build_sub_agent_profile`
- 旧的独立 json 配置(`config/auto_approval.json` / `goal_review.json` / `workflow_review.json.example`)已彻底废除,无向后兼容
- 调试开关(代码变量):`modules/approval_agent.py` 中 `DEBUG_SAVE_APPROVAL_AGENT_TRANSCRIPT`
- 开启后写入:`logs/approval_agent/`
- 记录以累积 `messages` 为主,便于对齐主/子智能体会话格式
## 10.6) 运行模式work_mode系统2026-08-13 新增)
运行模式(**work_mode**`plan` / `ask` / `execute`)控制**与用户的交互节奏**,与权限模式(限制模型能力)正交。命名注意:`run_mode` 已被 fast/thinking 思考模式占用,严禁混用。
### 三档语义
- **plan计划**:只制定计划并讨论;权限**锁定为只读**且执行环境**锁定为沙箱**UI 禁用 + 后端 `set_permission_mode`/`set_execution_mode` 双重强制——只读在宿主机依赖 OS 沙箱硬限制direct 下无沙箱形同虚设);唯一写例外是 `.astrion/plan/*.md` 计划文档(权限层判定,提示词要求用 write_file/edit_file 直写,不走沙箱);计划完成后调 `submit_plan` 工具提请批准,**批准后自动切换到 execute**(恢复进入 plan 前的权限与执行环境,无记录时权限回落个性化 `default_permission_mode`、执行环境保持沙箱安全默认)
- **ask询问**:先讨论后开工;**禁用 ask_user 工具**(开放式讨论必须直接写在回复里);与 execute 无执行层差异,仅提示词不同
- **execute执行**:自行梳理计划、脑补细节直接开工;仅硬阻塞才提问
### 实现要点(改代码必须知道)
- **后端核心**`core/main_terminal_parts/tools_policy.py`WORK_MODES、`get/set/switch_work_mode`、plan 锁;`RESTRICTED_PERMISSION_MODES` 受限档集合 + `set_permission_mode``_apply_restricted_execution_mode_link` 处理受限档⇄沙箱联动,`pre_readonly_execution_mode` 存对话 metadata——键名保留语义已泛化为受限档共用`switch_work_mode` 处理 plan⇄只读+沙箱联动,`pre_plan_permission_mode`/`pre_plan_execution_mode` 存对话 metadata执行环境 plan 锁与受限档锁同在 `core/main_terminal.py::set_execution_mode`(只拦 direct
- **创建对话的模式继承work_mode + permission_mode 同一原则)**三条创建路径prefer_defaults / 显式模式 / safe_navigation一律沿用 terminal 当前值,**不用个性化默认值覆盖**——/new 页面切换器显示什么新对话就是什么(切换经 `_sync_workspace_terminal_mode` 同步到工作区级 terminal个性化 `default_work_mode` / `default_permission_mode` 仅在 terminal 首次构造时生效tools_policy 加载)。多智能体创建路径(`server/multi_agent.py`)与工作流创建路径(`workflow_runtime_api.py`同样遵守。plan 锁存在于 `set_permission_mode`,任何创建路径不得在其之前调用非只读 set曾因此 500
- 历史教训permission_mode 曾长期例外(`a2a04b95` 引入「个性化默认优先」,彼时还没有 /new 切换器同步机制),导致 /new 切只读后新建对话回落无限制2026-08-30 修复,与 work_mode 对齐)
- **提示词**`prompts/work_mode.txt` 模板 + `mode.py::_build_work_mode_rules`(三档规则唯一来源,冻结注入与切换通知共用);冻结注入在 `messages.py`(执行环境之后);切换走现有 drift 机制(`_RUNTIME_MODE_KINDS` 第四种),通知携带完整新规则文本
- **API**`GET/POST /api/work-mode``server/chat/permission.py`**仅空闲可切换,运行中 409**(无 pending 队列);`plan-approvals` pending/answer 端点在 `server/chat/approval.py`
- **submit_plan 链路**:工具全模式注入(沟通类工具不过滤,非 plan 调用由 handler 运行时兜延返回引导);`PlanApprovalManager``modules/plan_approval_manager.py`+ `chat_flow_tool_loop.py::_handle_submit_plan` 阻塞等待 → 前端 `PlanApprovalDialog.vue` 弹窗 → 批准则工具循环内切 execute 并静默更新 baseline避免误发 drift 通知)
- **个性化**`default_work_mode`(默认 plan后端 `personalization_manager.py` 白名单 + 前端 `stores/personalization.ts` 三处(接口/默认值/sanitize
- **前端**:输入栏 `work-mode-switcher`InputComposerstreaming 时锁定plan 下权限菜单内「权限」「执行环境」两组选项禁用+锁定标记(**网络权限组保持可调**不整体锁死菜单slash 菜单同步禁用;`get_status` 带 `work_mode` 供多标签页同步
### 10.7) 后台命令只读沙箱修复2026-08-13
后台 `run_command``run_in_background=true`)此前在**所有环境**绕过只读权限(`background_command_manager` 固定用可写沙箱计划)。已修复:`create_background_command` 透传 `sandbox_write_access`,宿主机路径按它选只读/可写计划docker 路径按它决定是否以非特权 uid 执行(见 §10.8)。
### 10.8) 平台级只读强制2026-08-30
只读权限的强制由各平台原生机制兜底;`config/limits.py` 的命令文本白名单(`_is_readonly_run_command_allowed`)只是**审批决策的启发式**,不再是安全边界(已知可绕过,如 `find . -delete`;绕过后果只是多走一次审批)。
- **docker/web 模式 = 非特权 uid 执行角色 + Landlock 进程级只读域**`modules/docker_readonly_exec.py`
- 容器主进程保持 root可写执行不变`sandbox_write_access=False` 的执行通道(`terminal_ops/run.py`、`background_command_manager.py`、只读语境创建的持久终端)以 `-u 10001:10001` 运行,`DOCKER_READONLY_EXEC_UID/GID` 可覆盖
- 第一层强制力 = 内核 DAC工作区属主为宿主机 root非属主无写权600 权限文件(如 .env不可读逃逸需提权setuid/内核漏洞),无 umount 类捷径
- **第二层 = Landlock 只读域**2026-09 新增,云端实测 kernel 6.8 / ABI V4 / Docker 28 默认 seccomp 放行):纯 DAC 的残留漏洞是工作区内历史遗留的 world-writable777/o+w路径对只读 uid 仍可写;只读执行时命令再经 `modules/landlock_launcher.py`(首次执行时 docker cp 进容器并自检)进入「工作区写类操作全拒」的内核域,最终权限 = DAC ∩ Landlock该洞封死。要点
- launcher 规则handled 只含写类操作ro 路径(工作区挂载点)不加规则(无覆盖即拒绝);/tmp、/var/tmp、/dev/shm 显式授写权以对齐纯 DAC 现状行为(只读身份 HOME=/tmp读/执行不进 handled仍由 DAC 管。注意不能写「/ 授全量 + ro 授空」的交集规则——空授权规则被内核拒绝ENOMSG/errno 42
- 自检失败(内核 <5.13 / seccomp 拦截 / 容器无 python3自动降级纯 DACwarning 日志标注 enforcement level`DOCKER_READONLY_LANDLOCK=0` 可整体停用
- 语义边界Landlock 不管 chmod/chown 等元数据修改也不管未配置的网络——因此非特权 uid 层必须保留root+Landlock 的进程可 chmod 放宽权限位让域外进程受益
- 前提工作区属主与该 uid 不碰撞容器未挂 docker.sock云端已验证macOS Docker Desktop 双重不适用virtiofs fakeowner 不按 uid 检查linuxkit 内核未编译 Landlock Linux 宿主生效
- 持久终端在 readonly/approval/auto_approval 下同以只读身份创建`terminal_readonly_enabled` 判定`terminal_readonly_getter` 注入终端里的写入会被拒写命令走 run_command 审批通道权限跨界切换受限档unrestricted时销毁现有终端会话重建`_apply_restricted_execution_mode_link` `close_all()`
- Dockerfile 创建 `agent` 用户 + `/etc/gitconfig` safe.directory + setuid 加固数字 uid 不依赖镜像内用户存在旧镜像直接受益
- **macOS 宿主机 = Seatbelt 白名单读模型**`modules/host_sandbox_runner.py`
- 只读/可写两个 profile **共用同一白名单读模型**`_build_macos_whitelist_read_rules`唯一区别是写权限deny default + 系统路径白名单`MACOS_MINIMAL_READABLE_PATHS`+ 路径授权writable + readable_extra+ 工作区 + 祖先目录 literal allow缺一个祖先进程 exec 直接 Abort trap必须为 file-read*+ env 注入 `GIT_CONFIG_GLOBAL=/dev/null`可写/只读/持久终端三条 plan 均注入
- **deny 规则.env 正则、~/.ssh 必须位于所有 allow 之后**Seatbelt 后规则覆盖先规则两个 profile 均已修复旧顺序漏洞工作区 .env 曾实际可读
- 可写 profile 已于 2026-08-30 白名单化此前为全局可读导致 unrestricted/审批批准后能读授权范围外文件白名单固有代价祖先目录顶层文件名可列出读文件内容仍被拒
- 持久终端 shell plan 支持 readonly 参数`_build_macos_shell_plan` 复用 `_macos_readonly_profile_for_workspace`受限档终端以只读 profile 创建unrestricted 保持可写 profile
- 原生文件工具对齐`file_manager/path_mixin.py` roots 与沙箱白名单同源系统路径 + 工作区 + 授权+ 叠加同一禁读清单——至此 host+sandbox 下全部读通道只读/可写沙箱命令原生 read_file共享同一边界
- **Linux 宿主机 = bwrap**:只读为 `--ro-bind / /`全局只读可写为 `--ro-bind / /` + 工作区可写 bind——**读侧仍是全局可读尚未对齐白名单**本机无 Linux 测试环境刻意未动待后续**Windows = WSL2 最小根文件系统白名单**——命名空间内只有系统目录+工作区天然符合
## 11) 多智能体对话类型multi-agent conversation type
> **2026-08 重构**:已废除「多智能体模式」全局状态,多智能体是**对话的不可变属性**`metadata.multi_agent_mode = true`创建时确定、不可变。URL 统一为 `/<conv_id>`(旧 `/multiagent/*` 由前端 bootstrap 重定向到裸路径)。数据目录 `~/.astrion/astrion/host/mutiagents/`(保留原拼写)。重构方案见 `docs/conversation_type_unification_plan.md`(本地设计文档,未随仓库发布),本节只列 Agent 改代码时必须知道的硬约束。
### 11.0 前端对话类型模型(重构后)
- 三个语义单一的状态**互不复用**
- `currentConversationType``static/src/app/state.ts`当前已打开对话的类型`'normal' | 'multi_agent' | null` `enterConversation` 从对话 metadata 恢复`bootstrap.ts`空对话为 `null`
- `newConversationType`同上空对话时输入栏待创建类型`'agent' | 'multi_agent'`localStorage 持久化`agents_new_conversation_type`由输入栏底行 `+` 右侧的类型选择器修改`InputComposer.vue` `agent-type-switcher`)。
- `sidebarConversationType``stores/conversation.ts`侧边栏列表过滤器`'normal' | 'multi_agent'`localStorage 持久化`agents_sidebar_conversation_type`驱动列表/搜索的 `multi_agent_mode=0|1` 请求参数与侧边栏新建按钮的对话类型
- `stores/conversation.ts` `multiAgentMode` 字段保留但语义已变为当前对话是否多智能体」,仅由 `enterConversation`meta 落地与空对话 watcher复位 false写入——`stores/subAgent.ts` 的读取点依赖它不得删除
- 侧边栏类型切换 UI分段控件统一显示在搜索对话下方平铺/分组模式位置一致搜索时隐藏列表区域用 `<Transition>` 推挤式同步滑动动画 out-in新面板绝对定位从侧边滑入把旧面板顶出切多智能体向左切回反向切换时重置列表滚动位置)。
- 发送消息创建对话时按 `newConversationType` 选择 `/api/conversations` `/api/multiagent/conversations``message/send.ts`侧边栏新建按 `sidebarConversationType` 选择`conversation/action.ts`)。
### 11.1 角色与实例
- 主智能体显示名固定为 `Team Leader`不需要专门的预置角色文件
- 子智能体 = `role_id`(如 `ui-operator` / `full-stack-engineer` / `code-reviewer` / `researcher`+ 角色内编号同一 role_id 下从 1 递增)。显示名格式 `{Role Name}_{角色内编号}`例如 `UI Operator_1`后缀永远带数字
- **编号暴露原则2026-08-26 **角色内编号显示名是**唯一**对模型和用户暴露的身份全局 `agent_id` 是纯内部实现细节任务字典 key / task_id 生成由系统自动分配对话级最小空闲正整数不接受模型指定不出现在工具参数/结果文案/前端展示中所有寻址类工具send_message/stop/terminate/get_sub_agent_status/sleep wait_sub_agent_output子侧 ask_other_agent一律用显示名
- / / 子三种通信通过工具完成工具签名见 `modules/multi_agent/tools.py`
- **`send_message_to_sub_agent` `ask_sub_agent` 语义不同必须保留两者**前者插入引导消息不阻塞后者阻塞等待一轮回答
- 子智能体间通信要求同时向主智能体输出汇报不允许偷偷沟通」。
### 11.2 子智能体执行机制
- 子智能体在主进程内 `asyncio.Task`跑在独立后台事件循环线程里避开 Flask-SocketIO threading 冲突)。工具调用复用主进程沙箱/容器链路网络调用走 `utils.api_client.APIClient`
- **模型请求重试2026-08-26 **`_run_loop` `_call_model` 包重试循环与主智能体 `run_streaming_attempts` 同构——最多 5 次尝试`_SUB_AGENT_MAX_API_RETRIES=4`)、间隔 10s`_SUB_AGENT_RETRY_DELAY_SECONDS` `asyncio.sleep` 分段等待并响应软停止/取消)。重试条件**仅当零接收**未收到任何文本/思考/工具调用才重试已开始收到内容后断流`SubAgentModelCallError.received_any=True`)直接失败。失败终态分模式:多智能体模式下 5 次全失败 转为 idle + `_forward_output_to_master` 报错 Team Leader 重新下达指令输出期间断开 直接 failed 并同步向主智能体报错传统模式一律 `_write_failure`
- **工具正在调用进度事件2026-08-26 **`_call_model` 在工具名+id 首个流式 chunk 到达时即 emit `status="calling"` 进度事件与后续 running/completed 共用同一 tool_call id前端按 id 原地更新条目`RunnerDetailPanel.vue` / `SubAgentActivityDialog.vue` normalizeStatus 识别 `calling`显示 spinner + 调用中」),并支持同一 id 历史条目跨组原地更新多工具 calling 事件交错场景)。
- 子智能体在多智能体模式下
- `create_sub_agent` 强制 `run_in_background=False`不触发 `sub_agent_waiting` 事件不阻塞前端输入区
- 子智能体自然的 assistant 输出结束 tool_calls即本轮任务结束进入 `idle`上下文保留不算 failed
- 不能把 `output.success == null` 直接判为 `failed`—`_check_task_status()` 对多智能体任务 `status=running/idle` 视为正常态 `modules/sub_agent/state.py`)。
- idle 等待必须用 `asyncio.Event`不能用 `threading.Event`跨线程唤醒用 `call_soon_threadsafe`
- 任务记录里 `multi_agent_mode` 字段必须显式写入缺失会导致传统后台通知池把多智能体任务当后台任务处理。`reconcile_task_states()` 已对旧任务补回
### 11.3 消息池与派发链路(重点)
**输出端 ↔ 接收端分离**
- 输出端`SubAgentTask._forward_output_to_master`子智能体每次 assistant 文本输出都封为标准格式消息并 `push_master_message` 到会话的 `MultiAgentState.pending_master_messages`不再做事
- 接收端`MultiAgentState` + dispatch根据主智能体当前状态选择插入方式
- **情况1主运行中** `execute_tool_calls` 末尾 `process_multi_agent_master_messages(inline=True, after_tool_call_id=...)` 插入到下一轮模型 messages 列表里详见 `server/chat_flow_tool_loop.py:1326`)。
- **情况2主空闲**`poll_multi_agent_notifications` spawn 出后台 poll主对话空闲且 pool 有消息就 drain `_dispatch_multi_agent_idle_messages` 创建 `task_type="notice"` 的后续 task触发新一轮工作
- **情况3主最后一轮无工具调用**主循环 `if not tool_calls:` 分支调 `process_multi_agent_master_messages(inline=False)` `continue` 继续迭代Team Leader 在同 task 续跑处理子智能体输出。(实现上与情况2同路径即情况3通过后续 task 完成情况2)。
**情况2 硬约束(调试踩过坑)**
1. **Pool 优先,不能等所有 running 退出再 drain**`ask_master` await 期间 status=running但子智能体本身不会产生新输出只有等主对话回答才能解套。pool 有消息就立即 drain不管 running 状态。「对话处于运行状态只决定前端显示态不阻塞 pool 消费
2. **`_dispatch_multi_agent_idle_messages` 持久化不要重复**前置 N-1 条调 `inject_multi_agent_master_message` 绑定持久化最后一条只 emit 给在线客户端不持久化在后续 `task_manager.create_chat_task` 走的 `handle_task_with_sender` 里才 `add_conversation`否则历史里会有两条相同 user 消息前端刷新会被渲染两遍一条多智能体渲染一条通知渲染)。
3. **Metadata `visibility` 必须显式为 `"chat"`**`_user_message_ui_defaults("sub_agent")` 默认给 `{visibility: "compact"}` `_dispatch_multi_agent_idle_messages` 构造 `auto_user_message_payload`如果有`**ui_defaults`必须在它之**** `"visibility": "chat"`dict 字面量中后出现的key 胜出)。同样`handle_task_with_sender`处理多智能体消息时`user_message_metadata.update(multi_agent_meta)`之后要显式`user_message_metadata["visibility"] = "chat"`。
4. **必须传 `auto_message_type`**前端 `isMultiAgentMessage()` 只看 `auto_message_type.startsWith('multi_agent_')`。`auto_user_message_payload` `preceding_user_notices[i].payload` 都必须显式写 `auto_message_type`字段不能用空值否则前端 fallback 起通知渲染
### 11.4 通知池轮询器完全独立
- 传统后台子智能体 / 后台 `run_command` 通知`poll_completion_notifications` `handle_task_with_sender` 结尾的 `needs_completion_poll`
- 多智能体通知`poll_multi_agent_notifications` `needs_ma_poll`两者完全独立避免 task_manager 单工作区互斥竞争
- `task_complete` 事件中
- `has_running_sub_agents` 只算传统后台任务不算多智能体
- `has_running_multi_agent` 多智能体专用字段同时包含 _running_ 实例和 pending master 消息详见 `server/chat_flow_task_main.py:2324`)。前端通过该字段走独立的 `startMultiAgentTaskProbe`不启动 `sub_agent_waiting`)。
### 11.5 渲染与前端约定
- 多智能体消息渲染条件是**两个独立判断**
- `isMultiAgentMessage()` `auto_message_type.startsWith('multi_agent_')`
- `getMessageVisibility()` `metadata.visibility`两者都必须正确消息才能走多智能体渲染分支任一错都会 fallback 到通知渲染
- 多智能体消息不显示新的 assistant 回复头部Astrion/工作时间 `metadata.starts_work=false`但前端通过 `has_running_multi_agent` `task_complete` 中单独处理恢复轮询
- 子智能体进度弹窗输出与工具按真实时间线混排默认 3 过长用省略号或内部滚动颜色走 `--text-primary` 等语义 token
- 全局工具规范统一适用于多智能体权限UI 不能引入 `compact` 以外的 fallback 样式习惯复制到多智能体渲染
### 11.6 工具结果格式化与前端渲染位置
新增多智能体工具时必须同步补齐后端结果格式化前端结构化渲染」,否则前端会 fallback 到原始 JSON 或空白
**后端 formatter主智能体工具**
- 实现位置`utils/tool_result_formatter/agent_context.py`
- 注册位置`utils/tool_result_formatter/dispatch.py` `TOOL_FORMATTERS`
- 处理入口所有主智能体工具执行结果最终由 `format_tool_result_for_context()` 转换为自然语言摘要写入对话历史
**后端 formatter子智能体通信工具**
- 实现位置`modules/sub_agent/toolkit.py` `_format_tool_result()`
- 覆盖工具`ask_master` / `ask_other_agent` / `answer_other_agent` / `list_active_sub_agents`
- 处理入口子智能体 tool call 结果回填到子对话上下文前
**前端 renderer主路径**
- 实现位置`static/src/components/chat/actions/toolRenderers.ts` `renderEnhancedToolResult()`
- 调用方
- `static/src/components/chat/MinimalBlocks.vue`
- `static/src/components/chat/StackedBlocks.vue`
- 注意这两个视图已移除 `enhanced_tool_display` 开关判断**所有工具块都强制走结构化渲染**不再显示原始 JSON
**前端 renderer备用路径**
- 实现位置`static/src/components/chat/actions/ToolAction.vue` `renderToolResult()`
- 调用方`static/src/components/chat/ChatArea.vue`
- 同样已移除原始 JSON fallback仅作为 ChatArea 独立工具块渲染的备用
**新增工具 checklist**
1. `modules/multi_agent/tools.py` 定义工具签名
2. `core/main_terminal_parts/tools_execution.py` 实现工具 handler 并返回标准 dict`success` + 业务字段 + 可选 `error`)。
3. `utils/tool_result_formatter/agent_context.py` 新增 `_format_<tool_name>()``dispatch.py` 注册
4. `static/src/components/chat/actions/toolRenderers.ts` 新增对应 `render<PascalCaseToolName>()` 并在 `renderEnhancedToolResult()` 中分发
5. 如果是子智能体通信工具同步在 `modules/sub_agent/toolkit.py` `_format_tool_result()` 中补 formatter
### 11.7 调试机制
- 调试日志统一走 `modules/multi_agent/debug_logger.py` `ma_debug()` 函数写入 `~/.astrion/astrion/host/logs/multi_agent_loop.log`
- 关键状态转换点必须打 `ma_debug`便于复现 bug
- `handle_task_with_sender_start` `pending_master_messages_count`
- `state_push_master_message` 交出 `queue_len_before`
- `poll_ma_tick` 0.5s 一次包含 `instance_count` / `statuses` / `pending_count` / `main_active` 的完整状态快照
- `dispatch_ma_idle_enter` / `dispatch_ma_idle_before_create_task` / `dispatch_ma_idle_task_created` / `dispatch_ma_idle_sender_user_message` / `dispatch_ma_idle_exit_ok`
- `create_chat_task` 异常 / `provide_answer` 跨循环回写
- `runtime_injected` 字段标记metadata 里能区分多智能体 inline / idle / ask插入路径
### 11.8 已知坑
- 多智能体模式下根本不 `emit` `sub_agent_waiting` 事件避免前端进入等待后台子智能体的输入区阻塞态
- `_announced_sub_agent_tasks` / `notified` 等标记仅适用于传统后台子智能体任务多智能体任务不走这条通知路径
- 多智能体任务的 `output.json` `status` 在子智能体自然进入 idle 时会被写为 `"idle"` `_check_task_status` 中由 `_check_task_status_keep_alive` 跳过防止错误判定为 `failed`
- 传统后台通知池 `_collect_pending_completion_notices` `task.get("multi_agent_mode")` 为真时跳过该 task多智能体派出走独立的 `poll_multi_agent_notifications` 路径不当混用
- `_has_pending_completion_work` 主动排除 `multi_agent_mode=True` 的任务两者永远独立
### 11.9 对话切换与状态保留
- 切换会话不清理 `_running_tasks` `_sub_agent_instances`。`SubAgentManager` 的全局 tasks 字典按 `task_id` 保持多智能体状态由 `conversation_id` `get_multi_agent_state` 中查
- 子智能体对话存在 `~/.astrion/astrion/host/host/data/sub_agents/`重启后走 `manager.restore_sub_agent` 恢复实例引用
- **`MultiAgentState` 是进程级全局单例**2026-07 重构存放在 `modules/multi_agent/state.py` `GLOBAL_MULTI_AGENT_STATES`key=`conversation_id`),所有 `SubAgentManager` 实例共享`manager.multi_agent_states` 只是该全局 dict 的引用此前它是 manager 实例属性对话级 terminal 缓存重建会产生多个 manager各自的 `_load_state` 都从磁盘快照 `from_snapshot` 出一份独立副本导致 terminate 只标记其中一份前端轮询落到其他副本显示陈旧 idle。`get_or_create` / `drop` / `_load_state` restore 均通过 `GLOBAL_MULTI_AGENT_STATES_LOCK`RLock互斥
- **进程重启后的状态校准**2026-07 新增`_load_state` 恢复 ma 快照后会用任务记录持久真相校准实例终态——任务记录是 terminated/终态而快照里还是 idle 一律校准为终态同处还有存量 `_None` 后缀显示名的自愈迁移对话×角色×创建时间重编号)。
- **显示名编号语义**显示名后缀 `Full-Stack Engineer_1`**角色内编号**创建路径 `tools_execution.py` 中通过 `peek_agent_id_for_role` + `commit_agent_id_for_role` 两步走—— peek 构造显示名创建成功后才提交计数器**失败不消耗编号**避免跳号全局 `agent_id` `manager.next_free_agent_id()` 自动分配对话级最小空闲正整数两者是两套独立命名空间不得混用
---
本节按现有架构 + 多智能体分支方案描述如与代码冲突以代码为准并同步修订本节
---
## 12) 对话级主任务门闸与单写者不变量2026-08-12
> 事故背景一个对话并发运行了两个主聊天任务socketio 用户任务 + 完成通知轮询器派发的通知任务),交叉写入共享 `conversation_history`,产生 `assistant→assistant→tool→tool` 乱序段,最终 API 400 `tool_call_id is not found`、通知永久丢失。本节机制即为修复该事故引入。
### 12.1 单写者不变量(核心约束)
**一个 WebTerminal≈ 一个打开的对话)同一时刻只允许存在一个主聊天任务。** 主任务包括用户消息任务后台完成通知派发任务多智能体 idle 派发任务等一切会向 `conversation_history` 追加消息并请求模型的执行体宁可拒绝/推迟新任务也绝不并发写入
### 12.2 门闸本体
- 实现`server/main_task_gate.py`进程内字典key=terminal_idvalue=token
- 获取/认领`acquire_or_claim_main_task_gate(terminal_id, owner_desc)`——门闸空闲或持有者是同一任务时返回 token否则返回 None
- 释放`release_main_task_gate(terminal_id, token)`token 不匹配则拒绝防止错误释放他人门闸)。
### 12.3 唯一入口与 token 移交
- **唯一入口**`process_message_task``server/chat_flow.py`是所有主任务的门闸入口——进入即获取/认领门闸拿不到则向用户发 error 并返回绝不强行执行`finally` 中释放
- **通知链移交**完成通知轮询器在派发前先预占门闸`server/chat_flow_task_main.py`token `session_data["main_task_gate_token"]` 移交 `_run_chat_task` `run_chat_task_sync` `process_message_task` token 认领不重复获取派发失败时释放门闸并回滚已打的通知标记`_rollback_completion_notice_marks`)。
- **兜底释放**`_run_chat_task``server/tasks/models.py`外层 `finally` 兜底释放防止异常路径门闸泄漏
### 12.4 改代码注意事项(硬性)
1. **新增任何主任务入口必须走门闸**不要绕过 `process_message_task` 直接驱动一轮模型对话多智能体 idle 派发task_type="notice"目前依赖 `_multi_agent_main_task_active` 标志后续应统一纳管
2. **不要在 return 分支手写 `_tool_loop_active` 恢复**`execute_tool_calls``server/chat_flow_tool_loop.py`已改为守护包装try/finally 复位内层 `_execute_tool_calls_impl`新增提前返回路径无需也不应手动操作该标志——并发交错存旧值置True恢复旧值正是此前标志卡死的原因
3. **不要依赖 build_messages 防御层掩盖并发问题**`core/main_terminal_parts/context/messages.py` 的孤儿 tool 消息剥离只是坏数据不再 400的止血层乱序段本身意味着历史已被污染发现剥离 warning 日志应按事故排查而不是视为正常