agent-Specialization/AGENTS.md
JOJO 8257e4d34d feat(security): 沙箱只读真强制与权限模式边界收敛
- docker:只读/审批档 run_command、后台命令与持久终端改用非特权 uid(10001) 执行角色,内核 DAC 强制只读,取代文本特征识别;Dockerfile 加固(agent 用户 / git safe.directory / 去 setuid)
- macOS:只读与可写沙箱 profile 统一为白名单读模型(deny default + 系统目录/工作区/路径授权),修复 deny 顺序导致的工作区 .env 实际可读漏洞;可写 profile 白名单化后审批不再放大读取,越界读取唯一途径为路径授权
- 权限模式:受限档(readonly/approval/auto_approval)与 direct 执行环境硬互斥——进入受限档压回沙箱并记录,切回 unrestricted 恢复,存量受限+direct 对话加载自愈矫正
- 配置:路径授权来源收敛为 host_sandbox_policy.json + 环境变量两个通道(移除 settings.json 映射)
- 修复:新建对话权限模式被个性化默认值覆盖、/new 切只读后回落无限制的继承 bug
- 原生文件工具读边界与沙箱白名单同源对齐
2026-08-30 22:07:14 +08:00

57 KiB
Raw Blame History

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.pyweb_terminal.pymain_terminal_parts/*;其中 main_terminal_parts/context/main_terminal_parts/tools_definition 已拆分为 base + mixin 子包)
    • modules/: 可复用能力模块terminal/file/memory/sub_agent/upload_security/user 等;file_managerpersistent_terminalterminal_opsmcp_client_manager 已拆分为子包)
    • config/: 配置拆分(api.py, limits.py, terminal.py, paths.py ...),由 config/__init__.py 聚合并加载 .env
    • utils/: API client、日志、上下文与对话工具等公共函数api_clienttool_result_formattercontext_managerconversation_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.jsonskill_hints.json):是程序行为的一部分,随版本演进,仍锚定源码树。prompts/agentskills/ 同理。
  • 部署级配置custom_modelshost_workspacesauto_approvalgoal_reviewforbidden_commandshost_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.jsonweb 用户不覆盖已有)
  • 工作区列表:合并 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.logchunk_*.logconn_diag.logapi_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
  • 推荐启动 Webpython -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 watchnpm run dev
  • Lintnpm run lint

CLIReact / Ink

  • 安装依赖:npm --prefix cli install
  • 开发启动:npm run clinpm --prefix cli run dev
  • 构建:npm run cli:build
  • 类型检查:npm run cli:typecheck
  • 可执行命令名(构建后):agents / agents-cli

3) 测试现状(不要再写过时命令)

  • 当前仓库内可见自动化冒烟:test/test_server_refactor_smoke.pyunittest
    • 运行方式: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.tsxcli/src/components.tsxcli/src/eventMapper.tscli/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) 前端设计风格统一规范(强制)

适用范围:所有前端 UIstatic/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 三条规则做防回退栏杆,buildstylelint 步骤。

  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-scrimposition: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.vuetoolRenderers.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:优先复用项目已有 loggerutils/logger.pymodules/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 Commitsfeat: 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 commitgit push
  • commit / push 授权均为一次性:用户说“可以 commit/push”只代表允许这一次操作后续如需继续 commit / push必须由用户再次主动同意。
  • 允许 commit 不等于允许 push两者授权相互独立获得 commit 许可后仍需单独征得 push 许可。
  • 不需要每次都执行或输出 diff如用户要求 Review再在同一对话内使用 git diffgit 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 diffgit 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(本地私有脚本,未随仓库发布)

禁止只改前端代码而不更新版本号/更新说明,否则会导致客户端更新提示与分发信息不一致。

9) 给 Agent 的硬性要求

  • 先读当前代码再执行,不依赖历史文档记忆。
  • 输出结果按改动规模决定:
    • 大修改:必须包含核心变更点、验证结果(已执行命令/未执行原因)。
    • 小修改:只需包含核心变更点。
  • 不要求每次列出所有修改文件;仅在用户要求、改动复杂或有助于说明时列出。
  • 不要求每次执行或展示 diff仅在用户要求 Review 或排查差异时使用。
  • 如果发现本文档过时,直接更新 AGENTS.md 并在结果中说明依据。

10) 宿主机权限模式与沙箱执行机制2026-05 更新)

完整说明文档:docs/host_sandbox_and_permission_model.md(本地文档,未随仓库发布)

为避免误解,当前系统有两套独立但叠加的控制:

  1. 权限模式Permission Modereadonly / approval / auto_approval / unrestricted
  2. 执行环境Execution Modesandbox / direct(仅宿主机模式可切换)

10.1 权限模式

  • readonly
    • run_command 可调用,但在只读沙箱执行;写入由系统拒绝
    • 读取同样受限(各平台只读强制的可读边界见 §10.8);读不到属预期边界,不是故障
    • write_file / edit_file 等写入类工具直接拒绝
  • approval
    • run_command 先走只读沙箱(可读边界见 §10.8,读越界也会触发权限拒绝)
    • 若出现权限拒绝(例如 Operation not permitted / Permission denied),触发前端审批
    • 审批通过后,仅该次命令以可写沙箱重试;工具结果返回“重试后的最终结果”
    • 审批只授予该次命令的工作区内写权限,不放大读取2026-08-30 方案一):可写沙箱读边界与只读同一白名单,读越界唯一途径是「路径授权」;重试后仍为文件权限拒绝时,工具结果附带 read_scope_hint 引导模型说明
    • 执行环境锁定为沙箱2026-08-30 起direct 下无沙箱兜底,启发式漏判的写命令会直接执行成功、审批承诺被架空,故批准/自动审核与 direct 硬互斥(锁定机制同 plan/只读,见 §10.6
  • auto_approval
    • write_file / edit_file:工作区内路径直接执行,工作区外路径进入审批流程
    • run_command 先走只读沙箱(可读边界见 §10.8);触发权限拒绝后由后台审批智能体自动审核
    • 自动审核拒绝时,工具返回“被拒绝+理由”并继续主循环(不强制结束任务)
    • 批准后的读取边界与 approval 相同(不放大)
    • 执行环境同样锁定为沙箱(同 approval 条目)
  • unrestricted
    • 权限层不做拦截:工作区内读写自由、命令免批准;是否沙箱由执行环境决定
    • 注意「无限制」只管工作区内:宿主机沙箱执行时读边界仍是白名单(见 §10.8授权范围外读取需路径授权direct 模式才真正无边界

10.2 执行环境

  • sandbox(默认):使用 OS 沙箱执行macOS: sandbox-execLinux: bwrap + seccompWindows: 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.jsonUI 唯一读写目标)+ 真·环境变量 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.pyDEBUG_SAVE_APPROVAL_AGENT_TRANSCRIPT
    • 开启后写入:logs/approval_agent/
    • 记录以累积 messages 为主,便于对齐主/子智能体会话格式

10.6) 运行模式work_mode系统2026-08-13 新增)

运行模式(work_modeplan / 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.pyWORK_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 第四种),通知携带完整新规则文本
  • APIGET/POST /api/work-modeserver/chat/permission.py仅空闲可切换,运行中 409(无 pending 队列);plan-approvals pending/answer 端点在 server/chat/approval.py
  • submit_plan 链路:工具全模式注入(沟通类工具不过滤,非 plan 调用由 handler 运行时兜延返回引导);PlanApprovalManagermodules/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-switcherInputComposerstreaming 时锁定plan 下权限菜单内「权限」「执行环境」两组选项禁用+锁定标记(网络权限组保持可调不整体锁死菜单slash 菜单同步禁用;get_statuswork_mode 供多标签页同步

10.7) 后台命令只读沙箱修复2026-08-13

后台 run_commandrun_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 执行角色modules/docker_readonly_exec.py
    • 容器主进程保持 root可写执行不变sandbox_write_access=False 的执行通道(terminal_ops/run.pybackground_command_manager.py、只读语境创建的持久终端)以 -u 10001:10001 运行,DOCKER_READONLY_EXEC_UID/GID 可覆盖
    • 强制力 = 内核 DAC工作区属主为宿主机 root非属主无写权600 权限文件(如 .env不可读逃逸需提权setuid/内核漏洞),无 umount 类捷径
    • 前提:工作区属主与该 uid 不碰撞、无 o+w 文件、容器未挂 docker.sock云端已验证macOS Docker Desktopvirtiofs fakeowner不执行 uid 权限,仅 Linux 宿主生效
    • 持久终端在 readonly/approval/auto_approval 下同以只读身份创建(docker_terminal_readonly_enabled 判定,docker_readonly_getter 注入);终端里的写入会被拒,写命令走 run_command 审批通道
    • 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/审批批准后能读授权范围外文件);白名单固有代价:祖先目录顶层文件名可列出(读文件内容仍被拒)
    • 原生文件工具对齐(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 前端对话类型模型(重构后)

  • 三个语义单一的状态,互不复用
    • currentConversationTypestatic/src/app/state.ts):当前已打开对话的类型('normal' | 'multi_agent' | null),由 enterConversation 从对话 metadata 恢复(bootstrap.ts),空对话为 null
    • newConversationType(同上):空对话时输入栏待创建类型('agent' | 'multi_agent'localStorage 持久化(agents_new_conversation_type),由输入栏底行 + 右侧的类型选择器修改(InputComposer.vueagent-type-switcher)。
    • sidebarConversationTypestores/conversation.ts):侧边栏列表过滤器('normal' | 'multi_agent'localStorage 持久化(agents_sidebar_conversation_type),驱动列表/搜索的 multi_agent_mode=0|1 请求参数与侧边栏新建按钮的对话类型。
  • stores/conversation.tsmultiAgentMode 字段保留,但语义已变为「当前对话是否多智能体」,仅由 enterConversationmeta 落地)与空对话 watcher复位 false写入——stores/subAgent.ts 的读取点依赖它,不得删除。
  • 侧边栏类型切换 UI分段控件统一显示在「搜索对话」下方平铺/分组模式位置一致,搜索时隐藏);列表区域用 <Transition> 推挤式同步滑动动画(无 out-in新面板绝对定位从侧边滑入把旧面板顶出切多智能体向左切回反向切换时重置列表滚动位置
  • 发送消息创建对话时按 newConversationType 选择 /api/conversations/api/multiagent/conversationsmessage/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_agentask_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 退出再 drainask_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_payloadpreceding_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_agenttask_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.pyTOOL_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.tsrenderEnhancedToolResult()
  • 调用方:
    • static/src/components/chat/MinimalBlocks.vue
    • static/src/components/chat/StackedBlocks.vue
  • 注意:这两个视图已移除 enhanced_tool_display 开关判断,所有工具块都强制走结构化渲染,不再显示原始 JSON。

前端 renderer备用路径

  • 实现位置:static/src/components/chat/actions/ToolAction.vuerenderToolResult()
  • 调用方: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 并返回标准 dictsuccess + 业务字段 + 可选 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.pyma_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.jsonstatus 在子智能体自然进入 idle 时会被写为 "idle";在 _check_task_status 中由 _check_task_status_keep_alive 跳过防止错误判定为 failed
  • 传统后台通知池 _collect_pending_completion_noticestask.get("multi_agent_mode") 为真时跳过该 task多智能体派出走独立的 poll_multi_agent_notifications 路径,不当混用。
  • _has_pending_completion_work 主动排除 multi_agent_mode=True 的任务;两者永远独立。

11.9 对话切换与状态保留

  • 切换会话不清理 _running_tasks_sub_agent_instancesSubAgentManager 的全局 tasks 字典按 task_id 保持,多智能体状态由 conversation_idget_multi_agent_state 中查。
  • 子智能体对话存在 ~/.astrion/astrion/host/host/data/sub_agents/。重启后走 manager.restore_sub_agent 恢复实例引用。
  • MultiAgentState 是进程级全局单例2026-07 重构):存放在 modules/multi_agent/state.pyGLOBAL_MULTI_AGENT_STATESkey=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_LOCKRLock互斥。
  • 进程重启后的状态校准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_idmanager.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_taskserver/chat_flow.py)是所有主任务的门闸入口——进入即获取/认领门闸,拿不到则向用户发 error 并返回,绝不强行执行;finally 中释放。
  • 通知链移交:完成通知轮询器在派发前先预占门闸(server/chat_flow_task_main.pytoken 经 session_data["main_task_gate_token"] 移交 _run_chat_taskrun_chat_task_syncprocess_message_task(持 token 认领,不重复获取);派发失败时释放门闸并回滚已打的通知标记(_rollback_completion_notice_marks)。
  • 兜底释放_run_chat_taskserver/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_callsserver/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 日志应按事故排查,而不是视为正常。