myagent-ai 1.16.5 → 1.16.7

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.
@@ -628,9 +628,14 @@ class UpdateManager:
628
628
  # 验证更新是否成功 — npm 安装后需从 npm 全局目录读取新版本
629
629
  # 因为当前进程的 PROJECT_ROOT 指向旧版本,get_version() 会读到旧版本号
630
630
  new_version = self._read_installed_version() or get_version()
631
- if new_version == record.from_version and record.to_version != "latest":
632
- # 版本没变,更新可能未生效
633
- logger.warning(f"更新后版本仍为 {new_version},可能与预期不符")
631
+ if new_version == record.from_version:
632
+ # [v1.16.7] 修复:旧条件 `record.to_version != "latest"` 永远为 False
633
+ # 因为 record.to_version 初始值就是 "latest",导致版本未变化时不会报警
634
+ logger.warning(
635
+ f"更新后版本仍为 {new_version},更新可能未生效!"
636
+ f"(npm路径={is_npm_install})"
637
+ )
638
+ # 不直接抛异常,让重启继续尝试(重启后 start.js 会重新检测版本和依赖)
634
639
 
635
640
  # Step 3: 保存更新完成标记(用于重启后验证)
636
641
  record.status = "restarting"
@@ -658,8 +663,22 @@ class UpdateManager:
658
663
  import shutil
659
664
  node_cmd = shutil.which("node")
660
665
  new_start_js = self._find_npm_start_js()
661
- start_js = new_start_js if new_start_js else PROJECT_ROOT / "start.js"
662
- if start_js.exists():
666
+
667
+ if new_start_js:
668
+ start_js = new_start_js
669
+ elif (PROJECT_ROOT / "start.js").exists():
670
+ # [v1.16.7] 只有当 PROJECT_ROOT 下的 start.js 版本确实更新了才使用
671
+ # 如果版本未变,说明 npm install 没有更新 PROJECT_ROOT
672
+ logger.warning(
673
+ f"未在 npm 全局目录找到新版 start.js,"
674
+ f"回退使用 PROJECT_ROOT: {PROJECT_ROOT / 'start.js'}"
675
+ )
676
+ start_js = PROJECT_ROOT / "start.js"
677
+ else:
678
+ logger.error(f"未找到任何 start.js 文件!")
679
+ start_js = None
680
+
681
+ if start_js and start_js.exists():
663
682
  if node_cmd:
664
683
  # 提取原始参数 (去掉 main.py,还原 start.js 的参数格式)
665
684
  original_args = []
@@ -772,23 +791,86 @@ class UpdateManager:
772
791
  npm install -g 安装的新版本在 npm root -g/myagent-ai/ 下,
773
792
  但当前进程的 PROJECT_ROOT 可能指向旧的 npx 缓存目录。
774
793
  重启时必须用新版本的 start.js,否则等于没更新。
