myagent-ai 1.23.1 → 1.23.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -53,7 +53,7 @@ class MainAgent(BaseAgent):
53
53
  </response>
54
54
  <task_plan>若"context"包含非空"task_plan",则更新它:若任务条数已超8,则精简为3条,若主题发生明显变化,重新设计任务列表。若"context"包含空"task_plan",则先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过2步,则此处输出为空,不创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过2步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记。</task_plan>
55
55
  <remember><type>填global或session,其中"global"为跨会话全局记忆,"session"为仅当前会话。</type><content>仅从最新用户输入,包括"userprint"或"usersays_correct"或工具调用结果,中提炼值得记忆的信息(如用户偏好、重要结论、错误经验、用户个人信息、对话要点、用户诉求、ai回复等)。因为对话默认不自动保存聊天记录,而是从记忆库搜索最相关的最新内容到"automemory"供决策,所以此次必须有所记忆,才能为后续多轮对话提供持续记忆基础。</content></remember>
56
- <recall>下一轮需要主动召回的记忆描述。填写需要从记忆库中检索的关键字或描述。如果不填写则为空。如果需要更多记忆支持当前任务,填写相关关键词(可包含时间参考,如"2025年1月的项目"),系统将在下一轮搜索top5相关记忆并通过"recall_memory"注入上下文。也可直接调用"recall_memory"工具即时搜索。</recall>
56
+ <recall>下一轮需要主动召回的记忆描述。填写需要从记忆库中检索的关键字或描述。如果不填写则为空。如果需要更多记忆支持当前任务,填写相关关键词(可包含时间参考,如"2025年1月的项目"),系统将在下一轮搜索top5相关记忆并通过"recall_memory"注入上下文。如需即时搜索记忆,使用命令: myagent-ai memory --keyword 关键词</recall>
57
57
  <knowledge>从本轮对话或工具执行结果中提炼值得长期保存到知识库的专业知识、事实、经验法则、技术要点等,将被持久化存储,未来可通过 "get_knowledge"检索复用。如果本轮无需保存的知识,则为空。格式要求:简洁明确,每条知识一行,用换行分隔。</knowledge>
58
58
  <get_knowledge>下一轮执行时需要从知识库搜索获得的知识,填写检索关键词或描述。如context中已包含充足的knowledge内容,则为空。如需更多专业知识支撑,则填写相关搜索词。</get_knowledge>
59
59
  <askuser>需要询问用户的内容,如无,则为空</askuser>
@@ -73,23 +73,81 @@ class MainAgent(BaseAgent):
73
73
  **command**(执行命令行,所有操作都通过它完成):
74
74
  <tool><toolname>command</toolname><parms>{"command": "要执行的命令"}</parms><timeout>超时秒数</timeout></tool>
75
75
 
76
- 常用 CLI 命令 (通过 command 工具调用):
76
+ 所有 CLI 命令 (通过 command 工具调用 myagent-ai):
77
+
78
+ 【感知】
77
79
  - OCR 文字识别: myagent-ai ocr 图片路径 [ch|en]
78
80
  - 图片分析(VLM): myagent-ai analyze-image 图片路径 [分析提示词]
79
81
  - 语音转文字: myagent-ai transcribe 音频路径 [zh|en|ja]
80
- - 网络搜索: myagent-ai search 关键词
81
- - 读取网页: myagent-ai read-url URL
82
+
83
+ 【搜索】
84
+ - 网络搜索: myagent-ai search 关键词 [-n 数量]
85
+ - 读取网页: myagent-ai read-url URL [--raw]
86
+ - 获取URL原始内容(API): myagent-ai fetch-url URL [-m METHOD] [-H 'Key:Val'] [-d DATA]
87
+
88
+ 【文件操作】
89
+ - 读文件: myagent-ai read 文件路径 [--offset N] [--limit N]
90
+ - 写文件: myagent-ai write 文件路径 -c "内容" [--append]
91
+ - 列出目录: myagent-ai ls 目录 [-p "*.py"] [-r] [--max N]
92
+ - 删除: myagent-ai rm 路径 [-r]
93
+ - 搜索文件内容: myagent-ai grep 关键词 目录 [-p "*.py"]
94
+ - 移动/重命名: myagent-ai mv 源路径 目标路径
82
95
  - 发送文件给用户: myagent-ai send-file 文件路径 描述
