myagent-ai 1.15.73 → 1.15.75

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.
@@ -39,54 +39,34 @@ class MainAgent(BaseAgent):
39
39
  # =========================================================================
40
40
  # 系统提示词 — 结构化输出格式
41
41
  # =========================================================================
42
- SYSTEM_PROMPT = """你是一个强内容分析格式转化引擎,要深入分析以下上下文内容:
42
+ SYSTEM_PROMPT = """你是一个智能AI助手,请深入分析以下上下文内容。
43
43
 
44
- 严格以XML格式化输出以下内容:
44
+ 严格以XML格式化输出以下内容,否则解析器无法解析:
45
45
  <output>
46
- <response>直接回复用户的内容。这是一段友好、自然的话语,用于向用户说明你正在做什么,或者回应用户的问题/问候。要求简洁、有礼貌、符合对话场景。如果用户只是问候,简单回应即可;如果用户有具体任务,要说明你的计划。</response>
47
- <usersays_correct>根据用户输入的"usersays"内容(语音转写文本),结合对话语境优化为更准确的用户意图表达(修正识别错误、补充标点、口语转书面语)。如果"usersays"为空,这里输出为空。</usersays_correct>
48
- <task_plan>任务计划(仅复杂任务使用):如"context"包含非空"task_plan",则更新它。否则,先评估任务复杂度——如果预计操作步骤不超过3步(如:单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务),则<task_plan>输出为空,不要创建任务列表;只有当任务较复杂(预计超过3步操作,如:多文件修改、需要调研+实现+测试、涉及多个模块联动等),才以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记。</task_plan>
49
-
50
- <toolstocal>
51
- <tool><beforecalltext>连接词,介绍调用什么工具,达到什么目的。</beforecalltext><toolname>工具名</toolname><parms>JSON格式的参数对象,例如: {"query": "搜索关键词", "num": 5}</parms><timeout>预估超时时限(秒)</timeout></tool>
46
+ <mainsubject>当前对话的6字以内标题</mainsubject>
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>
52
50
  </toolstocal>
53
- <remember><type>global或session</type><content>仅从最新用户输入(userprint 或 usersays_correct)中提炼值得记忆的信息(如用户偏好、重要结论、错误经验等)。type=global表示跨会话全局记忆,type=session表示仅当前会话可用的记忆。如果本轮没有新信息需要记忆,则<content>为空、<type>不填。</content></remember>
54
- <recall>下一轮需要主动召回的记忆描述。填写需要从记忆库中检索的关键字或描述。如果不填写则为空。如果需要更多记忆支持当前任务,填写相关关键词(可包含时间参考,如"2025年1月的项目"),系统将在下一轮搜索top5相关记忆并通过<recall_memory>注入上下文。你也可以直接调用recall_memory工具即时搜索。</recall>
55
- <knowledge>从本轮对话或工具执行结果中提炼值得长期保存到知识库的专业知识、事实、经验法则、技术要点等。这些知识将被持久化存储,未来可通过 <get_knowledge> 检索复用。如果本轮没有需要保存的知识,则为空。格式要求:简洁明确,每条知识一行,用换行分隔。</knowledge>
56
- <get_knowledge>下一轮执行时需要从知识库搜索获得的知识,填写检索关键词或描述。如context中已包含充足的<knowledge>内容,则为空。如需更多专业知识支撑,则填写相关搜索词。</get_knowledge>
51
+ </response>
52
+ <task_plan>仅复杂任务使用任务计划,如"context"包含非空"task_plan",则更新它。否则,先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过3步,则此处输出为空,不要创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过3步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记。</task_plan>
53
+ <remember><type>填global或session,其中"global"为跨会话全局记忆,"session"为仅当前会话。</type><content>仅从最新用户输入,包括"userprint"或"usersays_correct"或工具调用结果,中提炼值得记忆的信息(如用户偏好、重要结论、错误经验等)。如本轮无新信息要记忆,则为空,type也为空。</content></remember>
54
+ <recall>下一轮需要主动召回的记忆描述。填写需要从记忆库中检索的关键字或描述。如果不填写则为空。如果需要更多记忆支持当前任务,填写相关关键词(可包含时间参考,如"2025年1月的项目"),系统将在下一轮搜索top5相关记忆并通过"recall_memory"注入上下文。也可直接调用"recall_memory"工具即时搜索。</recall>
55
+ <knowledge>从本轮对话或工具执行结果中提炼值得长期保存到知识库的专业知识、事实、经验法则、技术要点等,将被持久化存储,未来可通过 "get_knowledge"检索复用。如果本轮无需保存的知识,则为空。格式要求:简洁明确,每条知识一行,用换行分隔。</knowledge>
56
+ <get_knowledge>下一轮执行时需要从知识库搜索获得的知识,填写检索关键词或描述。如context中已包含充足的knowledge内容,则为空。如需更多专业知识支撑,则填写相关搜索词。</get_knowledge>
57
57
  <askuser>需要询问用户的内容,如无,则为空</askuser>