794
+
795
+ [v1.16.7] 增加多种查找方式:npm root -g / npm bin -g / which,
796
+ 确保找到新版本的 start.js,避免 fallback 到旧代码。
775
797
  """
798
+ candidates = []
799
+
800
+ # 方法1: npm root -g / myagent-ai / start.js
776
801
  try:
777
802
  result = subprocess.run(
778
803
  ["npm", "root", "-g"],
779
804
  capture_output=True, text=True, timeout=5,
780
805
  )
781
806
  npm_root = result.stdout.strip()
782
- if not npm_root:
783
- return None
784
- start_js = Path(npm_root) / "myagent-ai" / "start.js"
785
- if start_js.exists():
786
- logger.info(f"找到 npm 全局目录的新版 start.js: {start_js}")
787
- return start_js
788
- else:
789
- logger.debug(f"npm 全局目录中未找到 start.js: {start_js}")
790
- except Exception as e:
791
- logger.debug(f"查找 npm 全局 start.js 失败: {e}")
807
+ if npm_root:
808
+ candidates.append(Path(npm_root) / "myagent-ai" / "start.js")
809
+ except Exception:
810
+ pass
811
+
812
+ # 方法2: npm bin -g / myagent-ai (symlink)
813
+ try:
814
+ result = subprocess.run(
815
+ ["npm", "bin", "-g"],
816
+ capture_output=True, text=True, timeout=5,
817
+ )
818
+ npm_bin = result.stdout.strip()
819
+ if npm_bin:
820
+ candidates.append(Path(npm_bin) / "myagent-ai")
821
+ except Exception:
822
+ pass
823
+
824
+ # 方法3: which myagent-ai (全局命令路径,通常是 symlink)
825
+ try:
826
+ result = subprocess.run(
827
+ ["which", "myagent-ai"],
828
+ capture_output=True, text=True, timeout=5,
829
+ )
830
+ which_path = result.stdout.strip()
831
+ if which_path:
832
+ # which 返回的可能是 symlink,需要 resolve
833
+ candidates.append(Path(which_path).resolve())
834
+ except Exception:
835
+ pass
836
+
837
+ # 方法4: 直接读取 npm list -g 输出
838
+ try:
839
+ result = subprocess.run(
840
+ ["npm", "list", "-g", "myagent-ai", "--json"],
841
+ capture_output=True, text=True, timeout=10,
842
+ )
843
+ if result.returncode == 0:
844
+ import json as _json
845
+ data = _json.loads(result.stdout)
846
+ resolved = data.get("dependencies", {}).get("myagent-ai", {}).get("resolved", "")
847
+ if resolved:
848
+ candidates.append(Path(resolved) / "start.js")
849
+ except Exception:
850
+ pass
851
+
852
+ # 按优先级尝试
853
+ for candidate in candidates:
854
+ try:
855
+ if candidate.exists():
856
+ # 读取该 start.js 对应的 package.json 版本
857
+ pkg_dir = candidate.parent
858
+ pkg_json = pkg_dir / "package.json"
859
+ if pkg_json.exists():
860
+ data = json.loads(pkg_json.read_text(encoding="utf-8"))
861
+ ver = data.get("version", "")
862
+ logger.info(f"找到 npm start.js: {candidate} (v{ver})")
863
+ return candidate
864
+ else:
865
+ logger.info(f"找到 npm start.js: {candidate} (无 package.json)")
866
+ return candidate
867
+ except Exception:
868
+ continue
869
+
870
+ logger.warning(
871
+ f"未找到 npm 全局安装的新版 start.js "
872
+ f"(已尝试 {len(candidates)} 个候选路径)"
873
+ )
792
874
  return None
793
875
 
794
876
  def _get_npm_registry(self) -> str:
@@ -863,7 +945,11 @@ class UpdateManager:
863
945
  raise RuntimeError(f"npm install -g {pkg_name} 所有源均失败:\n{last_error}")
864
946
 
865
947
  async def _source_update(self, record: UpdateRecord):
866
- """执行源码安装更新 (git pull + pip install)"""
948
+ """执行源码安装更新 (git pull + pip install)
949
+
950
+ [v1.16.7] 当 git pull 因本地修改失败时,自动 stash → pull → pop,
951
+ 避免因未提交的修改导致更新永远失败。
952
+ """
867
953
  # git pull
868
954
  result = subprocess.run(
869
955
  ["git", "pull"],
@@ -873,6 +959,60 @@ class UpdateManager:
873
959
  pull_output = result.stdout + result.stderr
874
960
  logger.info(f"git pull: {pull_output[:500]}")
875
961
 
962
+ # [v1.16.7] git pull 失败时,尝试 stash 本地修改后重新 pull
963
+ if result.returncode != 0:
964
+ logger.warning("git pull 失败,尝试 stash 本地修改后重新拉取...")
965
+ # Stash 所有本地修改(包括未跟踪文件)
966
+ stash_result = subprocess.run(
967
+ ["git", "stash", "push", "-u", "-m", "auto-update-stash"],
968
+ capture_output=True, text=True, timeout=10,
969
+ cwd=PROJECT_ROOT,
970
+ )
971
+ if stash_result.returncode == 0:
972
+ logger.info(f"已 stash 本地修改: {stash_result.stdout.strip()}")
973
+ # 重新尝试 git pull
974
+ result = subprocess.run(
975
+ ["git", "pull"],
976
+ capture_output=True, text=True, timeout=30,
977
+ cwd=PROJECT_ROOT,
978
+ )
979
+ pull_output = result.stdout + result.stderr
980
+ logger.info(f"git pull (stash后): {pull_output[:500]}")
981
+
982
+ if result.returncode == 0:
983
+ # pull 成功,尝试恢复 stash(可能冲突,静默丢弃)
984
+ pop_result = subprocess.run(
985
+ ["git", "stash", "pop"],
986
+ capture_output=True, text=True, timeout=10,
987
+ cwd=PROJECT_ROOT,
988
+ )
989
+ if pop_result.returncode != 0:
990
+ logger.warning(
991
+ f"恢复 stash 失败(本地修改与新代码冲突),已丢弃: "
992
+ f"{pop_result.stderr[:200]}"
993
+ )
994
+ subprocess.run(
995
+ ["git", "stash", "drop"],
996
+ capture_output=True, text=True, timeout=5,
997
+ cwd=PROJECT_ROOT,
998
+ )
999
+ else:
1000
+ # pull 仍然失败,恢复 stash
1001
+ logger.error(f"stash 后 git pull 仍然失败: {pull_output[:300]}")
1002
+ subprocess.run(
1003
+ ["git", "stash", "pop"],
1004
+ capture_output=True, text=True, timeout=10,
1005
+ cwd=PROJECT_ROOT,
1006
+ )
1007
+ raise RuntimeError(
1008
+ f"git pull 失败(即使 stash 后): {pull_output[-300:]}"
1009
+ )
1010
+ else:
1011
+ raise RuntimeError(
1012
+ f"git pull 失败且无法 stash: "
1013
+ f"pull={pull_output[-200:]}, stash={stash_result.stderr[-200:]}"
1014
+ )
1015
+
876
1016
  # pip install
877
1017
  self._status = UpdateStatus.DOWNLOADING
878
1018
  loop = asyncio.get_running_loop()
package/memory/manager.py CHANGED
@@ -536,7 +536,7 @@ class MemoryManager:
536
536
 
537
537
  where = " AND ".join(conditions)
538
538
  # 取最近的记忆候选进行比对
539
- sql = f"SELECT id, content, summary, created_at FROM memories WHERE {where} ORDER BY created_at DESC LIMIT 50"
539
+ sql = f"SELECT * FROM memories WHERE {where} ORDER BY created_at DESC LIMIT 50"
540
540
  rows = conn.execute(sql, params).fetchall()
541
541
 
542
542
  if not rows:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.16.5",
3
+ "version": "1.16.7",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -43,4 +43,4 @@
43
43
  "python": ">=3.10",
44
44
  "node": ">=18"
45
45
  }
46
- }
46
+ }
@@ -125,13 +125,26 @@ class MCPClient:
125
125
  args.append("--slim")
126
126
 
127
127
  # [v1.16.4] 自动检测可用的浏览器,通过 --executablePath 传递给 MCP
128
+ # [v1.16.7] 找不到时自动尝试安装 Chromium
128
129
  browser_path = self._detect_browser()
129
130
  env = {**os.environ, "NO_COLOR": "1"}
130
131
  if browser_path:
131
132
  args.extend(["--executablePath", browser_path])
132
133
  logger.info(f"使用浏览器: {browser_path}")
133
134
  else:
134
- logger.warning("未找到 Chrome/Chromium 浏览器,尝试使用默认路径")
135
+ # [v1.16.7] 自动安装 Chromium
136
+ installed = self._try_install_chromium()
137
+ if installed:
138
+ browser_path = self._detect_browser()
139
+ if browser_path:
140
+ args.extend(["--executablePath", browser_path])
141
+ logger.info(f"已自动安装 Chromium: {browser_path}")
142
+ if not browser_path:
143
+ logger.error(
144
+ "未找到 Chrome/Chromium 浏览器且自动安装失败。"
145
+ "请手动安装: apt install -y chromium-browser "
146
+ "或 apt install -y chromium"
147
+ )
135
148
 
136
149
  # 容器/Termux 环境需要 --no-sandbox
137
150
  args.extend(["--chromeArg", "--no-sandbox", "--chromeArg", "--disable-setuid-sandbox"])
@@ -177,6 +190,7 @@ class MCPClient:
177
190
  1. 环境变量 CHROME_PATH / BROWSER_PATH
178
191
  2. PATH 中的常见浏览器命令
179
192
  3. Linux 常见安装路径
193
+ 4. Puppeteer 缓存目录
180
194
  """
