myagent-ai 1.19.3 → 1.19.4

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.
@@ -45,8 +45,8 @@ class MainAgent(BaseAgent):
45
45
  <output>
46
46
  <mainsubject>当前对话的6字以内标题(每轮都需输出,系统会每3轮自动更新会话名称)</mainsubject>
47
47
  <usersays_correct>通过修正识别错误、调整标点,结合上下文,将用户语音转写文本"usersays"修正为更准确的文本,但尽量少改动。如"usersays"为空,则此处为空。</usersays_correct>
48
- <response><reply>展示给用户的文本内容:直接回应用户问题;告诉用户,为完成任务,准备如何开展工作;根据工具调用结果,展示任务进展。注意:这是给用户展示信息的最重要标签。</reply><toolstocal>
49
- <tool><beforecalltext>展示给用户的工具调用信息,方便用户了解调用目的。格式:先使用"接下来、下一步、接着、现在、然后"等连接词,然后介绍调用什么工具,达到什么目的。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>预估调用超时时限(秒),工具调用超时会立即回调大语言模型,方便调整工具使用</timeout></tool>
48
+ <response><reply>展示给用户的文本,格式上尽量使用md格式,直观形象展示,甚至可以包括超链接等。内容上,直接回应用户问题;告诉用户,为完成任务,准备如何开展工作;根据工具调用结果,展示任务进展;任务完成后的最终总结。注意:这是给用户展示信息的最重要标签。</reply><toolstocal>
49
+ <tool><beforecalltext>展示给用户的工具调用信息,方便用户了解调用目的。格式:先使用"接下来、下一步、接着、现在、然后、最后"等连接词,然后介绍调用什么工具,达到什么目的。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>预估调用超时时限(秒),工具调用超时会立即回调大语言模型,方便调整工具使用</timeout></tool>
50
50
  </toolstocal>
51
51
  </response>
52
52
  <task_plan>仅复杂任务使用任务计划,如"context"包含非空"task_plan",则更新它。否则,先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过3步,则此处输出为空,不要创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过3步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记。</task_plan>
@@ -61,13 +61,13 @@ class MainAgent(BaseAgent):
61
61
  </output>
62
62
 
63
63
  事项注意:
64
- 1. toolstocal标签: 可列出所有需要执行的工具调用,可以多个工具。解析器会按顺序执行工具调用,最终全部执行完后,会连同所有结果,回调大语言模型。如果某个工具执行超时了,也会回调回调大模型,让大模型分析为什么超时,改用其他工具。
64
+ 1. toolstocal标签: 可列出所有需要执行的工具调用,可以多个工具。解析器会按顺序执行工具调用,最终全部执行完后,会连同所有结果,回调大语言模型。如果某个工具执行超时了,也会回调回调大模型,让大模型分析为什么超时,改用其他工具。
65
65
  2. 上下文中的记忆系统说明
66
66
  - <automemory>: 系统自动根据你通过 <remember> 保存的记忆和当前用户输入,搜索出的 top10 相关记忆。这些是你过去主动记住的内容(包含时间信息),可供参考。
67
67
  - <recall_memory>: 你在上一轮通过 <recall> 指定的记忆搜索结果。系统根据你提供的关键字和时间点搜索了 top5 相关记忆。
68
68
  - 两种记忆互补:automemory 是自动匹配的,recall_memory 是你主动指定搜索的。如果 automemory 不足,使用 <recall> 请求更多。
69
69
  3. 工具选择指南
70
- - **搜索信息**: 用 `web_search`(返回标题+URL+摘要),不要用 browser_open
70
+ - **搜索信息**: 用 `web_search`(返回标题+URL+摘要)
71
71
  - **读取网页内容**: 用 `web_read`(传入URL,提取正文)
72
72
  - **浏览器交互**(填表、点击、截图等): 才使用 browser_open / browser_click 等
73
73
  - **执行代码**: 用 `code` 工具(language: python/javascript/shell)
@@ -75,6 +75,24 @@ class MainAgent(BaseAgent):
75
75
  - **文件操作**: 用 `file_read` / `file_write` / `file_list` 等文件工具
76
76
  - **发送文件给用户**: 用 `file_send` 工具(参数: file_path=文件路径, description=说明),当你生成或处理了文件需要返回给用户时使用
