myagent-ai 1.23.34 → 1.23.35

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.
@@ -48,7 +48,7 @@ class MainAgent(BaseAgent):
48
48
  <mainsubject>当前对话的6字以内标题(每轮都需输出,系统会每3轮自动更新会话名称)</mainsubject>
49
49
  <usersays_correct>通过修正识别错误、调整标点,结合上下文,将用户语音转写文本"usersays"修正为更准确的文本,但尽量少改动。如"usersays"为空,则此处为空。</usersays_correct>
50
50
  <response><reply>展示给用户的文本,格式上尽量使用md格式,直观形象展示,甚至可以包括超链接、表格等。内容上,针对用户问题,直接回应问题;针对任务,开始的时候,告诉用户,为完成任务,准备如何开展工作;执行过程中,根据工具调用结果,简单展示任务进展;任务完成后的详细最终总结。注意:这是给用户展示信息的最重要标签,尽量不要跟上次回复重复,执行过程展示内容尽量简洁,执行总结可以丰富一点。</reply><toolstocal>
51
- <tool><beforecalltext>展示给用户的简单工具调用信息。格式:先使用"接下来、下一步、接着、现在、然后、最后、等下"等连接词➕调用"工具名"。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>预估调用超时时限(秒),工具调用超时会立即回调大语言模型,方便调整工具使用</timeout></tool>
51
+ <tool><beforecalltext>展示给用户的简单工具调用信息。格式:先使用"接下来、下一步、接着、现在、然后、最后、等下"等连接词➕调用"工具名"。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>最多给它执行多久(秒),工具调用超过这个时限会立即回调大语言模型,方便调整工具使用</timeout></tool>
52
52
  </toolstocal>
53
53
  </response>
54
54
  <task_plan>若"context"包含非空"task_plan",则更新它:若任务条数已超8,则精简为3条,若主题发生明显变化,重新设计任务列表。若"context"包含空"task_plan",则先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过2步,则此处输出为空,不创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过2步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记,排序按已完成在前。</task_plan>
@@ -63,7 +63,7 @@ class MainAgent(BaseAgent):
63
63
  </output>
64
64
 
65
65
  注意事项:
66
- 1. toolstocal标签: 尽量一次性列出所有需执行工具调用的,多个"tool"工具调用只要按顺序重复堆叠tool标签即可,解析器会按顺序执行工具调用,最终全部执行完后,会连同所有结果,回调大语言模型。如果某个工具执行超时了,也会回调大模型,让大模型分析为什么超时,改用其他工具。要求每个工具调用尽快合并多个命令行。注意: docx/xlsx/ppt/pdf-create 命令执行后会自动发送文件给用户,不需要再拼接 send-file。仅在需要发送其他已存在的独立文件时才需要手动调用 send-file。多个独立查询或系统命令才需要用 && 拼接。
66
+ 1. toolstocal标签: 尽量一次性列出所有需执行工具调用的,多个"tool"工具调用只要按顺序重复堆叠tool标签即可,解析器会按顺序执行工具调用,最终全部执行完后,会连同所有结果,回调大语言模型。如果某个工具执行超过预估的时限,也会回调大模型,让大模型分析为什么超时,改用其他工具。要求每个工具调用尽快合并多个命令行。注意: docx/xlsx/ppt/pdf-create 命令执行后会自动发送文件给用户,不需要再拼接 send-file。仅在需要发送其他已存在的独立文件时才需要手动调用 send-file。多个独立查询或系统命令才需要用 && 拼接。
67
67
  2. 上下文中的记忆系统说明
68
68
  - <automemory>: 系统自动根据你通过 <remember> 保存的记忆和当前用户输入,搜索出的 top10 相关记忆。这些是你过去主动记住的内容(包含时间信息),可供参考。
69
69
  - <recall_memory>: 你在上一轮通过 <recall> 指定的记忆搜索结果。系统根据你提供的关键字和时间点搜索了 top5 相关记忆。
