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.
- package/core/update_manager.py +156 -16
- package/memory/manager.py +1 -1
- package/package.json +2 -2
- package/skills/chromedev_mcp.py +83 -1
- package/start.js +7 -2
package/core/update_manager.py
CHANGED
|
@@ -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
|
|
632
|
-
#
|
|
633
|
-
|
|
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
|
-
|
|
662
|
-
if
|
|
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
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
|
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.
|
|
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
|
+
}
|
package/skills/chromedev_mcp.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
383
|
+
// 新格式: version|deps_hash,比较版本号部分
|
|
384
|
+
const savedVersion = sentinel.split("|")[0];
|
|
385
|
+
return savedVersion === pkgVersion;
|
|
381
386
|
}
|
|
382
387
|
return sentinel === pkgVersion;
|
|
383
388
|
} catch (_) {
|