memory-eternal 0.10.47 → 0.10.48
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 +3 -2
- package/README.md +3 -2
- package/lib/client.js +20 -18
- package/package.json +3 -2
- package/scripts/panel-check.mjs +160 -0
- package/web/app.js +21 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-eternal",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.48",
|
|
4
4
|
"description": "记忆核心(Memory Eternal):自研的 DeepSeek Harness 记忆插件,不移植任何既有记忆框架——对话结束后自动沉淀知识卡到本地 Markdown Vault(自研去重、自研 CJK 检索、可 git 管理),设置页提供图形化知识库(统计 / 搜索 / 知识图谱 + 侧边栏一键弹窗),Agent 通过 memory_recall 工具按需召回历史上下文。零人工干预。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -64,7 +64,8 @@
|
|
|
64
64
|
"prepublishOnly": "node build.mjs",
|
|
65
65
|
"release": "node scripts/release.mjs",
|
|
66
66
|
"release:dry": "node scripts/release.mjs --dry-run",
|
|
67
|
-
"
|
|
67
|
+
"panel-check": "node scripts/panel-check.mjs",
|
|
68
|
+
"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 && node tests/replace-claim.test.mjs && node tests/version-check-self.test.mjs && node tests/setup-codex.test.mjs && node tests/listener-netstat.test.mjs && node tests/issue-46-overlay.test.mjs && node tests/graph-initial-zoom.test.mjs && node tests/card-head-actions.test.mjs && node tests/card-reader-ux.test.mjs && node tests/tag-filter.test.mjs && node tests/ui-overflow.test.mjs && node tests/version-card-state.test.mjs"
|
|
68
69
|
},
|
|
69
70
|
"dependencies": {
|
|
70
71
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 面板视觉/布局自检(零依赖):用本机 Chrome headless + CDP 打开记忆面板,
|
|
3
|
+
// 逐步点击 → 断言「没有横向溢出 / 没有异常滚动条」→ 截图。
|
|
4
|
+
//
|
|
5
|
+
// 为什么需要它:0.10.42–0.10.47 的界面回归(按钮被挤压、横向滚动条、按钮消失)
|
|
6
|
+
// 全是「源码守卫测试全绿但肉眼一看就不对」—— 源码测试只能证明字符串还在,
|
|
7
|
+
// 证明不了布局。这个脚本把「看得见」变成可重复的验收动作。
|
|
8
|
+
//
|
|
9
|
+
// 用法:
|
|
10
|
+
// node scripts/panel-check.mjs # 默认 http://127.0.0.1:7999/
|
|
11
|
+
// node scripts/panel-check.mjs --url http://127.0.0.1:7999/ --out ./.panel
|
|
12
|
+
// node scripts/panel-check.mjs --click "text=配置" --click "text=知识卡"
|
|
13
|
+
// 退出码非 0 = 发现横向溢出(不该发的版)
|
|
14
|
+
import { spawn } from 'node:child_process'
|
|
15
|
+
import fs from 'node:fs'
|
|
16
|
+
import os from 'node:os'
|
|
17
|
+
import path from 'node:path'
|
|
18
|
+
|
|
19
|
+
const argv = process.argv.slice(2)
|
|
20
|
+
const flag = (name, def = '') => { const i = argv.indexOf('--' + name); return i >= 0 ? (argv[i + 1] ?? '') : def }
|
|
21
|
+
const flags = (name) => argv.reduce((acc, a, i) => (a === '--' + name ? [...acc, argv[i + 1]] : acc), [])
|
|
22
|
+
|
|
23
|
+
const URL_TARGET = flag('url', 'http://127.0.0.1:7999/')
|
|
24
|
+
const OUT_DIR = path.resolve(flag('out', '.panel'))
|
|
25
|
+
const CLICKS = flags('click')
|
|
26
|
+
const PORT = Number(flag('port', '9333'))
|
|
27
|
+
const CHROME = flag('chrome', process.platform === 'win32'
|
|
28
|
+
? 'C:/Program Files/Google/Chrome/Application/chrome.exe'
|
|
29
|
+
: process.platform === 'darwin' ? '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' : 'google-chrome')
|
|
30
|
+
|
|
31
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
32
|
+
|
|
33
|
+
function launchChrome() {
|
|
34
|
+
const profile = fs.mkdtempSync(path.join(os.tmpdir(), 'me-panel-'))
|
|
35
|
+
const child = spawn(CHROME, [
|
|
36
|
+
'--headless=new', '--disable-gpu', '--hide-scrollbars=false', '--no-first-run',
|
|
37
|
+
'--no-default-browser-check', '--disable-extensions', '--mute-audio',
|
|
38
|
+
'--user-data-dir=' + profile, '--remote-debugging-port=' + String(PORT), 'about:blank',
|
|
39
|
+
], { stdio: 'ignore', windowsHide: true })
|
|
40
|
+
return { child, profile }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function waitForDevtools() {
|
|
44
|
+
for (let i = 0; i < 100; i++) {
|
|
45
|
+
try {
|
|
46
|
+
const r = await fetch('http://127.0.0.1:' + PORT + '/json/version')
|
|
47
|
+
if (r.ok) return (await r.json()).webSocketDebuggerUrl
|
|
48
|
+
} catch { /* 还没起来 */ }
|
|
49
|
+
await sleep(150)
|
|
50
|
+
}
|
|
51
|
+
throw new Error('Chrome DevTools 没起来(端口 ' + PORT + ')')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class Cdp {
|
|
55
|
+
constructor(ws) { this.ws = ws; this.id = 0; this.pending = new Map(); this.events = [] }
|
|
56
|
+
static async connect(url) {
|
|
57
|
+
const ws = new WebSocket(url)
|
|
58
|
+
await new Promise((res, rej) => { ws.onopen = res; ws.onerror = (e) => rej(new Error('WebSocket 失败: ' + (e && e.message || e))) })
|
|
59
|
+
const cdp = new Cdp(ws)
|
|
60
|
+
ws.onmessage = (ev) => {
|
|
61
|
+
const msg = JSON.parse(ev.data)
|
|
62
|
+
if (msg.id && cdp.pending.has(msg.id)) {
|
|
63
|
+
const { res, rej } = cdp.pending.get(msg.id)
|
|
64
|
+
cdp.pending.delete(msg.id)
|
|
65
|
+
msg.error ? rej(new Error(JSON.stringify(msg.error))) : res(msg.result)
|
|
66
|
+
} else cdp.events.push(msg)
|
|
67
|
+
}
|
|
68
|
+
return cdp
|
|
69
|
+
}
|
|
70
|
+
send(method, params = {}) {
|
|
71
|
+
const id = ++this.id
|
|
72
|
+
return new Promise((res, rej) => { this.pending.set(id, { res, rej }); this.ws.send(JSON.stringify({ id, method, params })) })
|
|
73
|
+
}
|
|
74
|
+
async eval(expression) {
|
|
75
|
+
const r = await this.send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true })
|
|
76
|
+
if (r.exceptionDetails) throw new Error('页面脚本异常:' + (r.exceptionDetails.text || ''))
|
|
77
|
+
return r.result && r.result.value
|
|
78
|
+
}
|
|
79
|
+
async shot(file) {
|
|
80
|
+
const r = await this.send('Page.captureScreenshot', { format: 'png', captureBeyondViewport: false })
|
|
81
|
+
fs.writeFileSync(file, Buffer.from(r.data, 'base64'))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 每步都跑的断言:任何「可滚动容器的 scrollWidth > clientWidth」都算横向溢出(滚动条异常)
|
|
86
|
+
const OVERFLOW_PROBE = `(() => {
|
|
87
|
+
const out = []
|
|
88
|
+
const root = document.documentElement
|
|
89
|
+
const over0 = root.scrollWidth - root.clientWidth
|
|
90
|
+
if (over0 > 1) out.push({ what: 'html', over: over0 })
|
|
91
|
+
for (const sel of ['.me-dialog', '.me-dialog-body', '.mc-card', '.md-doc', '.mc-toolbar', '.mc-head-actions', '.me-head-actions', '.mc-tags', 'footer']) {
|
|
92
|
+
document.querySelectorAll(sel).forEach((el, i) => {
|
|
93
|
+
const over = el.scrollWidth - el.clientWidth
|
|
94
|
+
if (over <= 1) return
|
|
95
|
+
// 把「谁把它撑宽」也带出来:只报容器不够,修的时候还得猜
|
|
96
|
+
const cw = el.clientWidth
|
|
97
|
+
const kids = [...el.children].map((c) => {
|
|
98
|
+
const r = c.getBoundingClientRect()
|
|
99
|
+
return { tag: c.tagName, cls: String(c.className || '').slice(0, 48), w: Math.round(r.width), right: Math.round(r.right - el.getBoundingClientRect().left), over: c.scrollWidth - c.clientWidth, text: (c.innerText || '').replace(/\\s+/g, ' ').trim().slice(0, 56) }
|
|
100
|
+
}).filter((k) => k.right > cw + 1 || k.over > 1)
|
|
101
|
+
out.push({ what: sel + '#' + i, over, cls: String(el.className || '').slice(0, 60), kids })
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
return out
|
|
105
|
+
})()`
|
|
106
|
+
|
|
107
|
+
async function clickText(cdp, spec) {
|
|
108
|
+
// 支持 "text=xxx" 与 CSS 选择器
|
|
109
|
+
const isText = spec.startsWith('text=')
|
|
110
|
+
const expr = isText
|
|
111
|
+
? `(() => { const want = ${JSON.stringify(spec.slice(5))}; const els = [...document.querySelectorAll('button,a,[role=tab],.mc-chip,span,div')]; const hit = els.reverse().find((e) => (e.innerText || '').trim() === want) || els.reverse().find((e) => (e.innerText || '').includes(want)); if (!hit) return false; hit.click(); return true })()`
|
|
112
|
+
: `(() => { const el = document.querySelector(${JSON.stringify(spec)}); if (!el) return false; el.click(); return true })()`
|
|
113
|
+
const ok = await cdp.eval(expr)
|
|
114
|
+
if (!ok) throw new Error('点不到:' + spec)
|
|
115
|
+
await sleep(900)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const main = async () => {
|
|
119
|
+
fs.mkdirSync(OUT_DIR, { recursive: true })
|
|
120
|
+
const { child, profile } = launchChrome()
|
|
121
|
+
let cdp = null
|
|
122
|
+
try {
|
|
123
|
+
// /json/version 给的是**浏览器级** target(只有 Target.* 方法);要 Page/Runtime 必须新建页面 target。
|
|
124
|
+
await waitForDevtools()
|
|
125
|
+
const created = await fetch('http://127.0.0.1:' + PORT + '/json/new?' + encodeURIComponent(URL_TARGET), { method: 'PUT' })
|
|
126
|
+
if (!created.ok) throw new Error('创建页面 target 失败:HTTP ' + created.status)
|
|
127
|
+
const target = await created.json()
|
|
128
|
+
cdp = await Cdp.connect(target.webSocketDebuggerUrl)
|
|
129
|
+
await cdp.send('Page.enable')
|
|
130
|
+
await cdp.send('Runtime.enable')
|
|
131
|
+
await cdp.send('Page.navigate', { url: URL_TARGET })
|
|
132
|
+
await sleep(2500)
|
|
133
|
+
|
|
134
|
+
const report = []
|
|
135
|
+
let bad = 0
|
|
136
|
+
const step = async (label) => {
|
|
137
|
+
const over = await cdp.eval(OVERFLOW_PROBE)
|
|
138
|
+
const file = path.join(OUT_DIR, label + '.png')
|
|
139
|
+
await cdp.shot(file)
|
|
140
|
+
report.push({ label, over, png: file })
|
|
141
|
+
if (over.length) bad++
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await step('01-list')
|
|
145
|
+
for (let i = 0; i < CLICKS.length; i++) {
|
|
146
|
+
await clickText(cdp, CLICKS[i])
|
|
147
|
+
await step(String(i + 2).padStart(2, '0') + '-' + (CLICKS[i].replace(/^text=/, '').replace(/[^\w\u4e00-\u9fa5-]/g, '_') || 'step'))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(JSON.stringify({ url: URL_TARGET, out: OUT_DIR, steps: report, overflowSteps: bad }, null, 2))
|
|
151
|
+
if (bad) { console.error('✗ 有 ' + bad + ' 个步骤存在横向溢出(见上方 over 列表)'); process.exitCode = 1 }
|
|
152
|
+
else console.log('✓ 全部步骤无横向溢出')
|
|
153
|
+
} finally {
|
|
154
|
+
try { cdp && cdp.ws.close() } catch { /* 无所谓 */ }
|
|
155
|
+
try { child.kill() } catch { /* 已经退出 */ }
|
|
156
|
+
await sleep(300)
|
|
157
|
+
try { fs.rmSync(profile, { recursive: true, force: true, maxRetries: 3 }) } catch { /* 临时目录清理失败无妨 */ }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
main().catch((e) => { console.error('panel-check 失败:' + (e && e.message || e)); process.exitCode = 1 })
|