myagent-ai 1.16.1 → 1.16.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/start.js +127 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.16.1",
3
+ "version": "1.16.2",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/start.js CHANGED
@@ -11,6 +11,11 @@
11
11
  * - 先尝试 pip install -r requirements.txt (批量安装)
12
12
  * - 如果失败,逐包安装: 核心包失败=报错,可选包失败=跳过
13
13
  * - 确保即使部分可选包编译失败,核心功能也能正常运行
14
+ *
15
+ * [v1.16.1] 更新策略:
16
+ * - 版本变化不触发全量依赖重装
17
+ * - 只在 requirements.txt 哈希变化时检查新增/变更的包
18
+ * - 用 pip show 快速判断已安装的包,只安装缺失的
14
19
  */
15
20
  "use strict";
16
21
 
@@ -18,6 +23,7 @@ const { spawn, execSync, execFileSync } = require("child_process");
18
23
  const path = require("path");
19
24
  const fs = require("fs");
20
25
  const os = require("os");
26
+ const crypto = require("crypto");
21
27
 
22
28
  const IS_WIN = process.platform === "win32";
23
29
  const PKG_NAME = "myagent-ai";
@@ -331,19 +337,117 @@ function installAllDeps(venvPython, pkgDir) {
331
337
  return { ok: true, failedOptional };
332
338
  }
333
339
 