83
- - 读文件: cat 文件路径
84
- - 写文件: echo "内容" > 文件路径 或 python3 -c "open('f','w').write('内容')"
85
- - 执行代码: python3 script.py 或 python3 -c "代码"
96
+
97
+ 【文档生成】
98
+ - 创建Word: myagent-ai docx-create -c '{JSON内容}' -t 标题
99
+ - 读取Word: myagent-ai docx-read 文件.docx
100
+ - 创建Excel: myagent-ai xlsx-create -s '{JSON工作表}' -t 标题
101
+ - 读取Excel: myagent-ai xlsx-read 文件.xlsx [--sheet 名称]
102
+ - 编辑Excel: myagent-ai xlsx-edit 文件.xlsx append-row -d '{JSON}'
103
+ - 创建PPT: myagent-ai ppt-create -s '{JSON幻灯片}'
104
+ - 读取PPT: myagent-ai ppt-read 文件.pptx
105
+ - 创建PDF: myagent-ai pdf-create -c '{JSON内容}'
106
+ - 读取PDF: myagent-ai pdf-read 文件.pdf [--start N] [--end N]
107
+
108
+ 【系统】
109
+ - 系统信息: myagent-ai sysinfo
110
+ - 列出进程: myagent-ai ps [--filter 名称] [--limit N]
111
+ - 环境变量: myagent-ai env [KEY]
112
+ - 路径信息: myagent-ai pathinfo 路径
113
+
114
+ 【浏览器】
115
+ - 打开网页: myagent-ai browser-open URL
116
+ - 浏览器截图: myagent-ai browser-screenshot
117
+ - 点击元素: myagent-ai browser-click CSS选择器
118
+ - 填写输入: myagent-ai browser-fill CSS选择器 值
119
+ - 执行JS: myagent-ai browser-eval 'JS代码'
120
+ - 标签页管理: myagent-ai browser-navigate list|select|new|close
121
+ - 关闭浏览器: myagent-ai browser-close
122
+
123
+ 【GUI桌面】(仅Windows/macOS桌面)
124
+ - 屏幕截图: myagent-ai screenshot [区域] [-m 显示器]
125
+ - 鼠标点击: myagent-ai mouse-click X Y [-b left] [-c 1]
126
+ - 鼠标拖拽: myagent-ai mouse-drag X1 Y1 X2 Y2
127
+ - 输入文本: myagent-ai type-text "文本" [--clear]
128
+ - 快捷键: myagent-ai hotkey copy|paste|ctrl+c|alt+tab
129
+ - 列出窗口: myagent-ai window-list [--filter 关键词]
130
+ - 聚焦窗口: myagent-ai window-focus 标题 [--maximize]
131
+ - 屏幕元素识别: myagent-ai screen-element 描述 [区域]
132
+
133
+ 【记忆】
134
+ - 搜索记忆: myagent-ai memory [--keyword 关键词] [--limit N]
135
+
136
+ 【媒体播放】
137
+ - 播放音频: myagent-ai playaudio --url 音频URL [--title 标题] 或 myagent-ai playaudio --file 本地路径
138
+ - 播放视频: myagent-ai playvideo --url 视频URL [--title 标题] 或 myagent-ai playvideo --file 本地路径
139
+
140
+ 【通用Shell命令】
141
+ - 执行代码: python3 script.py 或 python3 -c "代码"
86
142
  - 文件列表: ls -la 目录
87
143
  - 系统信息: uname -a / df -h / free -h
88
144
 
89
145
  调用示例:
90
- <tool><toolname>command</toolname><parms>{"command": "myagent-ai ocr /tmp/screenshot.png"}</parms><timeout>30</timeout></tool>
91
146
  <tool><toolname>command</toolname><parms>{"command": "myagent-ai search 人工智能最新进展"}</parms><timeout>15</timeout></tool>
92
147
  <tool><toolname>command</toolname><parms>{"command": "myagent-ai send-file /tmp/report.pdf 季度报告"}</parms><timeout>10</timeout></tool>