77
77
  - **主动召回记忆**: 用 `recall_memory` 工具(参数: keyword=关键字, time_point=可选时间点如"2025-01", limit=数量默认5),根据关键字和时间搜索历史记忆
78
+ 4. 准备好内容后,最后,再检查输出格式,确保满足以下要求:
79
+ <output>
80
+ <mainsubject>当前对话的6字以内标题(每轮都需输出,系统会每3轮自动更新会话名称)</mainsubject>
81
+ <usersays_correct>通过修正识别错误、调整标点,结合上下文,将用户语音转写文本"usersays"修正为更准确的文本,但尽量少改动。如"usersays"为空,则此处为空。</usersays_correct>
82
+ <response><reply>展示给用户的文本,格式上尽量使用md格式,直观形象展示,甚至可以包括超链接等。内容上,直接回应用户问题;告诉用户,为完成任务,准备如何开展工作;根据工具调用结果,展示任务进展;任务完成后的最终总结。注意:这是给用户展示信息的最重要标签。</reply><toolstocal>
83
+ <tool><beforecalltext>展示给用户的工具调用信息,方便用户了解调用目的。格式:先使用"接下来、下一步、接着、现在、然后、最后"等连接词,然后介绍调用什么工具,达到什么目的。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>预估调用超时时限(秒),工具调用超时会立即回调大语言模型,方便调整工具使用</timeout></tool>
84
+ </toolstocal>
85
+ </response>
86
+ <task_plan>仅复杂任务使用任务计划,如"context"包含非空"task_plan",则更新它。否则,先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过3步,则此处输出为空,不要创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过3步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记。</task_plan>
87
+ <remember><type>填global或session,其中"global"为跨会话全局记忆,"session"为仅当前会话。</type><content>仅从最新用户输入,包括"userprint"或"usersays_correct"或工具调用结果,中提炼值得记忆的信息(如用户偏好、重要结论、错误经验、用户个人信息、对话要点、用户诉求、ai回复等)。因为对话默认不自动保存聊天记录,而是从记忆库搜索最相关的最新内容到"automemory"供决策,所以此次必须有所记忆,才能为后续多轮对话提供持续记忆基础。</content></remember>
88
+ <recall>下一轮需要主动召回的记忆描述。填写需要从记忆库中检索的关键字或描述。如果不填写则为空。如果需要更多记忆支持当前任务,填写相关关键词(可包含时间参考,如"2025年1月的项目"),系统将在下一轮搜索top5相关记忆并通过"recall_memory"注入上下文。也可直接调用"recall_memory"工具即时搜索。</recall>
89
+ <knowledge>从本轮对话或工具执行结果中提炼值得长期保存到知识库的专业知识、事实、经验法则、技术要点等,将被持久化存储,未来可通过 "get_knowledge"检索复用。如果本轮无需保存的知识,则为空。格式要求:简洁明确,每条知识一行,用换行分隔。</knowledge>
90
+ <get_knowledge>下一轮执行时需要从知识库搜索获得的知识,填写检索关键词或描述。如context中已包含充足的knowledge内容,则为空。如需更多专业知识支撑,则填写相关搜索词。</get_knowledge>
91
+ <askuser>需要询问用户的内容,如无,则为空</askuser>
92
+ <finish>true/false,是否结束循环调用llm。如"askuser"为非空,则"finish"为true。否则,根据"context"判断任务是否已完成,是否结束llm回调</finish>
93
+ <finish_reason>当"finish"为true 时必填,详细说明为什么现在结束任务(如:任务已完成/需要用户补充信息/信息不足无法继续等)。finish若为false ,此处为空。</finish_reason>
94
+ <next_step>当 finish=false 时必填,描述下一步计划做什么(简洁明了,1-2句话)。finish=true 时为空。</next_step>
95
+ </output>
78
96
  """
79
97
 
80
98
  def __init__(self, tool_agent=None, memory_agent=None, **kwargs):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.19.3",
3
+ "version": "1.19.4",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -128,6 +128,9 @@ class SkillRegistry:
128
128
  def auto_discover(self, package: str = "skills"):
129
129
  """
130
130
  自动发现并注册 skills/ 目录下的所有内置技能。