58
- <finish>true/false,是否结束循环调用llm。如"askuser"为非空,则"finish"输出true。否则,根据"context"判断任务是否已完成,是否结束llm回调</finish>
59
- <finish_reason>当 finish=true 时必填,详细说明为什么现在结束任务(如:任务已完成/需要用户补充信息/信息不足无法继续等)。finish=false 时为空。</finish_reason>
58
+ <finish>true/false,是否结束循环调用llm。如"askuser"为非空,则"finish"true。否则,根据"context"判断任务是否已完成,是否结束llm回调</finish>
59
+ <finish_reason>当"finish"为true 时必填,详细说明为什么现在结束任务(如:任务已完成/需要用户补充信息/信息不足无法继续等)。finish若为false ,此处为空。</finish_reason>
60
60
  <next_step>当 finish=false 时必填,描述下一步计划做什么(简洁明了,1-2句话)。finish=true 时为空。</next_step>
61
- <mainsubject>为当前对话生成一个简短的标题(6个字以内),概括对话主题。仅在对话刚开始(前几轮)时需要输出,已有标题的对话此标签留空即可。要求简洁精炼,例如"Python安装"、"请假流程"、"翻译助手"等。</mainsubject>
62
-
63
61
  </output>
64
62
 
65
- ## 核心规则
66
- 1. 你必须且只能输出 <output> XML 结构,不要输出任何其他文本
67
- 2. <response>: 必须输出一段直接回复用户的话语(这是用户实际看到的回复),要求简洁友好、自然流畅。不要只输出任务计划而不说话!
68
- 3. <usersays_correct>: 如果 context 中 usersays 非空(说明用户通过语音输入),则根据对话语境将语音转写文本优化为更准确的用户意图表达,修正识别错误、补充标点、口语转书面语。如果 usersays 为空,这里输出为空。
69
- 4. <task_plan>: 仅用于复杂任务(预计超过3步操作)。简单任务(≤3步)输出为空。复杂任务使用 Markdown 列表格式,每项包含任务描述和完成状态标记 [x]/[ ]
70
- 5. <toolstocal>: 列出所有需要执行的工具调用,每个工具包含完整的参数说明
71
- 6. <parms>: **必须使用严格合法的JSON格式**,例如 {"query": "关键词", "num": 10},不要使用其他格式
72
- 7. <timeout>: 预估超时秒数(简单操作10-30s,文件操作30-60s,网络请求60-120s,数据处理120-300s)
73
- 8. <remember>: 包含 <type> 和 <content> 子标签。type 填 "global"(跨会话全局记忆)或 "session"(仅当前会话)。content 填从最新用户输入中提炼的值得记忆的关键信息。如果本轮无需记忆,content 为空且不填 type。注意:用户个人偏好、重要结论、通用经验用 global;当前任务的临时上下文、过程信息用 session
74
- 9. <recall>: 填写下一轮需要从记忆库中主动召回的内容描述和关键字(可包含时间参考)。系统将根据这些信息搜索top5相关记忆,在下一轮通过 <recall_memory> 标签注入上下文。如果当前 <automemory> 中的记忆已足够完成任务,<recall> 为空;如果需要更多历史记忆支撑,则填写。你也可以直接使用 recall_memory 工具在当前轮即时搜索
75
- 10. <knowledge>: 从本轮对话或工具执行结果中提炼值得长期保存的专业知识、事实、经验法则、技术要点等。这些知识会被持久化到知识库文件,未来可通过 get_knowledge 检索复用。如果没有需要保存的知识,则为空。格式:简洁明确,每条知识一行
76
- 11. <get_knowledge>: 如果当前 <knowledge> 内容不足以完成任务,填写需要从知识库搜索的关键词;否则为空
77
- 12. <askuser>: 当信息不足需要用户补充时,在此填写要问的问题
78
- 13. <finish>: 当任务已完成或需要等待用户回应时为 true;否则为 false 继续执行
79
- 14. <finish_reason>: **finish=true 时必须填写**,详细说明结束原因(任务完成/等待用户/信息不足/无法处理等)
80
- 15. <next_step>: **finish=false 时必须填写**,描述下一步计划做什么,要求简洁明确(1-2句话)
81
- 16. <mainsubject>: 为当前对话生成6字以内的简短标题,概括对话主题。仅在对话刚开始的前几轮需要输出,已有标题后留空
82
- 17. 使用中文输出所有内容
83
-
84
- ## 上下文中的记忆系统说明
63
+ 事项注意:
64
+ 1. toolstocal标签: 可列出所有需要执行的工具调用,可以多个工具。解析器会按顺序执行工具调用,最终全部执行完后,会连同所有结果,回调大语言模型。如果某个工具执行超时了,也会回调回调大模型,让大模型分析为什么超时,改用其他工具。
65
+ 2. 上下文中的记忆系统说明
85
66
  - <automemory>: 系统自动根据你通过 <remember> 保存的记忆和当前用户输入,搜索出的 top10 相关记忆。这些是你过去主动记住的内容(包含时间信息),可供参考。