181
195
  # 1. 环境变量
182
196
  for env_key in ("CHROME_PATH", "BROWSER_PATH"):
@@ -211,8 +225,61 @@ class MCPClient:
211
225
  if os.path.isfile(p) and os.access(p, os.X_OK):
212
226
  return p
213
227
 
228
+ # 4. [v1.16.7] Puppeteer 缓存目录(npx puppeteer install 安装的 Chromium)
229
+ home = os.path.expanduser("~")
230
+ puppeteer_cache_dirs = [
231
+ os.path.join(home, ".cache", "puppeteer", "chrome"),
232
+ os.path.join(home, ".cache", "puppeteer"),
233
+ ]
234
+ for cache_dir in puppeteer_cache_dirs:
235
+ if os.path.isdir(cache_dir):
236
+ # 查找 chrome / chromium 可执行文件
237
+ for root, dirs, files in os.walk(cache_dir):
238
+ for fname in files:
239
+ if fname in ("chrome", "chromium"):
240
+ full = os.path.join(root, fname)
241
+ if os.access(full, os.X_OK):
242
+ return full
243
+
214
244
  return None
215
245
 
246
+ @staticmethod
247
+ def _try_install_chromium() -> bool:
248
+ """[v1.16.7] 尝试自动安装 Chromium。
249
+
250
+ 依次尝试: apt install chromium-browser → apt install chromium
251
+ 需要当前用户有 sudo 权限(容器环境通常有 root 权限)。
252
+ """
253
+ import stat
254
+
255
+ # 检查是否有 apt
256
+ if not shutil.which("apt"):
257
+ logger.debug("apt 不可用,跳过自动安装 Chromium")
258
+ return False
259
+
260
+ # 尝试不同的包名
261
+ packages = ["chromium-browser", "chromium"]
262
+ for pkg in packages:
263
+ try:
264
+ logger.info(f"正在自动安装 {pkg}...")
265
+ result = subprocess.run(
266
+ ["apt", "install", "-y", pkg],
267
+ capture_output=True, text=True, timeout=120,
268
+ )
269
+ if result.returncode == 0:
270
+ logger.info(f"成功安装 {pkg}")
271
+ return True
272
+ else:
273
+ logger.warning(
274
+ f"安装 {pkg} 失败: {result.stderr[-300:] if result.stderr else result.stdout[-300:]}"
275
+ )
276
+ except subprocess.TimeoutExpired:
277
+ logger.warning(f"安装 {pkg} 超时")
278
+ except Exception as e:
279
+ logger.warning(f"安装 {pkg} 异常: {e}")
280
+
281
+ return False
282
+
216
283
  def _read_stdout(self):