131
+ 支持两种格式:
132
+ 1. Python Skill 子类(*_skill.py 文件)
133
+ 2. SKILL.md 目录格式(目录下包含 SKILL.md 的 markdown 技能)
131
134
 
132
135
  Args:
133
136
  package: 技能包路径
@@ -136,6 +139,7 @@ class SkillRegistry:
136
139
  if not skills_dir.exists():
137
140
  return
138
141
 
142
+ # 格式1: 扫描 *_skill.py 文件
139
143
  for file in skills_dir.glob("*_skill.py"):
140
144
  if file.name.startswith("_") or file.name == "base.py":
141
145
  continue
@@ -159,6 +163,94 @@ class SkillRegistry:
159
163
  except Exception as e:
160
164
  logger.warning(f"模块导入失败 ({module_name}): {e}")
161
165
 
166
+ # 格式2: 扫描 SKILL.md 目录格式的技能
167
+ for subdir in skills_dir.iterdir():
168
+ if not subdir.is_dir() or subdir.name.startswith("_") or subdir.name.startswith("."):
169
+ continue
170
+ skill_md = subdir / "SKILL.md"
171
+ if not skill_md.exists():
172
+ continue
173
+ try:
174
+ instance = _load_skill_from_md(subdir)
175
+ if instance and instance.name not in self._skills:
176
+ self.register(instance)
177
+ logger.info(f"自动发现 SKILL.md 技能: {instance.name} ({subdir.name})")
178
+ except Exception as e:
179
+ logger.warning(f"SKILL.md 技能加载失败 ({subdir.name}): {e}")
180
+
181
+
182
+ def _load_skill_from_md(skill_dir: Path) -> Optional[Skill]:
183
+ """从 SKILL.md 目录加载一个 Skill 实例"""
184
+ import yaml # lazy import
185
+
186
+ skill_md_path = skill_dir / "SKILL.md"
187
+ if not skill_md_path.exists():
188
+ return None
189
+
190
+ content = skill_md_path.read_text(encoding="utf-8")
191
+
192
+ # 解析 YAML front matter
193
+ if not content.startswith("---"):
194
+ return None
195
+ parts = content.split("---", 2)
196
+ if len(parts) < 3:
197
+ return None
198
+ try:
199
+ meta = yaml.safe_load(parts[1]) or {}
200
+ except Exception:
201
+ meta = {}
202
+
203
+ name = meta.get("name", skill_dir.name)
204
+ description = meta.get("description", "")
205
+ if isinstance(description, list):
206
+ description = "\n".join(description)
207
+ description = description.strip()
208
+ category = meta.get("metadata", {}).get("category", "") if isinstance(meta.get("metadata"), dict) else ""
209
+ version = meta.get("metadata", {}).get("version", "") if isinstance(meta.get("metadata"), dict) else ""
210
+
211
+ # 计算 references 目录中的参考文档
212
+ ref_dir = skill_dir / "references"
213
+ references = []
214
+ if ref_dir.exists():
215
+ for ref_file in sorted(ref_dir.glob("*.md")):
216
+ references.append(ref_file.name)
217
+
218
+ class MarkdownSkill(Skill):
219
+ """SKILL.md 格式的技能包装器"""
220
+ def __init__(self):
221
+ super().__init__()
222
+ self.name = name
223
+ self.description = description or f"Markdown 技能 ({name})"
224
+ self.parameters = []
225
+ # 存储额外元信息
226
+ self._skill_type = "markdown"
227
+ self._skill_dir = str(skill_dir)
228
+ self._category = category
229
+ self._version = version
230
+ self._references = references
231
+ self._has_templates = (skill_dir / "templates").exists()
232
+ self._has_scripts = (skill_dir / "scripts").exists()
233
+
234
+ async def execute(self, **kwargs) -> SkillResult:
235
+ return SkillResult(
236
+ success=False,
237
+ error=f"SKILL.md 格式技能不支持直接调用,请通过 LLM 使用 /{self.name} 触发",
238
+ )
239
+
240
+ def to_openclaw_format(self) -> Dict[str, Any]:
241
+ info = super().to_openclaw_format()
242
+ info["skill_type"] = "markdown"
243
+ info["category"] = self._category
244
+ if self._version:
245
+ info["version"] = self._version
246
+ if self._references:
247
+ info["references"] = self._references
248
+ info["has_templates"] = self._has_templates
249
+ info["has_scripts"] = self._has_scripts
250
+ return info
251
+
252
+ return MarkdownSkill()
253
+
162
254
 