@@ -184,7 +184,7 @@ class MainAgent(BaseAgent):
184
184
  <mainsubject>当前对话的6字以内标题(每轮都需输出,系统会每3轮自动更新会话名称)</mainsubject>
185
185
  <usersays_correct>通过修正识别错误、调整标点,结合上下文,将用户语音转写文本"usersays"修正为更准确的文本,但尽量少改动。如"usersays"为空,则此处为空。</usersays_correct>
186
186
  <response><reply>展示给用户的文本,格式上尽量使用md格式,直观形象展示,甚至可以包括超链接、表格等。内容上,针对用户问题,直接回应问题;针对任务,开始的时候,告诉用户,为完成任务,准备如何开展工作;执行过程中,根据工具调用结果,简单展示任务进展;任务完成后的详细最终总结。注意:这是给用户展示信息的最重要标签,尽量不要跟上次回复重复,执行过程展示内容尽量简洁,执行总结可以丰富一点。</reply><toolstocal>
187
- <tool><beforecalltext>展示给用户的简单工具调用信息。格式:先使用"接下来、下一步、接着、现在、然后、最后、等下"等连接词➕调用"工具名"。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>预估调用超时时限(秒),工具调用超时会立即回调大语言模型,方便调整工具使用</timeout></tool>
187
+ <tool><beforecalltext>展示给用户的简单工具调用信息。格式:先使用"接下来、下一步、接着、现在、然后、最后、等下"等连接词➕调用"工具名"。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>最多给它执行多久(秒),工具调用超过这个时限会立即回调大语言模型,方便调整工具使用。</timeout></tool>
188
188
  </toolstocal>
189
189
  </response>
190
190
  <task_plan>若"context"包含非空"task_plan",则更新它:若任务条数已超8,则精简为3条,若主题发生明显变化,重新设计任务列表。若"context"包含空"task_plan",则先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过2步,则此处输出为空,不创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过2步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记,排序按已完成在前。</task_plan>
@@ -22,7 +22,6 @@ Expected XML schema produced by the LLM::
22
22
  <toolname>工具名</toolname>
23
23
  <parms>参数JSON或描述</parms>
24
24
  <timeout>预估超时时限(秒)</timeout>
25
- <callback>true/false</callback>
26
25
  </tool>
27
26
  </toolstocal>
28
27
  </response>
@@ -62,7 +61,6 @@ logger = get_logger("myagent.output_parser")
62
61
  # ---------------------------------------------------------------------------
63
62
 
64
63
  _DEFAULT_TIMEOUT: int = 120
65
- _DEFAULT_CALLBACK: bool = True
66
64
 
67
65
  # All top-level tags we recognise inside <output>.
68
66
  KNOWN_TOP_LEVEL_TAGS = [
@@ -89,7 +87,6 @@ TOOL_INNER_TAGS = [
89
87
  "toolname",
90
88
  "parms",
91
89
  "timeout",
92
- "callback",
93
90
  ]
94
91
 
95
92
  # Inner tags inside <remember>.
@@ -590,10 +587,6 @@ def _parse_toolstocal(toolstocal_content: str, *, conservative: bool = False) ->
590
587
  _extract_tag_content(block, "timeout", TOOL_INNER_TAGS, conservative=conservative),
591
588
  _DEFAULT_TIMEOUT,
592
589
  ),
593
- "callback": _parse_bool(
594
- _extract_tag_content(block, "callback", TOOL_INNER_TAGS, conservative=conservative),
595
- _DEFAULT_CALLBACK,
596
- ),
597
590
  }
598
591
  # Only add if toolname is present
599
592
  if tool["toolname"]:
@@ -300,16 +300,15 @@ class ToolDispatcher:
300
300
  ) -> Dict:
301
301
  """发送文件给用户 — 后端推送 v2_file SSE 事件 + 持久化到聊天记录"""
302
302
  try:
303
- from skills.file_send import FileSendSkill
304
- fskill = FileSendSkill()
303
+ from skills.file_send import UPLOADS_DIR
305
304
  fpath = params.get("file_path", "")