334
- // 标记依赖已安装
335
- function markDepsInstalled(version) {
340
+ // ── Sentinel(依赖安装标记) ─────────────────────────────────
341
+
342
+ /**
343
+ * [v1.16.1] 计算依赖哈希(requirements.txt 的 MD5 前 8 位)
344
+ */
345
+ function getDepsHash(pkgDir) {
346
+ const reqFile = path.join(pkgDir, "requirements.txt");
347
+ if (!fs.existsSync(reqFile)) return "";
348
+ try {
349
+ return crypto.createHash("md5").update(fs.readFileSync(reqFile, "utf8")).digest("hex").slice(0, 8);
350
+ } catch (_) {
351
+ return "";
352
+ }
353
+ }
354
+
355
+ /**
356
+ * 标记依赖已安装
357
+ * [v1.16.1] 写入 "版本号|依赖哈希" 格式
358
+ * 这样版本变化时 isDepsInstalled 仍返回 true,不会全量重装
359
+ */
360
+ function markDepsInstalled(version, pkgDir) {
336
361
  try {
337
362
  const dir = getDataDir();
338
363
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
339
- fs.writeFileSync(getSentinelFile(), version);
364
+ const depsHash = pkgDir ? getDepsHash(pkgDir) : "";
365
+ fs.writeFileSync(getSentinelFile(), `${version}|${depsHash}`);
340
366
  } catch (_) {}
341
367
  }
342
368
 
369
+ /**
370
+ * [v1.16.1] 检查依赖是否已安装
371
+ * - 无 sentinel 文件 → false(首次安装)
372
+ * - 新格式(含 |)→ true(只要 sentinel 存在就算已安装)
373
+ * - 旧格式(纯版本号)→ 精确匹配版本号
374
+ */
343
375
  function isDepsInstalled(pkgVersion) {
344
376
  try {
345
377
  if (!fs.existsSync(getSentinelFile())) return false;
346
- return fs.readFileSync(getSentinelFile(), "utf8").trim() === pkgVersion;
378
+ const sentinel = fs.readFileSync(getSentinelFile(), "utf8").trim();
379
+ if (sentinel.includes("|")) {
380
+ return true;
381
+ }
382
+ return sentinel === pkgVersion;
383
+ } catch (_) {
384
+ return false;
385
+ }
386
+ }
387
+
388
+ /**
389
+ * [v1.16.1] 检查依赖是否有变更并只安装新增/变更的包
390
+ * 对比 sentinel 中的依赖哈希与当前 requirements.txt 的哈希,
391
+ * 如果不同则用 pip show 逐包检查,只安装缺失或需要升级的包。
392
+ * @param {string} pkgDir 包目录路径
393
+ * @returns {boolean} 是否有依赖变更
394
+ */
395
+ function checkAndInstallChangedDeps(pkgDir) {
396
+ const venvPython = getVenvPython(getVenvDir());
397
+ if (!fs.existsSync(venvPython)) return false;
398
+
399
+ const reqFile = path.join(pkgDir, "requirements.txt");
400
+ if (!fs.existsSync(reqFile)) return false;
401
+
402
+ try {
403
+ const sentinel = fs.readFileSync(getSentinelFile(), "utf8").trim();
404
+ const currentHash = getDepsHash(pkgDir);
405
+
406
+ // 解析旧的哈希
407
+ const oldHash = sentinel.includes("|") ? sentinel.split("|")[1] : "";
408
+ if (oldHash && oldHash === currentHash) {
409
+ // 依赖没变,什么都不做
410
+ return false;
411
+ }
412
+
413
+ // 依赖有变更,找出新增/缺失的包并安装
414
+ const packages = parseRequirements(reqFile);
415
+ const newPkgs = [];
416
+ for (const pkg of packages) {
417
+ const baseName = pkgBaseName(pkg);
418
+ try {
419
+ // 用 pip show 检查包是否已安装(快速,不需要网络)
420
+ execFileSync(venvPython, [
421
+ "-m", "pip", "show", baseName, "--disable-pip-version-check"
422
+ ], { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 10000 });
423
+ } catch (_) {
424
+ // pip show 失败 = 包未安装,需要安装
425
+ newPkgs.push(pkg);
426
+ }
427
+ }
428
+
429
+ if (newPkgs.length === 0) return false;
430
+
431
+ console.log("");
432
+ console.log(` \x1b[36m检测到 ${newPkgs.length} 个新增依赖,正在安装...\x1b[0m`);
433
+ for (const pkg of newPkgs) {
434
+ const baseName = pkgBaseName(pkg);
435
+ const isCore = CORE_DEPS.includes(baseName);
436
+ process.stdout.write(` ${pkg} ... `);
437
+ const result = isCore ? pipInstallWithFallback(venvPython, pkg, true) : pipInstall(venvPython, pkg, true);
438
+ if (result.ok) {
439
+ const note = result.err === "(--no-deps)" ? " (部分子依赖跳过)" : "";
440
+ console.log("\x1b[32m✓\x1b[0m" + note);
441
+ } else {
442
+ if (isCore) {
443
+ console.log("\x1b[31m✗\x1b[0m");
444
+ console.log("\x1b[31m " + result.err.split("\n").join("\n ") + "\x1b[0m");
445
+ } else {
446
+ console.log("\x1b[33m✗ 跳过\x1b[0m");
447
+ }
448
+ }
449
+ }
450
+ return true;
347
451
  } catch (_) {
348
452
  return false;
349
453
  }
@@ -359,7 +463,7 @@ function cmdInstall(pkgDir) {
359
463
  installAllDeps(venvPython, pkgDir);
360
464
  let ver = "";
361
465
  try { ver = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")).version || ""; } catch (_) {}
362
- markDepsInstalled(ver);
466
+ markDepsInstalled(ver, pkgDir);
363
467
  console.log("");
364
468
  console.log(" \x1b[32m✓ 安装完成,可以启动了: myagent-ai web\x1b[0m");
365
469
  console.log("");
@@ -378,7 +482,7 @@ function cmdReinstall(pkgDir) {
378
482
  installAllDeps(venvPython, pkgDir);
379
483
  let ver = "";
380
484
  try { ver = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")).version || ""; } catch (_) {}
381
- markDepsInstalled(ver);
485
+ markDepsInstalled(ver, pkgDir);
382
486
  console.log("");
383
487
  console.log(" \x1b[32m✓ 重装完成\x1b[0m");
384
488
  console.log("");
@@ -390,7 +494,7 @@ function cmdUpdate(pkgDir) {
390
494
  try { curVer = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")).version || ""; } catch (_) {}
391
495
 
392
496
  console.log("");
393
- console.log(` MyAgent 更新${curVer ? " v" + curVer : "到最新版"}`);
497
+ console.log(` MyAgent 更新${curVer ? " v" + curVer : "到最新版"}`);
394
498
  console.log("");
395
499
 
396
500
  // 1. npm 升级全局包(--force 确保跳过缓存)
@@ -400,14 +504,14 @@ function cmdUpdate(pkgDir) {
400
504
  encoding: "utf8", stdio: "inherit", timeout: 120000,
401
505
  });
402
506
  } catch (e) {
403
- console.error("npm 更新失败,请尝试手动运行: npm install -g myagent-ai@latest --force");
507
+ console.error("npm 更新失败,请尝试手动运行: npm install -g myagent-ai@latest --force");
404
508
  process.exit(1);
405
509
  }
406
510
 
407
511
  // 2. 重新定位 pkgDir(npm 更新后路径可能变化)
408
512
  const newPkgDir = resolvePackageDir();
409
513
  if (!fs.existsSync(path.join(newPkgDir, "main.py"))) {
410
- console.error("更新后找不到 main.py,请重新安装: npm install -g myagent-ai@latest");
514
+ console.error("更新后找不到 main.py,请重新安装: npm install -g myagent-ai@latest");
411
515
  process.exit(1);
412
516
  }
413
517
 
@@ -417,11 +521,16 @@ function cmdUpdate(pkgDir) {
417
521
  // 2.5 清除 __pycache__(防止旧的 .pyc 字节码覆盖新的 .py 源码)
418
522
  cleanPycache(newPkgDir);
419
523
 
420
- // 3. 如果版本变了,用新的 start.js 重新执行自身,确保运行最新代码
524
+ // [v1.16.1] 3. 更新后只检查依赖是否有变更,不全量重装
525
+ checkAndInstallChangedDeps(newPkgDir);
526
+ markDepsInstalled(newVer, newPkgDir);
527
+
528
+ // 4. 如果版本变了,用新的 start.js 重新执行自身,确保运行最新代码
421
529
  if (curVer && newVer && curVer !== newVer) {
422
530
  const newStartJs = path.join(newPkgDir, "start.js");
423
531
  if (fs.existsSync(newStartJs)) {
424
- console.log(` 已下载 v${newVer},正在切换到新版本...`);
532
+ console.log("");
533
+ console.log(` 已下载 v${newVer},正在切换到新版本...`);
425
534
  console.log("");
426
535
  const { spawn } = require("child_process");
427
536
  const webChild = spawn(process.execPath, [newStartJs, "web"], {
@@ -432,13 +541,11 @@ function cmdUpdate(pkgDir) {
432
541
  }
433
542
  }
434
543
 
435
- markDepsInstalled(newVer);
436
-
437
544
  console.log("");
438
545
  if (newVer) {
439
- console.log(` ✓ 已是最新版 v${newVer}`);
546
+ console.log(` 已是最新版 v${newVer}`);
440
547
  } else {
441
- console.log(` ✓ 更新完成`);
548
+ console.log(` 更新完成`);
442
549
  }
443
550
  console.log(` 正在启动...`);
444
551
  console.log("");
@@ -448,7 +555,6 @@ function cmdUpdate(pkgDir) {
448
555
  }
449
556
 
450
557
 
451
-
452
558
  // ── 清除 __pycache__ ──────────────────────────────────────
453
559
 
454
560
  function cleanPycache(pkgDir) {
@@ -525,14 +631,17 @@ function cmdRun(pkgDir, userArgs) {
525
631
  let ver = "";
526
632
  try { ver = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")).version || ""; } catch (_) {}
527
633
 
528
- // 首次运行自动安装
634
+ // [v1.16.1] 首次运行: 无 sentinel → 全量安装;有 sentinel → 只检查增量
529
635
  if (!isDepsInstalled(ver)) {
530
636
  console.log("");
531
637
  console.log(" \x1b[36m首次运行,正在安装依赖...\x1b[0m");
532
638
  console.log("");
533
639
  installAllDeps(venvPython, pkgDir);
534
- markDepsInstalled(ver);
640
+ markDepsInstalled(ver, pkgDir);
535
641
  console.log("");
642
+ } else {
643
+ // sentinel 存在,检查依赖是否有变更(新增或升级)
644
+ checkAndInstallChangedDeps(pkgDir);
536
645
  }
537
646
 
538
647
  // 显示版本