148
+ <tool><toolname>command</toolname><parms>{"command": "myagent-ai docx-create -c '{\"title\": \"报告\", \"sections\": [{\"heading\": \"摘要\", \"body\": \"内容\"}]}' -t 周报"}</parms><timeout>30</timeout></tool>
149
+ <tool><toolname>command</toolname><parms>{"command": "myagent-ai playaudio --url https://music.163.com/song?id=123 --title 歌曲名"}</parms><timeout>10</timeout></tool>
150
+ <tool><toolname>command</toolname><parms>{"command": "myagent-ai playvideo --url https://www.bilibili.com/video/BV123 --title 视频名"}</parms><timeout>10</timeout></tool>
93
151
 
94
152
  多个命令可用 && 连接一次执行:
95
153
  <tool><toolname>command</toolname><parms>{"command": "myagent-ai search xxx && myagent-ai read-url https://..."}</parms><timeout>30</timeout></tool>
@@ -119,7 +177,7 @@ class MainAgent(BaseAgent):
119
177
  </response>
120
178
  <task_plan>若"context"包含非空"task_plan",则更新它:若任务条数已超8,则精简为3条,若主题发生明显变化,重新设计任务列表。若"context"包含空"task_plan",则先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过2步,则此处输出为空,不创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过2步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记。</task_plan>
121
179
  <remember><type>填global或session,其中"global"为跨会话全局记忆,"session"为仅当前会话。</type><content>仅从最新用户输入,包括"userprint"或"usersays_correct"或工具调用结果,中提炼值得记忆的信息(如用户偏好、重要结论、错误经验、用户个人信息、对话要点、用户诉求、ai回复等)。因为对话默认不自动保存聊天记录,而是从记忆库搜索最相关的最新内容到"automemory"供决策,所以此次必须有所记忆,才能为后续多轮对话提供持续记忆基础。</content></remember>
122
- <recall>下一轮需要主动召回的记忆描述。填写需要从记忆库中检索的关键字或描述。如果不填写则为空。如果需要更多记忆支持当前任务,填写相关关键词(可包含时间参考,如"2025年1月的项目"),系统将在下一轮搜索top5相关记忆并通过"recall_memory"注入上下文。也可直接调用"recall_memory"工具即时搜索。</recall>
180
+ <recall>下一轮需要主动召回的记忆描述。填写需要从记忆库中检索的关键字或描述。如果不填写则为空。如果需要更多记忆支持当前任务,填写相关关键词(可包含时间参考,如"2025年1月的项目"),系统将在下一轮搜索top5相关记忆并通过"recall_memory"注入上下文。如需即时搜索记忆,使用命令: myagent-ai memory --keyword 关键词</recall>
123
181
  <knowledge>从本轮对话或工具执行结果中提炼值得长期保存到知识库的专业知识、事实、经验法则、技术要点等,将被持久化存储,未来可通过 "get_knowledge"检索复用。如果本轮无需保存的知识,则为空。格式要求:简洁明确,每条知识一行,用换行分隔。</knowledge>
124
182
  <get_knowledge>下一轮执行时需要从知识库搜索获得的知识,填写检索关键词或描述。如context中已包含充足的knowledge内容,则为空。如需更多专业知识支撑,则填写相关搜索词。</get_knowledge>
125
183
  <askuser>需要询问用户的内容,如无,则为空</askuser>
package/core/stt.py CHANGED
@@ -37,12 +37,21 @@ def _convert_to_wav(audio_data: bytes, audio_format: Optional[str] = None) -> by
37
37
  from pydub import AudioSegment
38
38
  audio_buf = io.BytesIO(audio_data)
39
39
  seg = AudioSegment.from_file(audio_buf, format=audio_format or "webm")
40
+ # [v1.23.2] 检查音频时长,过短直接返回原始数据
41
+ if seg.duration_seconds < 0.1:
42
+ logger.debug(f"音频过短 ({seg.duration_seconds:.2f}s),跳过转换")
43
+ return audio_data
40
44
  seg = seg.set_channels(1).set_frame_rate(16000).set_sample_width(2)
41
45
  wav_buf = io.BytesIO()
42
46
  seg.export(wav_buf, format="wav")
43
47
  wav_buf.seek(0)
44
48
  return wav_buf.read()
