memory-eternal 0.10.32 → 0.10.34
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.en.md +5 -3
- package/README.md +5 -3
- package/index.js +54 -4
- package/lib/api.js +51 -1
- package/lib/capture.js +36 -1
- package/lib/client.js +16 -16
- package/lib/resident-state.js +26 -0
- package/lib/watchdog.js +19 -9
- package/lib/web.js +41 -1
- package/package.json +2 -2
- package/web/app.js +14 -14
package/lib/resident-state.js
CHANGED
|
@@ -45,6 +45,32 @@ export function recordReplaceBlocked(env = process.env, { error = '', lockPath =
|
|
|
45
45
|
try { writeResidentState(env, data); return { ok: true, recordedAt: at, firstTime } } catch { return { ok: false, recordedAt: at, firstTime } }
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* 记下「常驻实例由**宿主**托管」(issue #30 ④ 的落地):锁不可写时看门狗路径被永久拒绝,
|
|
50
|
+
* 于是宿主自己停掉旧实例、在原端口拉起新的并保活 —— 这条状态让面板/接口知道「虽然锁里没登记,
|
|
51
|
+
* 但实例是有人管的」,而不是显示成「未托管 / 孤儿」。
|
|
52
|
+
*/
|
|
53
|
+
export function recordHostManaged(env = process.env, { port = 0, version = '', served = '', reason = '' } = {}) {
|
|
54
|
+
const at = Date.now()
|
|
55
|
+
const data = { ...readResidentState(env), hostManaged: { at, port: Number(port) || 0, version: String(version || ''), served: String(served || ''), reason: String(reason || '').slice(0, 300), count: (Number((readResidentState(env).hostManaged || {}).count) || 0) + 1 } }
|
|
56
|
+
try { writeResidentState(env, data); return { ok: true, at } } catch { return { ok: false, at } }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 宿主托管状态(默认 24h 内算有效 —— 宿主进程一重启,这条就该被重新确认)。 */
|
|
60
|
+
export function hostManagedInfo(env = process.env, { freshMs = 24 * 3600 * 1000 } = {}) {
|
|
61
|
+
const h = readResidentState(env).hostManaged
|
|
62
|
+
if (!h) return null
|
|
63
|
+
return { ...h, fresh: Date.now() - Number(h.at || 0) < freshMs }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 看门狗重新登记(或用户手工接管)→ 清掉宿主托管状态。 */
|
|
67
|
+
export function clearHostManaged(env = process.env) {
|
|
68
|
+
const cur = readResidentState(env)
|
|
69
|
+
if (!cur.hostManaged) return false
|
|
70
|
+
delete cur.hostManaged
|
|
71
|
+
try { writeResidentState(env, cur); return true } catch { return false }
|
|
72
|
+
}
|
|
73
|
+
|
|
48
74
|
/** 环境修好(锁可写了)→ 清掉这条状态。 */
|
|
49
75
|
export function clearReplaceBlocked(env = process.env) {
|
|
50
76
|
const cur = readResidentState(env)
|
package/lib/watchdog.js
CHANGED
|
@@ -533,14 +533,23 @@ export async function probeServedVersion(port, timeoutMs = 1200) {
|
|
|
533
533
|
// issue #31/#33 第②层:`/overview` 会碰数据库,库只读/损坏时它 500 —— 于是「端口上明明活着、
|
|
534
534
|
// 版本却拿不到」:verify 报「(无响应)」、status 甚至误判成「v0.10.4 之前的旧版」,进而触发
|
|
535
535
|
// 一轮又一轮无效替换。`/version-check` **不碰库**(只读 package.json + 缓存 + 自探),先问它。
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
536
|
+
//
|
|
537
|
+
// 再叠一层**重试**(issue #30 第五轮实测):报告者看到「同一时刻 version-check 200、2 秒后
|
|
538
|
+
// status 说没有服务」—— 单发探测撞上重启窗口/瞬时拒绝就会得出相反结论。这里退避重试 3 次
|
|
539
|
+
// (每次约 150/300ms),让**只调一次**的那些调用方(status / inspectResident / 面板)不再被瞬时抖动骗到。
|
|
540
|
+
const tries = 3
|
|
541
|
+
for (let i = 0; i < tries; i++) {
|
|
542
|
+
try {
|
|
543
|
+
const v = await probeViaVersionCheck(port, timeoutMs)
|
|
544
|
+
if (v) return v
|
|
545
|
+
} catch { /* 回落到 /overview */ }
|
|
546
|
+
try {
|
|
547
|
+
const info = await probe(port, timeoutMs)
|
|
548
|
+
if (info && typeof info.version === 'string' && info.version) return info.version
|
|
549
|
+
} catch { /* 继续重试 */ }
|
|
550
|
+
if (i < tries - 1) await new Promise((r) => setTimeout(r, 150 * (i + 1)))
|
|
551
|
+
}
|
|
552
|
+
return ''
|
|
544
553
|
}
|
|
545
554
|
|
|
546
555
|
/** 问 `/version-check` 要「端口上那个进程加载的版本」(不碰数据库;旧版没有该路由时返回空串)。 */
|
|
@@ -604,7 +613,8 @@ export async function waitForTakeover(port, expect, {
|
|
|
604
613
|
const start = Date.now()
|
|
605
614
|
let version = ''
|
|
606
615
|
for (;;) {
|
|
607
|
-
|
|
616
|
+
// 单次给较小的超时:内层 probeServedVersion 自己会退避重试,外层还要轮询多轮(别把窗口吃光)
|
|
617
|
+
try { version = String((await probeVersion(p, 800)) || '') } catch { version = '' }
|
|
608
618
|
let fresh = []
|
|
609
619
|
try {
|
|
610
620
|
fresh = lockOf(env).watchdogs.filter((w) => Number(w.port) === p && isPidAlive(w.pid) && !beforePids.has(Number(w.pid)))
|
package/lib/web.js
CHANGED
|
@@ -281,10 +281,50 @@ async function waitForOurWeb(port, timeoutMs = 6000, intervalMs = 500) {
|
|
|
281
281
|
* 端口被非本项目进程占用时向后漂移(+1..+10)。
|
|
282
282
|
* @returns {Promise<{url:string, port:number, spawned:boolean}>}
|
|
283
283
|
*/
|
|
284
|
-
|
|
284
|
+
/**
|
|
285
|
+
* 强制接管端口上的**自家旧 web**:收掉它、等端口空出来(issue #30 第四点:锁不可写的机器上
|
|
286
|
+
* 看门狗路径被永久拒绝,只能由**宿主**自己把常驻实例换掉 —— 宿主能写、也能 kill 自己人的进程)。
|
|
287
|
+
*
|
|
288
|
+
* 安全闸门:只对 `looksLikeOurWeb(command)` 为真的占用者下手;别人的进程一个都不许动。
|
|
289
|
+
*
|
|
290
|
+
* @returns {Promise<{took:boolean, pid:number, reason:string}>}
|
|
291
|
+
*/
|
|
292
|
+
export async function takeoverOwnWeb(port, { findListener, kill, sleep, looksLike, timeoutMs = 4000 } = {}) {
|
|
293
|
+
const p = Number(port) || DEFAULT_WEB_PORT
|
|
294
|
+
const find = findListener || (await import('./watchdog.js')).findPortListener
|
|
295
|
+
const isOurs = looksLike || looksLikeOurWeb
|
|
296
|
+
const wait = sleep || ((ms) => new Promise((r) => setTimeout(r, ms)))
|
|
297
|
+
const killFn = kill || ((pid) => { try { process.kill(pid, 'SIGTERM'); return true } catch { return false } })
|
|
298
|
+
let occ = null
|
|
299
|
+
try { occ = await find(p) } catch { occ = null }
|
|
300
|
+
if (!occ || !occ.pid) return { took: false, pid: 0, reason: '端口上没有占用者' }
|
|
301
|
+
if (!isOurs(occ.command)) return { took: false, pid: Number(occ.pid), reason: '占用者不是本插件的 web(' + (occ.command || '命令行未知') + ')→ 不动它' }
|
|
302
|
+
let alive = true
|
|
303
|
+
try { alive = await isOurs(p) } catch { alive = true }
|
|
304
|
+
void alive
|
|
305
|
+
if (!killFn(occ.pid)) return { took: false, pid: Number(occ.pid), reason: '终止失败(可能无权限)' }
|
|
306
|
+
const deadline = Date.now() + Math.max(0, Number(timeoutMs) || 0)
|
|
307
|
+
while (Date.now() < deadline) {
|
|
308
|
+
await wait(200)
|
|
309
|
+
let left = null
|
|
310
|
+
try { left = await find(p) } catch { left = null }
|
|
311
|
+
if (!left || !left.pid) return { took: true, pid: Number(occ.pid), reason: '已收掉自家旧 web' }
|
|
312
|
+
}
|
|
313
|
+
return { took: false, pid: Number(occ.pid), reason: '已发出终止但端口仍被占用' }
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export async function ensureWebServer({ port = DEFAULT_WEB_PORT, vaultRoot = defaultVaultDir(), spawnEnv = {}, totalTimeoutMs = 15000, takeoverWaitMs = 6000, forceTakeover = false, deps = {} } = {}) {
|
|
285
317
|
// 探测可用端口:先找「已是我们的服务」的端口,再找空闲端口
|
|
286
318
|
let target = port
|
|
287
319
|
let found = null
|
|
320
|
+
// 强制接管(issue #30 ④):宿主自管的机器上,先把自家旧实例收掉,再在原端口起新的 ——
|
|
321
|
+
// 不这么做就会走到下面的「让位到 port+1」,于是既换不掉旧的、又多出一个实例。
|
|
322
|
+
if (forceTakeover) {
|
|
323
|
+
try {
|
|
324
|
+
const r = await takeoverOwnWeb(port, { findListener: deps.findListener, kill: deps.kill, sleep: deps.sleep, timeoutMs: Math.max(1500, Math.min(5000, totalTimeoutMs)) })
|
|
325
|
+
process.stderr.write(`[memory-eternal] 强制接管端口 ${port}:${r.reason}${r.pid ? '(pid ' + r.pid + ')' : ''}\n`)
|
|
326
|
+
} catch (e) { process.stderr.write(`[memory-eternal] 强制接管失败:${String((e && e.message) || e)}\n`) }
|
|
327
|
+
}
|
|
288
328
|
for (let p = port; p < port + 10; p++) {
|
|
289
329
|
const alive = await probeWebServer(p)
|
|
290
330
|
if (alive) { found = { url: `http://127.0.0.1:${p}`, port: p, spawned: false }; break }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-eternal",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.34",
|
|
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 && node tests/config-validate.test.mjs && node tests/issue-fixes-8.test.mjs && node tests/issue-fixes-9.test.mjs && node tests/release-verify.test.mjs && node tests/refine.test.mjs && node tests/db-restore.test.mjs && node tests/align.test.mjs && node tests/tags-normalize.test.mjs && node tests/resident-state.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 && node tests/issue-fixes-8.test.mjs && node tests/issue-fixes-9.test.mjs && node tests/release-verify.test.mjs && node tests/refine.test.mjs && node tests/db-restore.test.mjs && node tests/align.test.mjs && node tests/tags-normalize.test.mjs && node tests/resident-state.test.mjs && node tests/llm-route.test.mjs && node tests/host-managed.test.mjs"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@deepseek-ai/schemastery": "^3.18.1"
|