memory-eternal 0.10.16 → 0.10.18

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/lib/watchdog.js CHANGED
@@ -463,6 +463,39 @@ export async function waitForServedVersion(port, expect, { timeoutMs = 15000, in
463
463
  }
464
464
  }
465
465
 
466
+ /**
467
+ * 等「**新实例真的接管了**」:端口服着期望版本 **且** 锁里出现一个不在 `beforePids` 里的活看门狗。
468
+ *
469
+ * 为什么不能只看版本(issue #28 的根):升级前后版本号常常相同、或旧实例本来就是这个版本 ——
470
+ * 那时「端口服着 vX」与「替换还没发生」同时为真,只看版本的自检会立刻通过。实测现场:
471
+ * `restart` 打印「自检通过:端口 7999 正在服务 v0.10.16(等待 0s)」,紧接着 `status` 说
472
+ * 「锁文件里没有登记的看门狗(没有常驻实例)」—— 状态自相矛盾,面板据此给出的结论也会是错的。
473
+ *
474
+ * @returns {Promise<{ok:boolean, version:string, waitedMs:number, tookOver:boolean}>}
475
+ */
476
+ export async function waitForTakeover(port, expect, {
477
+ env = process.env, beforePids = new Set(), timeoutMs = 25000, intervalMs = 500,
478
+ probe, sleep, readLock,
479
+ } = {}) {
480
+ const p = Number(port) || 0
481
+ const want = String(expect || '')
482
+ const probeVersion = probe || probeServedVersion
483
+ const sleepFn = sleep || ((ms) => new Promise((r) => setTimeout(r, ms)))
484
+ const lockOf = readLock || ((e) => readWatchdogLock(e))
485
+ const start = Date.now()
486
+ let version = ''
487
+ for (;;) {
488
+ try { version = String((await probeVersion(p)) || '') } catch { version = '' }
489
+ let fresh = []
490
+ try {
491
+ fresh = lockOf(env).watchdogs.filter((w) => Number(w.port) === p && isPidAlive(w.pid) && !beforePids.has(Number(w.pid)))
492
+ } catch { fresh = [] }
493
+ if (version && version === want && fresh.length) return { ok: true, version, waitedMs: Date.now() - start, tookOver: true }
494
+ if (Date.now() - start >= Math.max(0, Number(timeoutMs) || 0)) return { ok: false, version, waitedMs: Date.now() - start, tookOver: false }
495
+ await sleepFn(Math.max(50, Number(intervalMs) || 500))
496
+ }
497
+ }
498
+
466
499
  // -- 常驻实例的「版本漂移自愈」--------------------------------------------------
467
500
  //
468
501
  // 为什么需要它:常驻 web(默认 7999)**比 DSH 宿主活得久**。升级 npm 包只换磁盘上的
