diff --git a/.astrion/memory/agent_hooks_design.md b/.astrion/memory/agent_hooks_design.md new file mode 100644 index 0000000..b1b1d12 --- /dev/null +++ b/.astrion/memory/agent_hooks_design.md @@ -0,0 +1,153 @@ +--- +name: agent_hooks_design +description: 当需要继续讨论或实现本项目的 hooks / 生命周期钩子功能时,应该索引本记忆 +--- + +# Hooks / 生命周期钩子功能设计讨论(进行中) + +## 调研结论 + +### 主流实现 +- **Claude Code**:最完整,20+ 事件、5 种 handler(command/http/mcp_tool/prompt/agent)、多层配置(全局/项目/本地/组织/插件/SDK)。 +- **OpenAI Codex**:11 个事件、command 回调为主、有信任审查机制和企业托管 hooks。 +- **Cursor**:`.cursor/hooks.json`,Beta,command 回调,项目/用户/企业三级。 +- **GitHub Copilot**:`.github/hooks/*.json`,command/http/prompt 三种回调,Cloud Agent 也支持。 +- **Windsurf/Cascade**:`.windsurf/hooks.json`,exit 2 阻断,12 个事件。 +- **Aider / Continue.dev / Kimi Code / Devin**:无原生 hooks 或资料有限。 + +### 共性模式 +``` +Agent 事件发生 + ↓ +stdin 发送 JSON 事件载荷 → Hook 脚本 / HTTP 端点 + ↓ +返回决策(allow/deny/ask/modify/injectMessage) + ↓ +Agent 根据决策继续执行 +``` + +## 用户(项目 owner)对 hooks 的理解 + +> 钩子 = 发生一件事件之前/中/后 → 自动触发另一个事件。用 JSON 格式写条件和行为,分为全局/项目级,放在数据存储目录下/工作区内。 + +用户认可的核心:**事件驱动 + JSON 配置 + 全局/项目两级 + before/after 拦截**。 + +## 已达成的设计共识 + +### 1. 配置位置 +- 全局:`~/.astrion/astrion/config/hooks.json` +- 项目级:`/.astrion/hooks.json` +- 未来可扩展:`.astrion/hooks.local.json`、企业策略级。 +- 多层配置**合并执行**,不是覆盖(更安全)。 + +### 2. 优先事件(MVP 范围建议) +- `PreToolUse`:工具执行前拦截/修改 +- `PostToolUse`:工具执行后触发后续动作 +- `Stop`:模型/turn 结束时注入反馈 +- `PreCompact`:上下文压缩前备份 + +### 3. 优先回调类型 +- MVP 只做 `command`(shell/python/任意可执行文件)。 +- 后续可扩展 `http`、`prompt`、`agent`、`mcp_tool`。 + +### 4. 通信协议(对齐 Claude Code/Codex) +- stdin 输入 JSON 事件载荷 +- stdout 返回 JSON 决策 +- exit code:0=通过,1=非阻断错误,2=阻断 + +### 5. JSON 结构草案 + +```json +{ + "version": 1, + "disabled": false, + "hooks": { + "PreToolUse": [ + { + "name": "block-dangerous-rm", + "matcher": "run_command", + "if": "command contains 'rm -rf /'", + "enabled": true, + "max_repeats": null, + "hooks": [ + { + "type": "command", + "command": "python3 ~/.astrion/astrion/hooks/block_dangerous.py", + "timeout": 5, + "statusMessage": "检查危险命令...", + "fail_policy": "continue", + "on_error": "log" + } + ] + } + ], + "Stop": [ + { + "name": "remind-check-completion", + "max_repeats": 3, + "hooks": [ + { + "type": "command", + "command": "python3 ~/.astrion/astrion/hooks/remind_check.py", + "timeout": 5 + } + ] + } + ] + } +} +``` + +## 已讨论的具体例子 + +### 例子 1:拦截危险命令 +- 事件:`PreToolUse` +- 条件:`matcher=run_command` + `if=command contains 'rm -rf /'` +- 行为:调用脚本,返回 `permissionDecision: deny` + +### 例子 2:edit_file 单行全替换触发审核 +- 事件:`PreToolUse` +- 条件:`matcher=edit_file` + `old_string` 只有一行 + `replace_all=true` +- 行为:返回 `permissionDecision: ask` + +### 例子 3:Stop 时自动插入 user 消息,最多 3 次 +- 事件:`Stop` +- 行为:返回 `injectMessage: {role: "user", content: "请仔细检查是否真的完成了所有工作"}` +- "最多 3 次"可用 `max_repeats` 或由脚本自己维护状态文件。 + +## 尚未决定的决策点 + +1. **`if` 条件表达式语法**: + - 选项 A:简单规则 DSL(如 `command contains '...'`) + - 选项 B:结构化 JSON(如 `{"field": "...", "op": "contains", "value": "..."}`) + - 选项 C:交给 hook 脚本自己判断,`if` 只保留工具名匹配 + +2. **"最多 N 次"由谁负责**: + - 框架负责 `max_repeats`(简单,但状态语义固定) + - 脚本自己维护状态文件(灵活,框架更轻量) + +3. **注入消息语义**: + - `injectMessage.user`:会触发模型再回复一轮 + - `addContext.system`:只是给下一轮加提示,不立刻触发回复 + +4. **hook 失败时的主流程策略**: + - 默认 `continue`(用户配置错误不瘫痪智能体) + - 或提供 `fail_policy: block` 让管理员强制策略 + +5. **工具名匹配**: + - 是否同时支持内部工具名(`run_command`/`edit_file`)和 Claude Code 风格别名(`Bash`/`Edit|Write`) + +## 推荐后续路线 + +1. 先定 JSON schema 和 `if` 语法。 +2. MVP 实现 `command` 回调 + `PreToolUse`/`PostToolUse`/`Stop`。 +3. 新增 `modules/hook_manager.py`。 +4. 在 `core/main_terminal_parts/tools_execution.py` 和 `core/web_terminal.py` 插入 hook 调用。 +5. 确保 API task 模式也走 hooks。 +6. 补文档 `docs/hooks.md`。 + +## 参考资料位置 + +- 调研报告:`research/claudecode_hooks/claudecode_hooks.md` +- 调研报告:`research/codex_hooks/codex_hooks.md` +- 调研报告:`research/other_agents_hooks/other_agents_hooks.md` diff --git a/.astrion/memory/ai_avatar_demo.md b/.astrion/memory/ai_avatar_demo.md new file mode 100644 index 0000000..5fe4af6 --- /dev/null +++ b/.astrion/memory/ai_avatar_demo.md @@ -0,0 +1,68 @@ +--- +name: ai_avatar_demo +description: 当需要了解或继续开发/修改 agents 项目的 AI 形象头像 demo 时,应该索引本记忆 +--- + +--- +name: ai_avatar_demo +description: 当需要了解或继续开发/修改 agents 项目的 AI 形象头像 demo 时,应该索引本记忆 +--- + +# AI 形象头像 Demo + +## 文件位置 +`/Users/jojo/Desktop/agents/正在修复中/agents/demo/ai-avatar/index.html` + +## 设计目标 +为项目设计一个独特、动态的 AI 形象,替代之前从网上找的通用小机器人 SVG。 + +## 当前设计 + +### 视觉构成 +- **外框**:flat-top 圆角正六边形(上下边水平,底部是一条横线),中心 `(100,100)`,外接圆半径 80,圆角半径 14。 +- **内部符号**:由两条等粗白色线段组成: + - 空闲态:两条竖线作为眼睛,长度 22,间距 32,位于 `y ≈ 84`(面部上半区域略偏上)。 + - 工作态:`>` 终端提示符(无下横线),尖端朝右,开角 60°,单条线长度 34。 + +### 状态与动画 +- **空闲**:六边形停到最近的 60° 正位,显示眼睛,眼睛跟随鼠标移动。 +- **工作**:六边形开始持续旋转,显示 `>` 符号。 +- 状态切换时内部符号立即变形,不等待外框停稳;外框则从当前角度平滑过渡到目标角度或开始旋转。 +- 内部符号在空闲/工作之间通过**同两条 ``** 进行 morph: + - `transform`(位置 + 旋转)使用 CSS `transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1)`。 + - `x2`(线段长度)使用同节奏(0.55s,相同 cubic-bezier)的 `requestAnimationFrame` 插值,避免长度跳变。 +- 提供一个独立的"停止追踪 / 开始追踪"按钮:停止追踪时眼睛平滑回到默认居中位置。 + +### 技术实现 +- **纯 SVG**:外框用 ``,眼睛 / `>` 用 ``,整体用 `` + ``。 +- **动画方式**: + - 六边形旋转:CSS `@keyframes spin` + CSS `transition`。 + - 符号变形:CSS `transition` 改变 `transform`;JS `requestAnimationFrame` 插值改变 `x2`。 + - 眼睛追踪:JS `requestAnimationFrame` 实时更新 SVG `` 的 `transform`。 +- **无 Canvas / WebGL / GIF / Lottie / 图片序列帧**。 +- 整体缩放不会导致比例或长度失调(矢量 SVG)。 + +## 设计约束 + +### 不可改动的核心动画 +`demo/ai-avatar/index.html` 中 **空闲(idle)** 与 **工作(work)** 两个核心状态的切换动画不可改动: + +- 六边形外框:空闲时从当前角度滑到最近的 60° 正位后停住;工作时从当前角度开始持续旋转。 +- 内部图标:空闲为眼睛,工作为 `>` 符号,二者通过同两条 `` 元素进行 morphing 变形。 +- 变形节奏统一为 0.55s cubic-bezier(0.22, 1, 0.36, 1): + - `transform` 由 CSS transition 处理。 + - `x2` 长度由 JS `requestAnimationFrame` 插值处理(CSS transition 不直接支持 SVG `x2` 属性)。 + +### 设计原则 +- 只允许 SVG 的**微动 / 线条 / 点**之间的**优雅细微动化**。 +- 禁止花哨特效:脉冲波纹、光标闪烁、发光、强烈缩放、大幅度弹跳等。 +- 新增工作状态时,应保持通用切换容器,避免为每个 SVG 单独适配位置。 +- 内部图标线条应比外框稍细,保持视觉层次感。 + +## 关键调整历史 +- 外框从 pointy-top 改为 flat-top(横线在底部)。 +- 边角从锐利直线改为 Q 贝塞尔圆角。 +- 状态含义反转:空闲 = 静止 + 眼睛 + 追踪;工作 = 旋转 + `>`。 +- 眼睛长度缩短,位置上移至 `y=84`。 +- 鼠标追踪范围水平/垂直均为 ±10px。 +- 2026-07-01:为 `x2` 长度变化增加与 `transform` 同节奏的 JS 插值动画,解决工作/休息切换时长度跳变问题。 diff --git a/.astrion/memory/app_initialization_flow.md b/.astrion/memory/app_initialization_flow.md new file mode 100644 index 0000000..7c9cee3 --- /dev/null +++ b/.astrion/memory/app_initialization_flow.md @@ -0,0 +1,30 @@ +--- +name: app_initialization_flow +description: 当需要适配桌面 App 初始化流程或理解项目首次启动配置时,应该索引本记忆 +--- + +## 项目初始化流程(首次启动 / 桌面 App 适配必读) + +### 关键文件 +- `_bootstrap.sh` — 共享引导(定位项目根、准备 Python venv + Node 依赖) +- `setup.sh` — 首次初始化入口:调 `_bootstrap.sh` 后运行 `python -m scripts.setup` +- `scripts/setup.py` — **交互式配置向导**(终端),5 步生成 `.env` + `custom_models.json` + +### setup.py 向导步骤 +1. **运行模式** — `host`(单机本地沙箱)/ `web`(多用户 Docker),决定 `TERMINAL_SANDBOX_MODE` +2. **Web 监听** — 端口 (`WEB_SERVER_PORT`) + 地址 (`WEB_SERVER_HOST`),host 模式默认 127.0.0.1 +3. **数据目录** — `~/.astrion/astrion//`,可自定义(设 `ASTRION_DATA_ROOT`) +4. **管理员账户** — `AGENT_ADMIN_USERNAME` + `AGENT_ADMIN_PASSWORD_HASH`(werkzeug hash) +5. **模型配置** — 写入部署级 `custom_models.json`(~/.astrion/astrion/config/):model_name / url / apikey / model_id / thinking 支持 +6. **自动生成** — `WEB_SECRET_KEY` + `API_TOKEN_SECRET`(secrets.token_hex(32)) + +### 设计要点 +- 向导**刻意不 import config**,以免在 `.env` 写之前过早锁定路径 +- 以 `.env.example` 为模板,仅替换被接管键,其余原样保留(含注释) +- `.env` 写完后 chmod 600 + +### 桌面 App 适配要点 +- 桌面 App 应固定 `TERMINAL_SANDBOX_MODE=host` +- 端口应自动找可用端口 +- 模型配置可在 Web UI 中后续修改,但首次启动至少需要一个模型 +- `_deploy_config_dir()` 推导逻辑必须与 `config/paths.py` 保持一致 diff --git a/.agents/memory/astrion_naming.md b/.astrion/memory/astrion_naming.md similarity index 100% rename from .agents/memory/astrion_naming.md rename to .astrion/memory/astrion_naming.md diff --git a/.astrion/memory/background_notification_pool.md b/.astrion/memory/background_notification_pool.md new file mode 100644 index 0000000..2b6b1aa --- /dev/null +++ b/.astrion/memory/background_notification_pool.md @@ -0,0 +1,109 @@ +--- +name: background_notification_pool +description: 当需要讨论或修复后台子智能体/后台 run_command 完成通知机制时,应该索引本记忆 +--- + +# 后台子智能体 / 后台 run_command 通知机制问题(待修复) + +## 问题描述 + +后台子智能体和后台 `run_command` 在“主智能体停止输出后完成”的场景下,通知机制存在串行化问题: + +- 若同时有多个后台任务在运行,它们不会随完成逐个通知。 +- 而是在所有后台任务全部完成后,按时间顺序一条一条地发送 user 消息。 +- 且每发送一条 user 消息就要触发一次“工作 → 停止 → 再工作”的循环,导致通知被拆成多次停止输出。 + +## 当前机制 + +运行期间(inline)和停止输出后(polling)是两条链路: + +### 1. 运行期间(inline,当前正常) + +- 位置:`server/chat_flow_tool_loop.py` 中 `execute_tool_calls` 末尾。 +- 调用: + - `process_sub_agent_updates(..., inline=True)` + - `process_background_command_updates(..., inline=True)` +- 行为:每次工具循环结束时,把**当前所有已完成且未通知**的任务一次性插入 `messages`,不触发新任务。 +- 结论:运行期间的批量通知已经是“池化”的,暂无问题。 + +### 2. 停止输出后(polling,问题所在) + +- 位置:`server/chat_flow_task_main.py` 中 `handle_task_with_sender` 结尾。 +- 调用: + - `poll_sub_agent_completion(...)` + - `poll_background_command_completion(...)` +- 行为: + - 两个轮询器各自由 `socketio.start_background_task(...)` 启动。 + - 每轮循环只处理**一个**完成的通知。 + - 处理完一个通知后,创建一个新的 chat task,然后 `return`。 + - 下一个通知必须等待这个新 chat task 跑完,并再次走到 `handle_task_with_sender` 结尾重新 spawn 轮询器时才会发出。 + +## 根因 + +后台任务管理器本身已经扮演了“后台池 + 通知池”的角色(任务完成但 `notified=False` 就是待通知状态)。 + +但轮询器是 **one-shot** 的:每次只消费一条通知就退出,导致通知被串行化到多次“工作 → 停止 → 再工作”循环中。 + +## 讨论中的设计方案 + +统一为“通知池轮询器”: + +1. 合并 `poll_sub_agent_completion` 和 `poll_background_command_completion` 为一个统一的 `poll_completion_notifications`。 +2. 每次轮询时,同时从以下两处取出**所有**待通知项: + - `sub_agent_manager.poll_updates()` + - `background_command_manager.poll_updates(conversation_id=...)` +3. 把所有待通知项作为 user 消息插入对话历史(倾向保留为多条消息,而非合并成一条)。 +4. 只创建 **一个**后续 chat task 来触发继续工作。 +5. 轮询器返回,由这个新任务在结束时继续下一轮轮询。 + +### 期望行为示例 + +- 3 个后台子智能体运行中,主智能体停止输出。 +- 第 1 个完成 → 通知池有 1 条 → 插入 1 条 user 消息 → 触发 1 个新任务。 +- 新任务处理期间,第 2、3 个完成 → 进入通知池。 +- 新任务停止输出 → 轮询器发现通知池还有 2 条 → 一次性插入 2 条 user 消息 → 再触发 1 个新任务。 +- 不再有后台任务时,轮询器直接退出。 + +## 涉及文件 + +| 文件 | 角色 | +|---|---| +| `server/chat_flow_task_support.py` | 新增统一轮询器 `poll_completion_notifications`;可废弃旧的两个轮询函数。 | +| `server/chat_flow_task_main.py` | 在 `handle_task_with_sender` 结尾,把分别 spawn 两个轮询器的逻辑替换为 spawn 一个统一轮询器。 | +| `modules/sub_agent/manager.py` | 提供 `poll_updates()`,基于 `notified` 标志表示待通知状态。 | +| `modules/background_command_manager.py` | 提供 `poll_updates()` 和 `has_pending_for_conversation()`。 | + +## 待决策点 + +- 是否坚持把通知拆成多条 user 消息插入历史(推荐),还是合并为一条。 +- 统一轮询器是否做成“持久单例”还是保持 one-shot batch(当前讨论倾向 one-shot batch,由新任务继续轮询)。 + +## 状态 + +已实施(2026-06-24,方案 A + one-shot batch)。 + +### 落地实现 + +- `server/chat_flow_task_support.py` 未改;新增逻辑集中在 `server/chat_flow_task_main.py`。 +- 新增统一轮询器 `poll_completion_notifications`,替换并删除旧的 `poll_sub_agent_completion` / `poll_background_command_completion`。 +- 新增 `_collect_pending_completion_notices`:每轮一次性从子智能体 + 后台命令两路取出所有待通知项(取出即就地标记 `notified` / `_announced_sub_agent_tasks`),按 `updated_at` 跨两路排序。 +- 新增 `_has_pending_completion_work`:判断是否还有运行中/待通知的后台工作,决定轮询是否继续。 +- 新增 `_build_sub_agent_notice_text`:抽出子智能体通知文本构建。 +- `handle_task_with_sender` 结尾:保留 `sub_agent_waiting` 事件探测,但把「分别 spawn 两个轮询器」改为只 spawn **一个** `run_completion_poll`。**这也顺带消除了两个轮询器同时 `create_chat_task` 撞 `create_chat_task` 单工作区互斥(`models.py` RuntimeError)的隐患。** + +### 方案 A 的「多条 + 单 task」如何落地 + +`create_chat_task` 只收单条 message,`handle_task_with_sender` 开头无条件 `add_conversation`。因此: + +- 一批取出 N 条:前 N-1 条为「前置通知」,最后 1 条作为后续 chat task 的触发消息。 +- `_dispatch_completion_user_notice` 新增 `preceding_notices` 参数: + - 通过 `_persist_and_echo_preceding_notice` 把前 N-1 条**写入对话历史**(`add_conversation`,落盘)+ **socketio 回显**(在线客户端即时可见),`starts_work=False`(渲染为完成态气泡,不起独立计时器)。 + - 把 `preceding_user_notices` 塞进 `session_data`,传给后续任务。 +- `server/tasks/models.py` 的 `_run_chat_task`:在补发触发 user_message 事件**之前**,先按顺序把 `preceding_user_notices` 逐条 `_append_event("user_message", ...)` 注入任务事件流——保证 REST 轮询客户端按时间顺序看到所有完成通知。 +- 触发消息(最后 1 条)走原有 `auto_user_message_event` 链路,正常 `starts_work=True` 启动一轮工作。 + +### 验证 + +- `python3 -m py_compile server/chat_flow_task_main.py server/tasks/models.py` 通过(注意本机 pycache 目录无写权限,需 `PYTHONPYCACHEPREFIX=$TMPDIR/pycache`)。 +- `python3 -m unittest test.test_server_refactor_smoke` 6 用例通过。 +- 前端未改:复用既有 `user_message` / `sub_agent_notice` 渲染链路(`messaging.ts` handleUserMessage + socketio `user_message`),dedup 与单通知场景一致。 diff --git a/.astrion/memory/chat_block_scroll_anchor.md b/.astrion/memory/chat_block_scroll_anchor.md new file mode 100644 index 0000000..6dcd4fa --- /dev/null +++ b/.astrion/memory/chat_block_scroll_anchor.md @@ -0,0 +1,49 @@ +--- +name: chat_block_scroll_anchor +description: 当需要优化聊天区域块展开/折叠滚动行为,或排查向上滚动后瞬间跳回问题时,应该索引本记忆 +--- + +# 聊天区域块展开/折叠滚动行为优化记录 + +## 背景 +聊天区域默认使用 `vue-stick-to-bottom` 保持底部锁定。块(thinking/tool)展开/折叠时,`stick-to-bottom` 的弹簧追底动画和 CSS 高度过渡不同步,会产生「向下展开一点、向上滚动一点」的顿挫感。 + +## 已尝试方案 + +### 1. 块展开锚定(useBlockExpansionAnchor) +- 在 `static/src/composables/useBlockExpansionAnchor.ts` 中实现 +- 块展开/折叠的 CSS 过渡期间(约 260–300ms),用 `requestAnimationFrame` 持续覆盖 `scrollTop` +- 目标:让被操作块的底边或顶边保持在原来视口位置 +- 方向判断: + - 用**底边**判断比顶边更准确。高块即使顶边位于上半,底边仍可能在下半;用顶边判断会把高块误判为 `down` 模式 + - `up` 模式:底边不动,头部向上展开 + - `down` 模式:顶边不动,内容向下展开 +- 方向在单次展开-收起周期内记忆(`preferredModes`),保证收起是展开的倒放;收起动画结束后清除记录 + +### 2. 运行期间策略 +- 运行中的 thinking/tool 块(带 `.processing`/`.running`/`.thinking-animation`)保持旧逻辑,由 `stick-to-bottom` 追底,避免被输入栏遮挡 +- 非运行中的块(包括运行期间的历史块)走锚定逻辑 +- 已实现 commit:`f43b5ea7 fix(chat): 运行期间恢复旧滚动逻辑,堆叠块更多按钮也接入锚定` + +### 3. 堆叠块「更多」按钮 +- `StackedBlocks.vue` 在 toggleMore 时 emit `more-toggle`,`ChatArea.vue` 监听后对 more-block 元素启动锚定 +- 注意:事件 payload 里的展开状态应使用 `payload.expanded`,避免 `ReferenceError` + +## 未解决的向上滚动跳回问题 + +### 现象 +- 在块较多的对话中,**向上滚动**后页面会瞬间跳回滚动前的位置 +- 向下滚动不会出现 +- 禁用 `anchorBlockElement` 调用、禁用 wheel-up 时的 `stopScroll()` 后问题依然存在 + +### 调试结论 +- 不是 `anchorBlockElement` 直接导致 +- 不是 `stopScroll()` 直接导致 +- `heightDelta` 可能为负(内容高度减小),但 large-growth 检测未触发 +- `escapedFromLock` 已为 `true`,`stick-state-change` 未出现异常变化 +- 原因未最终定位,可能与 `vue-stick-to-bottom` 内部状态、浏览器原生滚动行为或内容高度变化后的布局抖动有关 + +## 当前代码状态 +- 当前 HEAD:`f43b5ea7 fix(chat): 运行期间恢复旧滚动逻辑,堆叠块更多按钮也接入锚定` +- 工作区干净,所有调试和临时修改已撤回,stash 已清空 +- 后续若继续优化,建议从 `f43b5ea7` 开始,并优先定位向上滚动跳回的根本原因 diff --git a/.astrion/memory/cloud_server_disk_usage.md b/.astrion/memory/cloud_server_disk_usage.md new file mode 100644 index 0000000..f10c48b --- /dev/null +++ b/.astrion/memory/cloud_server_disk_usage.md @@ -0,0 +1,148 @@ +--- +name: cloud_server_disk_usage +description: 当需要了解云服务器磁盘使用现状、规划Docker镜像构建空间、或决定清理哪些文件释放空间时,应该索引本记忆 +--- + +# 云服务器磁盘使用现状 + +> 记录时间:2026-06-25 +> 服务器:59.110.19.30(Ubuntu 24.04.2 LTS, x86_64) + +## 总体情况 + +```text +Filesystem Size Used Avail Use% +/dev/vda3 49G 43G 4.0G 92% +``` + +- 总磁盘:约 49 GB +- 已用:约 43 GB(92%) +- 可用:约 4 GB + +**结论:磁盘非常紧张,不足以直接本地构建方案 B 的 2.9GB Docker 镜像。** + +## 占用大户(按大小排序) + +| 路径 | 大小 | 说明 | +|------|------|------| +| `/var/lib/docker` | **18 GB** | Docker 镜像、容器、volume | +| `/var/log/journal` | **4.1 GB** | systemd journal 日志 | +| `/opt/agent` | **5.0 GB** | agents 项目源码、运行时、备份、镜像 | +| `/opt/openwebui` | **3.6 GB** | OpenWebUI 部署目录 | +| `/var/lib/filebrowser` | **3.0 GB** | filebrowser 数据 | +| `/swapfile` | **2.1 GB** | swap 文件 | +| `/home/git/data` | **856 MB** | gitea 数据 | +| `/home/admin/go` | **770 MB** | Go 相关文件/缓存 | +| `/var/www` | **1.4 GB** | nginx web 目录 | +| `/var/lib/clamav` | **1.2 GB** | 病毒库 | +| `/usr` | **4.2 GB** | 系统软件 | + +## Docker 详细占用 + +```text +Images: 4 5.284 GB + - ghcr.io/open-webui/open-webui 4.32 GB + - my-agent-shell amd64 913 MB + - my-agent-shell latest 913 MB(与 amd64 同 image id) + - python 3.11-slim 124 MB + - ghcr.io/corentinth/it-tools latest 56.2 MB + +Volumes: 1 7.966 GB + - open-webui: 7.966 GB + +Containers: 3 + - agent-term-jojo-default my-agent-shell:latest Up + - openwebui-new open-webui image Up (healthy) + - it-tools it-tools image Exited (7 months ago) + +Build Cache: 10 / 2.182 kB(可忽略) +``` + +## `/opt/agent` 详细占用 + +| 路径 | 大小 | 说明 | +|------|------|------| +| `/opt/agent/backup` | **1.5 GB** | 6 个压缩备份包(runtime_backup_* ×3, users_data_backup_* ×3) | +| `/opt/agent/dockerimage` | **894 MB** | `my-agent-shell_amd64.tar` 旧镜像 tar | +| `/opt/agent/agents` | **883 MB** | 当前源码目录 | +| `/opt/agent/agents_old_20260614` | **540 MB** | 旧版本源码备份 | +| `/opt/agent/runtime` | **520 MB** | 运行时数据(对话、用户工作区等) | +| `/opt/agent/voice_models` | **230 MB** | 语音模型文件 | +| `/opt/agent/agents_old_20260612_012026` | **190 MB** | 旧版本源码备份 | +| `/opt/agent/agents_old_20260614_190527` | **190 MB** | 旧版本源码备份 | +| `/opt/agent/venv` | **96 MB** | Python 虚拟环境 | +| `/opt/agent/agents.zip` | **42 MB** | 源码 zip 包 | + +## 建议清理清单 + +### 低风险(约可释放 6.5 GB) + +| 操作 | 可释放 | 命令/路径 | +|------|--------|-----------| +| 清理 systemd journal 旧日志 | ~4 GB | `journalctl --vacuum-time=7d` 或 `--vacuum-size=500M` | +| 删除 `/opt/agent/backup` 旧备份,只留最新各 1 个 | ~1 GB | 手动删除旧的 `runtime_backup_*` 和 `users_data_backup_*` | +| 删除 `/opt/agent/dockerimage/my-agent-shell_amd64.tar` | 894 MB | `rm /opt/agent/dockerimage/my-agent-shell_amd64.tar` | +| 删除 `/opt/agent/agents_old_*` 旧版本目录 | ~920 MB | `rm -rf /opt/agent/agents_old_*` | +| 删除 `/opt/agent/agents.zip` | 42 MB | `rm /opt/agent/agents.zip` | +| 删除已退出容器 `it-tools` | ~514 kB | `docker rm it-tools` | + +### 中风险(需先确认是否仍在使用) + +| 操作 | 可释放 | 说明 | +|------|--------|------| +| 清理 `/var/lib/filebrowser` | 3 GB | 需确认 filebrowser 中是否还有需要保留的文件 | +| 清理 `/home/admin/go` 缓存 | ~770 MB | `go clean -cache` | +| 删除 `/var/lib/clamav` | 1.2 GB | 如果系统不使用 clamav 扫描 | + +### 不建议动 + +- `/opt/agent/runtime`:用户运行时数据(对话、项目文件等) +- `/opt/agent/agents`:当前项目源码 +- `/opt/openwebui`:如果 OpenWebUI 服务仍在使用 +- `/home/git/data`:gitea 数据 +- Docker volume `open-webui`:OpenWebUI 用户数据 + +## 关键命令(下次快速查看) + +```bash +# 总体磁盘 +ssh 59.110.19.30 +df -h + +# 根目录一级占用 +sudo du -sh /* 2>/dev/null | sort -h | tail -20 + +# /var 明细 +sudo du -sh /var/lib/* /var/log/* 2>/dev/null | sort -h | tail -20 + +# /opt/agent 明细 +sudo du -sh /opt/agent/* 2>/dev/null | sort -h | tail -20 + +# Docker 占用 +docker system df +docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}' +docker volume ls + +# 清理 journal +sudo journalctl --vacuum-time=7d + +# 清理 Docker +# docker image prune -a # 谨慎:会删除所有未使用镜像 +# docker volume prune # 谨慎:会删除未使用 volume +``` + +## 与本次 Docker 改造的关系 + +- 当前 `my-agent-shell` 镜像:913 MB +- 方案 B 目标镜像:约 2.9 GB(本地 arm64 实测) +- 构建过程需下载 586 MB apt 包,解压后约 2.2 GB +- 当前可用空间仅 4 GB,**不足以安全构建方案 B** +- 建议先执行低风险清理释放 6-8 GB,再考虑构建 +- 云端构建建议分阶段:先只加 LibreOffice + 字体 + Node + Python 必要包(约 1.5 GB 镜像),验证稳定后再决定是否加 chromium/ffmpeg + +## 注意事项 + +- 服务器架构为 x86_64,本地为 arm64,**禁止将本地构建的镜像上传到云端** +- 云端网络环境较差,大文件下载可能超时 +- 云端内存 2 GB,构建/运行大镜像需警惕 OOM +- 清理前务必确认文件是否仍在使用,尤其是 `/var/lib/filebrowser` 和 `/opt/openwebui` diff --git a/.astrion/memory/cloud_server_ssh_and_directories.md b/.astrion/memory/cloud_server_ssh_and_directories.md new file mode 100644 index 0000000..99f5d27 --- /dev/null +++ b/.astrion/memory/cloud_server_ssh_and_directories.md @@ -0,0 +1,75 @@ +--- +name: cloud_server_ssh_and_directories +description: 当需要SSH登录云服务器排查部署问题、查看日志、或操作服务器文件时,应该索引本记忆 +--- + +# 云服务器 SSH 与目录指南 + +## SSH 登录 + +```bash +ssh 59.110.19.30 +# 使用与本地相同的 id_ed25519 密钥 +``` + +## 关键目录 + +| 用途 | 路径 | +|------|------| +| **源码根目录** | `/opt/agent/agents/` | +| **运行时数据根** | `/opt/agent/runtime/`(`ASTRION_DATA_ROOT`) | +| **部署级配置** | `/opt/agent/runtime/config/` | +| **子智能体模型配置** | `/opt/agent/runtime/config/sub_agent_models.json` | +| **子智能体代码** | `/opt/agent/agents/modules/sub_agent/`(Python 主进程内协程) | +| **虚拟环境** | `/opt/agent/venv/` | + +## 用户数据 + +``` +/opt/agent/runtime/web/users/<用户名>/projects/<项目名>/ +├── project/ # 用户工作区文件 +└── data/ + └── conversations/ + └── default/ + └── conv_xxx.json # 对话记录 +``` + +## 子智能体相关 + +| 用途 | 路径 | +|------|------| +| **子智能体任务目录** | `/opt/agent/runtime/web/data/sub_agent_tasks//` | +| **任务文件** | `task.txt`、`system_prompt.txt` | +| **子智能体交付目录** | 在用户 project 下的 `sub_agent_results/` | + +## 服务管理 + +| 操作 | 命令/路径 | +|------|----------| +| **启动服务** | `bash /opt/agent/start_webapp_new.sh`(端口 8091) | +| **更新服务** | `bash /opt/agent/refresh_agents.sh`(git pull + 重启 + nginx reload) | +| **数据迁移脚本** | `python3 /opt/agent/agents/scripts/migrate_runtime_data.py`(执行前务必 `--dry-run`) | +| **Nginx 配置** | `/etc/nginx/sites-enabled/agent.cyjai.com` | +| **本地部署脚本** | `deploy_agents.sh`(git push → npm build → scp dist/ → 触发远程 refresh) | + +## 日志 + +| 用途 | 路径 | +|------|------| +| **Web 日志** | `/opt/agent/runtime/web/logs/` | +| **主机调试日志** | `/opt/agent/runtime/web/logs/host_workspace_debug.log` | + +## 常见排查流程 + +1. SSH 登录服务器 +2. 查看对话 JSON:`cat /opt/agent/runtime/web/users//projects//data/conversations/default/conv_xxx.json` +3. 查看子智能体任务:`ls /opt/agent/runtime/web/data/sub_agent_tasks/` +4. 查看日志:`tail -100 /opt/agent/runtime/web/logs/host_workspace_debug.log` +5. 查看子智能体任务文件:`ls /opt/agent/runtime/web/data/sub_agent_tasks//` + +## 已知坑 + +- **前端构建不能服务器执行**:2G 内存会 OOM,必须本地 build 后 scp 上传 `static/dist/` +- **venv 符号链接问题**:从 macOS 复制来的 venv,启动脚本 `fix_venv()` 会自动检测重建 +- **子智能体现为 Python 协程**:`easyagent/` 目录已废弃,不再作为子智能体入口 +- **数据迁移后路径变更**:运行态数据根的环境变量由 `AGENTS_DATA_ROOT` 改为 `ASTRION_DATA_ROOT`,工作区内部目录由 `.agents/` 改为 `.astrion/` diff --git a/.astrion/memory/config_system_and_deployment.md b/.astrion/memory/config_system_and_deployment.md new file mode 100644 index 0000000..c4d8067 --- /dev/null +++ b/.astrion/memory/config_system_and_deployment.md @@ -0,0 +1,13 @@ +--- +name: config_system_and_deployment +description: 当修改配置加载逻辑、服务器部署流程、skill 归档路径或理解 settings.json 结构时,应该索引本记忆 +--- + +## Skill 归档路径约定(2026-07-09 更新) + +- **内置 skill 库**:`AGENT_SKILLS_DIR`(源码树 `agentskills/`),只放通用、自带、适用于项目的 skill。 +- **用户自定义 skill 归档目录**:`CUSTOM_SKILLS_DIR = //agentskills` + - host 模式:`~/.astrion/astrion/host/agentskills/`(全局,不按用户拆分) + - web/docker 模式:`~/.astrion/astrion/web/agentskills/`(可与 per-user `personal/agentskills` 并存) +- **create_skill 行为**:统一通过 `infer_private_skills_dir(data_dir)` 解析目标目录,host 模式不再特殊处理为源码树 `agentskills/`。 +- **实验/测试 skill**:如 `test` 应从源码树 `agentskills/` 移出,归档到 `CUSTOM_SKILLS_DIR`,不作为内置 skill 发布。 diff --git a/.agents/memory/conversation_model_persistence.md b/.astrion/memory/conversation_model_persistence.md similarity index 100% rename from .agents/memory/conversation_model_persistence.md rename to .astrion/memory/conversation_model_persistence.md diff --git a/.astrion/memory/docker_toolbox_enhancement_plan.md b/.astrion/memory/docker_toolbox_enhancement_plan.md new file mode 100644 index 0000000..de96b57 --- /dev/null +++ b/.astrion/memory/docker_toolbox_enhancement_plan.md @@ -0,0 +1,210 @@ +--- +name: docker_toolbox_enhancement_plan +description: 当需要继续推进 my-agent-shell 容器镜像增强改造、查看本地实验结果、或在云端构建新版镜像时,应该索引本记忆 +--- + +# my-agent-shell 容器镜像增强改造计划与本地实验进度 + +> 记录时间:2026-06-25 +> 相关记忆:`cloud_server_disk_usage`(服务器磁盘现状) + +## 背景 + +当前运行在云端的 `my-agent-shell` 容器(镜像 `my-agent-shell:latest`,基于 `python:3.11-slim`)依赖不足: + +- docx/pptx skill 缺少 `defusedxml` +- pptx skill 推荐的 `markitdown` 未安装 +- 完全缺少 LibreOffice / soffice +- 完全缺少 pandoc、pdftoppm(poppler-utils) +- 完全缺少 Node.js / npm +- 完全缺少中文字体(`fc-list` 为 0) + +导致用户常见的 Office 转换、图片处理、OCR、网页截图等需求无法跑通。 + +## 改造方案 + +### 方案 B(已选定) + +在最小方案基础上扩展常见业务能力: + +**系统 apt 包:** +- Office:libreoffice、pandoc、poppler-utils +- 图片/视频:imagemagick、ffmpeg +- OCR:tesseract-ocr、tesseract-ocr-chi-sim、tesseract-ocr-chi-tra +- 网页:chromium +- 字体:fonts-noto-cjk、fonts-noto-color-emoji、fonts-liberation +- 开发工具:jq、ripgrep、fd-find、tree + +**Python pip 包(加入 `docker/toolbox-requirements.txt`):** +- defusedxml +- markitdown[docx] +- pytesseract +- pdf2image + +**Node 全局包:** +- docx +- pptxgenjs + +**特殊处理:** +- 用 NodeSource 安装 Node 20 LTS,避免 Debian 源版本过老 +- 修改 ImageMagick policy.xml 允许读写 PDF +- 设置 `NODE_PATH=/usr/lib/node_modules` + +## 已修改文件 + +### 1. `docker/terminal.Dockerfile` + +主要变更: +- apt 安装段加入 libreoffice、pandoc、poppler-utils、imagemagick、ffmpeg、tesseract、chromium、中文字体、开发工具 +- 增加 ImageMagick PDF 策略调整 RUN 层 +- 增加 NodeSource Node 20 安装 +- 增加 `npm install -g docx pptxgenjs` +- 末尾添加 `ENV NODE_PATH="/usr/lib/node_modules"` + +### 2. `docker/toolbox-requirements.txt` + +新增: +- `defusedxml` +- `markitdown[docx]` +- `pytesseract` +- `pdf2image` + +## 本地实验进度 + +### 实验环境 + +- 本地 macOS 15.6.1, arm64 +- Docker Desktop +- 构建命令: + ```bash + cd /Users/jojo/Desktop/agents/正在修复中/agents + docker build -f docker/terminal.Dockerfile -t my-agent-shell:local . + ``` + +### 实验结果 + +| 指标 | 结果 | +|------|------| +| 构建成功 | ✅ 是 | +| 新镜像大小 | **2.9 GB**(原镜像 913 MB) | +| 镜像增加 | 约 **2 GB** | +| 空闲内存 | ~10 MB | +| 单任务峰值内存 | ~40 MB | +| 并发 5×soffice + 1×chromium 峰值内存 | **~237 MB** | + +### 功能验证(全部通过) + +| 测试项 | 结果 | +|--------|------| +| `soffice --headless --convert-to pdf` docx→pdf | ✅ | +| `pandoc docx -o md` | ✅ | +| `pdftoppm` pdf→png | ✅ | +| `markitdown` 读取 docx/pptx | ✅ | +| `python-docx` / `python-pptx` | ✅ | +| Node `docx-js` 生成 docx | ✅ | +| Node `pptxgenjs` 生成 pptx | ✅ | +| ImageMagick 图片裁剪 | ✅ | +| matplotlib 生成中文字体表格图片 | ✅ | +| tesseract OCR 中英文 | ✅(可运行,中文识别准确度一般) | +| chromium 网页截图 | ✅ | +| `pdf2image` PDF 转图片 | ✅ | +| docx/pptx skill 脚本 import | ✅ | + +### 遇到的问题与解决 + +1. **markitdown 读 docx 报 MissingDependencyException** + - 原因:默认 `markitdown` 不包含 docx converter 的 `mammoth` 依赖 + - 解决:改成 `markitdown[docx]` + +2. **Node 全局安装的 `docx`/`pptxgenjs` require 不到** + - 原因:node 默认不搜索 `/usr/lib/node_modules` + - 解决:Dockerfile 添加 `ENV NODE_PATH="/usr/lib/node_modules"` + +3. **chromium 运行时有 dbus 报错** + - 不影响截图结果,属于 headless 环境下的常见警告 + +4. **ImageMagick 默认禁止 PDF 处理** + - 解决:修改 `/etc/ImageMagick-6/policy.xml` 中 PDF 的 rights 为 `read|write` + +5. **onnxruntime 启动警告** + - 警告:`Unknown CPU vendor. cpuinfo_vendor value: 0` + - 不影响功能,arm64 容器中的常见提示 + +## 尚未完成的工作 + +1. **云端构建验证** + - 当前只在本地 arm64 构建验证 + - 需要在云端 x86_64 上实际构建 + - 云端磁盘紧张(只剩 4GB),需先清理或分阶段构建 + +2. **精简镜像体积(可选)** + - 2.9GB 较大,可考虑去掉 chromium/ffmpeg 做成"最小方案 B"(约 1.5GB) + - 或保留完整方案 B,但需确保云端有足够空间 + +3. **react-icons / react / react-dom / sharp** + - pptxgenjs skill 文档中提到图标生成需要这几个包 + - 当前未预装,需要时可在运行时安装或后续加入镜像 + - `sharp` 涉及原生编译,构建时间和失败风险较高 + +4. **更详细的内存压力测试** + - 当前只做了简单并发测试 + - 未测试大文件转换、长时间运行场景 + +5. **ImageMagick 安全策略复核** + - 当前为支持 PDF 放宽了限制,需确认是否符合安全要求 + +## 下一步建议 + +### 推荐路径:分阶段上云端 + +1. **阶段一:精简必要包** + - 去掉 chromium、ffmpeg、imagemagick、tesseract + - 保留:libreoffice、pandoc、poppler-utils、node、npm、docx、pptxgenjs、字体、defusedxml、markitdown[docx] + - 目标镜像约 1.3-1.5GB + - 先验证 Office skill 能跑通 + +2. **阶段二:按需加回媒体能力** + - 根据实际使用频率,决定是否加回 imagemagick、ffmpeg、chromium、tesseract + +3. **阶段三:优化构建缓存** + - 考虑把 apt 层拆成多个 RUN,便于失败后重试 + - 云端构建时加 `--progress=plain` 便于排查 + +## 关键命令 + +```bash +# 本地构建 + cd /Users/jojo/Desktop/agents/正在修复中/agents + docker build -f docker/terminal.Dockerfile -t my-agent-shell:local . + +# 本地测试容器 + docker run -d --name agent-test-local my-agent-shell:local tail -f /dev/null + +# 验证基础依赖 + docker exec agent-test-local bash -c "which soffice pandoc pdftoppm node npm convert ffmpeg chromium tesseract jq rg fdfind tree" + +# 验证 Python 包 + docker exec agent-test-local bash -c "/opt/agent-venv/bin/python -c 'import defusedxml, markitdown, pytesseract, pdf2image'" + +# 验证 Node 包 + docker exec agent-test-local bash -c "node -e 'require(\"docx\"); require(\"pptxgenjs\"); console.log(\"OK\")'" + +# 查看镜像大小 + docker images my-agent-shell:local + +# 监控运行时内存 + docker stats agent-test-local --no-stream +``` + +## 重要约束 + +- **本地 arm64,云端 x86_64,禁止把本地构建的镜像上传到云端** +- 云端磁盘只剩 4GB,必须清理或分阶段构建 +- 云端内存 2GB,构建/运行需警惕 OOM +- 云端网络差,大文件下载可能超时 + +## 相关文件 + +- `docker/terminal.Dockerfile` +- `docker/toolbox-requirements.txt` +- 项目记忆:`cloud_server_disk_usage`(服务器磁盘现状) diff --git a/.agents/memory/frontend_debug_log_to_file.md b/.astrion/memory/frontend_debug_log_to_file.md similarity index 96% rename from .agents/memory/frontend_debug_log_to_file.md rename to .astrion/memory/frontend_debug_log_to_file.md index a41bbaa..1a2efac 100644 --- a/.agents/memory/frontend_debug_log_to_file.md +++ b/.astrion/memory/frontend_debug_log_to_file.md @@ -17,7 +17,7 @@ description: 当需要把前端日志输出到后端文件(如调试目标模 - 端点:`POST /api/client_debug_log` - 写入函数:`log_goal_mode_debug_entry(data: Dict[str, Any])` - 日志文件:`{LOGS_DIR}/goal_mode_debug.log` - - 宿主机模式示例:`/Users/jojo/.agents/agents/host/logs/goal_mode_debug.log` + - 宿主机模式示例:`/Users/jojo/.astrion/astrion/host/logs/goal_mode_debug.log` ### 前端函数 diff --git a/.astrion/memory/ocr_client_initialization.md b/.astrion/memory/ocr_client_initialization.md new file mode 100644 index 0000000..79b9ad3 --- /dev/null +++ b/.astrion/memory/ocr_client_initialization.md @@ -0,0 +1,41 @@ +--- +name: ocr_client_initialization +description: 当 OCRClient/OpenAI 初始化报错、WebTerminal 创建失败、或升级 openai 包时,应该索引本记忆 +--- + +# OCRClient 初始化与 OpenAI 包兼容性 + +## 问题 + +`modules/ocr_client.py` 在 `__init__` 中直接创建 `OpenAI(api_key=OCR_API_KEY)`。 + +- `openai` 包较新版本(>= 2.40)在 `api_key` 为空时会抛出 `openai.OpenAIError: Missing credentials` +- 服务器 settings.json 通常未配置 `OCR_API_KEY` +- 这会导致 `WebTerminal`/`MainTerminal` 初始化失败,所有依赖 terminal 的 API 都返回 500 + +## 修复 + +在 `OCRClient.__init__` 中,仅在 `OCR_API_KEY` 存在时才创建 `OpenAI` 客户端: + +```python +self.client = None +if OCR_API_KEY: + self.client = OpenAI( + api_key=OCR_API_KEY, + base_url=base_url or None, + http_client=self.http_client, + ) +``` + +在 `vlm_analyze` 中同时检查 `self.client`: + +```python +if not self.client or not OCR_API_KEY or not OCR_API_BASE_URL or not self.model: + return {"success": False, "error": "VLM 配置缺失,请设置 OCR_API_BASE_URL / OCR_API_KEY / OCR_MODEL_ID", "warnings": warnings} +``` + +## 教训 + +- 升级依赖包后要检查破坏性变更 +- 可选功能(OCR/VLM)的客户端初始化不应阻塞主终端启动 +- 服务器环境缺少某个 API key 时,应优雅降级而不是直接崩溃 diff --git a/.astrion/memory/opencode_key_replacement_script.md b/.astrion/memory/opencode_key_replacement_script.md new file mode 100644 index 0000000..6dc9146 --- /dev/null +++ b/.astrion/memory/opencode_key_replacement_script.md @@ -0,0 +1,100 @@ +--- +name: opencode_key_replacement_script +description: 当需要批量替换 ~/.astrion/astrion、~/.astrion/astrion-clone 和服务器 /opt/agent/runtime 中的 OpenCode API key 时,应该索引本记忆 +--- + +--- +name: opencode_key_replacement_script +description: 当需要批量替换 ~/.astrion/astrion、~/.astrion/astrion-clone 和服务器 /opt/agent/runtime 中的 OpenCode API key 时,应该索引本记忆 +--- + +# OpenCode API key 批量替换脚本 + +## 脚本位置 + +项目根目录下的 `replace_opencode_keys.py`。 + +## 作用 + +一次性批量替换以下目录中所有 OpenCode 相关的 API key: + +- 本地:`/Users/jojo/.astrion/astrion` +- 本地:`/Users/jojo/.astrion/astrion-clone` +- 服务器:`/opt/agent/runtime/`(或服务器上 `ASTRION_DATA_ROOT` 指向的目录) + +## 扫描范围 + +仅扫描配置文件,自动跳过运行时数据: + +- `settings.json` +- `config/*.json` +- 跳过 `data/`、`logs/`、对话历史等目录 +- 跳过已有的 `.bak_` / `.bak` 备份文件 + +## 替换规则 + +脚本会查找已知的旧 OpenCode key 值(被多个 OpenCode Go 相关环境变量共用的同一串 key),并将其替换为新的 OpenCode key。 + +实际会改到的键名通常包括: + +- `settings.json` 中 `env_vars` 下的: + - `API_KEY_OPENCODE` + - `AGENT_API_KEY` + - `AGENT_THINKING_API_KEY` + - `AGENT_TITLE_API_KEY` + - 指向 `opencode.ai` 的 `API_KEY_KIMI`、`API_KEY_DEEPSEEK`、`API_KEY_QWEN`、`API_KEY_MINIMAX` 等 +- `config/sub_agent_models.json` 中 `url` 为 `https://opencode.ai/zen/go/v1` 的模型条目的 `apikey` + +`config/custom_models.json` 中一般使用 `${API_KEY_OPENCODE}` 变量引用,key 本身存在 `settings.json`,脚本不会直接修改它。 + +## 用法 + +```bash +# 只预览,不写入(循环轮换模式) +python3 replace_opencode_keys.py --dry-run + +# 正式执行循环轮换(默认:key1→key2, key2→key3, key3→key1) +python3 replace_opencode_keys.py + +# 单次指定替换(覆盖默认循环模式) +python3 replace_opencode_keys.py --old-key <旧key> --new-key <新key> +``` + +## 服务器替换流程 + +1. 确保脚本已上传至 `/opt/agent/agents/replace_opencode_keys.py` +2. 在服务器上修改脚本,把 `Path("/opt/agent/runtime")` 加入 `TARGET_DIRS` +3. 先执行 `--dry-run` 预览影响范围 +4. 去掉 `--dry-run` 正式执行 +5. 执行 `bash /opt/agent/refresh_agents.sh` 重启 webapp 服务使配置生效 +6. 删除生成的 `.bak_时间戳` 备份文件 + +## 服务器执行方式建议 + +- **推荐**:通过 `terminal_session` 打开一个 SSH 会话,在会话里直接运行 `python3 replace_opencode_keys.py ...` +- **次选**:使用服务器虚拟环境的完整路径,例如 `/opt/agent/venv/bin/python3 replace_opencode_keys.py ...` +- **注意**:在旧版本或未重启前,通过 `run_command` 直接执行 `ssh host "python3 ..."` 时,`run_command` 可能把命令中的 `python3` 重写为本地 macOS 的 Python 绝对路径,导致远程服务器找不到解释器。该问题已在 `modules/terminal_ops/run.py` 中修复:当命令通过 `ssh` 在远程执行时,跳过 `python3`/`pip` 重写。 + +## 注意事项 + +- 执行前会自动在同级目录生成 `.bak_时间戳` 备份。 +- 如果在沙箱/受限执行环境下运行,原目录不可写时脚本会把备份和修改后的文件写到项目工作区的 `opencode_key_replacement_backup/` 目录下,需要手动复制覆盖。 +- 建议执行后删除生成的 `.bak_时间戳` 备份文件。 +- 服务器上配置文件修改后,需要重启 webapp 服务才能生效。 + +## 历史记录 + +- 2026-06-19:使用脚本将本地两个目录的配置从主 key 替换为备用 key,并删除备份。 +- 2026-06-19:将服务器 `/opt/agent/runtime/` 下的 `settings.json` 和 `config/sub_agent_models.json` 也从主 key 替换为备用 key,并删除备份。 +- 2026-06-24:将服务器 `/opt/agent/runtime/` 下的 `settings.json`(9 处)和 `config/sub_agent_models.json`(1 处)从备用 key 替换回主 key,随后执行 `bash /opt/agent/refresh_agents.sh` 重启 webapp 服务,并删除备份。期间发现 `run_command` 执行 `ssh host "python3 ..."` 会错误重写 `python3` 为本地 macOS 路径,已修复 `modules/terminal_ops/run.py`。 +- 2026-06-25:重构脚本为三 key 循环轮换模式(key1/key2/key3,去掉主/备用命名)。将本地 `~/.astrion/astrion` 和 `~/.astrion/astrion-clone` 的配置从 key2 替换为 key3(4 文件 20 处)。服务器尚未操作,当前仍为 key1。 +- 2026-06-25:将本地 `~/.astrion/astrion` 和 `~/.astrion/astrion-clone` 的配置从 key2 替换为 key3(4 文件 20 处),删除备份。服务器仍为 key1。 + +## 相关 key 记录 + +| 说明 | Key | +|---|---| +| key1 | `sk-zNU867TGGXNKxoM0H6Pg6Jq4PYUPUOPDgMlzrOl3SS6jECRMWK5nNi6YMSsHzoZ` | +| key2 | `sk-sgt4ERgERPx2axU18PSfmenHv9zNDD3y8M5yr0p4ryhcNSm04CZsCxDHftJ9AkFD` | +| key3 | `sk-Jhvi76vJdCEQzvEdl8cAS5MIwBxBS4iTrJYSru2zCAXVnDVQpiZIvrcn9hBwkdZ4` | +| legacy(已停用,等同 key1 轮换到 key2) | `sk-j0NdTvEDoqqkKCHEAFXXYneEsuXEiOXNCgLeZARJmfYUBf2Riym7KPFWq1amkB6z` | diff --git a/.astrion/memory/pywebview_macos_packaging.md b/.astrion/memory/pywebview_macos_packaging.md new file mode 100644 index 0000000..f2bd15b --- /dev/null +++ b/.astrion/memory/pywebview_macos_packaging.md @@ -0,0 +1,94 @@ +--- +name: pywebview_macos_packaging +description: 当打包 PyWebview macOS App、调试 "The string did not match" 错误、修改 _packaging/ 目录时,应该索引本记忆 +--- + +# PyWebview macOS 打包项目记忆 + +## 目标 +将 Vue 3 + Python Flask 项目打包为 macOS 桌面 App(PyWebview),不使用 Electron/Tauri 或原生 macOS 对话框。 + +## 核心文件 +| 文件 | 作用 | +|------|------| +| `_packaging/app.py` | PyWebview 主入口:检测 `.env` → 直接启动 / setup页面 | +| `_packaging/setup.html` | 首次设置页面(4步:工作区→模型→管理员→确认),classic暖奶油主题 | +| `_packaging/app.spec` | PyInstaller 打包配置 | +| `_packaging/dist/AI-Agent-System.app` | 打包产物 | +| `_packaging/AI-Agent-System-v8.dmg` | 最后一个可用DMG(527MB) | + +## 关键修复历史 + +### 1. DMG 禁止标志 +- **原因**:DMG 内同时存在 `AI-Agent-System/`(onedir目录)和 `AI-Agent-System.app` +- **修复**:DMG 只放 `.app` + `Applications` 符号链接 +- **hdiutil 创建需完全访问权限**:当前受限制,需切换到完全访问权限模式 + +### 2. OpenSSL 符号冲突 +- **原因**:`_ssl.cpython-311-darwin.so` 加载 psycopg2 自带的旧版 `libcrypto.3.dylib` +- **修复**:用 Homebrew OpenSSL 3.6.2 的 dylib 替换 bundle 中 `__dot__dylibs` 目录 + +### 3. `.env` 加载路径 +- **修复**:`config/__init__.py` 的 `_load_dotenv()` 改为 frozen 时读 `~/.astrion/astrion/host/.env` + +### 4. engineio Invalid async_mode +- **原因**:PyInstaller frozen 环境缺少 `engineio.async_drivers.threading` +- **修复**:`app.spec` hiddenimports 添加 `'engineio.async_drivers.threading'` + +### 5. PyWebview 6.x private_mode 🚨 **关键问题** +- **现象**:Playwright 浏览器中正常(0 errors),PyWebview App 中所有 API 返回 HTML 导致 "The string did not match the expected pattern" +- **根因**:`webview.start()` 默认 `private_mode=True`,WKWebView 隐私模式下 cookie 不工作,session 丢失 +- **修复**:`webview.start(private_mode=False)` + +### 6. App Transport Security +- **修复**:`app.spec` Info.plist 添加 `NSAppTransportSecurity: { NSAllowsLocalNetworking: True }` + +### 7. 环境变量污染 +- **问题**:旧环境变量 `AGENTS_HOST_HOME=~/.agents/host-clone` 导致数据目录错误(现已被 `ASTRION_DATA_ROOT` 替代) +- **注意**:`~/.astrion/astrion-clone` 是本对话副本数据,不要动 +- **修复**:`_start_flask` 导入前清除 `ASTRION_DATA_ROOT`/`DATA_DIR`/`LOGS_DIR` 等旧环境变量污染 + +### 8. JSON 错误处理器 +- **修复**:`_start_flask` 中注册全局 error handler,确保任何异常都返回 JSON 而非 HTML + +## 当前状态(2026-06-12) + +### ✅ 已确认正常 +- Flask 后端在浏览器中完全正常(curl/Playwright 测试 0 errors) +- 登录、对话列表、数据加载均正常 +- API 返回纯 JSON + +### ❌ 仍存在问题 +- **PyWebview App 中仍然无数据**,前端报 "The string did not match the expected pattern" +- 已排除:backend 崩溃、engineio 错误、private_mode、ATS、环境变量污染 +- **问题定位**:在 PyWebview 的 WKWebView 内部,fetch API 请求被某种机制拦截/修改,导致返回非 JSON 响应 + +### 🔍 下一步排查方向 +1. 在 PyWebview 中启用远程调试(`webview.start(debug=True)`)查看 WKWebView console +2. 检查 PyWebview 6.x 的 `request_sent`/`response_received` 事件是否拦截了请求 +3. 检查 WKWebView 对 `127.0.0.1` 的 same-origin 策略是否有特殊处理 +4. 尝试用 `localhost` 替代 `127.0.0.1` +5. 考虑降级 PyWebview 到 5.x 或尝试其他打包方案(Tauri/Electron) + +## 打包命令 +```bash +# Python 必须是 Homebrew Python 3.11(Xcode Python 3.9 会 SIGKILL) +cd _packaging +pyinstaller --noconfirm --distpath ./dist --workpath ./build app.spec + +# 安装到 /Applications +cp -R dist/AI-Agent-System.app /Applications/ +codesign --force --deep -s - /Applications/AI-Agent-System.app +xattr -cr /Applications/AI-Agent-System.app + +# 创建 DMG(需完全访问权限) +hdiutil create -srcfolder dist/AI-Agent-System.app -volname "AI_Agent_System" -ov -format UDZO AI-Agent-System.dmg +``` + +## 用户要求 +- 禁止原生 macOS/浏览器组件 +- setup 页面需与项目设计风格统一 +- 只读一个 `.env`(数据存储目录 ~/.astrion/astrion/host/.env) +- 等待时间不要设置太长 +- `~/.astrion/astrion-clone` 是本对话副本数据,不要乱动 +- `~/.astrion/astrion/host` 是正常运行时数据 diff --git a/.agents/memory/remaining_long_files_to_split.md b/.astrion/memory/remaining_long_files_to_split.md similarity index 100% rename from .agents/memory/remaining_long_files_to_split.md rename to .astrion/memory/remaining_long_files_to_split.md diff --git a/.astrion/memory/server_deployment_venv.md b/.astrion/memory/server_deployment_venv.md new file mode 100644 index 0000000..c06f81f --- /dev/null +++ b/.astrion/memory/server_deployment_venv.md @@ -0,0 +1,68 @@ +--- +name: server_deployment_venv +description: 当服务器部署时遇到 venv/pip 损坏、Python 路径错误,或需要更新部署脚本时,应该索引本记忆 +--- + +# 服务器 venv 部署注意事项 + +## 背景 + +服务器 `/opt/agent/venv` 最初是从 macOS 复制到 Linux(Ubuntu 24.04)的,导致: + +- `python3` 等符号链接指向 macOS 路径(`/Library/Developer/...`) +- `pip`、`flask` 等脚本的 shebang 指向 macOS 路径或错误路径 +- 结果:`python3` 可能能用,但 `pip` 无法执行 + +## 修复方法 + +### 1. 重建 venv(推荐) + +在服务器上用 Linux 系统 Python 重建: + +```bash +/usr/bin/python3.12 -m venv --clear /opt/agent/venv +/opt/agent/venv/bin/pip install -r /opt/agent/agents/requirements.txt +``` + +### 2. 修改后的部署脚本 + +#### `/opt/agent/start_webapp_new.sh` + +`fix_venv()` 同时检查 `python3` 和 `pip` 是否可用: + +```bash +fix_venv() { + if [ -x "$VENV_DIR/bin/python3" ] && "$VENV_DIR/bin/python3" --version &>/dev/null && \ + [ -x "$VENV_DIR/bin/pip" ] && "$VENV_DIR/bin/pip" --version &>/dev/null; then + return 0 + fi + # ... 重建 venv ... +} +``` + +#### `/opt/agent/refresh_agents.sh` + +安装依赖前验证 `pip` 是否可执行,不可执行则自动重建 venv: + +```bash +VENV_DIR="/opt/agent/venv" +if [ -x "$VENV_DIR/bin/pip" ] && "$VENV_DIR/bin/pip" --version &>/dev/null; then + "$VENV_DIR/bin/pip" install -r "$AGENTS_DIR/requirements.txt" -q 2>&1 | tail -3 +else + log "venv pip 不可用,重建虚拟环境..." + sys_python=$(command -v python3.12 2>/dev/null || command -v python3 2>/dev/null || command -v python 2>/dev/null || true) + if [ -z "$sys_python" ]; then + log "错误: 找不到系统 Python" + exit 1 + fi + "$sys_python" -m venv --clear "$VENV_DIR" + "$VENV_DIR/bin/pip" install -r "$AGENTS_DIR/requirements.txt" -q 2>&1 | tail -3 + log "虚拟环境重建完成" +fi +``` + +## 注意事项 + +- 服务器内存只有 2G,前端构建不在服务器执行,由本地 `deploy_agents.sh` scp 上传 `static/dist/` +- 以后部署不要再从 macOS 复制 venv 到服务器 +- 如果 pip 包版本升级引入破坏性变更(如 `openai>=2.40` 强制要求 `api_key`),需要同步修复代码 diff --git a/.astrion/memory/server_frontend_build_oom.md b/.astrion/memory/server_frontend_build_oom.md new file mode 100644 index 0000000..55a99b7 --- /dev/null +++ b/.astrion/memory/server_frontend_build_oom.md @@ -0,0 +1,27 @@ +--- +name: server_frontend_build_oom +description: 当服务器执行前端构建失败、OOM、或需要部署前端时,应该索引本记忆 +--- + +# 服务器前端构建 OOM + +## 问题 +服务器(2G 内存 Linux)执行 `npm run build` 时因内存不足导致 OOM,进程被 kill,服务器可能 SSH 超时无响应。 + +## 解决方案 +**不在服务器上构建前端。** 在本地(macOS,充足内存)构建,然后 scp 上传 `static/dist/`。 + +## 操作步骤 +```bash +# 本地构建 +cd agents && npm run build + +# 打包上传 +tar czf /tmp/static_dist.tar.gz static/dist/ +scp /tmp/static_dist.tar.gz root@59.110.19.30:/tmp/ +ssh root@59.110.19.30 "cd /opt/agent/agents && tar xzf /tmp/static_dist.tar.gz" +``` + +## 已落实到脚本 +- `deploy_agents.sh`(本地):自动本地 build + 上传 +- `refresh_agents.sh`(服务器):已移除 npm build 步骤,注释说明原因 diff --git a/.astrion/memory/slash_menu_highlight_animation.md b/.astrion/memory/slash_menu_highlight_animation.md new file mode 100644 index 0000000..bff66a3 --- /dev/null +++ b/.astrion/memory/slash_menu_highlight_animation.md @@ -0,0 +1,23 @@ +--- +name: slash_menu_highlight_animation +description: 当修改/菜单选中高亮动画时,应该索引本记忆 +--- + +## / 菜单选中高亮动画 — 设计教训 + +### 最终方案 +- 提取 `skill-slash-menu-wrapper` 外壳层,高亮条(`.skill-slash-menu__highlight`)放在 wrapper 内但**滚动容器外**,用 `position: absolute` 相对于 wrapper 定位 +- 用一个 `requestAnimationFrame` 循环**同时驱动 scrollTop 和 slashHighlightTop**,相同 easing(easeOutCubic)、相同时间(180ms),帧帧同步 +- 不使用 CSS transition、scroll-behavior: smooth、scroll 事件 + +### 踩过的坑 +1. **高亮条必须在滚动容器外**:放在内部会随内容滚动,钉死在中间的效果无法实现 +2. **CSS transition + scroll-behavior: smooth 无法精确同步**:easing 曲线和时间窗口不完全匹配,导致运动快慢不一致 +3. **scroll 事件有盲区**:当 scrollTop 不变(边界、所有项可见)时不触发,导致高亮条卡死 +4. **底部 5px 偏差**:`maxScroll` 不是 step(43px) 的整数倍,用 `visibleRows` 推算永远无法精确对齐。正确做法:`highlightTop = gap + index * step - targetScroll`,公式本身就保证了精确对齐 + +### 中间区域"固定"原理 +高亮条和列表以相同距离(step)、相同时间(180ms)、相同 easing 反向运动,视觉上高亮条钉在屏幕中间不动,只有列表在滚。到达滚动极限时,高亮条才开始实际移动。 + +### 已选中项 hover 不叠加 +`.skill-slash-item--active:hover` 设为 `background: transparent`,避免高亮条 + hover 两层背景叠加。 diff --git a/.astrion/memory/sub_agent_network_permission.md b/.astrion/memory/sub_agent_network_permission.md new file mode 100644 index 0000000..b602a19 --- /dev/null +++ b/.astrion/memory/sub_agent_network_permission.md @@ -0,0 +1,24 @@ +--- +name: sub_agent_network_permission +description: 当需要理解或调整子智能体在受限网络下的行为时,应该索引本记忆 +--- + +# 子智能体进程网络权限设计 + +## 问题 +当用户把「网络权限」设置为受限(`HOST_SANDBOX_NETWORK_PERMISSION=restricted`)时,创建子智能体会失败,子智能体进程内报 `fetch failed (ENOTFOUND)`。原因是子智能体进程本身也被加上了与工具命令相同的宿主机沙箱,导致它无法解析模型 API 域名、无法调用模型接口。 + +## 解决方案 +子智能体进程使用独立的网络权限配置 `SUB_AGENT_HOST_NETWORK_PERMISSION`(默认 `full`),与工具级 `HOST_SANDBOX_NETWORK_PERMISSION` 解耦。 + +- 子智能体本身需要调用模型 API,因此默认拥有完整网络,避免在受限网络下直接无法启动。 +- 主进程对普通 `run_command` / `terminal_input` 等命令单独施加 `HOST_SANDBOX_NETWORK_PERMISSION` 沙箱,这部分限制不受影响。 +- 子智能体内部执行的 `run_command` 目前不额外加沙箱(macOS 的 `sandbox-exec` 不支持嵌套沙箱,无法在同一个子智能体进程内再套一层)。 + +## 配置项 +- `config/sub_agent.py` 新增 `SUB_AGENT_HOST_NETWORK_PERMISSION`,可通过环境变量覆盖,默认 `full`。 +- `modules/sub_agent_manager.py` 在构建宿主机沙箱启动计划时使用该配置,而不是直接使用 `HOST_SANDBOX_NETWORK_PERMISSION`。 + +## 注意事项 +- 若未来需要在子智能体内部也对命令做网络限制,必须采用非嵌套方案(例如由主进程提供本地代理/执行服务,或让子智能体通过 localhost 调用主进程代理后再外出)。不能简单在子智能体里再次调用 `sandbox-exec`。 +- 在 Docker/容器模式下,网络由容器自身策略控制,上述配置不影响容器行为。 diff --git a/.astrion/memory/sub_agent_python_rewrite.md b/.astrion/memory/sub_agent_python_rewrite.md new file mode 100644 index 0000000..f42799f --- /dev/null +++ b/.astrion/memory/sub_agent_python_rewrite.md @@ -0,0 +1,73 @@ +--- +name: sub_agent_python_rewrite +description: 当需要了解子智能体当前实现、修改子智能体代码、或排查子智能体相关问题时,应该索引本记忆 +--- + +# 子智能体现状(Python 主进程内协程) + +## 核心变更 + +子智能体已经从 **Node.js 子进程**(`easyagent/src/batch/index.js`)彻底改为 **Python 主进程内协程**。 + +## 代码位置 + +所有子智能体专用代码集中在 `modules/sub_agent/` 包内: + +``` +modules/sub_agent/ +├── __init__.py # 导出 SubAgentManager +├── manager.py # 任务调度、创建、终止、状态查询 +├── task.py # LLM 循环、模型调用、工具执行驱动 +├── toolkit.py # 8 个工具定义、工具结果格式化、模型配置解析 +├── prompts.py # 用户消息和系统提示词构建 +├── tools.py # search_workspace、read_mediafile 本地实现 +├── state.py # 状态加载、保存、刷新、超时、僵尸清理 Mixin +├── stats.py # 统计摘要与结果消息组装 Mixin +└── creation.py # 创建参数校验、任务 ID 生成、交付目录解析 Mixin +``` + +## 执行机制 + +- 子智能体作为 `asyncio.Task` 在独立的后台事件循环线程中运行(避免与 Flask-SocketIO threading 模式冲突)。 +- 所有实际工具调用都通过 `WebTerminal.handle_tool_call()` 执行,因此**自然复用主进程的宿主机沙箱 / Docker 容器链路**。 +- 子智能体自身不再被整体沙箱限制网络(因为它需要调用模型 API),网络调用走 `utils.api_client.DeepSeekClient`。 +- 权限固定为 `unrestricted`,但工具执行本身受主进程沙箱/容器/审批链路约束。 + +## 模型配置 + +- 子智能体模型独立配置,不跟主模型合并。 +- 配置文件:`~/.astrion/astrion/config/sub_agent_models.json`(由 `SUB_AGENT_MODELS_CONFIG_FILE` 指定)。 +- 默认读取该文件中的 `default_model` 和 `models` 列表。 + +## 可用工具(8 个) + +1. `read_file` - 读取/搜索/抽取文件 +2. `write_file` - 创建或覆盖文件(支持 append) +3. `edit_file` - 精确字符串替换,支持 `replacements` 数组和 `replace_all` +4. `run_command` - 执行命令(走主进程沙箱/容器) +5. `web_search` - 网络搜索 +6. `extract_webpage` - 网页内容提取 +7. `search_workspace` - 工作区搜索 +8. `read_mediafile` - 读取图片/视频文件(base64 返回) + +## easyagent/ 目录状态 + +- `easyagent/` 是旧的 Node.js 子智能体实现,目前**暂时保留但已不再使用**。 +- 主项目 Python 代码没有任何地方引用 `easyagent/`。 +- 是否删除待后续决策。 + +## 入口点 + +- 主项目创建子智能体:`modules.sub_agent.manager.SubAgentManager.create_sub_agent()` +- `core/main_terminal.py` 中通过 `SubAgentManager.set_terminal(self)` 注入 WebTerminal 引用。 + +## 回归测试 + +- 测试脚本:`test/test_sub_agent_regression.py` +- 覆盖:write_file、edit_file(多处替换 + replace_all)、search_workspace、read_mediafile、terminate + +## 已删除/清理的内容 + +- `config/sub_agent.py` 中已删除 `SUB_AGENT_HOST_NETWORK_PERMISSION` 临时配置。 +- `easyagent/src/batch/index.js` 已删除(旧子进程入口)。 +- `AGENTS.md` 和 `CLAUDE.md` 中关于子智能体的描述已更新为当前实现。