306
305
  fdesc = params.get("description", "")
307
306
  if not fpath:
308
307
  logger.warning(f"[{task_id}] file_send: 缺少 file_path 参数")
309
308
  return {"success": False, "error": "缺少 file_path 参数,请提供要发送的文件路径"}
310
309
  logger.info(f"[{task_id}] file_send: 发送文件 {fpath}")
311
-
312
- # [v1.23.29] 先复制文件(不依赖 file_send.execute 的 SSE 发送)
310
+
311
+ # [v1.23.35] 先复制文件(不依赖 file_send.execute 的 SSE 发送)
313
312
  from pathlib import Path as _P
314
313
  import shutil, uuid as _uuid, time as _time
315
314
  fpath_resolved = _P(fpath.strip().strip("'\"")).expanduser()
@@ -319,7 +318,8 @@ class ToolDispatcher:
319
318
  return {"success": False, "error": f"不是文件: {fpath}"}
320
319
 
321
320
  file_id = str(_uuid.uuid4())[:12]
322
- date_dir = fskill.UPLOADS_DIR / _time.strftime("%Y-%m")
321
+ # [v1.23.35] 直接使用模块级 UPLOADS_DIR,不依赖 FileSendSkill 实例属性
322
+ date_dir = UPLOADS_DIR / _time.strftime("%Y-%m")
323
323
  date_dir.mkdir(parents=True, exist_ok=True)
324
324
  stored_name = f"{file_id}_{fpath_resolved.name}"