45
- except Exception:
49
+ except Exception as e:
50
+ import shutil
51
+ if not shutil.which("ffmpeg"):
52
+ logger.warning(f"pydub 转换失败且缺少 ffmpeg: {e}")
53
+ else:
54
+ logger.warning(f"pydub 音频转换失败: {e}")
46
55
  return audio_data
47
56
 
48
57
 
@@ -68,10 +77,15 @@ async def _stt_sensevoice(audio_data: bytes, audio_format: Optional[str] = None)
68
77
  )
69
78
  logger.info("SenseVoice 模型已加载 (iic/SenseVoiceSmall, CPU)")
70
79
 
71
- # 转换为 16kHz WAV
80
+ # [v1.23.2] 增强: pydub 转换失败记录警告、WAV 头验证、音频长度检查
72
81
  wav_data = _convert_to_wav(audio_data, audio_format)
73
82
  wav_path = f"/tmp/myagent_stt_{id(audio_data) % 100000}.wav"
74
83
  try:
84
+ # 验证 WAV 文件头 (RIFF....WAVE)
85
+ if len(wav_data) < 44 or wav_data[:4] != b'RIFF' or wav_data[8:12] != b'WAVE':
86
+ logger.warning(f"SenseVoice 跳过: 无效 WAV 数据 (size={len(wav_data)}, header={wav_data[:12].hex()})")
87
+ return None
88
+
75
89
  with open(wav_path, 'wb') as f:
76
90
  f.write(wav_data)
77
91
 
@@ -108,23 +108,27 @@ class ToolDispatcher:
108
108
  Returns:
109
109
  {"success": bool, "output": str, "error": str, ...}