86
67
  - <recall_memory>: 你在上一轮通过 <recall> 指定的记忆搜索结果。系统根据你提供的关键字和时间点搜索了 top5 相关记忆。
87
68
  - 两种记忆互补:automemory 是自动匹配的,recall_memory 是你主动指定搜索的。如果 automemory 不足,使用 <recall> 请求更多。
88
-
89
- ## 工具选择指南
69
+ 3. 工具选择指南
90
70
  - **搜索信息**: 用 `web_search`(返回标题+URL+摘要),不要用 browser_open
91
71
  - **读取网页内容**: 用 `web_read`(传入URL,提取正文)
92
72
  - **浏览器交互**(填表、点击、截图等): 才使用 browser_open / browser_click 等
@@ -206,39 +186,70 @@ class MainAgent(BaseAgent):
206
186
  """[v1.15.73] 从不完整的 LLM 输出中提取部分回复内容。
207
187
 
208
188
  当 <output> 块被截断(缺少 </output>)时,尝试:
209
- 1. 提取 <response>...</response> 中已闭合的内容
210
- 2. 提取 <output> 后到截断点之间的纯文本
211
- 3. 去除 XML 标签后的残余文本
189
+ 1. 提取 <reply>...</reply> 中已闭合的内容(优先,v1.15.74)
190
+ 2. 提取 <response>...</response> 中已闭合的内容(兜底)
191
+ 3. 提取 <knowledge>...</knowledge> 中已闭合的内容(兜底)
192
+ 4. 提取 <output> 后到截断点之间的纯文本
193
+ 5. 去除 XML 标签后的残余文本
212
194
  """
213
195
  if not llm_raw:
214
196
  return ""
215
197
 
216
198
  import re
199
+ _parts = []
217
200
 