325
325
  stored_path = date_dir / stored_name
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.23.34",
3
+ "version": "1.23.35",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/web/api_server.py CHANGED
@@ -1810,12 +1810,9 @@ window.addEventListener('beforeunload', function() {{
1810
1810
  agent_name = data.get("agent_name", "default") or "default"
1811
1811
  # 支持 path 格式 (如 "coder/python-expert")
1812
1812
  agent_path = data.get("agent_path", agent_name)
1813
- raw_session_id = data.get("session_id", "") or "web_default"
1814
- # Avoid double-prefixing: if the session_id already starts with agent_path_, use it directly
1815
- if raw_session_id.startswith(f"{agent_path}_"):
1816
- session_id = raw_session_id
1817
- else:
1818
- session_id = f"{agent_path}_{raw_session_id}"
1813
+ # [v1.23.35] 直接使用前端传来的 session_id,不再拼接 agent_path 前缀
1814
+ # 新格式(sess_uuid)和旧格式(agent_path_web_xxx)均兼容
1815
+ session_id = data.get("session_id", "") or "web_default"
1819
1816
  chat_mode = data.get("mode", "") # "exec" = 执行模式
1820
1817
  escalated = data.get("escalated", False) # 临时提权到 local
1821
1818
 
@@ -1970,12 +1967,8 @@ window.addEventListener('beforeunload', function() {{
1970
1967
  return web.Response(text="data: " + json.dumps({"error": "message is required"}) + "\n\n", content_type="text/event-stream")
1971
1968
 
1972
1969
  agent_path = data.get("agent_path", data.get("agent_name", "default")) or "default"
1973
- raw_session_id = data.get("session_id", "") or "web_default"
1974
- # Avoid double-prefixing: if the session_id already starts with agent_path_, use it directly
1975
- if raw_session_id.startswith(f"{agent_path}_"):
1976
- session_id = raw_session_id
1977
- else:
1978
- session_id = f"{agent_path}_{raw_session_id}"
1970
+ # [v1.23.35] 直接使用前端传来的 session_id,不再拼接 agent_path 前缀
1971
+ session_id = data.get("session_id", "") or "web_default"
1979
1972
  chat_mode = data.get("mode", "")
1980
1973
  escalated = data.get("escalated", False)
1981
1974
  voice_text = data.get("voice_text", "").strip() # 语音转文字原始文本(用于 usersays_correct)
@@ -2255,12 +2248,8 @@ window.addEventListener('beforeunload', function() {{
2255
2248
  return web.json_response({"error": "message is required"}, status=400)
2256
2249
 
2257
2250
  agent_path = data.get("agent_path", "default")
2258
- raw_session_id = data.get("session_id", "web_default")
2259
- # Avoid double-prefixing: if the session_id already starts with agent_path_, use it directly
2260
- if raw_session_id.startswith(f"{agent_path}_"):
2261
- session_id = raw_session_id
2262
- else:
2263
- session_id = f"{agent_path}_{raw_session_id}"
2251
+ # [v1.23.35] 直接使用前端传来的 session_id,不再拼接 agent_path 前缀
2252
+ session_id = data.get("session_id", "web_default")
2264
2253
  choice = data.get("choice", "queue") # "continue" (插入后继续) 或 "queue" (排队)
2265
2254
 
2266
2255
  # 检查会话是否正在运行
@@ -453,8 +453,12 @@ async function initChat() {
453
453
  selectAgent(resolved);
454
454
  }
455
455
  } else if (urlSession) {
456
- // 只有 session 没有 agent,尝试从 session ID 推断 agent
457
- const targetAgent = urlSession.split('_web_')[0] || 'default';
456
+ // 只有 session 没有 agent,尝试从 session ID 推断 agent(兼容旧格式 agent_web_xxx)
457
+ var targetAgent = 'default';
458
+ if (urlSession.indexOf('_web_') >= 0) {
459
+ targetAgent = urlSession.split('_web_')[0] || 'default';
460
+ }
461
+ // 新格式 sess_xxx 无法推断 agent,回退 default
458
462
  var target = targetAgent;
459
463
  if (!findAgentByPath(target)) {
460
464
  console.warn('Session-inferred agent not found, fallback to default:', target);
@@ -2078,7 +2082,8 @@ function formatSessionName(id) {
2078
2082
  if (!id) return '新会话';
2079
2083
  if (id.startsWith('web_')) return id.replace('web_', '').replace(/_/g, ' ');
2080
2084
  if (id.startsWith('cli_')) return 'CLI: ' + id.replace('cli_', '');
2081
- // Strip agent prefix if present (e.g., "default_web_2024..." -> "web_2024...")
2085
+ // [v1.23.35] 新格式 sess_xxx 直接显示"新对话",由后端自动命名
2086
+ if (id.startsWith('sess_')) return '新对话';
2082
2087
  return id.length > 20 ? id.slice(0, 20) + '...' : id;
2083
2088
  }
2084
2089
 
@@ -3069,9 +3074,9 @@ window.buildMessageHtml = function(msg, idx, agent) {
3069
3074
  taskPlanHtml +
3070
3075
  finishReasonHtml +
3071
3076
  imageAttachmentHtml +
3072
- mediaEmbedHtml +
3073
3077
  wcHtml +
3074
3078
  bubbleHtml +
3079
+ mediaEmbedHtml +
3075
3080
  fileAttachmentHtml +
3076
3081
  streamingIndicator +
3077
3082
  (msg.time ? '<div class="message-time">' + formatTime(msg.time) + '</div>' : '') +
@@ -692,16 +692,16 @@ function updateStreamingMessage(msgIdx) {
692
692
  }
693
693
 
694
694
  // ── [v1.23.19] 增量渲染在线媒体嵌入播放器(v2_media 事件) ──
695
- // [v1.23.20] 媒体容器放在 bubbleWrapper 外部(和 buildMessageHtml 结构一致)
695
+ // [v1.23.35] 媒体容器放在 bubbleWrapper 之后(对话底部),不再放在前面
696
696
  if (msg._media && msg._media.length > 0) {
697
697
  var existingMedia = contentArea.querySelectorAll(':scope > .msg-attachments-media');
698
698
  var mediaContainer = existingMedia.length > 0 ? existingMedia[0] : null;
699
699
  if (!mediaContainer) {
700
700
  mediaContainer = document.createElement('div');
701
701
  mediaContainer.className = 'msg-attachments msg-attachments-media';
702
- // 插入到 bubbleWrapper 之前(和 buildMessageHtml 顺序一致:media 先,bubble 后)
702
+ // [v1.23.35] 插入到 bubbleWrapper 之后(对话末尾)
703
703
  if (bubbleWrapper && bubbleWrapper.parentNode) {
704
- bubbleWrapper.parentNode.insertBefore(mediaContainer, bubbleWrapper);
704
+ bubbleWrapper.parentNode.insertBefore(mediaContainer, bubbleWrapper.nextSibling);
705
705
  } else {
706
706
  contentArea.appendChild(mediaContainer);
707
707
  }
@@ -1592,7 +1592,7 @@ async function sendMessage(opts) {
1592
1592
  return;
1593
1593
  }
1594
1594
 
1595
- // Create session if needed (incorporate agent name)
1595
+ // [v1.23.35] Create session if needed 使用 UUID 防止 ID 重复/乱串
1596
1596
  let sessionId = state.activeSessionId;
1597
1597
  if (!sessionId || sessionId === '__new__') {
1598
1598
  // 防御:如果当前已有消息(说明有活跃会话但 ID 丢失),尝试复用已有会话
@@ -1601,8 +1601,8 @@ async function sendMessage(opts) {
1601
1601
  state.activeSessionId = sessionId;
1602
1602
  console.warn('[sendMessage] activeSessionId was empty but messages exist, reusing session:', sessionId);
1603
1603
  } else {
1604
- const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
1605
- sessionId = `${state.activeAgent}_web_${ts}`;
1604
+ // 使用 UUID v4 保证全局唯一性,不再暴露 agent 名明文
1605
+ sessionId = 'sess_' + crypto.randomUUID().replace(/-/g, '').slice(0, 20);
1606
1606
  state.activeSessionId = sessionId;
1607
1607
  document.getElementById('headerTitle').textContent = formatSessionName(sessionId);
1608
1608
  // ── 立即在左侧边栏添加新会话条目(不等后端返回) ──
@@ -842,6 +842,10 @@ function showAgentProfile(agentPath) {
842
842
  var toolsStr = tools.length > 0 ? tools.join(', ') : '默认工具集';
843
843
 
844
844
  var container = document.getElementById('groupModalContainer');
845
+ // [v1.23.35] 添加"对话"按钮 — 点击后退出群聊,切换到该 Agent 的个人对话
846
+ var chatBtnHtml = typeof selectAgent === 'function'
847
+ ? '<button class="agent-modal-btn agent-modal-btn-primary" onclick="closeGroupModal();selectAgent(\'' + escapeHtml(agentPath) + '\')">💬 对话</button>'
848
+ : '';
845
849
  container.innerHTML = '<div class="modal-overlay" onclick="if(event.target===this)closeGroupModal()"><div class="group-modal" style="max-width:380px">'
846
850
  + '<div style="display:flex;align-items:center;gap:14px;margin-bottom:16px">'
847
851
  + '<div class="group-msg-avatar" style="background:' + color + ';color:#fff;width:48px;height:48px;border-radius:12px;font-size:22px">' + emoji + '</div>'
@@ -850,6 +854,7 @@ function showAgentProfile(agentPath) {
850
854
  + '<div style="font-size:14px;color:var(--text2);line-height:1.6;margin-bottom:14px;padding:12px;background:var(--bg);border-radius:var(--radius-sm);border:1px solid var(--border-light)">' + escapeHtml(desc) + '</div>'
851
855
  + (tools.length > 0 ? '<div style="font-size:12px;color:var(--text3);margin-bottom:14px"><span style="font-weight:600">工具:</span> ' + escapeHtml(toolsStr) + '</div>' : '')
852
856
  + '<div class="agent-modal-actions">'
857
+ + chatBtnHtml
853
858
  + '<button class="agent-modal-btn agent-modal-btn-cancel" onclick="closeGroupModal()">关闭</button>'
854
859
  + '</div></div></div>';
855
860
  }