myagent-ai 1.15.67 → 1.15.73

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.
package/README.md CHANGED
@@ -124,17 +124,13 @@ curl -fsSL https://raw.githubusercontent.com/ctz168/myagent/main/install/install
124
124
 
125
125
  ### 快速升级
126
126
 
127
- > 已安装过 MyAgent 的环境下,重新安装最新版:
127
+ > 已安装过 MyAgent 的环境下,一行命令更新到最新版:
128
128
 
129
129
  ```bash
130
- npm install -g myagent-ai@latest
131
- myagent-ai reinstall
130
+ myagent-ai update
132
131
  ```
133
132
 
134
- 或使用脚本重新安装:
135
- ```bash
136
- curl -fsSL https://raw.githubusercontent.com/ctz168/myagent/main/install/install.sh | bash
137
- ```
133
+ 该命令会自动完成:npm 升级全局包 → 重建虚拟环境 → 重装所有依赖。
138
134
 
139
135
  ---
140
136
 
@@ -190,6 +186,9 @@ export MYAGENT_ANTHROPIC_API_KEY="sk-ant-..."
190
186
  ### 2. 运行
191
187
 
192
188
  ```bash
189
+ # 更新到最新版(推荐)
190
+ myagent-ai update
191
+
193
192
  # Web 管理后台(推荐)
194
193
  myagent-ai web
195
194
 
@@ -201,6 +200,15 @@ myagent-ai tray
201
200
 
202
201
  # API 服务模式
203
202
  myagent-ai server
203
+
204
+ # 安装/更新依赖
205
+ myagent-ai install
206
+
207
+ # 重装依赖(删除 venv 重建)
208
+ myagent-ai reinstall
209
+
210
+ # 卸载
211
+ myagent-ai uninstall
204
212
  ```
205
213
 
206
214
  > 首次运行会自动安装所有依赖,后续启动秒开。
@@ -202,6 +202,50 @@ class MainAgent(BaseAgent):
202
202
  except Exception as e:
203
203
  logger.debug(f"V2 SSE 事件发送失败 ({event_type}): {e}")
204
204
 
205
+ def _try_extract_partial_response(self, llm_raw: str) -> str:
206
+ """[v1.15.73] 从不完整的 LLM 输出中提取部分回复内容。
207
+
208
+ 当 <output> 块被截断(缺少 </output>)时,尝试:
209
+ 1. 提取 <response>...</response> 中已闭合的内容
210
+ 2. 提取 <output> 后到截断点之间的纯文本
211
+ 3. 去除 XML 标签后的残余文本
212
+ """
213
+ if not llm_raw:
214
+ return ""
215
+
216
+ import re
217
+
218
+ # 策略1: 尝试提取已闭合的 <response> 内容
219
+ response_match = re.search(
220
+ r"<response[^>]*>(.*?)</response>",
221
+ llm_raw,
222
+ re.DOTALL | re.IGNORECASE,
223
+ )
224
+ if response_match:
225
+ text = response_match.group(1).strip()
226
+ if text:
227
+ return text
228
+
229
+ # 策略2: 提取 <output> 标签后的内容(可能包含未闭合的 <response>)
230
+ output_match = re.search(r"<output[^>]*>", llm_raw, re.IGNORECASE)
231
+ if output_match:
232
+ after_output = llm_raw[output_match.end():].strip()
233
+ if after_output:
234
+ # 去除所有 XML 标签,保留纯文本
235
+ cleaned = re.sub(r"<[^>]+>", "", after_output).strip()
236
+ # 去除 reasoning/assistant 前缀
237
+ cleaned = re.sub(r"^(reasoning|assistant)\s*", "", cleaned, flags=re.IGNORECASE).strip()
238
+ if cleaned and len(cleaned) > 5:
239
+ return cleaned
240
+
241
+ # 策略3: 提取去除 XML 标签后的整体文本
242
+ cleaned = re.sub(r"<[^>]+>", "", llm_raw).strip()
243
+ cleaned = re.sub(r"^(reasoning|assistant)\s*", "", cleaned, flags=re.IGNORECASE).strip()
244
+ if cleaned and len(cleaned) > 10:
245
+ return cleaned
246
+
247
+ return ""
248
+
205
249
  async def _merge_duplicate_memory(
206
250
  self,
207
251
  old_memory,
@@ -460,6 +504,8 @@ class MainAgent(BaseAgent):
460
504
  get_knowledge_content = ""
461
505
  # 追踪流式推送的 reasoning 文本(用于构建有意义的最终回复)
462
506
  _v2_reasoning_collected: List[str] = []
507
+ # [v1.15.73] 追踪上一轮保存到 memory 的位置,避免重复保存
508
+ _last_saved_len: int = 0
463
509
  # XML 解析失败时的 LLM 修正重试计数
464
510
  _xml_correction_retries: int = 0
465
511
 
@@ -694,6 +740,14 @@ class MainAgent(BaseAgent):
694
740
  )
