From 5ab3acef9cdeba82ba8caf3b510ffc2113f435be Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Mon, 16 Mar 2026 21:17:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20terminal-guide=20?= =?UTF-8?q?=E5=92=8C=20sub-agent-guide=20skills?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 terminal-guide skill: 持久化终端使用指南 - 新增 sub-agent-guide skill: 子智能体使用指南 - 优化终端工具定义和执行逻辑 - 更新系统提示词以引用新 skills - 添加 utils/__init__.py 模块初始化文件 Co-Authored-By: Claude Sonnet 4.5 --- agentskills/sub-agent-guide/SKILL.md | 197 ++++++++ agentskills/terminal-guide/SKILL.md | 489 +++++++++++++++++++ core/main_terminal_parts/tools_definition.py | 10 +- core/main_terminal_parts/tools_execution.py | 5 +- modules/persistent_terminal.py | 4 +- modules/terminal_manager.py | 27 +- prompts/main_system.txt | 2 +- prompts/main_system_qwenvl.txt | 2 +- prompts/sub_agent_guidelines.txt | 24 - requirements.txt | 1 + server/chat_flow_stream_loop.py | 2 +- server/tasks.py | 1 + sub_agent/core/main_terminal.py | 11 +- sub_agent/modules/terminal_manager.py | 7 +- utils/__init__.py | 1 + utils/tool_result_formatter.py | 6 +- 16 files changed, 733 insertions(+), 56 deletions(-) create mode 100644 agentskills/sub-agent-guide/SKILL.md create mode 100644 agentskills/terminal-guide/SKILL.md create mode 100644 utils/__init__.py diff --git a/agentskills/sub-agent-guide/SKILL.md b/agentskills/sub-agent-guide/SKILL.md new file mode 100644 index 0000000..dbc75fd --- /dev/null +++ b/agentskills/sub-agent-guide/SKILL.md @@ -0,0 +1,197 @@ +--- +name: sub-agent-guide +description: 子智能体使用指南。创建子智能体之前必须阅读此技能。子智能体用于并行处理多个完全独立的任务,如:并行生成多个模块的文档、同时进行多个网络调研、大范围信息搜集(分析项目认证实现、收集系统信息)。关键限制:子智能体之间无法通信,看不到主对话历史,只能通过文件共享信息。不适合需要协作或精细控制的任务(如代码重构)。 +--- + +# 子智能体使用指南 + +## 核心原则 + +**一个子智能体 = 一个完整的独立任务** + +子智能体拥有完整工具能力,但与主智能体和其他子智能体完全隔离。它们看不到你的对话历史,无法互相通信,只能通过文件系统共享信息。 + +## 何时使用 + +**适合:** +- **并行独立任务**:同时生成3个不同模块的文档、并行测试多个功能 +- **大范围信息搜集**:分析项目的前后端认证实现、收集系统硬件信息(CPU/GPU/内存) +- **网络调研**:搜索并整理英伟达2025财报、对比 Claude 和 ChatGPT 最新模型 +- **耗时分析任务**:大规模日志分析、代码库依赖关系梳理 + +**不适合:** +- 任务之间需要频繁协调(子智能体无法互相通信) +- 简单快速的任务(创建子智能体有开销) +- 需要你参与决策的任务(子智能体是自主运行的) +- 代码重构等需要精细控制的任务(建议自己完成) + +## 任务描述的艺术 + +子智能体看不到你的对话历史,task 参数是它收到的唯一指令。 + +**关键要素:** +1. **明确的目标** - 要做什么,产出什么 +2. **清晰的范围** - 操作哪些文件/目录 +3. **具体的交付物** - 生成什么文件,放在哪里 +4. **完成标准** - 什么算完成 + +**好的示例:** + +``` +# 信息搜集任务 +分析项目的前后端认证与鉴权实现: +1. 查找所有与认证相关的代码文件(login, auth, jwt, session 等关键词) +2. 分析前端如何存储和传递认证信息 +3. 分析后端如何验证和管理用户权限 +4. 整理成一份技术文档(auth_analysis.md),包含流程图和代码位置 + +# 网络调研任务 +搜索并整理英伟达2025年的财报信息: +1. 搜索英伟达2025年Q1-Q4财报 +2. 提取关键财务数据(营收、利润、增长率) +3. 分析主要业务板块表现 +4. 生成结构化报告(nvidia_2025_report.md) + +# 系统信息收集 +收集当前计算机的硬件和系统信息: +1. CPU 架构、型号、核心数 +2. GPU 型号、显存、驱动版本 +3. 内存大小、系统版本 +4. 保存为 system_info.md +``` + +**避免:** +- ❌ "帮我分析一下认证代码" - 太模糊 +- ❌ "如果发现问题请告诉我" - 子智能体无法与你交互 +- ❌ "先做 A,然后等我确认再做 B" - 子智能体是一次性执行的 + +## 交付目录设计 + +`deliverables_dir` 必须是**不存在的新目录**,子智能体会把所有成果放在这里。 + +**命名建议:** +``` +sub_agent_results/task_description/ # 清晰描述任务 +sub_agent_results/module_a_docs/ # 按模块组织 +sub_agent_results/refactor_auth/ # 按功能组织 +``` + +**避免:** +- ❌ 使用已存在的目录(会报错) +- ❌ 使用绝对路径(必须是相对于项目根目录的相对路径) +- ❌ 使用通用名称如 "output" "result"(多个子智能体会冲突) + +## 并行执行模式 + +创建多个子智能体时,确保它们的任务**完全独立**: + +```python +# ✅ 好的并行:各自独立 +create_sub_agent(agent_id=1, task="生成模块 A 的文档", deliverables_dir="docs/module_a", ...) +create_sub_agent(agent_id=2, task="生成模块 B 的文档", deliverables_dir="docs/module_b", ...) + +# ✅ 好的并行:信息搜集 +create_sub_agent(agent_id=1, task="搜索并整理 Claude 最新模型信息", deliverables_dir="research/claude", ...) +create_sub_agent(agent_id=2, task="搜索并整理 ChatGPT 最新模型信息", deliverables_dir="research/chatgpt", ...) +# 等两个都完成后,你再对比整合 + +# ✅ 好的并行:系统调研 +create_sub_agent(agent_id=1, task="分析前端认证实现", deliverables_dir="analysis/frontend_auth", ...) +create_sub_agent(agent_id=2, task="分析后端鉴权实现", deliverables_dir="analysis/backend_auth", ...) + +# 等待所有完成 +wait_sub_agent(agent_id=1) +wait_sub_agent(agent_id=2) +``` + +```python +# ❌ 不好的并行:需要协作 +create_sub_agent(agent_id=1, task="收集数据", ...) +create_sub_agent(agent_id=2, task="分析数据", ...) # 依赖子智能体1的结果 +# 子智能体2不知道子智能体1什么时候完成,无法协调 +``` + +## 常见陷阱 + +### 1. 期望子智能体协作 + +❌ **错误:** +```python +create_sub_agent(agent_id=1, task="实现功能 A,完成后通知子智能体2") +create_sub_agent(agent_id=2, task="等待子智能体1完成,然后实现功能 B") +``` + +子智能体之间无法通信。如果任务有依赖,应该: +- 方案1:顺序执行(等第一个完成后再创建第二个) +- 方案2:让一个子智能体完成整个流程 + +### 2. 忘记子智能体看不到对话历史 + +❌ **错误:** +```python +# 你在对话中说:"我们要重构认证模块,使用 JWT 替代 Session" +create_sub_agent(task="按照我们讨论的方案重构认证模块") +``` + +✅ **正确:** +```python +create_sub_agent(task=""" +重构 src/auth/ 目录的认证模块: +- 移除基于 Session 的认证 +- 实现基于 JWT 的认证 +- 保持 API 接口不变 +- 更新相关测试 +""") +``` + +### 3. 超时时间设置不当 + +子智能体超时后会被强制终止,已完成的工作可能丢失。 + +**经验值:** +- 文档生成:600-900 秒 +- 信息搜集/网络调研:900-1800 秒 +- 大规模分析:1800-3600 秒 + +宁可设置得长一些,也不要让子智能体因超时而前功尽弃。 + +### 4. agent_id 重复使用 + +同一对话中,每个 agent_id 只能使用一次。如果子智能体失败需要重试,必须使用新的 agent_id。 + +```python +create_sub_agent(agent_id=1, ...) # 失败了 +# ❌ create_sub_agent(agent_id=1, ...) # 会报错 +# ✅ create_sub_agent(agent_id=2, ...) # 使用新的 ID +``` + +## 检查结果 + +子智能体完成后,检查 `success` 字段和 `deliverables_dir`: + +```python +result = wait_sub_agent(agent_id=1) + +if result["success"]: + # 读取交付目录中的文件 + deliverables = result["deliverables_dir"] + # 处理生成的文件... +else: + # 检查失败原因 + if result["status"] == "timeout": + # 考虑增加 timeout_seconds 重试 + else: + # 查看 result["message"] 了解失败原因 + # 检查交付目录中是否有部分结果 +``` + +## thinking_mode 选择 + +- `fast`:常规任务(文档生成、信息搜集、数据分析) +- `thinking`:需要深度推理的任务(架构设计、复杂算法分析、安全审计) + +大多数情况下使用 `fast` 即可。 + +## 总结 + +子智能体的本质是**并行执行完全独立的任务**。如果你发现自己在设计子智能体之间的协作流程,那可能不应该使用子智能体,而应该自己顺序执行这些步骤。 diff --git a/agentskills/terminal-guide/SKILL.md b/agentskills/terminal-guide/SKILL.md new file mode 100644 index 0000000..981f748 --- /dev/null +++ b/agentskills/terminal-guide/SKILL.md @@ -0,0 +1,489 @@ +--- +name: terminal-guide +description: 持久化终端使用指南。使用 terminal_session、terminal_input、terminal_snapshot 工具前必须阅读此技能。用于理解持久化终端与 run_command 的区别、output_wait 参数的正确含义、以及如何处理长时间运行的任务。 +--- + +# 持久化终端使用指南 + +## 核心原则 + +**持久化终端 = 长期运行的命令行会话** + +Terminal 工具提供了一个可以持续存在的终端会话,命令在其中运行不会因为你去做其他事情而中断。 + +### 与 run_command 的本质区别 + +| 特性 | terminal 系列 | run_command | +|------|--------------|-------------| +| 会话类型 | 持久会话,可以一直存在 | 一次性执行 | +| 超时行为 | output_wait 到期后命令继续运行 | timeout 到期强制终止命令 | +| 适用场景 | 长时间任务、多步操作、后台服务 | 快速查看信息、一次性命令 | +| 状态检查 | 可随时用 snapshot 查看 | 执行完就结束,无法再查看 | +| 典型用途 | pip install、git clone、npm run dev | ls、file、grep -n | + +**选择标准:** +- 需要运行超过 30 秒?→ terminal +- 需要启动后继续做其他事?→ terminal +- 需要多次检查进度?→ terminal +- 只是快速查看信息?→ run_command + +## 何时使用 + +### 适合 Terminal 的场景 + +**长时间运行的任务:** +- 下载大文件(wget、curl、git clone) +- 安装依赖(pip install、npm install、apt install) +- 编译构建(make、cargo build、npm run build) +- 数据处理(批量转换、压缩、解压) + +**需要保持运行的服务:** +- 开发服务器(npm run dev、python -m http.server) +- 数据库服务(redis-server、mongod) +- 监控工具(tail -f、watch) + +**多步交互操作:** +- cd 到某个目录后继续执行多个命令 +- 激活虚拟环境后运行命令 +- 设置环境变量后执行任务 + +**需要监控进度:** +- 运行测试套件并查看进度 +- 批量处理文件并检查完成情况 +- 长时间脚本执行并定期查看输出 + +### 不适合 Terminal 的场景 + +**快速信息查看(用 run_command):** +- 查看文件信息(file、stat、ls -la) +- 检查编码(iconv -l、file -i) +- 简单的 grep 定位 + +**交互式程序(禁止使用):** +- Python/Node REPL +- vim、nano、emacs +- top、htop +- 任何需要完整 TTY 的程序 + +**一次性简单命令(用 run_command):** +- 创建目录(mkdir) +- 移动文件(mv、cp) +- 查看环境变量(echo $PATH) + +## 三步工作流 + +使用 terminal 工具的标准流程: + +### 1. 打开会话 + +```python +terminal_session( + action="open", + session_name="main", # 给会话起个名字 + working_dir="project" # 可选:指定工作目录 +) +``` + +**会话命名建议:** +- `main` - 主要工作终端 +- `server` - 运行开发服务器 +- `build` - 执行构建任务 +- `test` - 运行测试 + +### 2. 发送命令 + +```python +terminal_input( + command="pip install -r requirements.txt", + session_name="main", + output_wait=30 # 收集输出的窗口期(秒) +) +``` + +### 3. 检查状态 + +```python +terminal_snapshot( + session_name="main", + lines=50, # 可选:返回最近 50 行 + max_chars=5000 # 可选:限制字符数 +) +``` + +## output_wait 的艺术 + +**output_wait 不是命令超时!** 这是最容易误解的地方。 + +### 它是什么 + +`output_wait` 是"收集输出的窗口期": +- 在这个时间内,工具会等待并收集命令的输出 +- 窗口期结束后,**命令继续在后台运行**,只是不再等待输出 +- 如果命令在窗口期内完成,会提前返回结果 + +### 它不是什么 + +- ❌ 不是命令超时(命令不会被终止) +- ❌ 不是最大执行时间(命令可以运行更久) +- ❌ 不是强制等待时间(命令完成会提前返回) + +### 设置策略 + +**场景 1:快速命令,只需确认启动** +```python +# 例如:启动开发服务器 +terminal_input( + command="npm run dev", + output_wait=10 # 10秒足够看到启动信息 +) +# 服务器会继续运行,你可以去做其他事情 +``` + +**场景 2:长任务,只需确认开始** +```python +# 例如:下载大文件 +terminal_input( + command="wget https://example.com/large-file.zip", + output_wait=30 # 30秒确认下载开始 +) +# 下载继续进行,稍后用 snapshot 检查进度 +``` + +**场景 3:必须等待完成的任务** +```python +# 例如:运行测试套件(预计 2 分钟完成) +terminal_input( + command="pytest tests/", + output_wait=150 # 设置足够长,确保完成 +) +``` + +**场景 4:可能长时间无输出的任务** +```python +# 例如:git clone 大仓库(可能长时间无输出) +# 技巧:在命令后加 ; echo "__DONE__" 作为完成标记 +terminal_input( + command="git clone https://github.com/large/repo.git ; echo '__DONE__'", + output_wait=30 # 30秒确认开始 +) +# 稍后用 snapshot 检查,看到 __DONE__ 就知道完成了 + +# 例如:批量处理文件 +terminal_input( + command="for f in *.mp4; do ffmpeg -i \"$f\" \"${f%.mp4}.mp3\"; done ; echo '__DONE__'", + output_wait=60 # 给足时间,或稍后用 snapshot 检查 __DONE__ +) +``` + +### 经验值参考 + +- **确认启动**:5-10 秒 +- **确认开始**:20-30 秒 +- **短任务完成**:30-60 秒 +- **中等任务完成**:60-120 秒 +- **长任务完成**:120-300 秒 + +**原则:宁可设长,不要设短。** 设短了可能看不到关键输出,设长了最多就是等一会儿。 + +## 常见场景示例 + +### 场景 1:启动开发服务器(启动后不需要等待) + +```python +# 1. 打开终端 +terminal_session(action="open", session_name="server") + +# 2. 启动服务器 +terminal_input( + command="npm run dev", + session_name="server", + output_wait=15 # 15秒看到启动信息即可 +) +# 输出:Server running on http://localhost:3000 + +# 3. 服务器在后台运行,你可以继续做其他事情 +# 需要时可以随时查看日志 +terminal_snapshot(session_name="server", lines=30) +``` + +### 场景 2:下载大文件(启动后定期检查) + +```python +# 1. 打开终端 +terminal_session(action="open", session_name="download") + +# 2. 启动下载(加完成标记) +terminal_input( + command="wget https://example.com/dataset.tar.gz ; echo '__DONE__'", + session_name="download", + output_wait=20 # 20秒确认下载开始 +) + +# 3. 去做其他事情... + +# 4. 稍后检查进度 +terminal_snapshot(session_name="download") +# 输出:50% [======> ] 512MB 10.2MB/s eta 45s + +# 5. 再次检查 +terminal_snapshot(session_name="download") +# 输出:100% [=============] 1024MB 10.5MB/s in 98s +# __DONE__ ← 看到这个就知道完成了 +``` + +### 场景 3:运行测试套件(需要等待完成) + +```python +# 1. 打开终端 +terminal_session(action="open", session_name="test", working_dir="backend") + +# 2. 运行测试(预计 2 分钟) +terminal_input( + command="pytest tests/ -v", + session_name="test", + output_wait=150 # 设置足够长,等待完成 +) +# 输出:完整的测试结果 + +# 3. 如果不确定是否完成,再检查一次 +terminal_snapshot(session_name="test", lines=20) +``` + +### 场景 4:批量处理文件(需要等待完成) + +```python +# 1. 打开终端 +terminal_session(action="open", session_name="process") + +# 2. 批量转换(预计 5 分钟) +terminal_input( + command="for img in *.png; do convert \"$img\" -resize 800x600 \"thumb_$img\"; done", + session_name="process", + output_wait=300 # 5分钟 +) + +# 3. 检查是否完成 +terminal_snapshot(session_name="process") +``` + +### 场景 5:多步操作(cd + 虚拟环境 + 命令) + +```python +# 1. 打开终端 +terminal_session(action="open", session_name="main") + +# 2. 切换目录 +terminal_input( + command="cd backend && source venv/bin/activate", + session_name="main", + output_wait=5 +) + +# 3. 在虚拟环境中安装依赖 +terminal_input( + command="pip install -r requirements.txt", + session_name="main", + output_wait=60 +) + +# 4. 运行应用 +terminal_input( + command="python app.py", + session_name="main", + output_wait=10 +) +``` + +## 常见陷阱 + +### 1. 把 output_wait 当作命令超时 + +❌ **错误理解:** +```python +# "我想让命令最多运行 30 秒,超时就终止" +terminal_input(command="long_task.sh", output_wait=30) +``` + +✅ **正确做法:** +```python +# 如果需要强制超时终止,使用 run_command +run_command(command="long_task.sh", timeout=30) + +# 如果任务可以持续运行,使用 terminal +terminal_input(command="long_task.sh", output_wait=30) +# 30秒后命令继续运行,你可以稍后用 snapshot 检查 +``` + +### 2. 不检查命令是否完成就继续输入 + +❌ **错误:** +```python +terminal_input(command="npm install", output_wait=10) +# 10秒后立即执行下一个命令,但 npm install 可能还在运行 +terminal_input(command="npm run build", output_wait=10) +# 可能会冲突或失败 +``` + +✅ **正确:** +```python +terminal_input(command="npm install", output_wait=10) + +# 检查是否完成 +terminal_snapshot(session_name="main") +# 如果看到还在运行,等待或增加 output_wait + +# 确认完成后再继续 +terminal_input(command="npm run build", output_wait=60) +``` + +### 3. 在终端中启动交互式程序 + +❌ **错误:** +```python +terminal_input(command="python", output_wait=5) # 启动 Python REPL +terminal_input(command="print('hello')", output_wait=5) # 不会工作 +``` + +❌ **错误:** +```python +terminal_input(command="vim file.txt", output_wait=5) # vim 需要完整 TTY +``` + +✅ **正确:** +```python +# 使用 run_python 执行 Python 代码 +run_python(code="print('hello')", timeout=5) + +# 使用 edit_file 修改文件 +edit_file(file_path="file.txt", old_string="...", new_string="...") +``` + +### 4. 对快速命令使用 terminal + +❌ **低效:** +```python +terminal_session(action="open", session_name="check") +terminal_input(command="ls -la", session_name="check", output_wait=5) +terminal_session(action="close", session_name="check") +``` + +✅ **高效:** +```python +run_command(command="ls -la", timeout=5) +``` + +### 5. 忘记关闭不再使用的终端 + +```python +# 任务完成后,记得关闭终端释放资源 +terminal_session(action="close", session_name="build") +``` + +或者重置终端(清空输出但保持会话): +```python +terminal_session(action="reset", session_name="main") +``` + +### 6. output_wait 设置过短导致看不到关键输出 + +❌ **问题:** +```python +# pip install 可能需要 30-60 秒 +terminal_input(command="pip install tensorflow", output_wait=5) +# 5秒后返回,只看到 "Collecting tensorflow...",看不到安装结果 +``` + +✅ **改进:** +```python +# 方案 1:设置足够长的 output_wait +terminal_input(command="pip install tensorflow", output_wait=90) + +# 方案 2:短 output_wait 确认开始,稍后用 snapshot 检查 +terminal_input(command="pip install tensorflow", output_wait=10) +# ... 做其他事情 ... +terminal_snapshot(session_name="main") # 检查是否完成 +``` + +## 检查命令是否完成 + +使用 `terminal_snapshot` 判断命令状态的技巧: + +### 技巧 1:使用完成标记(推荐) + +对于可能长时间无输出的任务,在命令后加 `; echo "__DONE__"`: + +```python +# git clone 大仓库 +terminal_input( + command="git clone https://github.com/large/repo.git ; echo '__DONE__'", + output_wait=30 +) + +# 稍后检查 +terminal_snapshot(session_name="main") +# 看到 __DONE__ 就知道完成了 +``` + +**为什么有用:** +- git clone、wget、scp 等命令可能长时间无输出 +- 完成后也可能没有明显的提示信息 +- `__DONE__` 是明确的完成信号,不会误判 + +### 技巧 2:看提示符 + +```bash +# 命令还在运行(没有提示符) +Downloading... 45% + +# 命令已完成(出现提示符) +Downloading... 100% +user@host:~/project$ +``` + +### 技巧 3:看输出内容 + +```bash +# 安装完成的标志 +Successfully installed package-1.0.0 + +# 测试完成的标志 +===== 10 passed in 2.5s ===== + +# 构建完成的标志 +Build completed successfully + +# 下载完成的标志 +100% [=============] 1024MB +``` + +### 技巧 4:看进程状态 + +```python +# 如果不确定,可以检查进程 +terminal_input(command="ps aux | grep your_process", output_wait=5) +``` + +## 总结 + +**Terminal 工具的本质:** 持久化会话,让命令可以长期运行,随时查看状态。 + +**核心工作流:** +1. `terminal_session(action="open")` - 打开会话 +2. `terminal_input(command="...", output_wait=N)` - 发送命令 +3. `terminal_snapshot()` - 检查状态 + +**关键理解:** +- `output_wait` 是收集输出的窗口期,不是命令超时 +- 命令会在后台持续运行,即使 output_wait 结束 +- 随时可以用 `snapshot` 查看最新状态 + +**选择标准:** +- 长时间任务、后台服务、多步操作 → terminal +- 快速查看信息、一次性命令 → run_command +- 交互式程序 → 禁止使用,改用专用工具 + +**最佳实践:** +- output_wait 宁长勿短 +- 不确定是否完成时,先用 snapshot 检查 +- 任务完成后关闭或重置终端 +- 给会话起有意义的名字 diff --git a/core/main_terminal_parts/tools_definition.py b/core/main_terminal_parts/tools_definition.py index b5de149..956f19d 100644 --- a/core/main_terminal_parts/tools_definition.py +++ b/core/main_terminal_parts/tools_definition.py @@ -436,8 +436,8 @@ class MainTerminalToolsDefinitionMixin: { "type": "function", "function": { - "name": "terminal_input", - "description": "向指定终端发送命令或输入。禁止启动会占用终端界面的程序(python/node/nano/vim 等);如遇卡死请结合 terminal_snapshot 并使用 terminal_session 的 reset 恢复。timeout 必填:传入数字(秒,最大300)表示本次等待输出的时长,不会封装命令、不会强杀进程;在等待窗口内若检测到命令已完成会提前返回,否则在超时后返回已产生的输出并保持命令继续运行。需要强制超时终止请使用 run_command。\n若不确定上一条命令是否结束,先用 terminal_snapshot 确认后再继续输入。", + "name": "terminal_input", + "description": "向指定终端发送命令或输入。禁止启动会占用终端界面的程序(python/node/nano/vim 等);如遇卡死请结合 terminal_snapshot 并使用 terminal_session 的 reset 恢复。output_wait 必填:本次收集/等待输出的最大时长(秒,1-300),不会封装命令、不会强杀进程;在窗口内若检测到命令已完成会提前返回,否则到时返回已产生的输出并保持命令继续运行。需要强制超时终止请使用 run_command。\n用法建议:\n1) 短命令多次执行可设 5-10 秒;\n2) 只需确认启动的长任务(下载/clone/pip/启动服务)可设约 30 秒,运行期间可做其他事情,后续用 terminal_snapshot 判断状态/是否完成;\n3) 必须等待结果再继续的任务(大表/文件批量处理、前端构建、批量测试、压缩/解压、数据导出/统计)设足够覆盖实际执行时间;\n4) 可能长时间无输出的任务(遍历海量文件/转码/打包)短窗口不一定有输出,可适当加长或加完成标记;\n5) 长时间无输出且怀疑卡死时,使用 terminal_session 的 reset 重置终端;\n6) 需要仅发送回车时,可传入一个空格字符作为 command(等效按下 Enter)。\n若不确定上一条命令是否结束,先用 terminal_snapshot 确认后再继续输入。", "parameters": { "type": "object", "properties": self._inject_intent({ @@ -449,12 +449,12 @@ class MainTerminalToolsDefinitionMixin: "type": "string", "description": "目标终端会话名称(必填)" }, - "timeout": { + "output_wait": { "type": "number", - "description": "等待输出的最长秒数,必填,最大300;不会封装命令、不会中断进程" + "description": "等待/收集输出的最大秒数(1-300,必填);非命令超时,到点即返回已产生输出并保持命令运行" } }), - "required": ["command", "timeout", "session_name"] + "required": ["command", "output_wait", "session_name"] } } }, diff --git a/core/main_terminal_parts/tools_execution.py b/core/main_terminal_parts/tools_execution.py index 9e9d86c..00156a9 100644 --- a/core/main_terminal_parts/tools_execution.py +++ b/core/main_terminal_parts/tools_execution.py @@ -269,10 +269,13 @@ class MainTerminalToolsExecutionMixin: # 终端输入工具 elif tool_name == "terminal_input": + output_wait = arguments.get("output_wait") + if output_wait is None: + output_wait = arguments.get("timeout") result = self.terminal_manager.send_to_terminal( command=arguments["command"], session_name=arguments.get("session_name"), - timeout=arguments.get("timeout") + output_wait=output_wait ) if result["success"]: print(f"{OUTPUT_FORMATS['terminal']} 执行命令: {arguments['command']}") diff --git a/modules/persistent_terminal.py b/modules/persistent_terminal.py index a145e59..a0e180d 100644 --- a/modules/persistent_terminal.py +++ b/modules/persistent_terminal.py @@ -760,10 +760,10 @@ class PersistentTerminal: "awaiting_input": "命令已发送,终端等待进一步输入或仍在运行", "echo_loop": "检测到终端正在回显输入,命令可能未成功执行", "output_with_echo": "命令产生输出,但终端疑似重复回显", - "timeout": f"命令超时({int(timeout)}秒)" + "timeout": f"输出等待达到上限({int(timeout)}秒)" } if timeout >= 60: - message = f"[运行了{int(timeout)}秒的所有输出]" + message = f"[已收集约{int(timeout)}秒内的输出]" else: message = message_map.get(status, "命令执行完成") if output_truncated: diff --git a/modules/terminal_manager.py b/modules/terminal_manager.py index cdc5a12..36dca3b 100644 --- a/modules/terminal_manager.py +++ b/modules/terminal_manager.py @@ -492,7 +492,7 @@ class TerminalManager: self, command: str, session_name: str = None, - timeout: float = None + output_wait: float = None ) -> Dict: """ 向终端发送命令 @@ -500,7 +500,7 @@ class TerminalManager: Args: command: 要执行的命令 session_name: 目标终端(None则使用活动终端) - timeout: 必填,执行/等待的最长秒数 + output_wait: 必填,收集/等待输出的最长秒数 Returns: 执行结果 @@ -528,34 +528,35 @@ class TerminalManager: # 发送命令 terminal = self.terminals[target_session] - if isinstance(timeout, str): + if isinstance(output_wait, str): try: - timeout = float(timeout) + output_wait = float(output_wait) except (TypeError, ValueError): return { "success": False, - "error": "timeout 参数必须是数字", + "error": "output_wait 参数必须是数字", "status": "error", - "output": "timeout 参数无效" + "output": "output_wait 参数无效" } - if timeout is None or timeout <= 0: + if output_wait is None or output_wait <= 0: return { "success": False, - "error": "timeout 参数必填且需大于0", + "error": "output_wait 参数必填且需大于0", "status": "error", - "output": "timeout 参数缺失" + "output": "output_wait 参数缺失" } - timeout = min(timeout, 300) + output_wait = min(output_wait, 300) result = terminal.send_command( command, - timeout=timeout, - timeout_cutoff=timeout, + timeout=output_wait, + timeout_cutoff=output_wait, enforce_full_timeout=True, sentinel=None, ) - result["timeout"] = timeout + result["timeout"] = output_wait + result["output_wait"] = output_wait result["never_timeout"] = False self._apply_terminal_input_timeout_hint(target_session, result) return result diff --git a/prompts/main_system.txt b/prompts/main_system.txt index 957ca56..08c9bf5 100644 --- a/prompts/main_system.txt +++ b/prompts/main_system.txt @@ -63,7 +63,7 @@ **持久终端**(`terminal_session` 系列): - `terminal_session`:管理会话(open/close/list/reset),最多3个 -- `terminal_input`:发送命令(`timeout` 必填,最大300s) +- `terminal_input`:发送命令(`output_wait` 为输出收集窗口,必填,最大300s) - `terminal_snapshot`:获取输出快照(判断状态必备) - 终端卡死需用 `terminal_session` 的 reset 操作恢复 diff --git a/prompts/main_system_qwenvl.txt b/prompts/main_system_qwenvl.txt index 521495b..d043493 100644 --- a/prompts/main_system_qwenvl.txt +++ b/prompts/main_system_qwenvl.txt @@ -81,7 +81,7 @@ **持久终端**(`terminal_session` 系列): - `terminal_session`:管理会话(open/close/list/reset),最多3个 -- `terminal_input`:发送命令(`timeout` 必填,最大300s) +- `terminal_input`:发送命令(`output_wait` 为输出收集窗口,必填,最大300s) - `terminal_snapshot`:获取输出快照(判断状态必备) - 终端卡死需用 `terminal_session` 的 reset 操作恢复 diff --git a/prompts/sub_agent_guidelines.txt b/prompts/sub_agent_guidelines.txt index 0069f08..e69de29 100644 --- a/prompts/sub_agent_guidelines.txt +++ b/prompts/sub_agent_guidelines.txt @@ -1,24 +0,0 @@ -# 子智能体工具指南 - -当你通过 `create_sub_agent`/`wait_sub_agent` 管理子智能体时,请遵循以下规则: - -1. **何时创建**:当单个回复难以在时限内完成、需要长时间探索/编写大量文件或会阻塞主智能体上下文时,再考虑创建子智能体。特别是涉及大量信息搜集、网页提取、跨多篇资料的阅读与总结、需要输出结构化文件的任务,应该优先使用子智能体,避免主对话被海量搜索结果占满。子智能体适合“单一方向”的独立任务,比如按车企拆分调研、按章节拆分报告;不要让一个子智能体承担多个平行方向的需求。 -2. **描述方式**:调用 `create_sub_agent` 前,先总结任务目标(summary)、详细分工(task),并指明交付目录(target_dir)以及需要一并提供的参考文件列表。任务描述要清晰、可执行,不要把问题交给子智能体自行理解。 -3. **参考目录**:主智能体可将必要的文件列入 `reference_files`;这些文件会在子智能体的 `references/` 目录下以只读方式提供,适合提供需求文档、接口约束或已有实现片段。不要传递包含敏感信息或过于庞大的目录。 -4. **交付目录要求**:子智能体只能在其 `deliverables/` 下输出成果,主智能体最终会把该目录复制到 `target_project_dir/子任务ID_deliverables`。交付目录必须包含: - - `result.md`:用中文或中英双语写明任务完成情况、交付清单、风险与下一步建议。 - - 任务成果文件:按照主任务约定的路径/格式组织,必要时包含 README/使用说明。 -5. **等待与跟进**:创建后使用 `wait_sub_agent` 轮询;如超时或失败,需要主动查看 `copied_path` 分析原因,再决定是否重试或人工补救。不要在子任务运行期间向其发送额外消息,它无法与主智能体实时通信。设置 `timeout_seconds` 时可参考: - - 单/双次搜索即可完成的任务:180 秒; - - 需要多轮搜索、整理多篇资料:300 秒; - - 深度调研/多份长文总结:600 秒(上限依据配置) -6. **善后**:记录系统返回的 `system_message`,同步给用户;若交付不满足预期,可在主流程中补充说明或直接修改复制出的成果。 - -**拆分示例** -- “调研比亚迪/吉利/奇瑞/长安/长城新能源品牌情况” → 建议为每家车企创建一个子智能体,各自总结销量与经营情况,主智能体负责合并。 -- “对 6 个 API 做差异分析” → 可按 API 或功能模块拆分,每个子智能体负责一组接口。 -- “阅读 20 篇行业报告并整理要点” → 可按主题或时间段划分,避免单个子智能体上下文爆炸。 -- “把 8 个用户反馈邮件整理成 FAQ” → 可按邮件批次或问题类型分配给不同子智能体,保证每份输出精简清晰。 -- “提取 3 份 PDF 报告的参数表并生成对比 Markdown” → 每个子智能体负责一份 PDF 的提取与结构化,再由主流程合并成总对比表。 - -牢记:主智能体与子智能体完全隔离,只能通过上述API交互。提供明确任务、参考和交付标准,才能让子智能体按预期产出可直接交付的结果。 diff --git a/requirements.txt b/requirements.txt index 8ce5c1b..d1bfdf4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ httpx openai cryptography pillow +websockets diff --git a/server/chat_flow_stream_loop.py b/server/chat_flow_stream_loop.py index 9e41c5b..c6b1af4 100644 --- a/server/chat_flow_stream_loop.py +++ b/server/chat_flow_stream_loop.py @@ -360,7 +360,7 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien arg_chunk = tc["function"]["arguments"] existing_fn = existing.get("function", {}) existing_args = existing_fn.get("arguments", "") - existing_fn["arguments"] = (existing_args or "") + arg_chunk + existing_fn["arguments"] = (existing_args or "") + (arg_chunk or "") existing["function"] = existing_fn combined_args = existing_fn.get("arguments", "") diff --git a/server/tasks.py b/server/tasks.py index 5dc583d..4763750 100644 --- a/server/tasks.py +++ b/server/tasks.py @@ -109,6 +109,7 @@ class TaskManager: "username": session.get("username"), "role": session.get("role"), "is_api_user": session.get("is_api_user"), + "host_mode": session.get("host_mode"), "workspace_id": workspace_id, "run_mode": session.get("run_mode"), "thinking_mode": session.get("thinking_mode"), diff --git a/sub_agent/core/main_terminal.py b/sub_agent/core/main_terminal.py index 100471e..473ec15 100644 --- a/sub_agent/core/main_terminal.py +++ b/sub_agent/core/main_terminal.py @@ -1153,7 +1153,7 @@ class MainTerminal: "type": "function", "function": { "name": "terminal_input", - "description": "向活动终端发送命令或输入。禁止启动会占用终端界面的程序(python/node/nano/vim 等);如遇卡死请结合 terminal_snapshot 并使用 terminal_reset 恢复。默认等待输出120秒,最长300秒,超时会尝试中断并返回已捕获输出。", + "description": "向活动终端发送命令或输入。禁止启动会占用终端界面的程序(python/node/nano/vim 等);如遇卡死请结合 terminal_snapshot 并使用 terminal_reset 恢复。output_wait 为本次收集/等待输出的最大时长(秒,1-300),不会封装命令、不会强杀进程;在窗口内若检测到命令已完成会提前返回,否则到时返回已产生的输出并保持命令继续运行。\n用法建议:\n1) 短命令多次执行可设 5-10 秒;\n2) 只需确认启动的长任务(下载/clone/pip/启动服务)可设约 30 秒,运行期间可做其他事情,后续用 terminal_snapshot 判断状态/是否完成;\n3) 必须等待结果再继续的任务(大表/文件批量处理、前端构建、批量测试、压缩/解压、数据导出/统计)设足够覆盖实际执行时间;\n4) 可能长时间无输出的任务(遍历海量文件/转码/打包)短窗口不一定有输出,可适当加长或加完成标记;\n5) 长时间无输出且怀疑卡死时,使用 terminal_reset 重置终端;\n6) 需要仅发送回车时,可传入一个空格字符作为 command(等效按下 Enter)。", "parameters": { "type": "object", "properties": { @@ -1169,9 +1169,9 @@ class MainTerminal: "type": "boolean", "description": "是否等待输出(默认true)" }, - "timeout": { + "output_wait": { "type": "number", - "description": "等待输出的最长秒数,默认120,最大300" + "description": "等待/收集输出的最大秒数(默认120,最大300);非命令超时,到点即返回已产生输出并保持命令运行" } }, "required": ["command"] @@ -1568,11 +1568,14 @@ class MainTerminal: # 终端输入工具 elif tool_name == "terminal_input": + output_wait = arguments.get("output_wait") + if output_wait is None: + output_wait = arguments.get("timeout") result = self.terminal_manager.send_to_terminal( command=arguments["command"], session_name=arguments.get("session_name"), wait_for_output=arguments.get("wait_for_output", True), - timeout=arguments.get("timeout") + output_wait=output_wait ) if result["success"]: print(f"{OUTPUT_FORMATS['terminal']} 执行命令: {arguments['command']}") diff --git a/sub_agent/modules/terminal_manager.py b/sub_agent/modules/terminal_manager.py index d33fb0b..492f283 100644 --- a/sub_agent/modules/terminal_manager.py +++ b/sub_agent/modules/terminal_manager.py @@ -350,7 +350,7 @@ class TerminalManager: command: str, session_name: str = None, wait_for_output: bool = True, - timeout: float = None + output_wait: float = None ) -> Dict: """ 向终端发送命令 @@ -359,6 +359,7 @@ class TerminalManager: command: 要执行的命令 session_name: 目标终端(None则使用活动终端) wait_for_output: 是否等待输出 + output_wait: 等待输出的最大秒数 Returns: 执行结果 @@ -382,7 +383,9 @@ class TerminalManager: # 发送命令 terminal = self.terminals[target_session] - result = terminal.send_command(command, wait_for_output, timeout=timeout) + result = terminal.send_command(command, wait_for_output, timeout=output_wait) + result["timeout"] = output_wait + result["output_wait"] = output_wait return result diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..6d34ec4 --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1 @@ +"""项目工具包初始化文件。""" diff --git a/utils/tool_result_formatter.py b/utils/tool_result_formatter.py index a231d85..2d1e920 100644 --- a/utils/tool_result_formatter.py +++ b/utils/tool_result_formatter.py @@ -328,6 +328,8 @@ def _plain_command_output(result_data: Dict[str, Any]) -> str: output = result_data.get("output") or "" status = (result_data.get("status") or "").lower() timeout = result_data.get("timeout") + if timeout is None: + timeout = result_data.get("output_wait") return_code = result_data.get("return_code") truncated = result_data.get("truncated") error = result_data.get("error") @@ -411,10 +413,10 @@ def _format_terminal_input(result_data: Dict[str, Any]) -> str: text = _plain_command_output(result_data) timeout_hint = result_data.get("timeout_hint") if timeout_hint == "suggest_adjust_timeout": - suggestion = "请根据指令和输出结果判断是否需要提高超时时间或修改指令输入" + suggestion = "请根据指令和输出结果判断是否需要提高等待输出时长或修改指令输入" return f"{text}\n{suggestion}" if text else suggestion if timeout_hint == "suggest_never_timeout": - suggestion = "请考虑设置timeout为never,让终端持续执行该命令(⚠️注意 在该命令彻底执行完成前该终端会被占用,处于不可输入的状态)" + suggestion = "请考虑设置 timeout 为 never,让终端持续执行该命令(⚠️注意 在该命令彻底执行完成前该终端会被占用,处于不可输入的状态)" return f"{text}\n{suggestion}" if text else suggestion return text