217
284
  """持续读取 MCP Server 的 stdout"""
218
285
  if not self._process or not self._process.stdout:
@@ -521,10 +588,25 @@ class BrowserOpenSkill(Skill):
521
588
 
522
589
  try:
523
590
  client = await get_mcp_client()
591
+ if not client.is_running() or not client._initialized:
592
+ return SkillResult(
593
+ success=False,
594
+ error="浏览器启动失败。系统未安装 Chrome/Chromium,自动安装也未成功。"
595
+ "请手动执行: apt install -y chromium-browser 或 apt install -y chromium"
596
+ )
524
597
 
525
598
  # 1. 导航到目标 URL
526
599
  nav_result = await client.call_tool("navigate_page", {"url": url}, timeout=30)
527
600
  if nav_result.get("error"):
601
+ # [v1.16.7] 检测 Chrome not found 错误,给出清晰提示
602
+ err_text = str(nav_result["error"])
603
+ if "Could not find" in err_text and "Chrome" in err_text:
604
+ return SkillResult(
605
+ success=False,
606
+ error=f"浏览器未安装: {err_text}。"
607
+ f"请执行: apt install -y chromium-browser 或 apt install -y chromium,"
608
+ f"安装后重试即可。"
609
+ )
528
610
  return SkillResult(success=False, error=f"导航失败: {nav_result['error']}")
529
611
 
530
612
  # 2. 等待动态内容
package/start.js CHANGED
@@ -369,15 +369,20 @@ function markDepsInstalled(version, pkgDir) {
369
369
  /**
370
370
  * [v1.16.1] 检查依赖是否已安装
371
371
  * - 无 sentinel 文件 → false(首次安装)
372
- * - 新格式(含 |)→ true(只要 sentinel 存在就算已安装)
372
+ * - 新格式(含 |)→ 比较版本号,版本相同则 true,不同则 false
373
373
  * - 旧格式(纯版本号)→ 精确匹配版本号
374
+ *
375
+ * [v1.16.7] 修复:旧逻辑只要 sentinel 含 | 就返回 true,不比较版本号,
376
+ * 导致更新到新版本后 isDepsInstalled 仍返回 true,新依赖不会安装。
374
377
  */
375
378
  function isDepsInstalled(pkgVersion) {
376
379
  try {
377
380
  if (!fs.existsSync(getSentinelFile())) return false;
378
381
  const sentinel = fs.readFileSync(getSentinelFile(), "utf8").trim();
379
382
  if (sentinel.includes("|")) {
380
- return true;
383
+ // 新格式: version|deps_hash,比较版本号部分
384
+ const savedVersion = sentinel.split("|")[0];
385
+ return savedVersion === pkgVersion;
381
386
  }
382
387
  return sentinel === pkgVersion;
383
388
  } catch (_) {