695
741
  if _xml_correction_retries < 1:
696
742
  _xml_correction_retries += 1
743
+ # [v1.15.73] 在重试前,尝试从不完整的 llm_raw 中提取已有内容
744
+ # 防止 reasoning 模型因 max_tokens 截断导致内容完全丢失
745
+ _partial_text = self._try_extract_partial_response(llm_raw)
746
+ if _partial_text:
747
+ logger.info(f"[{task_id}] 从不完整XML中提取到部分回复: {_partial_text[:100]}")
748
+ _v2_reasoning_collected.append(_partial_text)
749
+ _emitted_reasoning_this_iter = True
750
+ await self._emit_v2_event("v2_reasoning", {"content": _partial_text}, stream_callback)
697
751
  correction_prompt = (
698
752
  "你的输出缺少 </output> 闭合标签,XML块不完整,"
699
753
  "解析器不会处理不完整的块。\n"
@@ -716,12 +770,29 @@ class MainAgent(BaseAgent):
716
770
  else:
717
771
  # 已重试过,强制终止并提示用户
718
772
  logger.warning(f"[{task_id}] XML块仍不完整且已重试,终止循环")
719
- context.working_memory["final_response"] = "模型输出格式异常,请重新尝试。"
720
- await self._emit_v2_event(
721
- "v2_reasoning",
722
- {"content": "模型输出格式异常,已自动终止。"},
723
- stream_callback,
724
- )
773
+ # [v1.15.73] 保存已积累的内容,避免空回复
774
+ if _v2_reasoning_collected:
775
+ _fallback_save = "\n".join(_v2_reasoning_collected)
776
+ if _fallback_save.strip():
777
+ context.working_memory["final_response"] = _fallback_save
778
+ if self.memory:
779
+ self.memory.add_session(
780
+ session_id=context.session_id,
781
+ role="assistant",
782
+ content=_fallback_save,
783
+ )
784
+ await self._emit_v2_event(
785
+ "v2_reasoning",
786
+ {"content": _fallback_save},
787
+ stream_callback,
788
+ )
789
+ else:
790
+ context.working_memory["final_response"] = "模型输出格式异常,请重新尝试。"
791
+ await self._emit_v2_event(
792
+ "v2_reasoning",
793
+ {"content": "模型输出格式异常,已自动终止。"},
794
+ stream_callback,
795
+ )
725
796
  break
726
797
  elif not parsed.output_block_complete and parsed.parse_success:
727
798
  # 块不完整但已提取到有效内容,记录日志但正常继续
@@ -1285,15 +1356,17 @@ class MainAgent(BaseAgent):
1285
1356
  # 回调前,保存当前轮次的 LLM 输出到会话记忆
1286
1357
  # 这样每轮工具调用都有对应的 assistant 消息记录
1287
1358
  if self.memory:
1288
- _round_items = _v2_reasoning_collected[_reasoning_len_before_round:]
1289
- if _round_items:
1290
- _round_output = "\n".join(_round_items)
1359
+ # [v1.15.73] 只保存自上次保存以来的新增内容,避免重复
1360
+ _new_items = _v2_reasoning_collected[_last_saved_len:]
1361
+ if _new_items:
1362
+ _round_output = "\n".join(_new_items)
1291
1363
  if _round_output.strip():
1292
1364
  self.memory.add_session(
1293
1365
  session_id=context.session_id,
1294
1366
  role="assistant",
1295
1367
  content=_round_output,
1296
1368
  )
1369
+ _last_saved_len = len(_v2_reasoning_collected)
1297
1370
 
1298
1371
  # 循环正常结束(max_iter 耗尽)时兜底保存
1299
1372
  else:
File without changes
File without changes
File without changes
package/core/utils.py CHANGED
@@ -15,8 +15,8 @@ T = TypeVar("T")
15
15
 
16
16
 
17
17
  def timestamp() -> str:
18
- """返回 ISO 8601 格式时间戳"""
19
- return datetime.now(timezone.utc).isoformat()
18
+ """返回 ISO 8601 格式时间戳(本地时间)"""
19
+ return datetime.now().isoformat()
20
20
 
21
21
 
22
22
  def timestamp_ms() -> int:
@@ -216,6 +216,15 @@ fi
216
216
  echo ""
217
217
  echo -e " ${BOLD}${SUCCESS}安装完成!${NC}"
218
218
  echo ""
219
+ echo -e " 常用命令:"
220
+ echo -e " ${ACCENT}myagent-ai web${NC} 启动 Web 界面 (默认)"
221
+ echo -e " ${ACCENT}myagent-ai cli${NC} 命令行模式"
222
+ echo -e " ${ACCENT}myagent-ai tray${NC} 系统托盘模式"
223
+ echo -e " ${ACCENT}myagent-ai server${NC} 后台服务模式"
224
+ echo -e " ${ACCENT}myagent-ai update${NC} 更新到最新版"
225
+ echo -e " ${ACCENT}myagent-ai reinstall${NC} 重装依赖"
226
+ echo -e " ${ACCENT}myagent-ai uninstall${NC} 卸载"
227
+ echo ""
219
228
 
220
229
  # 启动 Web 模式
221
230
  myagent_cmd="$(find_myagent_cmd)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.15.67",
3
+ "version": "1.15.73",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
File without changes
package/start.js CHANGED
@@ -384,6 +384,49 @@ function cmdReinstall(pkgDir) {
384
384
  console.log("");
385
385
  }
386
386
 
387
+ function cmdUpdate(pkgDir) {
388
+ console.log("");
389
+ console.log(" \x1b[36mMyAgent 更新到最新版\x1b[0m");
390
+ console.log("");
391
+
392
+ // 1. npm 升级全局包
393
+ console.log(" 正在通过 npm 更新 myagent-ai ...");
394
+ try {
395
+ execFileSync("npm", ["install", "-g", PKG_NAME, "--prefer-online"], {
396
+ encoding: "utf8", stdio: "inherit", timeout: 120000,
397
+ });
398
+ } catch (e) {
399
+ console.error("\x1b[31mnpm 更新失败,请尝试手动运行: npm install -g myagent-ai@latest\x1b[0m");
400
+ process.exit(1);
401
+ }
402
+ console.log(" \x1b[32m✓\x1b[0m npm 包已更新");
403
+
404
+ // 2. 重新定位 pkgDir(npm 更新后路径可能变化)
405
+ const newPkgDir = resolvePackageDir();
406
+ if (!fs.existsSync(path.join(newPkgDir, "main.py"))) {
407
+ console.error("\x1b[31m更新后找不到 main.py,请重新安装: npm install -g myagent-ai@latest\x1b[0m");
408
+ process.exit(1);
409
+ }
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
+ let ver = "";
421
+ try { ver = JSON.parse(fs.readFileSync(path.join(newPkgDir, "package.json"), "utf8")).version || ""; } catch (_) {}
422
+ markDepsInstalled(ver);
423
+
424
+ console.log("");
425
+ console.log(` \x1b[32m✓ 更新完成${ver ? " → v" + ver : ""}\x1b[0m`);
426
+ console.log(" 运行 \x1b[36mmyagent-ai web\x1b[0m 启动");
427
+ console.log("");
428
+ }
429
+
387
430
  function cmdUninstall() {
388
431
  const venvDir = getVenvDir();
389
432
  const dataDir = getDataDir();
@@ -520,6 +563,7 @@ function main() {
520
563
  }
521
564
 
522
565
  if (cmd === "uninstall") { cmdUninstall(); return; }
566
+ if (cmd === "update") { cmdUpdate(pkgDir); return; }
523
567
  if (cmd === "reinstall") { cmdReinstall(pkgDir); return; }
524
568
  if (cmd === "install") { cmdInstall(pkgDir); return; }
525
569
 
File without changes
package/web/api_server.py CHANGED
@@ -2255,6 +2255,8 @@ class ApiServer:
2255
2255
  conn.execute("DELETE FROM memories WHERE session_id = ?", (sid,))
2256
2256
  conn.execute("DELETE FROM session_names WHERE session_id = ?", (sid,))
2257
2257
  logger.info(f" 清理会话记忆: {sid}")
2258
+ # 清理该会话的后端运行状态
2259
+ self._cleanup_session_state(sid)
2258
2260
  conn.commit()
2259
2261
  logger.info(f" 已清理 Agent '{path}' 的 {len(rows)} 个会话记忆")
2260
2262
  except Exception as e:
@@ -2697,10 +2699,51 @@ class ApiServer:
2697
2699
  entries = [e for e in entries if (e.key or "") not in self._HIDDEN_KEYS]
2698
2700
  return web.json_response([{"role": e.role, "content": e.content, "time": e.created_at, "key": e.key or ""} for e in entries])
2699
2701
 
2702
+ def _cleanup_session_state(self, sid: str):
2703
+ """删除会话时清理所有关联的后端状态"""
2704
+ cleaned = []
2705
+ # 1. 运行中的会话
2706
+ if sid in self._running_sessions:
2707
+ self._running_sessions[sid]["running"] = False
2708
+ self._running_sessions[sid]["done"] = True
2709
+ del self._running_sessions[sid]
2710
+ cleaned.append("running_session")
2711
+ # 2. 消息队列
2712
+ if sid in self._msg_queues:
2713
+ del self._msg_queues[sid]
2714
+ cleaned.append("msg_queue")
2715
+ # 3. 任务列表
2716
+ if sid in self._task_list_store:
2717
+ del self._task_list_store[sid]
2718
+ cleaned.append("task_list")
2719
+ # 4. 执行进度
2720
+ if sid in self._exec_progress:
2721
+ del self._exec_progress[sid]
2722
+ cleaned.append("exec_progress")
2723
+ # 5. Agent 活跃上下文
2724
+ if self.core.main_agent and sid in self.core.main_agent.active_contexts:
2725
+ self.core.main_agent.active_contexts.pop(sid, None)
2726
+ cleaned.append("active_context")
2727
+ # 6. 自动知识库文件
2728
+ try:
2729
+ cfg = self.core.config_mgr.config
2730
+ kb_dir = getattr(cfg, 'knowledge_base_dir', '') or ''
2731
+ if kb_dir:
2732
+ safe_session = sid.replace("-", "").replace("/", "_")[:12]
2733
+ kb_file = Path(kb_dir) / "auto_knowledge" / f"{safe_session}.md"
2734
+ if kb_file.exists():
2735
+ kb_file.unlink()
2736
+ cleaned.append("knowledge")
2737
+ except Exception:
2738
+ pass
2739
+ if cleaned:
2740
+ logger.info(f"会话 {sid} 后端状态已清理: {', '.join(cleaned)}")
2741
+
2700
2742
  async def handle_delete_session(self, request):
2701
- """DELETE /api/sessions/{sid} - 彻底删除会话(所有记忆)"""
2743
+ """DELETE /api/sessions/{sid} - 彻底删除会话(记忆 + 后端状态)"""
2702
2744
  sid = request.match_info["sid"]
2703
2745
  logger.info(f"删除会话: {sid}")
2746
+ self._cleanup_session_state(sid)
2704
2747
  if self.core.memory:
2705
2748
  count = self.core.memory.delete_session(sid)
2706
2749
  logger.info(f"会话 {sid} 已删除,共清除 {count} 条记忆")
@@ -2712,6 +2755,7 @@ class ApiServer:
2712
2755
  if not sid:
2713
2756
  return web.json_response({"ok": False, "error": "missing sid"}, status=400)
2714
2757
  logger.info(f"删除会话: {sid}")
2758
+ self._cleanup_session_state(sid)
2715
2759
  if self.core.memory:
2716
2760
  count = self.core.memory.delete_session(sid)
2717
2761
  logger.info(f"会话 {sid} 已删除,共清除 {count} 条记忆")