163
255
  # ==============================================================================
164
256
  # 全局注册表
package/web/ui/index.html CHANGED
@@ -230,9 +230,16 @@ tr:hover{background:var(--surface2)}
230
230
  .toast{left:16px;right:16px;bottom:16px}
231
231
  .agent-card{flex-direction:column;align-items:flex-start}
232
232
  .agent-card .flex.flex-col{flex-direction:row;gap:4px}
233
- /* 技能行移动端允许换行 */
234
- .skill-row{flex-wrap:wrap}
235
- .skill-row .flex{flex-wrap:wrap;gap:4px}
233
+ /* 技能行移动端优化:纵向堆叠,简介区域不受挤压 */
234
+ .skill-row{flex-wrap:wrap;gap:8px;align-items:flex-start;padding:12px}
235
+ .skill-row > div:first-child{display:none}
236
+ .skill-row .skill-info{flex:1 1 100%;min-width:0}
237
+ .skill-row .skill-name{font-size:14px}
238
+ .skill-row .skill-desc{font-size:13px;line-height:1.6}
239
+ .skill-row .flex{flex-wrap:wrap;gap:6px;width:100%;justify-content:flex-start}
240
+ .skill-row .flex .toggle{margin-left:auto}
241
+ .skill-row .flex .tag{font-size:11px}
242
+ .skill-row .flex .badge{font-size:10px}
236
243
  /* 任务表格优化 */
237
244
  .table-wrap table{min-width:auto}
238
245
  .table-wrap td[style*="max-width:300px"]{max-width:150px!important;white-space:normal!important;word-break:break-all}
@@ -1748,16 +1755,23 @@ async function renderSkills(){
1748
1755
  function skillRowHtml(s){
1749
1756
  const isDisabled=s.disabled;
1750
1757
  const paramCount=(s.parameters||[]).length;
1758
+ const isMarkdown=s.skill_type==='markdown';
1759
+ const icon=isMarkdown?'📝':'🔧';
1760
+ const typeBadge=isMarkdown?'<span class="badge badge-purple">Prompt</span>':'<span class="badge badge-green">内置</span>';
1761
+ const paramTag=paramCount>0?`<span class="tag">${paramCount} 参数</span>`:'';
1762
+ const refTag=(s.references||[]).length>0?`<span class="tag">${s.references.length} 参考资料</span>`:'';
1763
+ const tplTag=s.has_templates?'<span class="tag">含模板</span>':'';
1764
+ const scriptTag=s.has_scripts?'<span class="tag">含脚本</span>':'';
1751
1765
  return `<div class="skill-row" data-name="${escHtml(s.name||'')}" data-desc="${escHtml(s.description||'')}">
1752
- <div style="flex-shrink:0;width:36px;text-align:center;font-size:18px">🔧</div>
1766
+ <div style="flex-shrink:0;width:36px;text-align:center;font-size:18px">${icon}</div>
1753
1767
  <div class="skill-info">
1754
1768
  <div class="skill-name">${escHtml(s.name)} ${isDisabled?'<span class="badge badge-red">已禁用</span>':''} ${s.dangerous?'<span class="badge badge-red">⚠ 危险</span>':''}</div>
1755
1769
  <div class="skill-desc">${escHtml(s.description||'暂无描述')}</div>
1756
1770
  </div>
1757
1771
  <div class="flex gap-8 items-center" style="flex-shrink:0">
1758
- <span class="badge badge-green">内置</span>
1772
+ ${typeBadge}
1759
1773
  ${s.category?`<span class="badge badge-blue">${escHtml(s.category)}</span>`:''}
1760
- <span class="tag">${paramCount} 参数</span>
1774
+ ${paramTag}${refTag}${tplTag}${scriptTag}
1761
1775
  <label class="toggle" title="${isDisabled?'启用':'禁用'}"><input type="checkbox" ${!isDisabled?'checked':''} onchange="toggleSkill('${escHtml(s.name)}',this.checked)"><span class="slider"></span></label>
1762
1776
  <button class="btn btn-sm btn-ghost" onclick="viewSkillDetail('${escHtml(s.name)}')">详情</button>
1763
1777
  </div>