@@ -601,6 +634,8 @@ export async function restartResident({
601
634
  const p = Number(port) || 7999
602
635
  const expect = String(expectVersion !== undefined ? expectVersion : currentPkgVersion())
603
636
  const before = readWatchdogLock(env).watchdogs.filter((w) => Number(w.port) === p)
637
+ // 替换**前**就存在的 pid(看门狗 + 它拉起的 web):自检时用来区分「还是那个旧实例」与「新实例接管了」
638
+ const beforePids = new Set(before.flatMap((w) => [Number(w.pid), Number(w.webPid) || 0]).filter((n) => n > 0))
604
639
  let helperPid = 0
605
640
  try {
606
641
  const child = (deps.spawnHelper || spawnReplaceHelper)({ port: p, vaultRoot, interval, maxRestart })
@@ -609,7 +644,15 @@ export async function restartResident({
609
644
  return { ok: false, stage: 'spawn-helper', expect, served: '', waitedMs: 0, helperPid: 0, before, error: String(error?.message || error) }
610
645
  }
611
646
  let check = { ok: false, version: '', waitedMs: 0 }
612
- try { check = await (deps.waitForServedVersion || waitForServedVersion)(p, expect, { timeoutMs }) } catch { check = { ok: false, version: '', waitedMs: 0 } }
647
+ try {
648
+ // 只看版本会被**旧实例**骗过(版本号常常没变)→ 自检必须等到「新看门狗接管」才认成功(#28)
649
+ check = await (deps.waitForTakeover || waitForTakeover)(p, expect, {
650
+ env, beforePids, timeoutMs,
651
+ probe: deps.probeServedVersion,
652
+ sleep: deps.sleep,
653
+ readLock: deps.readLock,
654
+ })
655
+ } catch { check = { ok: false, version: '', waitedMs: 0 } }
613
656
  return {
614
657
  ok: Boolean(check.ok),
615
658
  stage: check.ok ? 'done' : 'verify',
@@ -618,7 +661,12 @@ export async function restartResident({
618
661
  waitedMs: Number(check.waitedMs) || 0,
619
662
  helperPid,
620
663
  before,
621
- error: check.ok ? undefined : `端口 ${p} 上服务的版本是 ${check.version || '(无响应)'},本机是 ${expect || '?'}`,
664
+ // 失败原因要分清两种:版本不对(旧实例还在服务)与**没人接管**(端口服务着期望版本,
665
+ // 但锁里没有新的活看门狗 —— 端口上那只是旧实例/孤儿)
666
+ error: check.ok ? undefined : (check.version === expect && !check.tookOver
667
+ ? `端口 ${p} 服务着 v${check.version},但没有新看门狗接管它(端口上可能仍是替换前的旧实例或孤儿)`
668
+ : `端口 ${p} 上服务的版本是 ${check.version || '(无响应)'},本机是 ${expect || '?'}`),
669
+ tookOver: Boolean(check.tookOver),
622
670
  }
623
671
  }
624
672
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-eternal",
3
- "version": "0.10.16",
3
+ "version": "0.10.18",
4
4
  "description": "记忆核心(Memory Eternal):自研的 DeepSeek Harness 记忆插件,不移植任何既有记忆框架——对话结束后自动沉淀知识卡到本地 Markdown Vault(自研去重、自研 CJK 检索、可 git 管理),设置页提供图形化知识库(统计 / 搜索 / 知识图谱 + 侧边栏一键弹窗),Agent 通过 memory_recall 工具按需召回历史上下文。零人工干预。",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -64,7 +64,7 @@
64
64
  "prepublishOnly": "node build.mjs",
65
65
  "release": "node scripts/release.mjs",
66
66
  "release:dry": "node scripts/release.mjs --dry-run",
67
- "test": "node build.mjs && node tests/vault.test.mjs && node tests/capture.test.mjs && node tests/capture-route.test.mjs && node tests/stall.test.mjs && node tests/vault-path.test.mjs && node tests/vault-resolve.test.mjs && node tests/minhash.test.mjs && node tests/settings-save.test.mjs && node tests/config-coverage.test.mjs && node tests/config-sync.test.mjs && node tests/config-merge.test.mjs && node tests/sse.test.mjs && node tests/api.test.mjs && node tests/import-roundtrip.test.mjs && node tests/graph-perf.test.mjs && node tests/settings-compat.test.mjs && node tests/node-bin.test.mjs && node tests/watchdog-lock.test.mjs && node tests/markdown.test.mjs && node tests/feedback.test.mjs && node tests/graph-lod.test.mjs && node tests/ssr.test.mjs && node tests/web-assets.test.mjs && node tests/manifests.test.mjs && node tests/two-store.test.mjs && node tests/noise-gate.test.mjs && node tests/e2e-recall.test.mjs && node tests/issue-fixes.test.mjs && node tests/issue-fixes-2.test.mjs && node tests/issue-fixes-3.test.mjs && node tests/watchdog-orphans.test.mjs && node tests/watchdog-restart.test.mjs && node tests/readme-i18n.test.mjs && node tests/issue-fixes-4.test.mjs && node tests/host-drain.test.mjs && node tests/e2e-host-drain.mjs && node tests/issue-fixes-5.test.mjs && node tests/issue-fixes-6.test.mjs && node tests/capture-json-repair.test.mjs && node tests/csrf-origin.test.mjs && node tests/watchdog-orphan-web.test.mjs"
67
+ "test": "node build.mjs && node tests/vault.test.mjs && node tests/capture.test.mjs && node tests/capture-route.test.mjs && node tests/stall.test.mjs && node tests/vault-path.test.mjs && node tests/vault-resolve.test.mjs && node tests/minhash.test.mjs && node tests/settings-save.test.mjs && node tests/config-coverage.test.mjs && node tests/config-sync.test.mjs && node tests/config-merge.test.mjs && node tests/sse.test.mjs && node tests/api.test.mjs && node tests/import-roundtrip.test.mjs && node tests/graph-perf.test.mjs && node tests/settings-compat.test.mjs && node tests/node-bin.test.mjs && node tests/watchdog-lock.test.mjs && node tests/markdown.test.mjs && node tests/feedback.test.mjs && node tests/graph-lod.test.mjs && node tests/ssr.test.mjs && node tests/web-assets.test.mjs && node tests/manifests.test.mjs && node tests/two-store.test.mjs && node tests/noise-gate.test.mjs && node tests/e2e-recall.test.mjs && node tests/issue-fixes.test.mjs && node tests/issue-fixes-2.test.mjs && node tests/issue-fixes-3.test.mjs && node tests/watchdog-orphans.test.mjs && node tests/watchdog-restart.test.mjs && node tests/readme-i18n.test.mjs && node tests/issue-fixes-4.test.mjs && node tests/host-drain.test.mjs && node tests/e2e-host-drain.mjs && node tests/issue-fixes-5.test.mjs && node tests/issue-fixes-6.test.mjs && node tests/capture-json-repair.test.mjs && node tests/csrf-origin.test.mjs && node tests/watchdog-orphan-web.test.mjs && node tests/config-validate.test.mjs"
68
68
  },
69
69
  "dependencies": {
70
70
  "@deepseek-ai/schemastery": "^3.18.1"