memory-eternal 0.10.52 → 0.10.54
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 +29 -11
- package/README.md +29 -11
- package/index.js +20 -4
- package/lib/api.js +16 -2
- package/lib/capture.js +6 -1
- package/lib/client.js +16 -16
- package/package.json +2 -2
- package/scripts/panel-check.mjs +190 -160
- package/web/app.js +14 -14
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "memory-eternal",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.54",
|
|
5
5
|
"description": "记忆核心(Memory Eternal):对话结束后自动沉淀知识卡到本地记忆库(~/.dsh/memory-vault),支持检索 / 召回 / 审核中心。",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "EternalNight996",
|
package/README.en.md
CHANGED
|
@@ -17,20 +17,36 @@
|
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
20
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/cards.png" width="900" alt="Knowledge card list: every conversation is distilled into a card you can search, audit and trace back" />
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
|
-
> **
|
|
24
|
-
> Fully self-built, zero third-party memory framework, no DSH source changes
|
|
23
|
+
> **Every conversation is distilled into knowledge cards; nothing is forgotten across sessions, and recall fetches only the relevant chunks — fewer tokens, less noise.**
|
|
24
|
+
> Fully self-built, zero third-party memory framework, no DSH source changes; one vault shared by every Agent, SQLite persistent storage, zero external dependencies.
|
|
25
25
|
|
|
26
|
-
<p align="center"><strong>⭐
|
|
26
|
+
<p align="center"><strong>⭐ If it saved you from explaining the same context again, a star means a lot</strong><br/><sub>DSH one-liner: <code>dsh plugin --profile web add memory-eternal</code></sub></p>
|
|
27
27
|
|
|
28
28
|
<p align="center">
|
|
29
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
30
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
31
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
29
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/graph.png" width="32%" alt="Knowledge graph: 897 nodes / 4257 edges, coloured by kind, cross-vault aggregation and export" />
|
|
30
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/health.png" width="32%" alt="Health overview: database / backups / auto-capture / versions — spot anything broken silently" />
|
|
31
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/audit.png" width="32%" alt="Audit centre: AI-written cards are reviewed before they land; batch approve / reject / recycle" />
|
|
32
32
|
</p>
|
|
33
33
|
|
|
34
|
+
<p align="center"><sub>▲ Card list · knowledge graph · health · audit centre — real screenshots from a running instance (1280×860)</sub></p>
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## ✨ What problem does it actually solve
|
|
39
|
+
|
|
40
|
+
| What you hit | Before | After |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| A project you talked about for three days, forgotten in a new session | dig through history, explain it again | each turn is distilled into a card; one `memory_recall` brings it back |
|
|
43
|
+
| Cramming memory into the prompt blows the token budget | paste everything, get more expensive | only the relevant chunk is recalled (capped), fewer tokens and less noise |
|
|
44
|
+
| You cannot fully trust what the AI wrote | no trace, no rollback | cards carry source / time / status, audited before landing, deletions go to the recycle bin |
|
|
45
|
+
|
|
46
|
+
- **Visible**: cards, graph, health overview and capture log — four layers of visibility;
|
|
47
|
+
- **Controllable**: audit modes, dedup and contradiction scans, batch merge, daily backups and one-command restore;
|
|
48
|
+
- **Portable**: the vault is local Markdown + SQLite — git-managed and exportable, never locked inside the plugin.
|
|
49
|
+
|
|
34
50
|
---
|
|
35
51
|
|
|
36
52
|
## 🚀 Get Started in 5 Minutes
|
|
@@ -170,7 +186,7 @@ Three concepts, don't mix them:
|
|
|
170
186
|
> All settings live in the **vault left rail "Memory Config"** (or DSH Settings → Memory) — edit and hit "Save". Here's the full page (plugin info / Agent MCP mount status / auto-audit config):
|
|
171
187
|
|
|
172
188
|
<p align="center">
|
|
173
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
189
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/config.png" width="880" alt="Memory config page" />
|
|
174
190
|
</p>
|
|
175
191
|
|
|
176
192
|
### 1. Most used
|
|
@@ -361,14 +377,16 @@ curl http://127.0.0.1:7999/memory-eternal/api/web-info # is the standalone we
|
|
|
361
377
|
|
|
362
378
|
| Version | Date | Highlights |
|
|
363
379
|
|---|---|---|
|
|
364
|
-
| **v0.10.
|
|
365
|
-
| **v0.10.
|
|
380
|
+
| **v0.10.54** | 2026-10-20 | **More reliable distillation with local models (#48), plus a refreshed README with new screenshots**. ① **#48**: a local 30B model takes 40-60s to cold start while the distill timeout was **hardcoded to 45s**, so every cold start aborted; the timeout is now configurable via **captureTimeoutMs** (still 45s by default, 120000+ recommended for local inference) and **a timeout retries the same candidate once with a longer budget** instead of switching models. ② **The second shape of #48**: when the provider does not report max-tokens, a truncated answer only shows up as unterminated JSON, so a dedicated **TRUNCATED_OUTPUT** code now joins MAX_TOKENS on the "double the output budget and retry the same candidate" ladder. ③ The settings page gains a distill timeout (ms) field. ④ **README refresh**: all real screenshots (cards, a graph with 897 nodes / 4257 edges, health overview, audit centre, settings) plus a "what problem does it solve" comparison table and a clearer value proposition and star call-to-action (both languages). npm test now runs **440 checks** |
|
|
381
|
+
| **v0.10.53** | 2026-10-20 | **Health panel (reliability line): all previously invisible state in one place**. A new "Health" card at the top of the usage page shows: ① **database** (journal mode, writability, last health check, with doctor guidance when bad); ② **backups** (count and age of the newest, amber past 7 days, red at zero); ③ **auto capture** (last success/failure with reason); ④ **versions** (disk / resident / host, plus a Start check button because align needs the npm registry and cannot run automatically). Data comes from existing endpoints: /overview now includes a **backups summary** (count/newestAt/last five filenames) alongside /capture-log and /align. Also **fixes a white-screen bug**: the card initially used tf(), which only exists in the ConfigPanel scope, so opening the usage page blanked the whole app; the panel checker gained two gates — **page alive (textLen<20 counts as blank)** and **Runtime.exceptionThrown capture** — because a blank page previously passed the overflow assertions. Adds tests/health-panel.test.mjs (4 checks). npm test now runs **437 checks** |
|
|
366
382
|
|
|
367
383
|
<details>
|
|
368
|
-
<summary>Older release notes (v0.10.
|
|
384
|
+
<summary>Older release notes (v0.10.52 and earlier · 79 releases, click to expand)</summary>
|
|
369
385
|
|
|
370
386
|
| Version | Date | Highlights |
|
|
371
387
|
|---|---|---|
|
|
388
|
+
| **v0.10.52** | 2026-10-19 | **Redaction hole in the diagnostic bundle (privacy fix; upgrade recommended)**. The 0.10.51 one-command bundle had a gap: the home directory appears in **JSON-escaped form** (C:\\Users\\x) inside doctor/align --json output and meta.json, while only the plain and forward-slash variants were replaced, so the bundle could still be grepped for the home path. All three variants (plain, forward slash, JSON escaped) now become <home>, with a test assert added for the escaped form; a freshly generated bundle on this machine verifies as **no home path and no sk- keys**. If you already generated and shared a bundle with 0.10.51, please regenerate it before sharing. npm test now runs **433 checks** |
|
|
389
|
+
| **v0.10.51** | 2026-10-19 | **One-command diagnostic bundle: dsh-memory support** (reliability line; in #47 the user-pasted context was truncated by GitHub so I could not see the full picture — this exists for exactly that kind of exchange). A single command produces a **zip** with meta (version matrix / platform / node / vault), doctor, align, status, the watchdog lock, the host heartbeat, the shared config, the tail of the capture log (200 lines by default) and a README; everything is **redacted before writing** (home directory to <home>, sk-/ghp_/github_pat/Bearer and key-like config fields to ***). Implemented with **no dependencies**: a hand-written store/deflate ZIP writer (lib/zip.js) whose output opens in Windows Explorer (verified by extracting with Expand-Archive). In restricted environments --no-spawn skips the sub-processes, while --out and --tail N control destination and log length. Adds tests/support-bundle.test.mjs (redaction, zip structure, no plaintext secrets inside the bundle, CLI wiring). npm test now runs **433 checks** |
|
|
372
390
|
| **v0.10.50** | 2026-10-19 | **Models that ignore JSON no longer burn a call every turn (a follow-up to #47)**. ① "The model replied with prose and produced no JSON object at all" is now separated from the generic UNPARSEABLE_OUTPUT failure and gets its own code **MODEL_NOT_JSON**, with wording that says the model likely does not follow structured output. ② New `lib/model-strikes.js` records which provider/model recently ignored the format (24h window, at most 50 entries) and the host moves those to the end of the candidate list when routing (never removing them; if every candidate was struck the original order is kept, so we never run out), logging a "candidates reordered" line. ③ Effect: models such as stepfun/step-api, which answer distillation prompts conversationally, stop being tried first from the second turn on, saving a pointless call each time. Adds `tests/model-strikes.test.mjs` (prose detection, stable ranking, record window and trimming, wiring guards). npm test now runs **429 checks** |
|
|
373
391
|
| **v0.10.49** | 2026-10-19 | **Fixes "cannot choose the distillation model" in the desktop app (#47): the standalone page now proxies /llm-routes and /llm-test to the host, plus one more state in the matrix**. Root cause: the desktop memory popup is a **standalone page inside an iframe** with no DSH llm service, so /llm-routes always failed and provider/model had to be typed by hand. The standalone page now reads the host heartbeat (the port in <config>.host.json, 127.0.0.1 only, valid for 120s) and forwards those two routes to the host, marking the reply as proxied; when the host is offline the panel shows the concrete reason instead of silently falling back to text inputs. ② The version-card matrix gains restartRepair: when versions already match but nothing keeps the instance alive it says "re-register and keep it alive", no longer the self-contradictory "switch to v0.10.48 (currently v0.10.48)". npm test now runs **425 checks** |
|
|
374
392
|
| **v0.10.48** | 2026-10-19 | **A visual gate plus exhaustive state coverage (the option we agreed on)**. ① **New `npm run panel-check`** (scripts/panel-check.mjs, zero dependencies): drives local headless Chrome over CDP, asserts **no horizontal overflow** on each screen and saves screenshots (list / reader / config / feedback modal). On its very first run it caught two real bugs: an unbreakable long run in a card title (`(begin/chunk/probe/complete/abort)`) overflowing by 45px, and the **feedback textarea overflowing by 22px** (width:100% plus padding under content-box) — exactly the layout/scrollbar anomaly reported. ② Fixes: word-breaking on `.mc-cardrow h4` and a shared border-box rule for form controls. ③ **The version-card decision is now a pure function** `src/client/version-state.js`: versionCardState returns {showButton, emphasis, warn, hint} and the component only maps keys to text; a table-driven test **enumerates 324 combinations** and enforces the invariant that the button is shown whenever version info exists — structurally preventing the 0.10.45 regression where a third state made it disappear. npm test now runs **420 checks** |
|
package/README.md
CHANGED
|
@@ -17,20 +17,36 @@
|
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
20
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/cards.png" width="900" alt="知识卡列表:对话结束自动沉淀成卡,可检索 / 可审核 / 可追溯来源" />
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
|
-
>
|
|
24
|
-
> 全自研、零第三方记忆框架、不改 DSH
|
|
23
|
+
> **对话结束自动沉淀成知识卡,跨会话不失忆;召回只取相关小块,省 token 少噪音。**
|
|
24
|
+
> 全自研、零第三方记忆框架、不改 DSH 源码;一个记忆库所有 Agent 共享,SQLite 持久存储,零外部依赖。
|
|
25
25
|
|
|
26
|
-
<p align="center"><strong>⭐
|
|
26
|
+
<p align="center"><strong>⭐ 如果它帮你少解释了一遍上下文,点个 Star 就是最大的支持</strong><br/><sub>DSH 一条命令:<code>dsh plugin --profile web add memory-eternal</code></sub></p>
|
|
27
27
|
|
|
28
28
|
<p align="center">
|
|
29
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
30
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
31
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
29
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/graph.png" width="32%" alt="知识图谱:897 节点 / 4257 连线,按类型着色、支持跨库聚合与导出" />
|
|
30
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/health.png" width="32%" alt="健康概览:知识库 / 备份 / 自动沉淀 / 版本,一眼看清有没有静默坏掉的东西" />
|
|
31
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/audit.png" width="32%" alt="审核中心:AI 写的卡先审后入,可批量批准 / 驳回 / 回收" />
|
|
32
32
|
</p>
|
|
33
33
|
|
|
34
|
+
<p align="center"><sub>▲ 知识卡列表 · 知识图谱 · 健康概览 · 审核中心 —— 全部是真实运行截图(1280×860)</sub></p>
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## ✨ 它到底解决什么问题
|
|
39
|
+
|
|
40
|
+
| 你遇到的 | 以前的做法 | 用了之后 |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| 聊了 3 天的项目,新会话全忘 | 手动翻历史 / 重新解释一遍 | 每轮结束自动沉淀成卡,开工前 `memory_recall` 一句就回来 |
|
|
43
|
+
| 记忆塞进 prompt,token 爆掉 | 全量粘贴、越聊越贵 | 只召回相关的一小块(可配上限),省 token 也少噪音 |
|
|
44
|
+
| AI 写的东西不敢直接信 | 无法追溯、无法回滚 | 卡片带来源 / 时间 / 状态,先审后入,删除进回收站 |
|
|
45
|
+
|
|
46
|
+
- **看得见**:知识卡 / 图谱 / 健康概览 / 沉淀日志,四层可视化;
|
|
47
|
+
- **控得住**:审核模式(全部人工 / 仅高风险)、去重与矛盾扫描、批量合并、每日备份与一键恢复;
|
|
48
|
+
- **带得走**:知识库是本地 Markdown + SQLite,可 git 管理、可导出,不锁在插件里。
|
|
49
|
+
|
|
34
50
|
---
|
|
35
51
|
|
|
36
52
|
## 🚀 五分钟上手
|
|
@@ -190,7 +206,7 @@ dsh-memory audit reject <卡片路径...> --reason "..." # 人工批量驳回
|
|
|
190
206
|
> 所有配置都在 **记忆库左栏「记忆配置」**(或 DSH 设置 → 记忆)里改,点「保存配置」生效。下图就是配置页全貌(含插件信息 / Agent MCP 挂载状态 / 自动审核配置):
|
|
191
207
|
|
|
192
208
|
<p align="center">
|
|
193
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
209
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/config.png" width="880" alt="记忆配置页:蒸馏 provider / model 下拉、跟随会话、仅试首选、超时与输出上限" />
|
|
194
210
|
</p>
|
|
195
211
|
|
|
196
212
|
### 一、最常用
|
|
@@ -393,14 +409,16 @@ curl http://127.0.0.1:7999/memory-eternal/api/web-info # 独立 web 是否活
|
|
|
393
409
|
|
|
394
410
|
| 版本 | 日期 | 关键改动 |
|
|
395
411
|
|---|---|---|
|
|
396
|
-
| **v0.10.
|
|
397
|
-
| **v0.10.
|
|
412
|
+
| **v0.10.54** | 2026-10-20 | **本地大模型蒸馏更稳(#48)+ README 焕新与最新截图**。① **#48**:本地 30B 模型冷启动要 40–60 秒,而蒸馏超时**硬编码 45 秒** → 每次冷启动必报 ABORTED;现在超时可配 **captureTimeoutMs**(默认仍 45s,本地推理建议 120000+),且**超时后自动用更长超时重试同一候选一次**(不直接换模型)。② **#48 的第二种形态**:模型不给 max-tokens finish 时,截断只表现为「JSON 未闭合」→ 新增独立错误码 **TRUNCATED_OUTPUT**,与 MAX_TOKENS 一起走「加倍输出预算重试同一候选」的阶梯。③ 配置页新增「蒸馏超时(毫秒)」输入项。④ **README 焕新**:全部换成真实运行截图(知识卡 / 知识图谱 897 节点 4257 连线 / 健康概览 / 审核中心 / 配置页)+ 新增「它到底解决什么问题」对照表 + 更清晰的卖点与 Star 引导(中英同步)。npm test 共 **440 项** |
|
|
413
|
+
| **v0.10.53** | 2026-10-20 | **健康面板(可靠性线):把看不见的状态集中一处**。在「用量/今日」顶部新增「🩺 健康概览」:① **知识库**(journal 模式 / 可写 / 最后一次体检时间,异常时给 doctor 指引);② **备份**(数量 + 最近一次多久前,>7 天标黄、0 备份标红);③ **自动沉淀**(最近一次成功/失败与原因);④ **版本**(磁盘 / 常驻 / 宿主;未检查时给「开始检查」按钮,因为 align 要联网比 npm 不能自动跑)。数据来自既有接口:/overview 新增 **backups 摘要**(count/newestAt/最近 5 个文件名),加上 /capture-log 与 /align。**顺带修一个白屏 bug**:健康卡最初误用了 ConfigPanel 作用域的 tf() → 点开「用量/今日」整页白屏;同时给 panel-check 补了两道闸 —— **页面存活(textLen<20 判空白页)** 与 **Runtime.exceptionThrown 捕获**,因为「白屏没有横向溢出」原本会让检查通过。新增 tests/health-panel.test.mjs(4 项)。npm test 共 **437 项** |
|
|
398
414
|
|
|
399
415
|
<details>
|
|
400
|
-
<summary>更早的更新日志(v0.10.
|
|
416
|
+
<summary>更早的更新日志(v0.10.52 及以前 · 共 79 版,点开查看)</summary>
|
|
401
417
|
|
|
402
418
|
| 版本 | 日期 | 关键改动 |
|
|
403
419
|
|---|---|---|
|
|
420
|
+
| **v0.10.52** | 2026-10-19 | **诊断包脱敏补漏(隐私修复,建议尽快升级)**。0.10.51 的「一键诊断包」有个漏点:doctor/align --json 与 meta.json 里的家目录是 **JSON 转义形态**(C:\\Users\\x),而我只替换了原样/正斜杠两种写法 → 包里仍能 grep 到家目录明文。现在三种写法(原样、正斜杠、JSON 转义)都替换成 <home>,并在测试里加了一条「JSON 转义形态不能漏」的断言;本机重新生成诊断包实测:**无家目录明文、无 sk- 密钥**。**如果你已经用 0.10.51 生成过诊断包并分享出去,建议重新生成一次再分享。** npm test 共 **433 项** |
|
|
421
|
+
| **v0.10.51** | 2026-10-19 | **一键诊断包 dsh-memory support**(可靠性线;#47 里用户贴的现场被 GitHub 截断,我拿不到完整信息,这功能就是为这类沟通做的)。一条命令生成 **zip**:meta(版本矩阵/平台/node/vault)/ doctor / align / status / 看门狗锁 / 宿主心跳 / 共享配置 / 沉淀日志尾巴(默认 200 行)+ README;写入前**统一脱敏**(家目录→<home>,sk-/ghp_/github_pat/Bearer/配置里 key 类字段→***)。实现为**零依赖**:自写 store/deflate ZIP 写入器(lib/zip.js,Windows 资源管理器可直接打开,实测 Expand-Archive 解压正常)。受限环境可加 --no-spawn 跳过子进程;--out 指定输出、--tail N 控制日志条数。新增 tests/support-bundle.test.mjs(4 项:脱敏、zip 结构、包内不含明文密钥、CLI 接线)。npm test 共 **433 项** |
|
|
404
422
|
| **v0.10.50** | 2026-10-19 | **「模型不守 JSON」不再每轮白烧调用(#47 衍生改进)**。① 把「模型回了散文、输出里根本没有 JSON 对象」从笼统的 UNPARSEABLE_OUTPUT 里拆出来,给独立错误码 **MODEL_NOT_JSON**,文案直接说清「该模型可能不遵守结构化输出」。② 新增 `lib/model-strikes.js`:记下「哪个 provider/model 最近不守格式」(24h 窗口、最多 50 条),宿主选路时把它们**排到候选末尾**(不删除;全部命中过则按原顺序兜底,绝不没候选),并留一条「蒸馏候选已调整」日志。③ 效果:像 stepfun/step-api 这种会把蒸馏当聊天回的模型,第二轮起就不再优先尝试,省掉每次的无效调用。新增 `tests/model-strikes.test.mjs`(4 项:散文识别 / 排序稳定性 / 记录窗口与裁剪 / 接线守卫)。npm test 共 **429 项** |
|
|
405
423
|
| **v0.10.49** | 2026-10-19 | **桌面版里「无法选择蒸馏模型」修复(#47):独立页把 /llm-routes 与 /llm-test 代理给宿主 + 文案补一个状态**。根因:桌面版的「记忆」弹窗是 iframe 里的**独立页**,它没有 DSH 的 llm 服务 → /llm-routes 恒失败 → provider/model 只能手填。现在独立页读宿主心跳(<config>.host.json 里的端口,仅 127.0.0.1、心跳 120s 内有效),把这两个路由**转发给宿主**执行并标记 proxied;宿主不在线时给明确原因(面板直接显示,不再静默回退成手填)。② 版本卡状态矩阵补 restartRepair:**版本已对但没人保活**时提示「重新托管并保活」,不再出现「换成 v0.10.48(当前 v0.10.48)」这种自相矛盾的文案(截图实测)。npm test 共 **425 项** |
|
|
406
424
|
| **v0.10.48** | 2026-10-19 | **界面验收手段 + 状态穷举(方案一落地)**。① **新增 `npm run panel-check`**(scripts/panel-check.mjs,零依赖):用本机 Chrome headless + CDP 打开面板,逐屏断言**没有横向溢出**并截图(列表 / 阅读器 / 配置 / 反馈弹窗)。它第一次跑就抓到两个真 bug:卡片标题里的无断点长串(`(begin/chunk/probe/complete/abort)`)溢出 45px;**反馈弹窗 textarea 溢出 22px**(width:100% + padding 在 content-box 下)—— 正是用户报的「排版不对 / 滚动条异常」。② 修复:`.mc-cardrow h4` 断词、`.me-overlay input/textarea/select` 统一 border-box。③ **版本卡判定抽成纯函数** `src/client/version-state.js`:`versionCardState(...)` 返回 `{showButton, emphasis, warn, hint}`,组件只做 key→文案;新增表驱动测试**穷举 324 种组合**,并守住不变式「只要拿到版本信息,按钮永远显示」—— 0.10.45「第三种状态导致按钮消失」从结构上不可能再发生。npm test 共 **420 项** |
|
package/index.js
CHANGED
|
@@ -77,6 +77,9 @@ export const Config = z.object({
|
|
|
77
77
|
// 默认值 900 → 2000(issue #18):prompt 要求 300-800 字正文,900 会让超长会话常态化撞上限,
|
|
78
78
|
// 输出被截断后旧代码还会把它误报成「解析失败」,最终落一张 raw 噪声卡。
|
|
79
79
|
captureMaxTokens: z.number().min(100).max(4000).default(DEFAULT_CAPTURE_MAX_TOKENS),
|
|
80
|
+
// issue #48:蒸馏单次超时(毫秒)。本地 30B 模型冷启动要 40–60s,45s 必超时;
|
|
81
|
+
// 默认仍 45s(云端模型够用),本地推理用户可调到 120000+。超时后宿主会自动加时重试一次。
|
|
82
|
+
captureTimeoutMs: z.number().min(5000).max(600000).default(45000),
|
|
80
83
|
// 召回相关性阈值(minScore),越高召回越少越精越省
|
|
81
84
|
recallMinScore: z.number().min(0).max(50).default(2),
|
|
82
85
|
// 注入体积可配置(召回)
|
|
@@ -736,16 +739,28 @@ export function apply(ctx, config) {
|
|
|
736
739
|
const maxTokens = Number(cfg.captureMaxTokens) || DEFAULT_CAPTURE_MAX_TOKENS
|
|
737
740
|
for (let i = 0; i < routes.length; i++) {
|
|
738
741
|
route = routes[i]
|
|
739
|
-
|
|
742
|
+
// issue #48:超时不再硬编码 45s(本地 30B 冷启动 40–60s 必超时),且超时后**同候选**用更长超时重试一次。
|
|
743
|
+
const baseTimeout = Math.max(5000, Number(cfg.captureTimeoutMs) || 45000)
|
|
744
|
+
const isTimeoutFailure = (x) => /ABORTED|TIMEOUT|ETIMEDOUT|aborted/i.test(String((x && (x.code + ' ' + x.message)) || ''))
|
|
745
|
+
const attempt = (budget, ms) => summarizeTurnDetailed(llm, route, text, { signal: AbortSignal.timeout(ms), existing: neighbors, maxTokens: budget })
|
|
746
|
+
let detailed = await attempt(maxTokens, baseTimeout)
|
|
747
|
+
if (detailed.failure && isTimeoutFailure(detailed.failure)) {
|
|
748
|
+
const longer = Math.min(Math.max(Math.round(baseTimeout * 2.5), baseTimeout + 30000), 180000)
|
|
749
|
+
logCapture(sessionId, 'listen', `蒸馏超时(${route.provider}/${route.model}):可能是模型冷启动 → 以 ${Math.round(longer / 1000)}s 超时重试同一候选`)
|
|
750
|
+
detailed = await attempt(maxTokens, longer)
|
|
751
|
+
}
|
|
740
752
|
// #18 建议 3 + #24:撞输出上限不是「模型坏了」,把上限一路翻倍到 schema 上限。
|
|
741
753
|
// 旧实现只翻倍**一次**:1200 → 2400 仍不够就照样退成原文卡,尽管 4000 本来装得下
|
|
742
754
|
// (issue #24 里 9 次失败中有一半正是被这一限制吃掉的)。
|
|
755
|
+
// #48:JSON 被截断(TRUNCATED_OUTPUT)与 MAX_TOKENS 同类处理 —— 靠加倍预算而不是换模型
|
|
756
|
+
const truncLike = (x) => Boolean(x && (x.code === 'MAX_TOKENS' || x.code === 'TRUNCATED_OUTPUT'))
|
|
743
757
|
let prevBudget = maxTokens
|
|
744
758
|
for (const bigger of maxTokenLadder(maxTokens)) {
|
|
745
|
-
|
|
746
|
-
|
|
759
|
+
if (!truncLike(detailed.failure)) break
|
|
760
|
+
logCapture(sessionId, 'listen', `输出撞到上限(${detailed.failure.code},maxTokens=${prevBudget})→ 以 ${bigger} 重试同一候选(${route.provider}/${route.model})`)
|
|
761
|
+
detailed = await attempt(bigger, baseTimeout)
|
|
747
762
|
prevBudget = bigger
|
|
748
|
-
|
|
763
|
+
|
|
749
764
|
}
|
|
750
765
|
if (detailed.card !== undefined) { result = detailed.card; failure = null; break }
|
|
751
766
|
if (detailed.failure) {
|
|
@@ -1194,6 +1209,7 @@ export function apply(ctx, config) {
|
|
|
1194
1209
|
captureProvider: cfg.captureProvider || '',
|
|
1195
1210
|
captureFollowSession: cfg.captureFollowSession === true,
|
|
1196
1211
|
captureFirstOnly: cfg.captureFirstOnly === true,
|
|
1212
|
+
captureTimeoutMs: cfg.captureTimeoutMs ?? 45000,
|
|
1197
1213
|
captureModel: cfg.captureModel || '',
|
|
1198
1214
|
recallEmbedding: cfg.recallEmbedding || '',
|
|
1199
1215
|
sessionBudgetChars: cfg.sessionBudgetChars ?? 80000,
|
package/lib/api.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import { gzipSync, brotliCompressSync, constants as zlibConstants } from 'node:zlib'
|
|
15
15
|
import os from 'node:os'
|
|
16
|
-
import { readFileSync } from 'node:fs'
|
|
16
|
+
import fs, { readFileSync } from 'node:fs'
|
|
17
|
+
import path from 'node:path'
|
|
17
18
|
import { listCards, countCards, readCard, search, searchAll, graph, graphAll, overview, exportCards, deleteCard, writeCard, parseCard, stats, optimizeCandidates, optimizeApply, addFeedback, dailyCounts, mergeCards, ensureVault, auditQueue, recycleList, purgeExpired, setCardStatus, restoreCard, mainStoreBodies } from './vault.js'
|
|
18
19
|
import { compressExcerpt, DEFAULT_CAPTURE_MAX_TOKENS } from './capture.js'
|
|
19
20
|
import { buildDiagnostics } from './feedback.js'
|
|
@@ -230,11 +231,24 @@ export function createApi(deps = {}) {
|
|
|
230
231
|
try { await ensureVault(vaultRoot) } catch (e) { return degraded((e && e.message) || e) }
|
|
231
232
|
let data
|
|
232
233
|
try { data = await overview(vaultRoot) } catch (e) { return degraded((e && e.message) || e) }
|
|
234
|
+
// 健康面板要看「最近一次备份多久前」:这里给摘要(最多 5 个文件名,避免把响应撑大)
|
|
235
|
+
let backups = { count: 0, newestAt: 0, files: [] }
|
|
236
|
+
try {
|
|
237
|
+
const dir = path.join(vaultRoot, 'backups')
|
|
238
|
+
const names = fs.readdirSync(dir).filter((n) => /\.db$/i.test(n)).sort()
|
|
239
|
+
let newestAt = 0
|
|
240
|
+
if (names.length) {
|
|
241
|
+
const newest = names[names.length - 1]
|
|
242
|
+
try { newestAt = fs.statSync(path.join(dir, newest)).mtimeMs } catch { newestAt = 0 }
|
|
243
|
+
}
|
|
244
|
+
backups = { count: names.length, newestAt, files: names.slice(-5) }
|
|
245
|
+
} catch { /* 没有备份目录 */ }
|
|
233
246
|
json(res, 200, {
|
|
234
247
|
ok: true,
|
|
235
248
|
vaultDir: vaultRoot,
|
|
236
249
|
version: servingVersion,
|
|
237
250
|
db: dbStatus(vaultRoot),
|
|
251
|
+
backups,
|
|
238
252
|
...data,
|
|
239
253
|
})
|
|
240
254
|
return
|
|
@@ -888,7 +902,7 @@ export function createApi(deps = {}) {
|
|
|
888
902
|
autoWeb: cfg.autoWeb ?? env.MEMORY_AUTO_WEB !== '0', autoWebMode: cfg.autoWebMode || 'init', webPort: Number(cfg.webPort) || Number(env.MEMORY_WEB_PORT) || 7999, webCheckIntervalMs: Number(cfg.webCheckIntervalMs) || 5000, webMaxRestart: Number(cfg.webMaxRestart) || 10, watchdogAutoSpawn: cfg.watchdogAutoSpawn ?? env.MEMORY_WATCHDOG === '1', autoRestartOnDrift: cfg.autoRestartOnDrift ?? true, autoMcpSetup: cfg.autoMcpSetup ?? false,
|
|
889
903
|
auditMode: cfg.auditMode ?? 'all', auditExemptAgents: cfg.auditExemptAgents ?? [], auditExemptKinds: cfg.auditExemptKinds ?? [], recycleRetentionDays: cfg.recycleRetentionDays ?? 30,
|
|
890
904
|
vaultProfiles: Array.isArray(cfg.vaultProfiles) ? cfg.vaultProfiles : [], activeVault: cfg.activeVault || '',
|
|
891
|
-
enabled: cfg.enabled !== false, vaultDir: cfg.vaultDir || '', captureProvider: cfg.captureProvider || '', captureFollowSession: cfg.captureFollowSession === true, captureFirstOnly: cfg.captureFirstOnly === true,
|
|
905
|
+
enabled: cfg.enabled !== false, vaultDir: cfg.vaultDir || '', captureProvider: cfg.captureProvider || '', captureFollowSession: cfg.captureFollowSession === true, captureFirstOnly: cfg.captureFirstOnly === true, captureTimeoutMs: cfg.captureTimeoutMs ?? 45000,
|
|
892
906
|
captureModel: cfg.captureModel || '', recallEmbedding: cfg.recallEmbedding || '', sessionBudgetChars: cfg.sessionBudgetChars ?? 80000,
|
|
893
907
|
webPort: cfg.webPort ?? 7999, webCheckIntervalMs: cfg.webCheckIntervalMs ?? 5000, webMaxRestart: cfg.webMaxRestart ?? 10,
|
|
894
908
|
}
|
package/lib/capture.js
CHANGED
|
@@ -376,7 +376,12 @@ export async function summarizeTurnDetailed(llm, route, conversation, opts = {})
|
|
|
376
376
|
}
|
|
377
377
|
const why = out ? '模型输出无法解析为 JSON' : '模型返回空文本'
|
|
378
378
|
// 括号没闭合 = 截断的强特征:即便宿主没给 max-tokens finish,也把方向指对(#18 建议 1)
|
|
379
|
-
|
|
379
|
+
// issue #48:本地模型(ollama)不会可靠地给 max-tokens finish,输出被截断时只表现为「JSON 未闭合」。
|
|
380
|
+
// 给独立错误码,让调用方走「加大预算重试同一候选」的阶梯,而不是直接换模型。
|
|
381
|
+
if (out && looksTruncatedJson(out)) {
|
|
382
|
+
return { failure: { code: 'TRUNCATED_OUTPUT', message: '输出被截断(JSON 未闭合)—— 模型侧输出上限/超时导致的半截 JSON:调大「蒸馏输出上限」或缩短会话输入;本地模型也可能撞到 num_predict 上限' } }
|
|
383
|
+
}
|
|
384
|
+
const hint = ''
|
|
380
385
|
// #24:JSON.parse 的原始报错(如 Bad control character in string literal at position N)
|
|
381
386
|
// 以前被两个裸 catch{} 完全吞掉,排查只能靠猜;现场摘要也从「只留前 160 字」改成首尾各留一段。
|
|
382
387
|
const raw = parsed.error ? '(JSON.parse 原始报错:' + parsed.error + ')' : ''
|