From f623294af63cd4089eb1b72d6033fcf7a0d55dfc Mon Sep 17 00:00:00 2001
From: qing <1776177662@qq.com>
Date: Thu, 13 Aug 2026 03:56:19 +0800
Subject: [PATCH] =?UTF-8?q?fix(aibox):=20=E6=8B=A6=E6=88=AA=E7=BC=96?=
=?UTF-8?q?=E6=8E=92=E8=80=85=E8=AF=AF=E8=B0=83=E6=89=A7=E8=A1=8C=E9=9D=A2?=
=?UTF-8?q?=E5=B7=A5=E5=85=B7=E5=B9=B6=E6=B8=85=E7=90=86=E6=97=A7=E7=89=88?=
=?UTF-8?q?=20run=5Fcommand=20=E5=BC=95=E5=AF=BC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
主代理未注册 run_command 等执行面工具,但内置默认提示词与
search_skills/create_skill/install_skill 文案仍引导其直接调用。
新增 WORKER_ONLY_TOOLS 拦截:编排者误调时返回纠正信息引导改用
create_subagent;同步 3/4/6/7 号默认提示词与默认 SOUL 为编排者口径。
---
aibox/aibox.py | 225 ++++++++++++++++++++++++-------------------------
1 file changed, 112 insertions(+), 113 deletions(-)
diff --git a/aibox/aibox.py b/aibox/aibox.py
index 2119e11..de587a3 100644
--- a/aibox/aibox.py
+++ b/aibox/aibox.py
@@ -174,7 +174,7 @@ class PromptManager:
## 我的角色定位
- **助手**:帮助用户解决问题、完成任务
- **顾问**:提供建议和指导
-- **执行者**:使用工具执行具体操作
+- **编排者**:拆解任务并通过 create_subagent 派生子代理执行具体操作(命令/文件/网页),自己负责记忆、情绪与调度管理,不直接执行命令
- **记录者**:保存重要信息和对话记忆
- **学习者**:从对话中学习用户偏好,不断优化服务
@@ -189,15 +189,14 @@ class PromptManager:
## 我的能力边界
### 我能做的事情
-- 文件操作:读写文件、列出目录
-- 网页访问:获取网页内容、搜索信息
- 记忆管理:保存和检索重要信息
- 任务管理:创建和管理定时任务
-- 命令执行:运行系统命令
-- 扩展技能:支持自定义Python脚本扩展
+- 任务编排:派生子代理执行命令、文件操作、网页访问、扩展技能
+- 扩展技能:支持自定义技能文件扩展
### 我不能做的事情
-- 无法访问互联网(除非使用web工具)
+- 不能直接执行系统命令或读写文件(须派生子代理)
+- 无法访问互联网(除非派生子代理使用 web 工具)
- 无法执行危险操作
- 无法保证100%准确(我会尽力)
- 无法替代专业医疗、法律建议
@@ -319,57 +318,38 @@ save_memory_and_end_conversation(
},
{
'filename': '3_file_operations.md',
- 'content': """# 文件操作能力
+ 'content': """# 文件操作能力(编排者模式)
+
+## 重要:你不直接操作文件
+你(主对话)没有 `read_file` / `write_file` / `list_files` / `run_command` 工具。文件读写、
+目录列举等操作一律通过 `create_subagent` 派生子代理完成——子代理拥有这些工具。
## 工作目录规范(重要)
-**所有你主动创建的临时文件、脚本、数据文件,必须写入 `~/.aibox/workspace/` 目录,不要在其他目录随便写文件。**
-- 该目录是你的专用工作区,用于存放代码片段、临时脚本、下载的数据、生成的文件等
-- 用户明确指定文件路径时,按照用户要求的路径操作
-- 读取系统文件、项目文件时使用实际路径,不限制
+派生子代理执行文件任务时,**所有主动创建的临时文件、脚本、数据文件必须写入 `~/.aibox/workspace/`**,
+不要在其他目录随便写文件;用户明确指定路径时按用户要求操作。
-## 重要:文件操作改用命令行
-`read_file` / `write_file` / `list_files` 工具会返回提示你使用 `run_command` 的 markdown 指引,**请按指引执行命令完成实际操作**,不要直接返回指引内容。
+## 派生执行的标准流程
+1. 调用 `create_subagent`,在 `task` 中写清文件路径与操作要求
+2. 大文件(>500KB 或 >2000 行)要求子代理用 `head`/`tail`/行范围分段读取,不要全文返回
+3. 子代理返回总结后向用户汇报;需要原文细节时可要求子代理落盘 `~/.aibox/workspace/` 后再分段读取
-优先使用以下命令行工具:
-- **读取文件**:`head -n N 文件`、`tail -n N 文件`、`sed -n 'A,Bp' 文件`
-- **统计行数**:`wc -l 文件`
-- **查看编码**:`file -b --mime-encoding 文件`
-- **写入文件**:`mkdir -p 目录 && cat > 文件 <<'EOF' ... EOF`(覆盖)或 `cat >> 文件`(追加)
-- **列出目录**:`ls -l 目录`、`ls -la 目录`(含隐藏文件)
-- **目录大小**:`du -sh 目录`
-
-## 大文件处理规则
-当遇到以下情况时,**必须分页读取**,不要一次性读取整个文件:
-1. 文件大小超过 500KB
-2. 文件行数超过 2000 行
-
-对于大文件,应该:
-- 先用 `head -n 50` 读取前50行了解文件结构
-- 使用 `sed -n 'A,Bp'` 指定行范围
-- 使用 `tail -n N` 读取文件末尾
-- 对于二进制文件或特殊格式文件,使用 `head`/`strings` 等命令查看
-- 建议用户提供更具体的查询条件以缩小范围
-
-## 安全限制
-- 单次最多读取 500 行,单行最长 10000 字符(超长用 `cut -c 1-10000` 截断)
-- 文件大小上限 10MB,路径参数需用引号包裹
+## 示例
+```
+create_subagent role="文件处理助手" task="读取 /var/log/syslog 的最后 50 行,总结是否有报错"
+```
"""
},
{
'filename': '4_web_access.md',
- 'content': """# 网页访问能力
+ 'content': """# 网页访问能力(编排者模式)
-## 重要:网页访问改用 curl
-`web` 工具会返回提示你使用 `run_command` 执行 `curl` 命令的 markdown 指引,**请按指引执行命令完成实际访问**,不要直接返回指引内容。
+## 重要:你不直接访问网页
+你(主对话)没有 `web` / `run_command` 工具。网页访问一律通过 `create_subagent` 派生子代理
+完成——子代理拥有 `web` 工具与 `run_command`(可用 `curl`)。
-## 常用 curl 命令
-- **获取网页**:`curl -sL --max-time 10 URL`
-- **查看状态码**:`curl -sL -o /dev/null -w '%{http_code}' URL`
-- **提取纯文本**:`curl -sL URL | sed -r 's/<[^>]+>//g'`(有 lynx 时用 `lynx -dump -stdin` 更准)
-- **提取标题**:`curl -sL URL | grep -oP '
\\K[^<]+'`
-- **提取链接**:`curl -sL URL | grep -oP 'href="\\K[^"]+'`
-- **搜索关键词**:`curl -sL URL | grep -c 关键词`
-- **下载文件**:`curl -sL URL -o 文件名`
+## 派生执行的标准流程
+1. 调用 `create_subagent`,在 `task` 中写明目标 URL 与要提取的信息(标题/正文/链接)
+2. 子代理返回总结后向用户汇报
## 推荐搜索引擎
- 百度:https://www.baidu.com
@@ -377,8 +357,12 @@ save_memory_and_end_conversation(
- 搜狗:https://www.sogou.com
## 注意
-- 下载大小默认上限 100MB,可用 `| head -c 100M` 限制
-- 如需代理,先调用 `set_proxy` 工具配置,`curl` 会自动附带 `--socks5`
+- 如需代理,先调用 `set_proxy` 工具配置 SOCKS5 代理(这是你的管理面工具),子代理的 `curl` 会自动附带代理参数
+
+## 示例
+```
+create_subagent role="网页信息提取助手" task="访问 https://www.baidu.com/s?wd=人工智能,提取搜索结果前5条的标题与链接"
+```
"""
},
{
@@ -407,34 +391,26 @@ save_memory_and_end_conversation(
},
{
'filename': '6_command_execution.md',
- 'content': """# 命令执行能力
+ 'content': """# 命令执行能力(编排者模式)
-## 工作目录规范(重要)
-**执行命令时,默认 work_dir 设置为 `~/.aibox/workspace/`**,所有命令执行产生的文件都落在这个专用工作区,不会污染其他目录。
-- 只有当用户明确要求在特定目录执行命令时,才修改 work_dir
-- 技能调用(~/.aibox/skills/)不受此限制
-- 需要在用户项目目录操作时,按用户指定路径设置 work_dir
+## 重要:你不直接执行命令
+你(主对话)没有 `run_command` 工具。所有系统命令、脚本执行等**执行类操作**,
+一律通过 `create_subagent` 派生子代理完成——子代理拥有 `run_command` 与文件、网页工具。
-## 可用工具
-- **run_command**: 执行系统命令
-
-## 参数说明
-- command: 要执行的命令(必填)
-- timeout: 超时时间(秒),默认30秒
-- stream: 是否实时输出,默认true
-- work_dir: 工作目录,默认 `~/.aibox/workspace/`
-
-## 安全提醒
-- 命令执行功能可能被禁用(根据配置)
-- 执行危险命令前请三思
-- 超时机制可以防止命令卡死
-- 命令输出有大小限制(1MB),超出会自动截断
+## 派生执行的标准流程
+1. 明确任务目标与验收标准
+2. 调用 `create_subagent`,在 `role` 中写明执行者身份,在 `task` 中写清要执行的具体命令或步骤
+3. 子代理返回执行总结后,向用户汇报关键结论;产物文件在 `~/.aibox/workspace/` 下,可再派生子代理读取细节
## 示例
```
-run_command command="ls -la" timeout=10
-run_command command="python script.py" stream=true work_dir="~/.aibox/workspace"
+create_subagent role="Linux 系统运维工程师" task="执行 free -h 与 df -h,汇总内存与磁盘使用情况"
```
+
+## 注意事项
+- 简单问答、记忆/任务管理不需要派生子代理,直接回答或使用管理面工具
+- 危险命令(删除、重启服务等)派生前先向用户确认(用 `ask_user`)
+- 子代理无法向你提问,任务描述必须自包含、无歧义
"""
},
{
@@ -453,10 +429,20 @@ run_command command="python script.py" stream=true work_dir="~/.aibox/workspace"
## 如何搜索和调用扩展技能
1. 使用 `search_skills` 工具搜索关键字,获取匹配技能的名称和简介列表(传入空字符串可列出全部)。
-2. 根据技能内容,按其说明执行相应操作。
+2. Markdown 技能(.md):设置 show_content=true 直接读取技能内容,按其说明行事。
+3. 可执行技能:调用 `create_subagent` 派生子代理执行,在 `task` 中写明:
+ - 先执行 `技能名 --help` 获取完整命令手册
+ - 按手册以正确的命令行格式调用该技能
+ - 汇报执行结果
+4. 子代理返回总结后,向用户汇报关键结论。
+
+示例:
+```
+create_subagent role="技能执行助手" task="执行 bing_search 技能搜索'天气预报':先运行 bing_search --help 查看用法,再按手册执行搜索,汇总前5条结果"
+```
## 如何创建新技能(AI自主编写)
-调用 `create_skill` 工具,工具会返回一段指令提示词,指导你自行完成技能文件的编写和部署:
+调用 `create_skill` 工具,工具会返回一段指令提示词,指导你完成技能文件的编写和部署:
```
create_skill skill_name="my-tool" description="一个将摄氏度转换为华氏度的工具,接受 -t 参数输入温度值"
```
@@ -465,7 +451,7 @@ create_skill skill_name="my-tool" description="一个将摄氏度转换为华氏
收到提示词后,你需要依次:
1. 自行编写满足格式要求的 Markdown 技能文件
-2. 用 `write_file` 写入 `~/.aibox/skills/.md`
+2. 调用 `create_subagent` 派生子代理,将完整内容写入 `~/.aibox/skills/.md` 并验证
3. 调用 `reload_skills` 热重载
## 如何手动安装新技能
@@ -3186,7 +3172,7 @@ class SearchSkillsTool(Tool):
工作流程:
1. 用关键字调用本工具,获取匹配技能的名称和简介列表
2. 对于 Markdown 技能,可以直接读取 content 字段获取完整技能内容
- 3. 对于可执行技能,通过 run_command 执行 "技能名 --help" 查看完整命令手册
+ 3. 对于可执行技能,通过 create_subagent 派生子代理执行 "技能名 --help" 查看完整命令手册
4. 根据内容,使用相应方式调用技能
"""
@@ -3206,9 +3192,9 @@ class SearchSkillsTool(Tool):
" 2. Markdown 文件 (.md):第一行为简介,其余为技能内容\n\n"
"使用方法:\n"
"1. 调用本工具搜索关键字,获取匹配技能列表(空字符串列出全部)。\n"
- "2. 对于 Markdown 技能:直接读取返回的 content 字段获取完整技能内容。\n"
- "3. 对于可执行技能:用 run_command 执行 \"技能名 --help\" 获取完整命令手册。\n"
- "4. 按照内容说明,使用相应方式调用该技能。"
+ "2. 对于 Markdown 技能:设置 show_content=true 读取返回的内容获取完整技能说明。\n"
+ "3. 对于可执行技能:用 create_subagent 派生子代理执行 \"技能名 --help\" 获取完整命令手册,再按手册调用该技能。\n"
+ "4. 按照内容说明,使用相应方式调用该技能(执行类操作一律经 create_subagent 派生子代理完成)。"
)
def get_parameters(self) -> Dict:
@@ -3274,12 +3260,12 @@ class SearchSkillsTool(Tool):
if item.get('is_markdown'):
lines.append(f" 💡 提示: 设置 show_content=true 可查看技能内容")
else:
- lines.append(f" 💡 提示: 用 run_command 执行 \"{item['name']} --help\" 查看完整手册")
+ lines.append(f" 💡 提示: 用 create_subagent 派生子代理执行 \"{item['name']} --help\" 查看完整手册")
lines.append("")
lines.append("📖 技能调用方式:")
- lines.append(" • Markdown 技能: 读取 content 字段获取完整技能指令")
- lines.append(" • 可执行技能: 用 run_command 执行 '技能名 --help' 查看手册")
+ lines.append(" • Markdown 技能: 设置 show_content=true 读取完整技能说明")
+ lines.append(" • 可执行技能: 用 create_subagent 派生子代理执行(先 '技能名 --help' 查手册,再按手册调用)")
return "\n".join(lines)
def execute_streaming(self, output_queue: queue.Queue, **kwargs):
@@ -3528,7 +3514,7 @@ class InstallSkillTool(Tool):
─────────────────────────────────────────────────────
步骤 1:确认文件存在且格式正确
─────────────────────────────────────────────────────
-运行以下命令,确认文件存在:
+调用 create_subagent 派生子代理执行以下命令,确认文件存在:
ls -l {src}
@@ -3542,7 +3528,7 @@ class InstallSkillTool(Tool):
─────────────────────────────────────────────────────
技能目录:{skills_dir}
-复制命令(目标文件名统一补 .md 扩展名):
+调用 create_subagent 派生子代理执行复制命令(目标文件名统一补 .md 扩展名):
cp {src} {dst}
@@ -3574,7 +3560,7 @@ class CreateSkillTool(Tool):
工具本身不调用 LLM,只输出一段结构化的指令提示词。
智能体读到提示词后,会自行:
1. 编写满足格式要求的 Markdown 技能文件
- 2. 用 write_file 写入 ~/.aibox/skills/.md
+ 2. 经 create_subagent 派生子代理写入 ~/.aibox/skills/.md
3. 调用 reload_skills 热重载
"""
@@ -3648,8 +3634,9 @@ class CreateSkillTool(Tool):
─────────────────────────────────────────────────
步骤 2:写入技能目录
─────────────────────────────────────────────────
-使用 write_file 工具,将完整内容写入(文件名必须带 .md 扩展名):
+调用 create_subagent 派生子代理,将你编写的完整内容写入(文件名必须带 .md 扩展名):
{skill_path}
+(在 task 中附上完整文件内容,并要求子代理写入后用 head -5 验证首行格式)
─────────────────────────────────────────────────
步骤 3:热重载
@@ -5706,6 +5693,9 @@ class GetProxyTool(Tool):
TOOL_PROFILE_ORCHESTRATOR = "orchestrator"
TOOL_PROFILE_WORKER = "worker"
+# 仅 worker 子代理注册的执行面工具;编排者误调时拦截并纠正
+WORKER_ONLY_TOOLS = {"run_command", "read_file", "write_file", "list_files", "web", "calculator"}
+
class DeepSeekChat:
"""单AI聊天类"""
@@ -6042,36 +6032,45 @@ class DeepSeekChat:
output_callback("line", f"🔧 调用工具: {tool_name}\n")
result_raw = None
- try:
- # 启动后台线程持续排空 output_queue,将流式输出实时转发给 output_callback,
- # 避免 SubAgent 等长时间工具执行期间前端收不到数据导致 WS 空闲断线
- def _drain_queue(q=output_queue, cb=output_callback, stop_event=self._cancel_event):
- while not stop_event.is_set():
+ if self.tool_profile == TOOL_PROFILE_ORCHESTRATOR and tool_name in WORKER_ONLY_TOOLS:
+ # 编排者不持有执行面工具:拦截并纠正,引导改用 create_subagent
+ result_raw = (
+ f"⛔ '{tool_name}' 调用被拦截:你是编排者,没有执行面工具(命令/文件/网页由 worker 子代理持有)。\n"
+ "请改用 create_subagent 派生子代理执行该任务:在 role 中写明执行者身份,"
+ "在 task 中写清目标、具体步骤与验收标准(任务描述须自包含,子代理无法向你提问)。"
+ )
+ self.logger.warning(f"编排者误调执行面工具 {tool_name},已拦截并引导改用 create_subagent")
+ else:
+ try:
+ # 启动后台线程持续排空 output_queue,将流式输出实时转发给 output_callback,
+ # 避免 SubAgent 等长时间工具执行期间前端收不到数据导致 WS 空闲断线
+ def _drain_queue(q=output_queue, cb=output_callback, stop_event=self._cancel_event):
+ while not stop_event.is_set():
+ try:
+ msg_type, content = q.get(timeout=0.1)
+ if cb and msg_type == "line":
+ cb("line", str(content) if content is not None else "")
+ except queue.Empty:
+ continue
+
+ import threading as _th
+ drain_thread = _th.Thread(target=_drain_queue, daemon=True)
+ drain_thread.start()
+
+ result_raw = self.tool_registry.execute_tool(tool_name, **args)
+
+ # 工具执行完毕,排空队列中剩余内容并等待 drain 线程退出
+ while not output_queue.empty():
try:
- msg_type, content = q.get(timeout=0.1)
- if cb and msg_type == "line":
- cb("line", str(content) if content is not None else "")
+ msg_type, content = output_queue.get_nowait()
+ if output_callback and msg_type == "line":
+ output_callback("line", str(content) if content is not None else "")
except queue.Empty:
- continue
-
- import threading as _th
- drain_thread = _th.Thread(target=_drain_queue, daemon=True)
- drain_thread.start()
-
- result_raw = self.tool_registry.execute_tool(tool_name, **args)
-
- # 工具执行完毕,排空队列中剩余内容并等待 drain 线程退出
- while not output_queue.empty():
- try:
- msg_type, content = output_queue.get_nowait()
- if output_callback and msg_type == "line":
- output_callback("line", str(content) if content is not None else "")
- except queue.Empty:
- break
- # drain 线程会在下一次 get 超时后自动退出(队列已空)
- except Exception as e:
- result_raw = f"❌ 工具 {tool_name} 执行异常: {str(e)}"
- self.logger.error(f"工具 {tool_name} 执行异常: {e}")
+ break
+ # drain 线程会在下一次 get 超时后自动退出(队列已空)
+ except Exception as e:
+ result_raw = f"❌ 工具 {tool_name} 执行异常: {str(e)}"
+ self.logger.error(f"工具 {tool_name} 执行异常: {e}")
# 安全转换并清理结果
result = self._sanitize_tool_result(result_raw)