218
- # 策略1: 尝试提取已闭合的 <response> 内容
219
- response_match = re.search(
220
- r"<response[^>]*>(.*?)</response>",
201
+ # 策略1: 尝试提取已闭合的 <reply> 内容(v1.15.74 新结构)
202
+ reply_match = re.search(
203
+ r"<reply[^>]*>(.*?)</reply>",
221
204
  llm_raw,
222
205
  re.DOTALL | re.IGNORECASE,
223
206
  )
224
- if response_match:
225
- text = response_match.group(1).strip()
207
+ if reply_match:
208
+ text = reply_match.group(1).strip()
226
209
  if text:
227
- return text
210
+ _parts.append(text)
211
+
212
+ # 策略2: 尝试提取已闭合的 <response> 内容(不含嵌套XML则为旧格式)
213
+ if not _parts:
214
+ response_match = re.search(
215
+ r"<response[^>]*>(.*?)</response>",
216
+ llm_raw,
217
+ re.DOTALL | re.IGNORECASE,
218
+ )
219
+ if response_match:
220
+ raw_resp = response_match.group(1).strip()
221
+ if raw_resp:
222
+ # 去除嵌套的 <reply>/<toolstocal> 标签
223
+ cleaned = re.sub(r"<[^>]+>", "", raw_resp).strip()
224
+ if cleaned:
225
+ _parts.append(cleaned)
226
+
227
+ # 策略3: 尝试提取已闭合的 <knowledge> 内容(兜底)
228
+ if not _parts:
229
+ knowledge_match = re.search(
230
+ r"<knowledge[^>]*>(.*?)</knowledge>",
231
+ llm_raw,
232
+ re.DOTALL | re.IGNORECASE,
233
+ )
234
+ if knowledge_match:
235
+ text = knowledge_match.group(1).strip()
236
+ if text and len(text) > 20:
237
+ _parts.append(text)
238
+
239
+ if _parts:
240
+ return "\n".join(_parts)
228
241
 
229
- # 策略2: 提取 <output> 标签后的内容(可能包含未闭合的 <response>)
242
+ # 策略4: 提取 <output> 标签后的内容(可能包含未闭合的标签)
230
243
  output_match = re.search(r"<output[^>]*>", llm_raw, re.IGNORECASE)
231
244
  if output_match:
232
245
  after_output = llm_raw[output_match.end():].strip()
233
246
  if after_output:
234
- # 去除所有 XML 标签,保留纯文本
235
247
  cleaned = re.sub(r"<[^>]+>", "", after_output).strip()
236
- # 去除 reasoning/assistant 前缀
237
248
  cleaned = re.sub(r"^(reasoning|assistant)\s*", "", cleaned, flags=re.IGNORECASE).strip()
238
249
  if cleaned and len(cleaned) > 5:
239
250
  return cleaned
240
251
 
241
- # 策略3: 提取去除 XML 标签后的整体文本
252
+ # 策略5: 提取去除 XML 标签后的整体文本
242
253
  cleaned = re.sub(r"<[^>]+>", "", llm_raw).strip()
243
254
  cleaned = re.sub(r"^(reasoning|assistant)\s*", "", cleaned, flags=re.IGNORECASE).strip()
244
255
  if cleaned and len(cleaned) > 10:
@@ -603,7 +614,7 @@ class MainAgent(BaseAgent):
603
614
  # Step 2: 构建系统消息 — 将 context XML 插入 SYSTEM_PROMPT 的 "上下文" 占位处
604
615
  _CONTEXT_PLACEHOLDER = "__CONTEXT_PLACEHOLDER__"
605
616
  _prompt_with_placeholder = (
606
- "你是一个强内容分析格式转化引擎,要深入分析以下上下文内容:\n\n"
617
+ "你是一个智能AI助手,请深入分析以下上下文内容。\n\n"
607
618
  + _CONTEXT_PLACEHOLDER + "\n\n"
608
619
  + self.SYSTEM_PROMPT.split("\n", 1)[1]
609
620
  )
@@ -860,19 +871,38 @@ class MainAgent(BaseAgent):
860
871
  if parsed.usersays_correct:
861
872
  context.working_memory["usersays_correct"] = parsed.usersays_correct
862
873
 
863
- # Step 5.5: 处理 response — 直接回复用户的内容
864
- if parsed.response:
865
- response_text = parsed.response.strip()
866
- if response_text:
867
- logger.debug(f"[{task_id}] 模型回复用户: {response_text[:100]}")
868
- context.working_memory["model_response"] = response_text
869
- _v2_reasoning_collected.append(response_text)
870
- _emitted_reasoning_this_iter = True
871
- await self._emit_v2_event(
872
- "v2_reasoning",
873
- {"content": response_text},
874
- stream_callback,
875
- )
874
+ # Step 5.5: 处理 reply + response — 直接回复用户的内容
875
+ # [v1.15.74] reply(嵌套在<response>内,优先) 作为用户主要可见文本
876
+ # response 可能包含嵌套的 <reply> 和 <toolstocal> XML,需提取纯文本作为兜底
877
+ import re as _re
878
+ _user_visible_parts = []
879
+ if parsed.reply:
880
+ reply_text = parsed.reply.strip()
881
+ if reply_text:
882
+ logger.debug(f"[{task_id}] 模型reply: {reply_text[:100]}")
883
+ _user_visible_parts.append(reply_text)
884
+ if not _user_visible_parts and parsed.response:
885
+ # 兜底:从 response 中去除 XML 标签提取纯文本
886
+ response_clean = _re.sub(r"<[^>]+>", "", parsed.response).strip()
887
+ if response_clean:
888
+ logger.debug(f"[{task_id}] 模型response(去除标签): {response_clean[:100]}")
889
+ _user_visible_parts.append(response_clean)
890
+ # 防御性兜底:如果 knowledge 非空且 reply/response 都空,追加 knowledge
891
+ if not _user_visible_parts and parsed.knowledge:
892
+ kb_text = parsed.knowledge.strip()
893
+ if kb_text and len(kb_text) > 20:
894
+ logger.debug(f"[{task_id}] knowledge内容兜底追加到用户回复: {kb_text[:100]}")
895
+ _user_visible_parts.append(kb_text)
896
+ if _user_visible_parts:
897
+ combined_text = "\n".join(_user_visible_parts)
898
+ context.working_memory["model_response"] = combined_text
899
+ _v2_reasoning_collected.append(combined_text)
900
+ _emitted_reasoning_this_iter = True
901
+ await self._emit_v2_event(
902
+ "v2_reasoning",
903
+ {"content": combined_text},
904
+ stream_callback,
905
+ )
876
906
 
877
907
  # Step 6: 处理 remember — 按 type 分全局/会话存储
878
908
  if parsed.remember:
@@ -12,17 +12,20 @@ pure Python + regex to achieve maximum fault tolerance.
12
12
  Expected XML schema produced by the LLM::
13
13
 
14
14
  <output>
15
+ <mainsubject>当前对话的6字以内标题</mainsubject>
15
16
  <usersays_correct>...</usersays_correct>
16
- <task_plan>...</task_plan>
17
- <toolstocal>
18
- <tool>
19
- <beforecalltext>连接词,介绍调用什么工具</beforecalltext>
20
- <toolname>工具名</toolname>
21
- <parms>参数JSON或描述</parms>
22
- <timeout>预估超时时限(秒)</timeout>
23
- <callback>true/false</callback>
24
- </tool>
25
- </toolstocal>
17
+ <response>
18
+ <reply>展示给用户的文本内容</reply>
19
+ <toolstocal>
20
+ <tool>
21
+ <beforecalltext>连接词,介绍调用什么工具</beforecalltext>
22
+ <toolname>工具名</toolname>
23
+ <parms>参数JSON或描述</parms>
24
+ <timeout>预估超时时限(秒)</timeout>
25
+ <callback>true/false</callback>
26
+ </tool>
27
+ </toolstocal>
28
+ </response>
26
29
  <remember>
27
30
  <type>global|session</type>
28
31
  <content>记忆内容</content>
@@ -31,7 +34,6 @@ Expected XML schema produced by the LLM::
31
34
  <askuser>需要询问用户的内容</askuser>
32
35
  <get_knowledge>下一轮需要搜索获得的知识</get_knowledge>
33
36
  <finish>true/false</finish>
34
- <response>模型对用户的直接回复</response>
35
37
  </output>
36
38
 
37
39
  Fault-tolerance features:
@@ -76,6 +78,7 @@ KNOWN_TOP_LEVEL_TAGS = [
76
78
  "finish",
77
79
  "finish_reason",
78
80
  "next_step",
81
+ "reply", # [v1.15.74] 用户可见文本(嵌套在<response>内)
79
82
  "response",
80
83
  "mainsubject", # [v1.15.8] 会话标题自动命名
81
84
  ]
@@ -128,7 +131,8 @@ class ParsedOutput:
128
131
  finish: When ``True`` the execution loop should terminate.
129
132
  finish_reason: When finish=True, explains why the task is ending.
130
133
  next_step: When finish=False, describes what to do next.
131
- response: Model's direct reply to the user (friendly natural language).
134
+ response: Model's direct reply to the user (may contain nested <reply> and <toolstocal>).
135
+ reply: User-visible text content extracted from <reply> tag (primary display content).
132
136
  raw_text: The verbatim raw text returned by the LLM.
133
137
  parse_success: Whether parsing extracted at least one meaningful field.
134
138
  needs_correction: When ``True``, the caller should send the raw text
@@ -148,6 +152,7 @@ class ParsedOutput:
148
152
  finish_reason: str = ""
149
153
  next_step: str = ""
150
154
  response: str = ""
155
+ reply: str = "" # [v1.15.74] 用户可见文本(嵌套在<response>内)
151
156
  mainsubject: str = "" # [v1.15.8] 会话标题自动命名(6字以内)
152
157
  raw_text: str = ""
153
158
  parse_success: bool = False
@@ -456,6 +461,10 @@ def _custom_parse(raw_text: str) -> ParsedOutput:
456
461
  raw_val = _extract_tag_content(body, "response", conservative=conservative)
457
462
  parsed.response = _safe_strip(raw_val)
458
463
 
464
+ # reply [v1.15.74] 用户可见文本(嵌套在<response>内,优先使用)
465
+ raw_val = _extract_tag_content(body, "reply", conservative=conservative)
466
+ parsed.reply = _safe_strip(raw_val)
467
+
459
468
  # recall
460
469
  raw_val = _extract_tag_content(body, "recall", conservative=conservative)
461
470
  parsed.recall = _safe_strip(raw_val)
@@ -516,6 +525,7 @@ def _custom_parse(raw_text: str) -> ParsedOutput:
516
525
  # ── Step 5: Determine parse success ──
517
526
  has_content = bool(
518
527
  parsed.response
528
+ or parsed.reply
519
529
  or parsed.usersays_correct
520
530
  or parsed.task_plan
521
531
  or parsed.tools_to_call
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.15.73",
3
+ "version": "1.15.75",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/start.js CHANGED
@@ -408,15 +408,6 @@ function cmdUpdate(pkgDir) {
408
408
  process.exit(1);
409
409
  }
410
410
 
411
- // 3. 删除旧 venv 并重建
412
- const venvDir = getVenvDir();
413
- if (fs.existsSync(venvDir)) {
414
- console.log(" 删除旧虚拟环境...");
415
- fs.rmSync(venvDir, { recursive: true, force: true });
416
- }
417
- const venvPython = ensureVenv();
418
- installAllDeps(venvPython, newPkgDir);
419
-
420
411
  let ver = "";
421
412
  try { ver = JSON.parse(fs.readFileSync(path.join(newPkgDir, "package.json"), "utf8")).version || ""; } catch (_) {}
422
413
  markDepsInstalled(ver);
@@ -1155,7 +1155,23 @@ function _stripXmlTags(xml) {
1155
1155
  // 当只有开始标签没有闭合标签时,将开始标签到文本末尾的内容完全移除
1156
1156
  text = text.replace(/<task_plan[^>]*>[\s\S]*?<\/task_plan>/g, ''); // 已闭合的完整 task_plan
1157
1157
  text = text.replace(/<task_plan[^>]*>[\s\S]*$/g, ''); // 未闭合的 task_plan(流式中标签已打开但未关闭)
1158
- // Remove all other XML tags, collapse excessive whitespace
1158
+ // [v1.15.74] 优先提取 <reply> 标签内容(新结构,嵌套在<response>内)
1159
+ var replyMatch = text.match(/<reply[^>]*>([\s\S]*?)<\/reply>/i);
1160
+ if (replyMatch && replyMatch[1] && replyMatch[1].trim()) {
1161
+ return replyMatch[1].trim();
1162
+ }
1163
+ // [v1.15.74] 兜底:提取 <response> 标签内容并去除内部嵌套标签
1164
+ var responseMatch = text.match(/<response[^>]*>([\s\S]*?)<\/response>/i);
1165
+ if (responseMatch && responseMatch[1] && responseMatch[1].trim()) {
1166
+ var innerContent = responseMatch[1]
1167
+ .replace(/<toolstocal[\s\S]*?<\/toolstocal>/gi, '') // 移除嵌套的toolstocal
1168
+ .replace(/<reply[^>]*>[\s\S]*?<\/reply>/gi, '') // 移除嵌套的reply(已提取)
1169
+ .replace(/<[^>]+>/g, ' ') // 去除其他标签
1170
+ .replace(/\s{3,}/g, ' ')
1171
+ .trim();
1172
+ if (innerContent) return innerContent;
1173
+ }
1174
+ // 兜底:去除所有XML标签
1159
1175
  return text
1160
1176
  .replace(/<[^>]+>/g, ' ') // Replace tags with space
1161
1177
  .replace(/&lt;/g, '<')