dsh-memu-memory 1.2.0 → 1.2.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.
- package/AGENTS.md +1 -1
- package/README.md +19 -8
- package/README.zh.md +12 -6
- package/docs/contributing.md +16 -3
- package/docs/env-and-commands.md +8 -0
- package/docs/host-api.md +3 -1
- package/docs/memory-semantics.md +21 -2
- package/docs/troubleshooting.md +42 -3
- package/lib/activity-log.js +22 -5
- package/lib/backup.js +24 -2
- package/lib/binding-diagnostics.js +3 -3
- package/lib/client.js +1610 -117
- package/lib/consolidation-worker.js +68 -17
- package/lib/curator-evidence.js +131 -0
- package/lib/curator-policy.js +81 -0
- package/lib/curator.js +1299 -83
- package/lib/cycle.js +3 -0
- package/lib/embedding-download.js +39 -4
- package/lib/engine.js +6 -3
- package/lib/global-graph.js +282 -0
- package/lib/graph.js +190 -14
- package/lib/inbound.js +19 -0
- package/lib/index.js +33 -1
- package/lib/inject.js +277 -17
- package/lib/lexicon.js +1115 -0
- package/lib/local-vector-service.js +8 -6
- package/lib/managed-runtime.js +33 -2
- package/lib/memory.js +585 -76
- package/lib/memu.js +2 -2
- package/lib/migrate.js +83 -2
- package/lib/private-links.js +587 -0
- package/lib/private.js +128 -7
- package/lib/prompts/global-curator.md +76 -19
- package/lib/prompts/private-consolidation.coding.md +4 -1
- package/lib/prompts/private-consolidation.md +3 -1
- package/lib/prompts/private-consolidation.mixed.md +3 -1
- package/lib/prompts/private-consolidation.rp.md +3 -1
- package/lib/prompts.js +1 -1
- package/lib/rpc.js +622 -13
- package/lib/state.js +4 -1
- package/lib/text-retrieval.js +661 -0
- package/lib/text-similarity.js +124 -0
- package/lib/tools.js +230 -16
- package/lib/vector-migration.js +28 -3
- package/lib/vector-profile.js +24 -0
- package/lib/webui/standalone.css +17 -0
- package/lib/webui/standalone.js +2 -0
- package/package.json +3 -2
- package/scripts/acceptance-local.mjs +8 -5
- package/scripts/curator-pair-calibrate.mjs +259 -0
- package/scripts/lexicon-calibrate.mjs +230 -0
- package/scripts/lexicon-dedupe.mjs +318 -0
- package/scripts/replay.mjs +573 -0
- package/tests/absorb-review.test.mjs +353 -0
- package/tests/appendix-b.test.mjs +6 -2
- package/tests/appendix-b2.test.mjs +2 -2
- package/tests/client-export.test.mjs +520 -0
- package/tests/client-render.test.mjs +19 -3
- package/tests/consolidation-credential.test.mjs +16 -5
- package/tests/consolidation-feed.test.mjs +96 -0
- package/tests/consolidation-fork.test.mjs +144 -10
- package/tests/consolidation-snapshot.test.mjs +82 -4
- package/tests/curator-absorb.test.mjs +443 -0
- package/tests/curator-calibrate.test.mjs +173 -0
- package/tests/curator-clusters.test.mjs +281 -0
- package/tests/curator-commit.test.mjs +324 -0
- package/tests/curator-evidence.test.mjs +102 -0
- package/tests/curator-link.test.mjs +316 -0
- package/tests/curator-pairing.test.mjs +252 -0
- package/tests/curator-probe.test.mjs +15 -6
- package/tests/curator-review-mode.test.mjs +314 -0
- package/tests/disclosure.test.mjs +2 -1
- package/tests/fixtures/curator-pairs/labeled.v1.jsonl +90 -0
- package/tests/fixtures/lexicon-stub-hooks.mjs +15 -0
- package/tests/fixtures/lexicon-stub.mjs +284 -0
- package/tests/graph-cascade.test.mjs +223 -0
- package/tests/graph.test.mjs +113 -0
- package/tests/help-page.test.mjs +415 -0
- package/tests/hybrid-recall.test.mjs +32 -2
- package/tests/inbound.test.mjs +6 -3
- package/tests/inject-guide.test.mjs +28 -0
- package/tests/inject.test.mjs +157 -5
- package/tests/lexicon-calibrate.test.mjs +48 -0
- package/tests/lexicon-dedupe.test.mjs +171 -0
- package/tests/lexicon-frequency.test.mjs +179 -0
- package/tests/lexicon-inject.test.mjs +410 -0
- package/tests/lexicon-perf.test.mjs +436 -0
- package/tests/lexicon-ui.test.mjs +547 -0
- package/tests/lexicon.test.mjs +922 -0
- package/tests/managed-memory.test.mjs +55 -0
- package/tests/managed-runtime.test.mjs +32 -0
- package/tests/private-links.test.mjs +445 -0
- package/tests/prompts.test.mjs +35 -0
- package/tests/recall-dedup.test.mjs +446 -0
- package/tests/sidebar-beat.test.mjs +5 -1
- package/tests/tool-schema.test.mjs +1 -1
- package/tests/tools.test.mjs +112 -3
- package/tests/vector-migration.test.mjs +58 -1
- package/tests/webui-auth.test.mjs +348 -2
- package/tests/webui.test.mjs +191 -2
package/AGENTS.md
CHANGED
|
@@ -61,7 +61,7 @@ node scripts/acceptance-local.mjs # 50/50
|
|
|
61
61
|
npm run check # node --check + i18n + package.json + 文档预算
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
**结果口径**:以 `node --test` 输出的 `ℹ tests / pass / fail / skipped` 为准。用例数变了先问「是不是我加的」,失败先问「是不是并发写了同一个库」——这仓库真会多个会话同时改它。测试数字只作**带日期快照**引用(2026-
|
|
64
|
+
**结果口径**:以 `node --test` 输出的 `ℹ tests / pass / fail / skipped` 为准。用例数变了先问「是不是我加的」,失败先问「是不是并发写了同一个库」——这仓库真会多个会话同时改它。测试数字只作**带日期快照**引用(2026-11-01 实测 1210 tests / 1208 pass / 0 fail / 2 skipped;两条真实引擎用例按设计跳过,历史 505/493/463/409/392 等不再代表当前),不当成当前保证。提交前逐条对 `docs/contributing.md` §10。
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
package/README.md
CHANGED
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
`dsh-memu-memory` is built for long-form RP and companion chats. It gives a DSH conversation a memory that behaves like memory instead of a log: three retention layers that fade, a first-turn anchor plus at most one per-turn recall, provenance that cannot be quietly upgraded from "I inferred it" to "they said it", contradictions that wait for a decision instead of both being read as true, and an optional relationship graph with evidence. Speakers, characters, worlds and story branches form hard boundaries, and what a character is allowed to know stays separate from what actually happened.
|
|
26
|
+
`dsh-memu-memory` is built for long-form RP and companion chats. It gives a DSH conversation a memory that behaves like memory instead of a log: three retention layers that fade, a first-turn anchor plus at most one per-turn recall, provenance that cannot be quietly upgraded from "I inferred it" to "they said it", contradictions that wait for a decision instead of both being read as true, near-identical facts collapse to one entry and one recall seat, and an optional relationship graph with evidence. Speakers, characters, worlds and story branches form hard boundaries, and what a character is allowed to know stays separate from what actually happened.
|
|
27
27
|
|
|
28
28
|
It is **local-first by default**. The default `agent` backend stores full text in JSON indexes and recalls lexically — no Python, no embedding service, no API key. The `api` backend switches recall to the optional, pinned MemU engine (vector search). MemU is a plugin for people who want it, not a requirement for the plugin to start.
|
|
29
29
|
|
|
30
30
|
> [!WARNING]
|
|
31
31
|
> **Early-stage software.** This plugin is still early and may contain unforeseen errors, bugs or breaking changes. Back up your data, and please [report an issue](https://github.com/Tkingxiao/dsh-memu-memory/issues) or send a pull request — feedback helps the project improve.
|
|
32
32
|
|
|
33
|
-
> **Status:** 1.
|
|
33
|
+
> **Status:** 1.2.2. The first public release was 1.0.0. The manual now lives in [`docs/`](docs/), with [`AGENTS.md`](AGENTS.md) as a short index. Back up `~/.dsh/dsh-memu-memory` before upgrades, and read [Compatibility & limits](#compatibility--limits) before storing data you cannot re-create.
|
|
34
34
|
|
|
35
35
|
## Screenshots
|
|
36
36
|
|
|
@@ -57,7 +57,9 @@ All screenshots are the real WebUI on isolated example data, not anyone's memori
|
|
|
57
57
|
## What it remembers
|
|
58
58
|
|
|
59
59
|
- **Three layers, one timeline.** `short_term` drafts expire after 72 hours by default; `long_term` is the body of ordinary memory; `must_remember` needs a reason and a source, decays over 365 days with a 0.35 strength floor, and can be *marginalized* but never auto-deleted to make room.
|
|
60
|
-
- **Recall is a moment, not a dump.** The first turn after a session starts (or after compaction) gets an anchor block with budgets for pinned, core and global memory. After that, each user turn gets at most one recall. Deduplication and a token budget are enforced in code, not left to the prompt.
|
|
60
|
+
- **Recall is a moment, not a dump.** The first turn after a session starts (or after compaction) gets an anchor block with budgets for pinned, core and global memory. After that, each user turn gets at most one recall. Deduplication and a token budget are enforced in code, not left to the prompt. Anchor and recall entries are **title cards**: title, id and semantic tags only. The body stays in the drawer and behind `memu_private_recall` / `memu_global_recall`, so the same budget exposes far more memories without pasting text the model never asked for.
|
|
61
|
+
- **A private lexicon writes itself.** The consolidation worker extracts stable, evidenced slang, memes and project terms with one term per entry. Ordinary entries become active immediately (no human review queue), while sensitive triggers and sensitive edits stay user-only. The lexicon page is a card flow with active/archived tabs, edit, archive/restore and two-step permanent delete.
|
|
62
|
+
- **Private links are algorithm-first.** After consolidation, a zero-token pass connects memories that share a rare name or alias in the same context/track and creates/reuses an entity hub (for example `yuike`). Uncertain pairs are handed to the consolidation worker for an evidence-based decision. Links are metadata (`source: 'auto'`), shown with an “Auto” badge in the graph and removable; they never rewrite a body, write a vector or delete anything.
|
|
61
63
|
- **A date is either an anniversary or one day.** `MM-DD` recurs every year (a birthday); `YYYY-MM-DD` fires the day before and the day itself, then is spent. A memory that says "tomorrow" is stored as the dated form, so an appointment does not come back every year.
|
|
62
64
|
- **Memory fades unevenly.** Recency, access count, peak and end effects, primacy, habituation and flashbulb accuracy feed one strength score. Mood is described by four components (joy / anger / sorrow / fear, summing to 1) plus arousal (intensity) and dominance (sense of control); a compound mood such as jealousy is a named mix of the four, not a new axis. Mood only reorders recall: it never enters the injected text and never rewrites a memory body. Gating is the OR of three independent conditions — the dominant share is >= 0.35, |arousal - 0.35| >= 0.1, or |dominance| >= 0.25; when none holds, both multipliers are exactly 1. Older entries carry no emotion and keep the old valence path; the four axes are never inferred back from valence. A vivid memory can be more confident than it is accurate, and the UI says so.
|
|
63
65
|
- **Provenance is a fact, not a style.** Every entry carries `source` (`user_said` / `inferred` / `imagined` / `told`), `confidence`, `accuracy`, and disclosure rules. A vague recollection cannot impersonate a quote.
|
|
@@ -99,6 +101,8 @@ For scale, the whole 4-hour session tree measured here consumed 132M input and 1
|
|
|
99
101
|
|
|
100
102
|
The sidebar has exactly **one** entry: **MemU WebUI**. The memory wall and all settings live in the same navigation, with deep links such as `#/settings/memories`, `#/settings/folders` and `#/settings/stats`. The interface follows the browser locale (Chinese or English).
|
|
101
103
|
|
|
104
|
+
**Private / global groups with multi-select.** The "Memories" page splits into **Private** and **Global** blocks (private first, default). The private block supports **multi-select**: bulk archive, restore and permanent delete (two-step confirm) — one failed row never eats the rest. Every row on the Global memory page has a "View" action that jumps straight to that entry on the wall.
|
|
105
|
+
|
|
102
106
|
**In-place work.** Clicking a memory card opens a right-side drawer with the full text and the fields that decide how it behaves — title, content, tags, disclosure, confidence, open loop — plus save, archive, restore and "open in the browser" for heavier work. Archived memories can be deleted in the same drawer; the first click only arms it and the second confirms. Global memories are read-only in the drawer and link to their own page. Filters and scroll position stay where they were. Cards that carry images use the first usable attachment as a cover, and the drawer opens the full gallery; `secret` and `never_say` entries never receive a pixel URL, so they show a label instead.
|
|
103
107
|
|
|
104
108
|
**Graph and list together.** The **Relations** tab renders the graph canvas and the adjacency list side by side (stacked on narrow screens), with the focus selector above them and link/entity/relationship tools below. There is no mode switch to forget.
|
|
@@ -107,13 +111,13 @@ The locally bundled D3 force layout preserves positions and the camera across ba
|
|
|
107
111
|
|
|
108
112
|
| Page | What it is for |
|
|
109
113
|
|---|---|
|
|
110
|
-
| Memories |
|
|
114
|
+
| Memories | Private/global grouped wall, source/status/library/preview, in-place drawer, multi-select bulk ops, Relations tab |
|
|
111
115
|
| Engine & connection | Optional MemU engine install/switch, embedding endpoint, backups and restore |
|
|
112
116
|
| Memory settings | Injection mode, token budgets, topK, relevance threshold, retention, automation |
|
|
113
117
|
| Folders | Folder-level private/global ceiling, expandable conversations and their own switches |
|
|
114
118
|
| Libraries | Create, rename, archive, restore, explicitly delete libraries |
|
|
115
119
|
| Memory browser | Full-text editing, attachments, review queue, graph and bounded canvas, JSON/ZIP import/export |
|
|
116
|
-
| Global memory | Domain-scoped global memories, evidence, review queue and the separate curator |
|
|
120
|
+
| Global memory | Domain-scoped global memories, evidence, review queue and the separate curator; every row can jump to the wall |
|
|
117
121
|
| Prompts | General / RP / Coding / Mixed variants, overrides and per-key reset; new conversations start on RP and can pick a domain in the first-run prompt |
|
|
118
122
|
| Stats | Daily activity, hit distribution, health, never-recalled shortlist, 7-day write/read ratio |
|
|
119
123
|
|
|
@@ -121,7 +125,7 @@ Settings save as you change them (250 ms debounce), with about 2.5 s cross-windo
|
|
|
121
125
|
|
|
122
126
|
### Injection token budgets
|
|
123
127
|
|
|
124
|
-
`anchorTokens` and `turnTokens` bound anchor and recall content independently (default 800 each). `injectionTotalTokens` caps the whole plugin message (default 2000); it does not replace the recall budget.
|
|
128
|
+
`anchorTokens` and `turnTokens` bound anchor and recall content independently (default 800 each). `injectionTotalTokens` caps the whole plugin message (default 2000); it does not replace the recall budget. Title cards spend far fewer tokens per entry than full text, so the same budget exposes more memories; the budget itself was deliberately not increased in 1.2.2.
|
|
125
129
|
|
|
126
130
|
### Domain Prompts
|
|
127
131
|
|
|
@@ -234,7 +238,10 @@ Folder policy is a **ceiling, not an initial value**: turning a folder switch of
|
|
|
234
238
|
| `memu_private_update` | Edit, without bypassing pin admission, review or visibility rules |
|
|
235
239
|
| `memu_private_archive` | Archive; never delete |
|
|
236
240
|
| `memu_private_unpin` | Marginalize a permanent memory with a first-person reason, quota and cooldown |
|
|
237
|
-
| `memu_private_link` | Link memories/entities with evidence, or update a relationship snapshot; cannot delete edges |
|
|
241
|
+
| `memu_private_link` | Link memories/entities with evidence, or update a relationship snapshot; also adjudicates `memu_private_link_suggest` candidates; cannot delete edges |
|
|
242
|
+
| `memu_private_link_suggest` | Run the local high-confidence linking/entity-hub pass for ids written this consolidation and return only uncertain candidate pairs |
|
|
243
|
+
| `memu_lexicon_search` | Search active private-lexicon terms; archived and tombstoned terms stay hidden |
|
|
244
|
+
| `memu_lexicon_propose` | Add or update a lexicon term; ordinary entries become active immediately, sensitive triggers remain user-only |
|
|
238
245
|
| `memu_global_recall` / `memu_global_list` | Read the global layer by domain |
|
|
239
246
|
| `memu_status` | Storage health, sync heartbeat, recall state and image-turn status |
|
|
240
247
|
|
|
@@ -345,10 +352,14 @@ The preview serves the real WebUI against isolated example data at `http://127.0
|
|
|
345
352
|
|
|
346
353
|
With the preview running, use `npm run test:graph-browser -- http://127.0.0.1:7789/dsh-memu-memory/ui#/settings/memories` in another terminal to check 200 nodes, desktop/narrow layouts, dragging, refresh stability, themes and load retries. Run `npx playwright install chromium` once, or set `GRAPH_BROWSER_CHANNEL=msedge` on Windows to use installed Edge. Stress data is intercepted only in the test browser and never written to memory. No CDN is needed at runtime; bundled D3 licenses are in `lib/webui/graph-LICENSE.txt`.
|
|
347
354
|
|
|
348
|
-
The suite includes
|
|
355
|
+
The suite includes two optional real-engine integration cases: curator writing requires `DSH_MEMU_TEST_ENGINE_PYTHON`, and the backup venv/WAL case requires `DSH_MEMU_BACKUP_REAL_ENGINE=1`; both are skipped by default. The 50 automated acceptance checks pass. The optional engine was installed and smoke-tested in an isolated directory with the pinned wheel; embedding tests use a local deterministic server, which proves the protocol, not semantic quality.
|
|
349
356
|
|
|
350
357
|
## Changelog
|
|
351
358
|
|
|
359
|
+
**1.2.2 (2026-11-01)**: title-card anchor/recall (id + title + semantic tags, body behind recall), automatic lexicon lifecycle and card management, private auto-links/entity hubs, help page v2, and WebUI layout/selector polish; together with the previously unreleased curator three-stage pipeline, global links, absorb/undo, review modes and lexicon store baseline. Prompt contracts are unified (remote curation stays tool-free, title 8–40 characters), the PTC worker mounts 5 write + 3 read tools, and no new dependencies or build steps were added.
|
|
360
|
+
|
|
361
|
+
**1.2.1 (2026-10-31)**: fact-level dedup (write-side gate + recall-side cluster folding); consolidation now works in PTC (run_code) sessions; sidebar consolidation-failure alerts (cross-session, clearable, jump-to-library); the "currently in vector store" count follows real additions and deletions; private/global groups and multi-select bulk ops on the memory wall; one-click view from the global page; graph delete cascades with orphan-entity cleanup; remote consolidation counts only what actually landed.
|
|
362
|
+
|
|
352
363
|
**1.1.3 (2026-10-30)**: managed local vectors are usable end to end (runtime/model download, switch-and-rebuild, progress with per-item failures); activity log with a Logs page; consolidation results move under the sidebar button; archive/restore and list refresh drop from seconds to milliseconds; a date written in the text is picked up when the field is empty; default topK 5/5; fixes the blank settings/WebUI panel (React #310).
|
|
353
364
|
|
|
354
365
|
**1.1.2 (2026-10-29)**: anchor and recall delivery in agent-preset conversations; startup catch-up consolidation; panel TDZ crash fix.
|
package/README.zh.md
CHANGED
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
`dsh-memu-memory` 面向长篇 RP 与陪伴对话,给 DSH
|
|
26
|
+
`dsh-memu-memory` 面向长篇 RP 与陪伴对话,给 DSH 一份「像记忆」的记忆,而不是一本日志:三种留存层会淡;首轮有锚点,之后每个用户回合最多召回一次;来源不能被悄悄从「我推断」升级成「对方说过」;互相矛盾的记忆会挂起等待裁决,而不是两条都被当成真的;几乎相同的事实只入一条、召回时同簇只占一席;关系可以是一条带证据的边。说话人、角色、世界线与剧情分支构成硬边界,角色「知道什么」与实际发生过什么分开保存。
|
|
27
27
|
|
|
28
28
|
默认**本地优先**。`agent` 后端把完整正文放在 JSON 索引里、用词法检索——不需要 Python、embedding 服务或 API key。`api` 后端才把检索交给可选的、锁定版本的 MemU 引擎(向量)。MemU 是「想要就用」的后端,不是插件能否启动的前提。
|
|
29
29
|
|
|
30
30
|
> [!WARNING]
|
|
31
31
|
> **早期版本提示。** 本插件仍处于早期阶段,可能存在无法预见的错误、bug 或不兼容变更。请先备份数据;欢迎[提交 Issue](https://github.com/Tkingxiao/dsh-memu-memory/issues) 或 PR 帮助改进。
|
|
32
32
|
|
|
33
|
-
> **状态:** 1.
|
|
33
|
+
> **状态:** 1.2.2。首个公开发布版本为 1.0.0。细节文档已迁到 [`docs/`](docs/),[`AGENTS.md`](AGENTS.md) 只做索引。升级前请备份 `~/.dsh/dsh-memu-memory`;如果要存无法重建的数据,请先读[兼容性与边界](#兼容性与边界)。
|
|
34
34
|
|
|
35
35
|
## 界面
|
|
36
36
|
|
|
@@ -99,21 +99,23 @@
|
|
|
99
99
|
|
|
100
100
|
侧边栏只留**一个**入口:**MemU WebUI**。记忆墙与全部设置在同一套导航内,支持深链,如 `#/settings/memories`、`#/settings/folders`、`#/settings/stats`。
|
|
101
101
|
|
|
102
|
+
**私有 / 全局分组与多选。** 「MemU 记忆」页按 **私有 / 全局** 分块(默认私有),私有块支持**多选**:批量归档、恢复、彻底删除(两段确认),一条失败不影响其余成果。「全局记忆」页每条记忆带「查看此记忆」,点击直达 MemU 记忆页对应条目。
|
|
103
|
+
|
|
102
104
|
**原地操作。** 点记忆卡片会从右侧滑出详情抽屉:完整正文,以及决定它行为的字段——标题、正文、标签、说出口径、置信度、未了之事——外加保存、归档、恢复,以及需要重编辑时的「在记忆浏览中打开」。已归档的记忆可以直接在抽屉里删除:第一次点击只是准备,第二次才真正删除。全局记忆在抽屉里只读,并给出跳转。筛选条件与滚动位置保持不动。带图片的卡片会把第一张可用附件作为封面,抽屉里展开完整图片组;`secret` 与 `never_say` 条目不会拿到像素 URL,只显示标签占位。
|
|
103
105
|
|
|
104
106
|
**关系图与邻接列表一起看。** 「关系」页签把关系画布与邻接列表并排显示(窄屏上下堆叠),焦点选择在上方,建边/实体/关系状态工具在下方;没有需要记得切换的模式。
|
|
105
107
|
|
|
106
|
-
图谱使用本地打包的 D3
|
|
108
|
+
图谱使用本地打包的 D3 力导向布局,后台数据刷新保留节点位置与视角。支持拖拽节点、空白处平移、滚轮/双指缩放、适应全图与关联高亮;标签自动避让,设置中可调整斥力、连线距离,以及关闭标签或动态布局。图谱布局只表达已记录的关联,不会推断或写入新的关系。实体可以直接在图谱里删除(仍被引用时会明确拒绝);永久删除一条记忆会连带清理指向它的边与孤立实体,孤立节点有启动扫描自动回收。
|
|
107
109
|
|
|
108
110
|
| 页面 | 用途 |
|
|
109
111
|
|---|---|
|
|
110
|
-
| MemU 记忆 |
|
|
112
|
+
| MemU 记忆 | 私有/全局分组记忆墙、来源/状态/库名/预览、原地详情、多选批量操作、关系统一视图 |
|
|
111
113
|
| 引擎与连接 | 可选 MemU 引擎的安装与切换、embedding 配置、备份恢复 |
|
|
112
114
|
| 记忆设置 | 注入模式、token 预算、topK、相关度下限、留存与自动化 |
|
|
113
115
|
| 文件夹 | 文件夹级私有/全局上限,展开对话修改各自选择 |
|
|
114
116
|
| 记忆库 | 新建、改名、归档、恢复、显式删除 |
|
|
115
117
|
| 记忆浏览 | 全文编辑、附件、复核池、有界图谱、JSON/ZIP 导入导出 |
|
|
116
|
-
| 全局记忆 | 分域全局记忆、证据、复核队列与独立 curator |
|
|
118
|
+
| 全局记忆 | 分域全局记忆、证据、复核队列与独立 curator,每条可跳转到记忆墙查看 |
|
|
117
119
|
| 提示词 | 通用 / RP / Coding / Mixed 变体,覆盖与单项恢复;新会话默认 RP,并可在首次弹窗里直接选领域 |
|
|
118
120
|
| 统计 | 每日活跃、命中分布、库健康度、最久没被想起的 5 条、7 天写读比 |
|
|
119
121
|
|
|
@@ -336,10 +338,14 @@ node --import ./tests/loader-register.mjs scripts/preview.mjs --port 7789
|
|
|
336
338
|
|
|
337
339
|
预览运行后,可在另一终端执行 `npm run test:graph-browser -- http://127.0.0.1:7789/dsh-memu-memory/ui#/settings/memories`,验证 200 节点、桌面/窄屏、拖拽、刷新稳定性、主题与加载重试。首次使用需执行 `npx playwright install chromium`;Windows 也可设置 `GRAPH_BROWSER_CHANNEL=msedge` 使用已安装的 Edge。压力数据仅在测试浏览器内拦截,不写入记忆库。图谱运行时不依赖 CDN,D3 许可随资源保存在 `lib/webui/graph-LICENSE.txt`。
|
|
338
340
|
|
|
339
|
-
|
|
341
|
+
测试包含两条可选真实引擎集成用例:curator 写入需设置 `DSH_MEMU_TEST_ENGINE_PYTHON`,备份 venv/WAL 需设置 `DSH_MEMU_BACKUP_REAL_ENGINE=1`,默认按设计跳过;50 条自动验收全部通过。可选引擎已在隔离目录安装并跑通锁定 wheel 的 smoke;embedding 测试使用本机确定性服务,只证明协议可用,不证明语义质量。
|
|
340
342
|
|
|
341
343
|
## 更新日志
|
|
342
344
|
|
|
345
|
+
**1.2.2(2026-11-01)**:锚点/召回改为标题卡(正文留在 recall 工具与抽屉);词典自动入库与卡片管理;私有自动关联/实体枢纽;帮助页 v2;WebUI 布局与选择器优化;并合并此前未单独发布的 curator 三段式、全局关联、absorb 撤销、审核模式与词典库基线。远程整理提示词保持无工具、标题统一 8–40 字;PTC worker 挂载 5 写 + 3 读工具;无新增依赖或构建步骤。
|
|
346
|
+
|
|
347
|
+
**1.2.1(2026-10-31)**:事实级去重(写入近似门 + 召回按簇折叠);PTC(run_code)会话整理可用;侧边栏整理失败提醒(跨会话、可清除、可跳转);「当前已入向量库」计数跟随真实增减;记忆墙私有/全局分组与多选批量操作;全局记忆一键查看;关系图删除级联与孤儿回收;整理写入计数说真话。
|
|
348
|
+
|
|
343
349
|
**1.1.3(2026-10-30)**:受管本地向量可用(运行时/模型下载、切换重建、进度与失败明细);活动日志与「日志」页;整理结果落到侧边栏按钮下方;归档/还原与列表刷新提速到毫秒级;日期漏填时从正文兜底;默认 topK 5/5;修复设置/WebUI 白屏。
|
|
344
350
|
|
|
345
351
|
**1.1.2(2026-10-29)**:人格预设会话的锚点与召回送达;启动补扫空闲整理;面板 TDZ 崩溃修复。
|
package/docs/contributing.md
CHANGED
|
@@ -20,7 +20,7 @@ node scripts/acceptance-local.mjs # 50/50;视觉证据只证明本次已审
|
|
|
20
20
|
npm run check # node --check + i18n 键对齐 + package.json 可解析
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
**结果口径**:以 `node --test` 输出的 `ℹ tests / pass / fail / skipped` 为准。用例数变了先问「是不是我加的」,失败先问「是不是并发写了同一个库」——这仓库真会多个会话同时改它。文档里出现的测试数字一律当作**带日期的快照**:2026-
|
|
23
|
+
**结果口径**:以 `node --test` 输出的 `ℹ tests / pass / fail / skipped` 为准。用例数变了先问「是不是我加的」,失败先问「是不是并发写了同一个库」——这仓库真会多个会话同时改它。文档里出现的测试数字一律当作**带日期的快照**:2026-11-01 实测 1210 tests / 1208 pass / 0 fail / 2 skipped(curator 真实引擎与备份 venv/WAL 两条为 opt-in,按设计跳过);更早的 505 / 392 / 321 / 327 / 333 等是历史快照或转述,不能当作当前保证。
|
|
24
24
|
|
|
25
25
|
### 4.3 每类改动的连带清单
|
|
26
26
|
|
|
@@ -75,9 +75,9 @@ npm run check # node --check + i18n 键对齐 + package.j
|
|
|
75
75
|
|
|
76
76
|
### 5.3 新增一个 RPC 方法
|
|
77
77
|
|
|
78
|
-
<!-- rpc-method-count:
|
|
78
|
+
<!-- rpc-method-count: 93 -->
|
|
79
79
|
|
|
80
|
-
1. `lib/rpc.js` 的 `switch` 加 case(共
|
|
80
|
+
1. `lib/rpc.js` 的 `switch` 加 case(共 93 个方法,按功能分组,别乱插)。
|
|
81
81
|
2. 返回**形状稳定**:客户端 `call()` 直接解构,列表类给 `{items, total}` 而不是裸数组。
|
|
82
82
|
3. `revisionOnly` 分支只回 `{revision, stamp}`、**不要求 sessionId**(三端轮询,设置页与独立页没会话)、**不塞业务数据**(每 2.5 秒一次);顺带 `noteMemoryBeat()`,`memu_status` 靠它分「没写」与「没人轮询」。
|
|
83
83
|
4. 写成功后 `bumpMemoryRevision()`/`notifyMemoryWrite()`(§7);能让 `state.json` 或索引落盘的写,`state.js` 自动记写事件供 `memu_status` 展示。
|
|
@@ -186,3 +186,16 @@ npm run check # node --check + i18n 键对齐 + package.j
|
|
|
186
186
|
- [ ] `npm run check` 输出 `all checks passed`?
|
|
187
187
|
- [ ] 新文案同时进了 `zh` / `en`?
|
|
188
188
|
- [ ] `README.md` 能力/限制段同步了?「会坏在哪」补进了 §3 对应段?
|
|
189
|
+
|
|
190
|
+
### 新增设置
|
|
191
|
+
|
|
192
|
+
`curatorReviewMode`(`auto`/`review-all`,默认 auto)——全局提炼审核模式;conflict 恒人审。三处注册:DEFAULT_SETTINGS / updateSettings.enums / rpc setSettings+resetSettings。
|
|
193
|
+
|
|
194
|
+
### 锁顺序不变式
|
|
195
|
+
|
|
196
|
+
插件有多个文件锁(库索引、entities、词典、整理账本、向量代际闸)。当前唯一允许的嵌套方向是
|
|
197
|
+
`entity(file) → index(file)`(`deleteEntity` 先拿 entities 锁,再在锁内清入边并写索引),以及
|
|
198
|
+
`curator(vector) → index/lexicon(file)`(`curator.js` 的 managed 写闸先于索引/词典锁)。**不要**
|
|
199
|
+
在持有索引锁时回头拿 entities 锁,也不要在词典锁内拿向量闸——AB-BA 死锁不会在单会话测试里
|
|
200
|
+
稳定复现,但会在大库并发删除/改名时出现。`pruneOrphanEntities` 在 entities 锁内只读索引,不取
|
|
201
|
+
索引写锁;新增路径若需要写索引,先释放 entities 锁或明确记录新的方向。
|
package/docs/env-and-commands.md
CHANGED
|
@@ -22,6 +22,14 @@ execFile(process.execPath, ["--check", "lib/x.js"], { cwd, env });
|
|
|
22
22
|
|
|
23
23
|
**源码是 CRLF**:任何补丁都按「读 → `split("\r\n").join("\n")` → 断言 `from` 命中 **1 次** → 替换 → 写回还原 `\r\n`」。命中 0 或 >1 次先停下看,不要盲写。(`tests/webui.test.mjs` 是 LF,别套 CRLF。)
|
|
24
24
|
|
|
25
|
+
### 9.1.1 链路回放巡检
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run replay
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`scripts/replay.mjs`:不走测试框架,按生产代码路径端到端重放七条链路(事实级去重工具层、PTC 整理成功/失败、整理提醒与日志页数据、侧边栏提醒块真实渲染、向量计数账本+戳、MemU 记忆页分组/多选/查看跳转)。只有 MemU 引擎子进程换成内存桩,其余全是真实模块;退出码 0 = 全部检查点通过。改动整理/去重/向量计数/提醒任一链路后跑一次,比单测更接近「用户点下去会发生什么」。
|
|
32
|
+
|
|
25
33
|
### 9.2 本地 link 到 DSH profile
|
|
26
34
|
|
|
27
35
|
`~/.dsh/profiles/<profile>/package.json` 里 `"dsh-memu-memory": "link:D:/Code/dsh-plugin/dsh-memu-memory"` 并加入 `dsh.profile.bundles`。是符号链接 → 改源码即刻生效(无 bundle 缓存)。`lstat` 验证是否 symlink(§3.10)。
|
package/docs/host-api.md
CHANGED
|
@@ -55,6 +55,8 @@ ctx.inject([sidebarRightTabs], ...) + ctx.slots.register({ name: sidebar.right.p
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
- 客户端当前 session:`sidebar.right.pane.tab` body 的 `sessionId` prop;客户端会话列表:`ctx.sessions.list`(`ObservableSnapshot`)。
|
|
58
|
+
- **库级授权回调(管理/standalone)**:宿主可提供 `c.connection.authorizeLibrary(req, { libraryId, operation })`。插件只接受严格的 `true`;缺失、`false`、非 `true` 或抛错都按 403 处理。嵌入式请求必须携带 `sessionId`,服务端用持久绑定的 `getSessionBinding(sessionId).libraryId` 与目标 `libraryId` 精确比较;显式 `libraryId`、URL、query 或客户端 token 都不是凭据。`operation` 目前为 `read`、`write`、`archive`、`delete`、`rename`、`restore`、`import`、`export`、`attachment.read`、`review.manage`。HTTP handler 先调用 `requestRejection(req)`;ZIP import 在迭代 request body / `importMediaStream` 前完成库授权。
|
|
59
|
+
- `listReviews`/`resolveReview` 是双作用域端点。带私有 `libraryId` 时遵循同一 session/callback 约束;无 `libraryId`(全局审核队列)时:嵌入式请求(携带 `sessionId`)可读队列,但 `resolveReview` 写操作要求该 `sessionId` 存在且绑定仍然有效(空/悬空 sessionId 一律 403);无 `sessionId` 的 standalone/管理请求必须通过 `authorizeLibrary(req, { libraryId: null, operation: 'review.manage' })`,宿主未实现该 operation 时 fail-closed(403)。`browseWall` 必须明确单库,不能无目标枚举私有库。
|
|
58
60
|
- **注入折叠是宿主自带的,不要自己做。** `dsh-client-ui-chat` 用 `ContextInjectionRow`(一个 `DisclosureRow`)渲染所有带 `source.kind` 的消息:折叠标题 `message.contextInjection`(跨会话召回为 `message.contextRecall`)。宿主认识的 `KNOWN_FORMS = instructions | catalog | snapshot | notice | relay | recall`;表外的 form 走 opaque,仍会折叠。
|
|
59
61
|
- producer label 就是 **kind 原文**(如 `plugin:dsh-memu-memory`),不可自定义。`form: recall` 只有在 `kind: session-reference` + `references[].label` 时才走专门的召回正文;本插件用 `recall` 只是拿一个折叠行,要一行可读的折叠摘要就用 `form: notice` + `source.summary`。
|
|
60
62
|
|
|
@@ -66,7 +68,7 @@ ctx.inject([sidebarRightTabs], ...) + ctx.slots.register({ name: sidebar.right.p
|
|
|
66
68
|
|
|
67
69
|
#### 已证实:标准会话
|
|
68
70
|
|
|
69
|
-
- 标准会话 `session-<redacted-standard-a>`(`agentPreset: "standard"`,21 个非 session 事件):模型请求头 `request/header` 的工具表为 **82 个工具**,首尾两次一致;其中 `memu_*` 共 **10
|
|
71
|
+
- 标准会话 `session-<redacted-standard-a>`(`agentPreset: "standard"`,21 个非 session 事件):模型请求头 `request/header` 的工具表为 **82 个工具**,首尾两次一致;其中 `memu_*` 共 **10 个**(1.1.2 历史快照;1.2.2 当前为 13 个,新增 `memu_lexicon_search`、`memu_lexicon_propose` 与 `memu_private_link_suggest`):`memu_private_list`、`memu_private_recall`、`memu_private_remember`、`memu_private_update`、`memu_private_archive`、`memu_private_link`、`memu_private_unpin`、`memu_global_list`、`memu_global_recall`、`memu_status`。同一会话中 `===== memory anchor =====` 出现 1 次,锚点内是真实私有 id。
|
|
70
72
|
- 标准会话 `session-<redacted-standard-b>`(`agentPreset: "standard"`,本轮扫描时 1737 个非 session 事件):`<memory-guide>` 只对应 **1 个** plugin 消息 id(`<redacted-plugin-message-id>`),锚点 1 次。该 id 在多个快照/工具记录里重复出现是日志回放,不是重复投放。
|
|
71
73
|
- 因此“标准会话的工具表里有 `memu_*`”“标准会话 anchor/guide 只投放一次”是**已证实**。注意这是 2026-10-29 的历史快照:当前版本标准和预设都走 `<memory-guide rules="…">` 插件消息,system prompt 不再有本插件段。
|
|
72
74
|
|
package/docs/memory-semantics.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
### 8.2 私有记忆的写入规则
|
|
18
18
|
|
|
19
19
|
- 主 agent 自己写;**不做「你是她的记忆」的二次人格**。`must_remember` **满了也不许为腾名额而 unpin**。
|
|
20
|
-
- 移出永久记忆 = **边缘化**:`marginalized=true`、`strength × 0.2
|
|
20
|
+
- 移出永久记忆 = **边缘化**:`marginalized=true`、`strength × 0.2`、召回排序里 `marginalized` 乘以 0.35(`recallPenalty` 字段仅作历史记录,当前不参与排序),**不删除**;条目仍在库里,`marginalReason` 在侧边栏可见(替代人类闸门的**事后**监督)。
|
|
21
21
|
- **unpin 由模型自己判断,侧边栏没有标记按钮、用户不参与。** 三重护栏:① `reason` 必填、第一人称、去空白后 ≥ 20 字符;② 每会话配额 `unpinBudgetPerSession`(默认 2);③ 冷却 `unpinCooldownMinutes`(默认 3)。放行后记 `unpinnedBy: model`、`unpinnedAt`、`originalPinSource`(保留原 pinSource 以便复 pin)。
|
|
22
22
|
- 正当的 unpin 只有两种:想起它本身正在伤人;它已经不再是我的一部分。不正当的:inconvenient、不想面对、用户这轮没提、永久记忆快满了。**这个区别要写进提示词。**
|
|
23
23
|
- 矛盾检测:同库同题改写或说法相反 → 新条挂 `pending_review` + `reviewReason` + `conflictingWith`,**不进注入不进召回**,等 `listReviews` / `resolveReview` 裁决(§3.9)。
|
|
@@ -26,12 +26,22 @@
|
|
|
26
26
|
|
|
27
27
|
只有 curator 写,主 agent 只读。domain:`universal / coding / rp / project:<name> / tooling / personal`,**RP 与 coding 冲突分域存,不合并**。RP 剧情事实、关系进度、临时情绪**不写全局**;每条必须有 `evidence` + `confidence`。
|
|
28
28
|
|
|
29
|
+
**审核模式(`curatorReviewMode`)**:`auto`(默认)= 有落地证据的提炼结果自动通过;`review-all` = 提炼产物一律进待复核。`conflict`(说法矛盾)与无证据的条目在任何模式下都进人审;rewrite 类操作(merge/supersede/absorb)在 auto 下风险 ≥0.70 也暂扣。每条都带 `reviewRisk/reviewRiskReasons` 供审计与复核排序。
|
|
30
|
+
|
|
31
|
+
**证据落地(grounding)**:evidence 的 quote 必须是本次策划所含会话转录的规范化子串(≥6 字,省略号分段校验,先脱敏)——模型编造的"原话"进不了库;未落地恒 pending 或不落库。
|
|
32
|
+
|
|
33
|
+
**全局关联(link)**:curator 可给两条全局记忆连边(`related/refines/supports`),note 必须含转录逐字引用;每端 ≤20 条、有向边防环、移除只删边不删记忆。前端只读展示,不支持手动全局关联。
|
|
34
|
+
|
|
35
|
+
**集合记忆(absorb)**:多条碎片合成一条集合记忆——合并稿 active、target superseded(absorbedInto)、sources archived(absorbedBy,**不删除**,可还原);`absorbSnapshot` 支持一键撤销合并(人工改动过的条目拒绝撤销);簇外重复 ≥0.90 的合并稿转人审。
|
|
36
|
+
|
|
29
37
|
### 8.4 注入
|
|
30
38
|
|
|
31
39
|
- 静态 guide 与动态记忆都走 plugin 消息;当前版本不再注册本插件的 system prompt 段。所有会话(含 `persona` `complete: true`)统一由 `lib/inject.js` 的 `ensureUnifiedGuide` 在**回合首步**投放一条 `<memory-guide rules="…">` 插件消息;`rulesKey` 由协议版本、domain 和实际渲染正文计算,domain/覆盖/默认稿变化或压缩丢失(`guideLost`)时在下一个合法首步替换,同一规则不逐回合重复。旧 binding 只有 `guideMessageAt`、没有 `guideRulesKey` 时视为旧版,补投一次;失败 fail-open,不写新版本。
|
|
32
40
|
- 首轮锚点 + 每用户回合最多一次;长工具回合仅有新的真实人类消息批次时追加一次,普通工具步骤不触发。私有优先、全局补充;冲突时私有胜。
|
|
41
|
+
- **锚点和本回合召回只放标题卡**(1.2.2):`formatPrivateTitle` / `formatGlobalTitle` 输出 `[id]`、标题、`source`、owner、layer、日期/年龄、marginalized/open_loop、accuracy/uncertainty/disclosure 与附件计数,**不输出正文与附件 caption**。工具返回、记忆抽屉与浏览器仍是全文;要正文就按标题关键词或方括号里的 id 调 `memu_private_recall` / `memu_global_recall`。标题为空时回退正文前 40 字;`imagined`、`never_say` 等语义标签不能因为标题化而丢失。
|
|
42
|
+
- **rule 是前言,不是页脚**:`buildAnchor` 先输出锚点头与 `[rule]`,再输出 private/global/short_term 各段;inbound 路径的锚点之后直接衔接 `<memory-context>`,中间不再隔一条规则。
|
|
33
43
|
- **`injectedIds` 只能记真正进入文本的条目**,否则从未展示的记忆被白白压制 8 轮。
|
|
34
|
-
- 锚点分段预算 pin 40% / core 30% / global 30%;pin 装不下时末尾明示「另有 N 条未展开」,**不静默丢弃**。
|
|
44
|
+
- 锚点分段预算 pin 40% / core 30% / global 30%;pin 装不下时末尾明示「另有 N 条未展开」,**不静默丢弃**。1.2.2 移除了 core≤5 / global≤8 的预先切片:标题卡比正文短,条数应由预算打包器决定,而不是固定上限;`anchorTokens` / `turnTokens` / `injectionTotalTokens` 默认值不变,inbound 的 `floor(turnBudget/2)` 保留。
|
|
35
45
|
- 预算单位是**模型 token**不是字符:中文 ≈1 字符/token、英文 ≈3.4,一律走 `lib/budget.js`,**不要再用 `tokens * 3`**(英文口径,中文超支约 3 倍)。
|
|
36
46
|
- 压缩后下一条用户消息重注入锚点;子代理不注入。文件夹两开关都关 → 不注入(看 clamp 后生效值,不是 binding 原始值)。召回为空必须留信号(§1.10)。
|
|
37
47
|
- **召回排序的准确口径**:`privateWeighted` 只服务**私有召回流水线**(`privateRecall` 调用它);全局召回(`globalRecall`)走 `localRecall`,不经过它。cue 会经 `cueRecall` **间接**调用 `privateRecall`,所以**不能**概括成「cue 不走私有排序」。排序里的心境项默认关闭——`privateWeighted` 自己的缺省上下文就是 `moodEnabled: false`,真正打开它的是 `privateRecall`,条件是 `|valence| ≥ MOOD_MIN_STRENGTH` 或 `|arousal − BASE_AROUSAL| ≥ 0.1`(`BASE_AROUSAL` 来自 `lib/mood.js`);平缓对话里两项乘数恒为 1。`privateRecall` 的 topK 兜底是「传入 topK → `settings.privateTopK`(默认 5)→ 4」:引用 3 或 4 时必须说清是**设置默认值**还是**代码兜底值**,两个数不是同一个。
|
|
@@ -47,6 +57,7 @@
|
|
|
47
57
|
- **职责分工**:`system-guide.*` 是唯一常驻规则,只写领域行为、检索时机、回答口径;`private-policy.*` 保留为字段规约参考,不再投放,避免两套常驻文本互相漂移。
|
|
48
58
|
- **只有三个 key 分域**:`system-guide`、`private-policy`、`private-consolidation`;`global-curator` 不分域。
|
|
49
59
|
- **远程与本地共用一份 `private-consolidation`**:正文讲「记什么、字段怎么填」,差异只有输出协议(`CONSOLIDATION_CONTRACTS.local` 调工具 / `.remote` 返回 JSON,代码追加)。不要拆第二份。
|
|
60
|
+
- **1.2.2 起整理稿负责标题质量**:title 按「只看标题就知道具体事实」写(8–40 字、具体关键词、集合稿覆盖全部来源),`global-curator` 同一口径。词典/关联的工具调用属于**本地工作流**:领域整理稿与远程 JSON 路径共用,因此不写 `memu_*` 工具名;领域稿只保留「可抽什么词」的差异,稳定复用的黑话/梗/术语先 `memu_lexicon_search` 再 `memu_lexicon_propose`、写完用 `memu_private_link_suggest` 裁决候选的工具流程只出现在 local worker brief。远程契约明确「没有工具」,title 长度同样是 8–40 字。
|
|
50
61
|
- RP 与 coding 是**内容**差异:coding 记事实(决定、约束、踩过的坑、工程偏好),召回当「可能过期的事实」,与代码冲突时以代码为准;rp 记经验(具体瞬间、半句原话、身体感觉、没说出口的话),允许模糊记错,不确定就标低 `confidence`。
|
|
51
62
|
- **KV cache**:section 文本随 domain 变,但同一会话内恒定,缓存不受影响;中途改 domain 会让该会话失效一次缓存。别在文档里写「文本恒定」。
|
|
52
63
|
|
|
@@ -133,5 +144,13 @@
|
|
|
133
144
|
|
|
134
145
|
回归与管线测试:`tests/hybrid-recall.test.mjs` 仅保留 RRF、去重、稳定排序、资格过滤与候选上限等通用融合 helper 覆盖;managed vector 行为由 `tests/managed-memory.test.mjs` 覆盖。
|
|
135
146
|
|
|
147
|
+
### 8.13 标题卡、词典生命周期与私有自动关联(1.2.2)
|
|
148
|
+
|
|
149
|
+
**标题卡与标题写作。** 锚点/召回的可见单位从正文改为标题卡,标题因此成为索引而不是装饰:提示词要求 8–40 字、含具体人名/物名/事件/结论关键词,禁止「一些事」「那段对话」「关于 XX 的事」;合并/吸收稿的标题要覆盖全部来源关键词。Lexicon 的注入行仍保留「term + ≤80 字短义」(不是裸词),因为词典的唯一价值是解释;UI 卡片也是 term 标题 + meaning 正文。
|
|
150
|
+
|
|
151
|
+
**词典生命周期。** 公开状态只有 `active` / `archived`:用户新建、模型 `memu_lexicon_propose`、curator 带证据抽取都直接 active;`archiveLexiconTerm` / `restoreLexiconTerm` 负责可逆退场,删除只属于管理面。`rejected` 只作为 legacy 墓碑/指纹留在数据里,UI、注入、检索与计数都看不到;`pending` 只作为旧版本迁移输入:新提案 → active(敏感 → archived),revision 草稿 → archived。敏感 trigger 的新建/修改只能由用户完成,模型/curator 得到 typed 拒绝;非用户修改普通 active 词条的敏感字段也直接拒绝,不再生成 pending revision。RPC 的 `lexiconReview` 已删除,方法数 93。
|
|
152
|
+
|
|
153
|
+
**私有自动关联。** 整理结算后 `lib/private-links.js` 只做本地算法:同库、同 context/track、可见非 secret、标题共享稀有 name-like token、pairScore < 0.82 且无冲突时,建立 memory↔memory `relation` 并写 `source:'auto'`;≥2 条记忆共享该 token 时复用/创建实体枢纽(人物信号 → `person`,无信号的拉丁名保守 → `thing`,CJK 无信号或 kind 冲突 → 不确定候选)。高置信写库在算法侧完成,不确定候选最多 8 对交给本次整理 worker 用 `memu_private_link_suggest` / `memu_private_link` 带证据裁决;不另起整理、不重读转录。算法/写入失败只记 warn,不能让已经成功的整理失败;自动关联不改正文、不写向量、不删除,边可在关系图移除。worker 写工具为 remember/update/link/link_suggest/lexicon_propose,读工具为 recall/list/lexicon_search,但「有没有写能力」仍只由 remember/update 判定。
|
|
154
|
+
|
|
136
155
|
---
|
|
137
156
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -205,8 +205,9 @@ S8 展示回写 client.js 面板 + useMemoryBeat 心跳 + rpc.js allowed + tool
|
|
|
205
205
|
- **新回合被评估为「不值得整理」。** `assessConsolidationValue` 只拒绝两种:`lastSleepSeq` 之后没有任何人类消息;或只有一条纯应答(「谢谢」「好的」「ok」,无工具调用)。其余都会进 fork,由整理提示词决定写不写——**写 0 条是合法结果**,不是故障。
|
|
206
206
|
- **整理失败一直退避。** 看 binding 的 `lastSleepError` / `sleepRetryAt` / `sleepAttempts`。会话被删会得到 `session "…" not found`,现在会标 `sessionGone: true` 并停止重试(以前会每小时重来)。
|
|
207
207
|
- **fork 失败被当成成功。** 旧版只等 `whenIdle()`;turn error 也会 resolve,于是 `lastSleepSeq` 被推进。现在 fork 必须继承父 `provider` / `model`(活跃会话取最近一轮 `requestHeader().config`,关闭会话取最后一条 `request/header` 的 `data.header.config`);没有路由就拒绝创建,报 `cannot inherit the parent model/provider`。`whenIdle` 后还要检查 `turn/end.data.reason.kind === 'completed'`,`error` / `aborted` / 没有 `turn/end` 都返回 `ok:false`,不推进游标,由 `runSleepPass` 记 `lastSleepError`、`sleepRetryAt`、`sleepAttempts` 退避重试。
|
|
208
|
+
- **PTC 会话(`run_code` 预设)曾被一刀切拒绝(2026-10-31 已修)。** 旧 probe 看到 header 工具表里没有 `memu_private_remember/update` 就判 `worker-tools-ptc-only` 不建 fork——但 PTC 模式下 header 本来就只有 `run_code`,其余工具走 run_code 程序里的 SDK 绑定,header 名单不承载"能不能写"。现在的口径:PTC 不构成否证(probe 返回 `source: 'ptc-header'`),fork 创建后把 worker 的 5 个写工具(remember/update/link/link_suggest/lexicon_propose)与 3 个读工具(recall/list/lexicon_search)挂进 worker 自己的 agent ctx scope(注册表按 scope 链取可见面,只对这个一次性 worker 可见,整理结束即卸载);任务书写明 `await tools.memu_private_remember({...})` 的调用形态。写能力探测仍只认 remember/update,不会被 link/lexicon 工具误判。宿主不暴露 agent ctx(记录上没有 `ctx.tools`)时挂载失败只 warn,worker 会按任务书如实回执 `tools-unavailable`,空闲扫描照常退避。证据看活动日志"整理已开始"行的 `detail.probe` 与 `detail.workerToolsAttached`。
|
|
208
209
|
|
|
209
|
-
**守卫**:`tests/cycle.test.mjs`(空闲阈值、启动补偿、`sessionGone` 只标记一次、寒暄不 fork、失败 fork 必须退避)+ `tests/consolidation-value.test.mjs`(评估边界:插件通知不算人类消息、纯应答跳过、「我不吃香菜」这种五个字的偏好必须留下、`lastSleepSeq` 之后的尾巴才算)+ `tests/consolidation-fork.test.mjs`(继承父模型、失败 turn 不推进 `lastSleepSeq
|
|
210
|
+
**守卫**:`tests/cycle.test.mjs`(空闲阈值、启动补偿、`sessionGone` 只标记一次、寒暄不 fork、失败 fork 必须退避)+ `tests/consolidation-value.test.mjs`(评估边界:插件通知不算人类消息、纯应答跳过、「我不吃香菜」这种五个字的偏好必须留下、`lastSleepSeq` 之后的尾巴才算)+ `tests/consolidation-fork.test.mjs`(继承父模型、失败 turn 不推进 `lastSleepSeq`、PTC 会话照常 fork 且只挂 worker 读写工具、非 PTC 名单缺写工具仍然拒绝)。手工核对:`state.json` 里 `lastSleepAt` / `lastSleepSeq` / `sessionGone` / `sleepAttempts` / `lastSleepError` / `sleepRetryAt` 六个字段。
|
|
210
211
|
|
|
211
212
|
### 3.19 人格预设(agent preset)的会话里完全没有记忆(S1 绑定 → S4 注入)
|
|
212
213
|
|
|
@@ -281,7 +282,7 @@ S8 展示回写 client.js 面板 + useMemoryBeat 心跳 + rpc.js allowed + tool
|
|
|
281
282
|
|
|
282
283
|
### 2.1 模块地图
|
|
283
284
|
|
|
284
|
-
<!-- rpc-method-count:
|
|
285
|
+
<!-- rpc-method-count: 93 -->
|
|
285
286
|
|
|
286
287
|
| 文件 | 职责 / 什么时候碰它 |
|
|
287
288
|
|---|---|
|
|
@@ -295,7 +296,7 @@ S8 展示回写 client.js 面板 + useMemoryBeat 心跳 + rpc.js allowed + tool
|
|
|
295
296
|
| `lib/private.js` | 私有整理:当前对话、历史 fork、notice 消息 |
|
|
296
297
|
| `lib/curator.js` / `lib/cycle.js` | 全局 curator 与 `pending_review`;空闲整理 pass(过期/降级/晋升/自动整理) |
|
|
297
298
|
| `lib/mood.js` / `lib/budget.js` / `lib/snippet.js` | 情绪价与峰值;token 换算(中文≈1 字符/token、英文≈3.4,任何 token 都走它);注入片段裁剪 |
|
|
298
|
-
| `lib/rpc.js` / `lib/client.js` / `lib/webui.js` + `lib/webui/standalone.js` | `/dsh-memu-memory` 的
|
|
299
|
+
| `lib/rpc.js` / `lib/client.js` / `lib/webui.js` + `lib/webui/standalone.js` | `/dsh-memu-memory` 的 93 个方法(旧路由别名同一鉴权);九页设置 + 侧边栏 `MemoryTab` + `useMemoryBeat`;独立壳挂载同一 SettingsPage,不复制实现 |
|
|
299
300
|
| `lib/graph.js` / `lib/context.js` / `lib/media.js` | 关系边与实体;说话人/频道/角色/世界线/知情边界;附件采集、缩略图、受保护路由、ZIP |
|
|
300
301
|
| `lib/prompts.js` + `lib/prompts/` | 提示词加载/覆盖/恢复/domain 变体解析 |
|
|
301
302
|
| `lib/engine.js` / `lib/memu.js` / `lib/memu-config.js` | Python 引擎安装探测切换回滚;MemU CLI 适配(**所有 CLI 字符串只在这里**);`~/.memu/config.env` 的 embedding |
|
|
@@ -393,3 +394,41 @@ section 的 `text(context)` 能拿到 `context.agent`,所以 system prompt **
|
|
|
393
394
|
10. **注入失败 fail-open,但必须留可观测信号。** 抛错不许改变宿主的 turn 错误语义(吞掉是允许的),代价是真实故障一起被吞:`inject.js` 若把 pre-step 的 `agent` 当 `exec` 传给 `buildRecall`(正确形状 `{ agent }`,§6.3),`no bound session` 就只被空 catch 吃掉——**每回合召回静默失效、只剩锚点、日志一个字都没有**。所以 fail-open 必配 `logger.warn` + binding 上的 `lastRecallError`(侧边栏可见),单测断言**召回到的 id**,不能只断言「消息插进去了」。**不能靠「没报错就是好了」。**
|
|
394
395
|
|
|
395
396
|
---
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
## WebUI 403 错误族(2026-11-01)
|
|
400
|
+
|
|
401
|
+
三个长得不一样、根因同源的错误,都发生在 standalone WebUI 或侧边栏向插件 HTTP 端点发请求时:
|
|
402
|
+
|
|
403
|
+
| 报错 | 阶段 | 含义 |
|
|
404
|
+
|---|---|---|
|
|
405
|
+
| `Unexpected token 'o', "forbidden" is not valid JSON` | standalone fetch 拿到 403 裸文本后 `response.json()` 崩溃 | 1.2.2 之前的 403 响应体是裸文本;已改 JSON 信封 |
|
|
406
|
+
| `Invalid RPC response: 403` | 信封解析成功但无 `result`,客户端只报状态码不报原因 | 已透传服务端 `error.message`,不再只报状态码 |
|
|
407
|
+
| 请求返回 JSON 信封 `dsh-memu-memory/forbidden` | 库级路由授权拒绝(当前态) | 见下方授权模型 |
|
|
408
|
+
|
|
409
|
+
**库级授权模型(`authorizeLibraryRoute`)**:
|
|
410
|
+
- 带 `sessionId` 的请求:持久绑定必须与目标 `libraryId` 完全一致——session A 不能操作库 B;带 sessionId 但解析不出库(空/悬空/非字符串)一律 403(坏凭据 ≠ 无凭据)。
|
|
411
|
+
- 不带 sessionId(standalone 管理面):宿主实现了 `connection.authorizeLibrary(req, { libraryId, operation })` 时严格 `=== true` 才放行(fail-closed);**未实现时回退放行**(HTTP 边界的 requestRejection 仍是门)——fail-closed 会在真实宿主上把 standalone WebUI 全部 403(本条目的来源)。
|
|
412
|
+
- 无 libraryId 的 `browseWall`("全部记忆库"视图)走同一回退模型;实现了 capability 的宿主可拒绝多库枚举。
|
|
413
|
+
- 全局复核队列:嵌入式会话可读、写需活绑定;standalone 需要 `review.manage` grant(capability 缺席时同样回退放行)。
|
|
414
|
+
- 授权拒绝的 403 是 JSON 信封:`{"ok":false,"error":{"code":"dsh-memu-memory/forbidden",...}}`。
|
|
415
|
+
|
|
416
|
+
## 提炼与词典排错(1.2.2)
|
|
417
|
+
|
|
418
|
+
- **absorb 一直停在待复核**:三种原因——`curatorReviewMode=review-all`、覆盖率不足(sourceCoverage<0.45 或 unionCoverage<0.60)、簇外重复 ≥0.90。复核队列接受前会校验 target/sources 状态与 updatedAt;并发变化会生成 held pending clone(原提案保留、已提交向量行补偿删除,活动日志可见)。
|
|
419
|
+
- **全局 link 没出现**:link 需要落地证据(note 含转录逐字引用),无证据不落边(活动日志 `link rejected`);每端 20 条上限、跨域只允许 related、rp↔coding 禁止;有向边防环 ≤3。
|
|
420
|
+
- **撤销合并失败**:撤销要求合并稿仍 active 且有 `absorbSnapshot`;条目被人工改动过(updatedAt > absorbedAt)会拒绝并给出可读错误——先人工恢复到吸收时状态,或放弃撤销。
|
|
421
|
+
- **词典词条没注入**:1.2.2 起见下节「标题卡、词典自动与私有自动关联排错」;旧版本的 pending 词条会在迁移后转 active/archived,不再需要人审。
|
|
422
|
+
- **词典提案被拒**:rejected fingerprint 只作为 legacy 墓碑拦截同规范词的重复提案(防每轮重放);新版本不再由人审产生 rejected。
|
|
423
|
+
|
|
424
|
+
## 标题卡、词典自动与私有自动关联排错(1.2.2)
|
|
425
|
+
|
|
426
|
+
- **锚点里为什么只有标题、没有正文**:这是 1.2.2 的设计,不是截断。锚点与本回合召回只放 `[id] + 标题 + 语义标签`,正文在记忆抽屉与 `memu_private_recall` / `memu_global_recall` 工具里。模型需要原文时按标题关键词或方括号里的 id 调工具。标题为空会回退正文前 40 字;如果连回退都没有,才显示 `(untitled)`。
|
|
427
|
+
- **同样预算下锚点条数变了**:core≤5 / global≤8 的固定切片已删除,条数由 pin 40% / core 30% / global 30% 的打包预算决定;标题卡比正文短,所以可见条数增加,但 `anchorTokens` 没有被调大。预算仍以 `lib/budget.js` 的模型 token 口径计算,inbound 路径仍减半。
|
|
428
|
+
- **词典词条没注入**:按顺序查——是否 `archived`(不注入、不检索,可恢复);是否 `secret`(不进锚点摘要,但用户点名时 B 通道可解释);context/knownBy 是否匹配;是否在 30 分钟冷却内;是否被锚点摘要 top8/敏感词单列的名额挤掉。词典没有 pending 人审状态;新词条直接 active。
|
|
429
|
+
- **模型说写了词典但没生效**:`memu_lexicon_propose` 命中 archived 词条会返回 notOwned(不复活);修改敏感字段会被 typed 拒绝;sensitive trigger 只能由用户在词典页管理。检查活动日志/工具回执,不要假设 pending。
|
|
430
|
+
- **旧库里的 pending 词条去哪了**:周期巡检(`runVisibilityMigrationPass` → `migrateLexiconLibraries`)把 pending 新提案转 active、敏感转 archived、pending revision 转 archived;rejected 保留为内部墓碑。迁移只写有变化的文件;损坏的词典文件不会被当空库覆盖,报 `LEXICON_CORRUPT` 并保持只读。
|
|
431
|
+
- **私有自动关联没出现**:算法宁漏勿错。检查两条记忆是否同库、同 context/track、都可见且非 secret;标题是否共享稀有 name-like token(英文 ≥3 字母,或低频 CJK 名称;通用词被停用词表挡掉);pairScore 是否 ≥0.82(重复应走 update/合并)、是否有冲突、是否已有同向 link;每轮还有 2 条/entry、5 条/轮、2 个实体/轮的上限。边上的「自动」Tag 与 `source:'auto'` 是算法写的,可在关系图移除。
|
|
432
|
+
- **实体枢纽名称重复(Yuike / yuike)**:算法按 NFKC/大小写/空白/标点归一化复用实体;`memu_private_link` 的 `entity` 参数也按同一规则复用同名实体。若已存在但 kind 冲突,会拒绝并提示改用既有 kind,而不是静默建第二个。
|
|
433
|
+
- **worker 没裁决不确定候选**:`memu_private_link_suggest` 只返回候选卡,不自动连;worker 未调用或证据不足就保持不连(宁漏勿错)。`lib/private.js` 的结算兜底只重跑高置信算法,不会把不确定候选偷偷连上。自动关联失败只写 warn,不影响整理成功。
|
|
434
|
+
- **要不要数据库/向量重算**:自动关联只是 `links[]` 元数据,不改正文、不写向量;移除边不会触发重建。实体删除仍按既有规则(有引用时需显式 force,避免悬空)。
|
package/lib/activity-log.js
CHANGED
|
@@ -161,13 +161,30 @@ export function logStats() {
|
|
|
161
161
|
return { total: entries.length, bytes, counts, file: activityFile(), capped: MAX_ENTRIES, maxBytes: MAX_FILE_BYTES }
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
/**
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
/**
|
|
165
|
+
* 清空日志。无参 = 全部清掉(显式操作,不是自动裁剪);带过滤 = 只删匹配行
|
|
166
|
+
* (整理失败提醒的"清除失败记录"用:{ kind: 'consolidate', ok: false })。
|
|
167
|
+
* 返回删除条数。永不抛错。
|
|
168
|
+
*/
|
|
169
|
+
export function clearEvents(filter = null) {
|
|
170
|
+
const entries = load()
|
|
171
|
+
const matches = (entry) => {
|
|
172
|
+
if (!filter) return true
|
|
173
|
+
if (filter.kind && entry.kind !== filter.kind) return false
|
|
174
|
+
if (typeof filter.ok === 'boolean' && entry.ok !== filter.ok) return false
|
|
175
|
+
return true
|
|
176
|
+
}
|
|
177
|
+
const kept = filter ? entries.filter(entry => !matches(entry)) : []
|
|
178
|
+
let removed = entries.length - kept.length
|
|
179
|
+
try {
|
|
180
|
+
rewrite(kept)
|
|
181
|
+
cache = kept
|
|
182
|
+
} catch (error) {
|
|
183
|
+
// 落盘失败就不动缓存:文件是权威,下次读盘仍是原样,别让内存和磁盘各说各话。
|
|
184
|
+
removed = 0
|
|
168
185
|
try { warn('activity log could not be cleared: ' + (error?.message || error)) } catch {}
|
|
169
186
|
}
|
|
170
|
-
return { ok: true }
|
|
187
|
+
return { ok: true, removed }
|
|
171
188
|
}
|
|
172
189
|
|
|
173
190
|
/** 导出为可直接贴进 issue 的 JSONL 文本。 */
|
package/lib/backup.js
CHANGED
|
@@ -59,6 +59,7 @@ const leaseFile = () => path.join(backupsDir(), '.leases.json')
|
|
|
59
59
|
const lockAnchor = () => path.join(backupsDir(), '.backup')
|
|
60
60
|
const stateFile = () => path.join(dataDir(), 'state.json')
|
|
61
61
|
const globalDbFile = () => path.join(dataDir(), 'global.sqlite3')
|
|
62
|
+
const lexiconRoot = () => path.join(dataDir(), 'lexicon')
|
|
62
63
|
const vectorActiveFile = () => path.join(indexDir(), 'vector-active.json')
|
|
63
64
|
const engineCurrentFile = () => path.join(dataDir(), 'engine', 'current.json')
|
|
64
65
|
|
|
@@ -505,8 +506,9 @@ function engineSummary() {
|
|
|
505
506
|
|
|
506
507
|
/**
|
|
507
508
|
* Copies every store a restore needs: plugin state, the global index and the
|
|
508
|
-
* per-library index files, the
|
|
509
|
-
* and their manifest, attachment references,
|
|
509
|
+
* per-library index files, the per-library lexicon files, the active vector
|
|
510
|
+
* profile journal, prompt overrides and their manifest, attachment references,
|
|
511
|
+
* and every MemU SQLite database.
|
|
510
512
|
* Deliberately absent: engine/, downloads/, logs/, tmp/ (large and
|
|
511
513
|
* re-downloadable) and any key material - the manifest carries identity, never
|
|
512
514
|
* a credential.
|
|
@@ -519,9 +521,21 @@ function writeStores(staging, exec) {
|
|
|
519
521
|
ensureDir(path.join(staging, 'index'))
|
|
520
522
|
ensureDir(path.join(staging, 'prompts'))
|
|
521
523
|
copyRegular(stateFile(), path.join(staging, 'state.json'))
|
|
524
|
+
// Consolidation ledger: per-session ranges/operations. It now participates in
|
|
525
|
+
// backup/restore so a restored install does not re-fork work it already did.
|
|
526
|
+
copyRegular(path.join(dataDir(), 'consolidation.json'), path.join(staging, 'consolidation.json'))
|
|
522
527
|
for (const name of readdirSafe(indexDir())) {
|
|
523
528
|
if (name.endsWith('.json')) copyRegular(path.join(indexDir(), name), path.join(staging, 'index', name))
|
|
524
529
|
}
|
|
530
|
+
// 词典:data/lexicon/<libraryId>.json 与索引同权进备份清单。只收安全 id
|
|
531
|
+
// 命名的 JSON(restore 侧 allowlist 用同一规则拒绝越界);目录还不存在(这个
|
|
532
|
+
// 数据根从未写过词典)就是空集。
|
|
533
|
+
ensureDir(path.join(staging, 'lexicon'))
|
|
534
|
+
for (const name of readdirSafe(lexiconRoot())) {
|
|
535
|
+
if (!name.endsWith('.json')) continue
|
|
536
|
+
if (!isSafeId(name.slice(0, -'.json'.length))) continue
|
|
537
|
+
copyRegular(path.join(lexiconRoot(), name), path.join(staging, 'lexicon', name))
|
|
538
|
+
}
|
|
525
539
|
copyRegular(path.join(dataDir(), 'prompts', 'manifest.json'), path.join(staging, 'prompts', 'manifest.json'))
|
|
526
540
|
copyTree(path.join(dataDir(), 'prompts'), path.join(staging, 'prompts'))
|
|
527
541
|
copyTree(path.join(dataDir(), 'attachments'), path.join(staging, 'attachments'))
|
|
@@ -903,6 +917,7 @@ function liveTargetFor(rel) {
|
|
|
903
917
|
const head = parts[0]
|
|
904
918
|
if (parts.length === 1) {
|
|
905
919
|
if (head === 'state.json') return stateFile()
|
|
920
|
+
if (head === 'consolidation.json') return path.join(dataDir(), 'consolidation.json')
|
|
906
921
|
if (head === 'global.sqlite3') return globalDbFile()
|
|
907
922
|
if (head === 'global.sqlite3-wal') return globalDbFile() + '-wal'
|
|
908
923
|
if (head === 'global.sqlite3-shm') return globalDbFile() + '-shm'
|
|
@@ -910,6 +925,13 @@ function liveTargetFor(rel) {
|
|
|
910
925
|
if ((head === 'index' || head === 'libraries') && parts.length === 2) {
|
|
911
926
|
return path.join(head === 'index' ? indexDir() : librariesDir(), parts[1])
|
|
912
927
|
}
|
|
928
|
+
if (head === 'lexicon' && parts.length === 2) {
|
|
929
|
+
// 词典只接受 <libraryId>.json:isSafeId 挡掉 ..、路径分隔符和任意文件名,
|
|
930
|
+
// 与 writeStores 收录时的同一规则对称。
|
|
931
|
+
const id = parts[1].endsWith('.json') ? parts[1].slice(0, -'.json'.length) : ''
|
|
932
|
+
if (!id || !isSafeId(id)) throw new Error('backup contains an entry this version cannot restore: ' + rel)
|
|
933
|
+
return path.join(lexiconRoot(), parts[1])
|
|
934
|
+
}
|
|
913
935
|
if ((head === 'prompts' || head === 'attachments') && parts.length >= 2) {
|
|
914
936
|
return path.join(dataDir(), parts.join(path.sep))
|
|
915
937
|
}
|
|
@@ -54,9 +54,9 @@ export const RUNTIME_STATE = Object.freeze([
|
|
|
54
54
|
{ layer: 'temporary-map', file: 'lib/inbound.js:13', name: 'calls', keyedBy: 'sessionId', clearedBy: 'tool/result or turn/end' },
|
|
55
55
|
{ layer: 'temporary-map', file: 'lib/inbound.js:14', name: 'sessions', keyedBy: 'sessionId', clearedBy: 'session/disposed (releaseInboundSession)' },
|
|
56
56
|
{ layer: 'temporary-map', file: 'lib/media.js:19', name: 'captures', keyedBy: 'sessionId', clearedBy: 'capture settle' },
|
|
57
|
-
{ layer: 'temporary-map', file: 'lib/private.js:
|
|
58
|
-
{ layer: 'temporary-map', file: 'lib/curator.js:
|
|
59
|
-
{ layer: 'cache', file: 'lib/state.js:
|
|
57
|
+
{ layer: 'temporary-map', file: 'lib/private.js:347', name: 'inFlight', keyedBy: 'sessionId', clearedBy: 'consolidation settle' },
|
|
58
|
+
{ layer: 'temporary-map', file: 'lib/curator.js:50', name: 'jobs', keyedBy: 'job id', clearedBy: 'curation settle' },
|
|
59
|
+
{ layer: 'cache', file: 'lib/state.js:970', name: 'indexCache', keyedBy: 'libraryId', clearedBy: 'next load / external write' },
|
|
60
60
|
{ layer: 'cache', file: 'lib/paths.js:52', name: 'readFailures', keyedBy: 'absolute file', clearedBy: 'a later successful read' },
|
|
61
61
|
])
|
|
62
62
|
|