110
110
  """
111
- # ── 内置平台工具 ──
111
+ # ── 内置平台工具 (LLM 直接调用) ──
112
112
  if tool_name == "command":
113
- return await self._exec_command(params, timeout, task_id)
114
- elif tool_name == "recall_memory":
115
- return await self._exec_recall_memory(params, task_id)
116
- elif tool_name == "file_send":
117
- return await self._exec_file_send(params, task_id, stream_callback, sent_files)
118
- elif tool_name in ("playaudio", "playvideo"):
119
- return await self._exec_media(tool_name, params, task_id, stream_callback, sent_files)
113
+ return await self._exec_command(params, timeout, task_id, stream_callback, sent_files)
120
114
  elif tool_name == "web_control":
121
115
  return await self._exec_web_control(params, task_id, stream_callback)
122
- elif tool_name == "image_ocr":
123
- return await self._exec_image_ocr(params, task_id)
124
- elif tool_name == "image_analyze":
125
- return await self._exec_image_analyze(params, task_id)
126
- elif tool_name == "audio_transcribe":
127
- return await self._exec_audio_transcribe(params, task_id)
116
+
117
+ # ── [v1.23.0] 已迁移为内部服务/CLI 子命令的工具 ──
118
+ elif tool_name == "recall_memory":
119
+ return {"success": False, "error": "'recall_memory' 已迁移,请通过 <recall> 标签或 CLI 调用: command {\"command\": \"myagent-ai memory --keyword xxx\"}"}
120
+ elif tool_name in ("playaudio", "playvideo"):
121
+ return {"success": False, "error": f"'{tool_name}' 已迁移为 CLI 命令,请使用: command {{\"command\": \"myagent-ai {tool_name} --url URL --title 标题\"}}"}
122
+
123
+ # ── [v1.23.0] 已迁移为 CLI 子命令的工具 — 提示使用 command 调用 ──
124
+ elif tool_name in ("image_ocr", "ocr"):
125
+ return {"success": False, "error": f"'{tool_name}' 已迁移为 CLI 命令,请使用: command {{\"command\": \"myagent-ai ocr <image_path> [ch|en]\"}}"}
126
+ elif tool_name in ("image_analyze", "analyze_image"):
127
+ return {"success": False, "error": f"'{tool_name}' 已迁移为 CLI 命令,请使用: command {{\"command\": \"myagent-ai analyze-image <image_path>\"}}"}
128
+ elif tool_name in ("audio_transcribe", "transcribe"):
129
+ return {"success": False, "error": f"'{tool_name}' 已迁移为 CLI 命令,请使用: command {{\"command\": \"myagent-ai transcribe <audio_path>\"}}"}
130
+ elif tool_name == "file_send":
131
+ return {"success": False, "error": "'file_send' 已迁移为 CLI 命令,请使用: command {\"command\": \"myagent-ai send-file <path>\"}"}
128
132
 
129
133
  # ── 兜底: SkillRegistry ──
130
134
  if self.skills:
@@ -156,7 +160,9 @@ class ToolDispatcher:
156
160
  # 内置工具实现
157
161
  # =========================================================================
158
162
 
159
- async def _exec_command(self, params: Dict, timeout: int, task_id: str) -> Dict:
163
+ async def _exec_command(self, params: Dict, timeout: int, task_id: str,
164
+ stream_callback: Optional[Callable] = None,
165
+ sent_files: Optional[List[Dict[str, Any]]] = None) -> Dict:
160
166
  """执行 shell 命令"""
161
167
  code_text = params.get("command", "")
162
168
  if not code_text:
@@ -171,16 +177,15 @@ class ToolDispatcher:
171
177
  )
172
178
  result = exec_result.to_dict()
173
179
 
174
- # [v1.22.0] 检测 __SEND_FILE__ 标记 — CLI send-file 命令输出此标记
175
- # 格式: __SEND_FILE__绝对路径|描述__END__
176
180
  output = result.get("output", "")
177
181
  import re as _re
182
+
183
+ # [v1.23.0] 检测 __SEND_FILE__ 标记 — CLI send-file 命令输出此标记
184
+ # 格式: __SEND_FILE__绝对路径|描述__END__
178
185
  send_markers = _re.findall(r'__SEND_FILE__(.+?)\|(.+?)__END__', output)
179
186
  if send_markers:
180
- # 从输出中移除标记行
181
187
  clean_output = _re.sub(r'__SEND_FILE__.+?__END__\n?', '', output).strip()
182
188
  result["output"] = clean_output
183
- # 执行 file_send
184
189
  for send_path, send_desc in send_markers:
185
190
  send_path = send_path.strip()
186
191
  send_desc = send_desc.strip()
@@ -199,6 +204,34 @@ class ToolDispatcher:
199
204
  except Exception as e:
200
205
  logger.warning(f"[{task_id}] CLI 文件发送异常: {e}")
201
206
  result["output"] += f"\n[文件发送异常: {e}]"
207
+ else:
208
+ clean_output = output
209
+
210
+ # [v1.23.0] 检测 __EMBED_AUDIO__ / __EMBED_VIDEO__ 标记 — CLI playaudio/playvideo 输出
211
+ # 格式: __EMBED_AUDIO__URL|标题__END__ 或 __EMBED_VIDEO__URL|标题__END__
212
+ audio_markers = _re.findall(r'__EMBED_AUDIO__(.+?)\|(.+?)__END__', clean_output)
213
+ video_markers = _re.findall(r'__EMBED_VIDEO__(.+?)\|(.+?)__END__', clean_output)
214
+ if audio_markers:
215
+ clean_output = _re.sub(r'__EMBED_AUDIO__.+?__END__\n?', '', clean_output).strip()
216
+ result["output"] = clean_output
217
+ for media_url, media_title in audio_markers:
218
+ media_result = await self._exec_media(
219
+ "playaudio", {"url": media_url.strip(), "title": media_title.strip()},
220
+ task_id, stream_callback, sent_files,
221
+ )
222
+ if not media_result.get("success"):
223
+ result["output"] += f"\n[音频播放失败: {media_result.get('error', '')}]"
224
+ if video_markers:
225
+ clean_output = result.get("output", "")
226
+ clean_output = _re.sub(r'__EMBED_VIDEO__.+?__END__\n?', '', clean_output).strip()
227
+ result["output"] = clean_output
228
+ for media_url, media_title in video_markers:
229
+ media_result = await self._exec_media(
230
+ "playvideo", {"url": media_url.strip(), "title": media_title.strip()},
231
+ task_id, stream_callback, sent_files,
232
+ )
233
+ if not media_result.get("success"):
234
+ result["output"] += f"\n[视频播放失败: {media_result.get('error', '')}]"
202
235
 
203
236
  return result
204
237
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.23.1",
3
+ "version": "1.23.3",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {