memory-eternal 0.15.0 → 0.17.0
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 +27 -20
- package/.codex-plugin/plugin.json +24 -24
- package/.cursor-plugin/plugin.json +14 -14
- package/README.md +138 -654
- package/README.zh-CN.md +138 -0
- package/bin/dsh-memory.mjs +33 -3
- package/cursor-extension/package.json +54 -54
- package/dsh-tui-plugin/README.md +63 -3
- package/dsh-tui-plugin/demo.mjs +113 -25
- package/dsh-tui-plugin/dsh-plugin.json +42 -42
- package/dsh-tui-plugin/main.js +65 -31
- package/dsh-tui-plugin/package.json +51 -31
- package/dsh-tui-plugin/scene.js +125 -16
- package/index.js +28 -3
- package/lib/api.js +89 -14
- package/lib/capture-run.js +35 -8
- package/lib/capture.js +25 -2
- package/lib/client.js +116 -20
- package/lib/db.js +19 -0
- package/lib/ledger.js +107 -0
- package/lib/mcp.js +8 -2
- package/lib/setup.js +67 -5
- package/orca-plugin/orca-plugin.json +36 -36
- package/package.json +7 -4
- package/scripts/release.mjs +5 -4
- package/web/app.js +121 -25
- package/web/index.html +14 -0
- package/README.en.md +0 -621
package/README.en.md
DELETED
|
@@ -1,621 +0,0 @@
|
|
|
1
|
-
# 🧠 memory-eternal — A "second brain" for your AI
|
|
2
|
-
|
|
3
|
-
[](https://hol.org/registry/plugins/eternalnight996%2Fmemory-eternal)
|
|
4
|
-
|
|
5
|
-
<!-- Language / 语言:this is the **English mirror**; the default README is Chinese (README.md).
|
|
6
|
-
The two are **separate files** with 1:1 heading levels — when you change one, change the other
|
|
7
|
-
(a new section must be added on both sides). npm / the DSH marketplace show README.md (Chinese) by default. -->
|
|
8
|
-
|
|
9
|
-
<p align="center">
|
|
10
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/icon.png" width="84" alt="memory-eternal" />
|
|
11
|
-
</p>
|
|
12
|
-
|
|
13
|
-
<p align="center">
|
|
14
|
-
<b style="font-size:20px">Memory Atlas · a local-first memory library for your AI</b><br/>
|
|
15
|
-
<sub>Auto-capture · review first · knowledge graph · shared by every Agent</sub>
|
|
16
|
-
</p>
|
|
17
|
-
|
|
18
|
-
<p align="center"><a href="README.md">简体中文(默认)</a> · <b>English</b></p>
|
|
19
|
-
|
|
20
|
-
<p align="center">
|
|
21
|
-
<img src="https://img.shields.io/badge/DeepSeek%20Harness-plugin-3B82F6" alt="DSH plugin" />
|
|
22
|
-
<img src="https://img.shields.io/npm/v/memory-eternal" alt="npm version" />
|
|
23
|
-
<img src="https://img.shields.io/badge/release-v0.15.0-FF7A45" alt="v0.15.0" />
|
|
24
|
-
<a href="https://eternalnight996.github.io/memory-eternal/"><img src="https://img.shields.io/badge/Live%20Demo-online-FF7A45" alt="Live demo" /></a>
|
|
25
|
-
<img src="https://img.shields.io/github/stars/EternalNight996/memory-eternal?style=flat" alt="GitHub stars" />
|
|
26
|
-
<img src="https://img.shields.io/github/license/EternalNight996/memory-eternal" alt="license" />
|
|
27
|
-
</p>
|
|
28
|
-
|
|
29
|
-
<p align="center">
|
|
30
|
-
<a href="https://eternalnight996.github.io/memory-eternal/demo/"><img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/library.png" width="920" alt="Memory Atlas: library, daily resurface, cards and connection graph" /></a>
|
|
31
|
-
</p>
|
|
32
|
-
<p align="center"><sub>▲ Memory Atlas · live demo (sample data) · <a href="https://eternalnight996.github.io/memory-eternal/demo/">open it</a></sub></p>
|
|
33
|
-
|
|
34
|
-
> **Every conversation is distilled into knowledge cards; nothing is forgotten across sessions, and recall fetches only the relevant chunks — fewer tokens, less noise.**
|
|
35
|
-
> Fully self-built, zero third-party memory framework, no DSH source changes; one vault shared by every Agent, SQLite persistent storage, zero external dependencies.
|
|
36
|
-
|
|
37
|
-
<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 desktop add memory-eternal</code></sub></p>
|
|
38
|
-
|
|
39
|
-
<p align="center"><a href="https://eternalnight996.github.io/memory-eternal/"><b>🌐 Live site</b></a> · <a href="https://eternalnight996.github.io/memory-eternal/demo/"><b>Open the memory library demo</b></a> <sub>(sample data, no install)</sub></p>
|
|
40
|
-
|
|
41
|
-
<p align="center">
|
|
42
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/audit.png" width="32%" alt="Review: type bar, queue and verdict sidebar" />
|
|
43
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/graph.png" width="32%" alt="Knowledge graph: kind colours, cluster legend, cross-vault aggregation" />
|
|
44
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/reader.png" width="32%" alt="Reader: content plus TOC, tags and related memories" />
|
|
45
|
-
</p>
|
|
46
|
-
|
|
47
|
-
<p align="center"><sub>▲ Real screenshots: review verdict · knowledge graph · reader</sub></p>
|
|
48
|
-
|
|
49
|
-
<p align="center">
|
|
50
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/terminal.png" width="49%" alt="Web Terminal page: copy commands for the full TUI or the native dsh-tui /audit" />
|
|
51
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/tui-audit.png" width="49%" alt="Native terminal audit: select-all, batch approve/reject, verdict pane" />
|
|
52
|
-
</p>
|
|
53
|
-
<p align="center">
|
|
54
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/tui-reader.png" width="72%" alt="Native terminal long-form reader: TOC, body, related memories and scrolling" />
|
|
55
|
-
</p>
|
|
56
|
-
<p align="center"><sub>▲ New in v0.14: in-Web terminal guide · native terminal audit (select-all/batch) · long-form reader</sub></p>
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## 🧩 Supported platforms
|
|
61
|
-
|
|
62
|
-
| Platform / host | Memory Atlas UI | Review / approve | Knowledge graph | Entry and status |
|
|
63
|
-
|---|---|---|---|---|
|
|
64
|
-
| **DSH desktop / Web** | Fully embedded | ✅ | ✅ | Sidebar “Memory”, supported |
|
|
65
|
-
| **Any terminal (TUI)** | Native terminal UI | ✅ select-all / batch | Clusters / adjacency | [`dsh-memory tui`](#-terminal-tui), no absolute path |
|
|
66
|
-
| **Orca** | Sidebar launcher | In Web UI | In Web UI | Right sidebar “Memory”, [new in this release](orca-plugin/README.md) |
|
|
67
|
-
| **dsh-tui** | Native `/audit` + full TUI | Native scene | Native scene | [new in this release](dsh-tui-plugin/README.md) |
|
|
68
|
-
| **Codex** | Opens browser | In Web UI | In Web UI | [`memory-open` skill](skills/memory-open/SKILL.md), new in this release |
|
|
69
|
-
| **Claude Code** | Browser / Pane | In Web UI | In Web UI | `/memory-eternal:memory-open`, new in this release |
|
|
70
|
-
| **Cursor** | Embedded Webview | ✅ | ✅ | [Extension Webview](cursor-extension/README.md), new in this release |
|
|
71
|
-
| **Pure Web** | Canonical implementation | ✅ | ✅ | `http://127.0.0.1:7999/` |
|
|
72
|
-
|
|
73
|
-
> Principle: **one Web UI is the single source of truth**. Hosts that can embed use a Webview; hosts that cannot get a thin launcher only. Every entry opens the same version, so DSH and Web never drift.
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## 🖥️ Terminal (TUI)
|
|
78
|
-
|
|
79
|
-
The Web UI is not the only entry. The terminal version is a **native TUI** over the same memory vault — built for fast review, select-all, long-form reading and a low-distraction workflow.
|
|
80
|
-
|
|
81
|
-
### One command for any user
|
|
82
|
-
|
|
83
|
-
No clone, no `cd`, no local absolute path:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
npx -y -p memory-eternal dsh-memory tui
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
On a machine where `memory-eternal` is already installed:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
dsh-memory tui # live by default: library/review/graph/status/recycle
|
|
93
|
-
dsh-memory tui --write # write: real approve / reject
|
|
94
|
-
dsh-memory tui --demo # sample data
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
If `dsh-memory` is not on PATH, use the `npx` command above; it downloads and runs the current npm version.
|
|
98
|
-
|
|
99
|
-
> The `tui` subcommand ships in **0.14.1+**. An older CLI prints help; upgrade with `npm i -g --force memory-eternal@0.15.0`.
|
|
100
|
-
|
|
101
|
-
### What is inside
|
|
102
|
-
|
|
103
|
-
| Page | Capability |
|
|
104
|
-
|---|---|
|
|
105
|
-
| Library | search, kind filters, today's resurface, reading |
|
|
106
|
-
| Review | select-all / invert, batch approve, batch reject, verdict |
|
|
107
|
-
| Star map | clusters, adjacency, related cards |
|
|
108
|
-
| Runtime | vault, backups, version, capture log |
|
|
109
|
-
| Recycle bin | restore, purge |
|
|
110
|
-
| Settings | auto-capture, failure alerts, audit, retention, theme |
|
|
111
|
-
| Reader | long-form wrapping, TOC, related memories, scrolling |
|
|
112
|
-
|
|
113
|
-
### Keys
|
|
114
|
-
|
|
115
|
-
| Key | Action |
|
|
116
|
-
|---|---|
|
|
117
|
-
| `1–6` | switch main page |
|
|
118
|
-
| `Tab` | cycle pages |
|
|
119
|
-
| `↑↓` / `j k` | move |
|
|
120
|
-
| `←→` / `t` | switch kind |
|
|
121
|
-
| `Space` | multi-select / toggle setting |
|
|
122
|
-
| `s` / `Ctrl+A` | select all / invert |
|
|
123
|
-
| `a` | approve current or batch |
|
|
124
|
-
| `r` | reject current or batch; restore in recycle bin |
|
|
125
|
-
| `d` | purge in recycle bin |
|
|
126
|
-
| `Enter` | read card / toggle setting |
|
|
127
|
-
| `L` | open the long-form reader directly |
|
|
128
|
-
| `j k` / `PgUp` `PgDn` / `g G` | reader scroll / page / top-bottom |
|
|
129
|
-
| `/` | search |
|
|
130
|
-
| `Esc` | back / clear |
|
|
131
|
-
| `?` | help |
|
|
132
|
-
| `q` | quit |
|
|
133
|
-
|
|
134
|
-
### Why the Web UI does not embed the TUI
|
|
135
|
-
|
|
136
|
-
The terminal TUI relies on the alternate screen, raw key events and a character grid; an iframe cannot reproduce it faithfully. The Web “Terminal” page therefore provides copy-ready commands and a preview; the real run happens in a terminal, while dsh-tui uses the native `/audit` scene.
|
|
137
|
-
|
|
138
|
-
### Limits
|
|
139
|
-
|
|
140
|
-
- No images / QR / Canvas graph in the terminal; the graph becomes clusters and adjacency.
|
|
141
|
-
- Colors and borders depend on the terminal font; on Windows use Windows Terminal or the VS Code integrated terminal.
|
|
142
|
-
- Read-only by default; real review requires `--write`; use `--demo` for sample data.
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## ✨ What problem does it actually solve
|
|
147
|
-
|
|
148
|
-
| What you hit | Before | After |
|
|
149
|
-
| --- | --- | --- |
|
|
150
|
-
| 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 |
|
|
151
|
-
| 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 |
|
|
152
|
-
| 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 |
|
|
153
|
-
|
|
154
|
-
- **Visible**: cards, graph, health overview and capture log — four layers of visibility;
|
|
155
|
-
- **Controllable**: audit modes, dedup and contradiction scans, batch merge, daily backups and one-command restore;
|
|
156
|
-
- **Portable**: the vault is local Markdown + SQLite — git-managed and exportable, never locked inside the plugin.
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## 🚀 Get Started in 5 Minutes
|
|
161
|
-
|
|
162
|
-
### 🟦 DeepSeek Harness (DSH) — focus
|
|
163
|
-
|
|
164
|
-
**Install** (official desktop app via the plugin marketplace, `dsh web` via one CLI line):
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
# 1) Official DeepSeek Harness desktop app (recommended): Settings -> Plugins -> search memory-eternal
|
|
168
|
-
# Equivalent CLI (that shell's profile is named "desktop"):
|
|
169
|
-
dsh plugin --profile desktop add memory-eternal
|
|
170
|
-
|
|
171
|
-
# 2) dsh web (browser):
|
|
172
|
-
dsh plugin --profile web add memory-eternal
|
|
173
|
-
|
|
174
|
-
# or update directly in the profile (pnpm workspace). Use pnpm — `npm install` throws EUNSUPPORTEDPROTOCOL
|
|
175
|
-
cd ~/.dsh/profiles/web && pnpm add memory-eternal@latest
|
|
176
|
-
|
|
177
|
-
# right after a release, @latest can be held back by pnpm's minimum-release-age gate or a cached
|
|
178
|
-
# packument (it silently stays on the old version): pin the version instead — pnpm adds it to the
|
|
179
|
-
# workspace's minimumReleaseAgeExclude automatically
|
|
180
|
-
cd ~/.dsh/profiles/web && pnpm add memory-eternal@0.15.0
|
|
181
|
-
|
|
182
|
-
# verify the version, then restart the host
|
|
183
|
-
node -e "console.log(require('memory-eternal/package.json').version)"
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
#### 🖥️ Desktop support policy (read this first)
|
|
187
|
-
|
|
188
|
-
- **The official DeepSeek Harness desktop build is the priority — and the only guaranteed target** (the official Electron release; its profile is named `desktop`). The v0.9.4 → v0.9.6 fixes all target it: the 0.1.7 settings-service rewrite, `schemastery 3.18.4` volatile live references, and window-titlebar avoidance (`data-windows-titlebar`, so a full-screen overlay can no longer cover minimize/maximize/close).
|
|
189
|
-
- **The author's own `dsh-desktop` (`dsh-desktop-shell` / `dsh-ui-*` / `dsh-plugin-marketplace`) is discontinued** and is no longer a support target — the official desktop build covers the same ground, and maintaining two shells only splits compatibility. Note that its profile still pins `dsh-desktop-shell@0.3.0`, which has been unpublished from npm and makes `pnpm install` fail outright; migrating to the official desktop build and dropping those dependencies is recommended.
|
|
190
|
-
- Compatibility matrix (both verified): the official `desktop` profile (schemastery **3.18.4**) and the `web` profile behind `dsh web` (schemastery **3.18.1**) share the same code path.
|
|
191
|
-
|
|
192
|
-
After **restarting the host**, three things are live immediately:
|
|
193
|
-
|
|
194
|
-
| Effect | Where |
|
|
195
|
-
|---|---|
|
|
196
|
-
| Auto-capture of knowledge cards | Happens every turn, no action needed |
|
|
197
|
-
| `memory_recall` tool | Agent calls it automatically when it needs history |
|
|
198
|
-
| Visual UI | Sidebar bottom `Memory` button / Settings → Memory |
|
|
199
|
-
|
|
200
|
-
**Entry**: The sidebar-footer `Memory` button opens the vault (its left rail includes Cards / Graph / Usage / **Audit Center** / Recycle Bin / **Memory Config**); "DSH Settings → Memory" is the pure config page.
|
|
201
|
-
|
|
202
|
-
**UI language**: The whole UI is bilingual (English / 中文) and follows **DSH Settings → Language** in real time — Cards, Graph, Audit Center, card templates, config panel all included; the standalone web page follows the browser language.
|
|
203
|
-
|
|
204
|
-
**Edit config**: Vault left rail "Memory Config" (or DSH Settings → Memory) → DSH memory config / cost control / auto-audit config / self-hosting — just hit "Save Config". `autoWebMode` / `watchdogAutoSpawn` changes require a DSH restart — and note an **already running watchdog is never stopped by a config change**: use `dsh-memory stop` / `dsh-memory restart` (see Self-hosting).
|
|
205
|
-
|
|
206
|
-
### 🟨 Claude Code
|
|
207
|
-
|
|
208
|
-
```bash
|
|
209
|
-
npm i -g memory-eternal # installs CLI + MCP (auto-writes ~/.claude.json + SessionEnd hook)
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
Ready after install: say "recall 数据库选型" in a session → auto-retrieves memory; on session end / before context compaction → auto-captures.
|
|
213
|
-
|
|
214
|
-
### 🟧 Codex CLI / Cursor
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
|
-
npm i -g memory-eternal # auto-writes Codex config.toml / Cursor mcp.json
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
Restart the tool → MCP is in the list; just use: `用 memory_recall 查一下项目历史决策`.
|
|
221
|
-
|
|
222
|
-
### 🟩 Browser (no Agent needed)
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
dsh-memory open # starts web + opens browser (default http://127.0.0.1:7999)
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
Stats / search / card grid (add-edit-merge-import-export) / knowledge graph — all here. Same UI as the DSH embed; data stays in sync.
|
|
229
|
-
|
|
230
|
-
> **zcode (Zhipu)**: no native MCP; bridge via [zcode-open-bridge](https://github.com/tizerluo/zcode-open-bridge) or use the CLI directly.
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
## 📖 Command Reference
|
|
235
|
-
|
|
236
|
-
```bash
|
|
237
|
-
dsh-memory recall "database selection" # retrieve
|
|
238
|
-
dsh-memory capture "important note..." # manual capture (- reads stdin)
|
|
239
|
-
dsh-memory sweep ~/.claude/projects # mine existing sessions
|
|
240
|
-
dsh-memory setup [--dry-run] # re-run / preview auto-mount (idempotent)
|
|
241
|
-
dsh-memory mcp # MCP stdio (mount to any MCP client)
|
|
242
|
-
dsh-memory serve [--port 7999] # run web in foreground
|
|
243
|
-
dsh-memory open # ensure web alive + open browser
|
|
244
|
-
dsh-memory tui # full terminal client (read-only; --write to review)
|
|
245
|
-
dsh-memory watchdog [--port 7799] # watchdog keep-alive (standalone process)
|
|
246
|
-
dsh-memory status [--json] # resident watchdog: pid / port / version / alive
|
|
247
|
-
# plus the version **actually served on the port**
|
|
248
|
-
dsh-memory stop [--port 7799] # stop the resident watchdog (+ the web it spawned)
|
|
249
|
-
# then re-checks who still holds the port and warns
|
|
250
|
-
dsh-memory restart [--port 7799] # stop + respawn (apply a new version)
|
|
251
|
-
# also reaps port owners the lock never registered, then verifies the served version
|
|
252
|
-
dsh-memory audit list [--status pending|rejected|approved|deleted|all] [--limit N] [--json]
|
|
253
|
-
# list cards; the total is never rewritten by --limit (which caps output only)
|
|
254
|
-
# all = audit queue (pending + rejected), excluding approved / deleted
|
|
255
|
-
dsh-memory audit approve <card path...> # human bulk approve
|
|
256
|
-
dsh-memory audit reject <card path...> --reason "..." # human bulk reject
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
When not running on DSH, the `dsh-memory` command comes from `npm i -g`.
|
|
260
|
-
|
|
261
|
-
---
|
|
262
|
-
|
|
263
|
-
## ⚙️ Self-hosting (plain words)
|
|
264
|
-
|
|
265
|
-
Three concepts, don't mix them:
|
|
266
|
-
|
|
267
|
-
- **How the web server stays alive** (`autoWebMode`) → `init`=pull once at DSH start (default); `interval`=DSH in-process timer probes & auto-restarts (0 extra memory); `manual`=fully manual, only from `dsh-memory open`.
|
|
268
|
-
- **Watchdog process** (`watchdogAutoSpawn`, default on) → a **standalone** node process that can pull web up even after DSH exits (~+47 MB RAM). Only turn on for 7×24 keep-alive.
|
|
269
|
-
- **Version-drift self-heal** (`autoRestartOnDrift`, default on) → the resident web **outlives DSH**: an upgrade only swaps files on disk, so the process on the port keeps running the code it loaded at startup (that is where "still on the old version after starting dsh" comes from). With this on, DSH asks **the port** which version it really serves and, on a mismatch, restarts the resident instance on the new code. Turn it off to only get a warning — the **Restart resident instance** button in Plugin info stays available.
|
|
270
|
-
- **Auto-mount MCP** (`autoMcpSetup`, default off) → whether to auto-write MCP into Claude Code/Codex/Cursor config. Off = don't touch your machine config; run `dsh-memory setup` manually when needed.
|
|
271
|
-
|
|
272
|
-
**Change these**: Vault left rail "Memory Config" (or DSH Settings → Memory) → edit the table and save; `autoWebMode` / `watchdogAutoSpawn` need a DSH restart.
|
|
273
|
-
|
|
274
|
-
> ⚠️ **A config change never auto-replaces the instance on the same port (#19)**: the watchdog is a **standalone process we deliberately never kill** (several sessions share one). So turning `watchdogAutoSpawn` off does **not** stop the running one, and changing `webCheckIntervalMs` / `webMaxRestart` has no effect on it.
|
|
275
|
-
> Stop or upgrade it explicitly with `dsh-memory stop [--port N]` / `dsh-memory restart [--port N]` (`stop` also stops the **web it spawned**, so no orphan keeps holding the port); `dsh-memory status` shows the resident pid, port, start time, whether its **version lags the installed package**, and separately the version **actually served on the port** (the lock's version is only the watchdog's self-report).
|
|
276
|
-
>
|
|
277
|
-
> ⚠️ **After upgrading, trust the served version (#23)**: `restart` no longer trusts the lock's `webPid` alone — it reaps whoever holds the port (including web processes the lock never registered; non-memory-eternal processes are only reported, never killed), and after spawning it verifies that the **served version equals the installed version**, exiting non-zero instead of printing "started" and walking away.
|
|
278
|
-
>
|
|
279
|
-
> ✅ **"Upgraded but still on the old version" now has two ways out (#19/#23)**: ① `autoRestartOnDrift` (default on) replaces a stale resident instance **automatically** when DSH activates; ② UI → Plugin info shows a **🔄 Restart resident instance** button whenever it detects drift (the server side is the same implementation as `dsh-memory restart --port N`: reap the old instance → take the lock → pull up the new web → verify the served version). Note the "running" chip means **the process serving this page** — the resident web on the standalone page, the host inside DSH Settings; the panel also lists the **resident instance** version, so you can tell at a glance which one to restart.
|
|
280
|
-
|
|
281
|
-
**MCP is a protocol, not a resident service**: the agent spawns it per session and exits when done — no "auto-start on boot" concept.
|
|
282
|
-
|
|
283
|
-
### Three deployment intensities
|
|
284
|
-
|
|
285
|
-
| Scenario | Config | Memory |
|
|
286
|
-
|---|---|---|
|
|
287
|
-
| Personal dev (recommended) | `autoWebMode=init` + **manually** turn `watchdogAutoSpawn` off (its default is **on**) | web 47 MB |
|
|
288
|
-
| Resident 7×24 | `watchdogAutoSpawn=on` | web + watchdog 47+47 MB |
|
|
289
|
-
| True boot auto-start (no DSH) | Windows Task Scheduler runs `dsh-memory watchdog --port 7799 --interval 5000 --max-restart 10` | same |
|
|
290
|
-
|
|
291
|
-
---
|
|
292
|
-
|
|
293
|
-
## ⚙️ Memory Config (plain words)
|
|
294
|
-
|
|
295
|
-
> 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):
|
|
296
|
-
|
|
297
|
-
<p align="center">
|
|
298
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/config.png" width="880" alt="Memory config page" />
|
|
299
|
-
</p>
|
|
300
|
-
|
|
301
|
-
### 1. Most used
|
|
302
|
-
|
|
303
|
-
| Setting | Default | In plain words |
|
|
304
|
-
|---|---|---|
|
|
305
|
-
| Auto capture | on | auto-store useful content as cards after each turn |
|
|
306
|
-
| Auto recall | on | AI auto-queries memory when it needs history |
|
|
307
|
-
| Vault dir | `~/.dsh/memory-vault` | where memory lives, plain Markdown & git-able |
|
|
308
|
-
|
|
309
|
-
#### Multiple vaults (per-project isolation)
|
|
310
|
-
|
|
311
|
-
Two extra fields in `memory-eternal-config.json`:
|
|
312
|
-
|
|
313
|
-
```json
|
|
314
|
-
{
|
|
315
|
-
"vaultProfiles": [{ "name": "work", "path": "D:/vaults/work" }],
|
|
316
|
-
"activeVault": "work"
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
Resolution order: `MEMORY_VAULT_DIR` env → the `path` of the `vaultProfiles` entry whose `name` matches `activeVault` → `vaultDir` → default `~/.dsh/memory-vault`.
|
|
321
|
-
|
|
322
|
-
**Since v0.9.8 the CLI / MCP / hooks / standalone web / sweep share the host's exact order** — previously only the host honoured `activeVault`, so switching vaults left the terminal and hooks writing to the default vault and split your memory in two.
|
|
323
|
-
|
|
324
|
-
> Note: the settings page still has no form for `vaultProfiles` / `activeVault` (edit the config file by hand), and per-workspace auto-selection is not implemented yet.
|
|
325
|
-
|
|
326
|
-
### 2. Save money (important)
|
|
327
|
-
|
|
328
|
-
| Setting | Default | In plain words |
|
|
329
|
-
|---|---|---|
|
|
330
|
-
| **Distill cards** | on | **compress** a conversation into a sharp card (calls AI, costs money). **Off = store raw text, zero cost** |
|
|
331
|
-
| **Dedup feeds AI** | on | judge if new content is a duplicate (calls AI). **Off = simple dedup**, saves one AI call |
|
|
332
|
-
| Distill output cap | 900 | max chars per compress, bigger = sharper but pricier |
|
|
333
|
-
| Recall min score | 2 | how "close" a match must be to return; bigger = sharper but leaks more (cheaper) |
|
|
334
|
-
| Min capture chars | 200 | too-short chats aren't stored (avoids small-talk waste) |
|
|
335
|
-
| Daily quota | 60 | max cards per day, prevents AI burning money |
|
|
336
|
-
|
|
337
|
-
### 3. How the service runs
|
|
338
|
-
|
|
339
|
-
| Setting | Default | In plain words |
|
|
340
|
-
|---|---|---|
|
|
341
|
-
| Keep-alive `autoWebMode` | init | `init`=open web once at DSH start; `interval`=periodically check & restart if dead; `manual`=fully manual |
|
|
342
|
-
| Watchdog `watchdogAutoSpawn` | on | a **standalone process** keeps web alive (+47 MB). Personal use can turn off — but an existing instance is **not** stopped automatically, run `dsh-memory stop` (#19) |
|
|
343
|
-
| Drift self-heal `autoRestartOnDrift` | on | when DSH activates and the version **served on the port ≠ the version on disk** (resident web still on old code after an upgrade), restart the resident instance on the new code. Off = warn only; the **🔄 Restart resident instance** button still works (#19/#23) |
|
|
344
|
-
| Auto-mount MCP `autoMcpSetup` | off | **lets Claude Code / Codex / Cursor use your memory**. On = auto-configures them; Off = never touches your config, run `dsh-memory setup` manually |
|
|
345
|
-
| Credential hint `secretHint` | empty | put your own one-liner here (e.g. "when you need an API key/token, look up the secret directory in memory first"); it is **injected into every session's systemPrompt**. Memory only stores the **directory** (names / locations / how to use) — secret **values** never enter the vault (see "Where secrets live" below) |
|
|
346
|
-
|
|
347
|
-
> 🔑 **Where secrets live (how to use `secretHint`)**: keep the **values** in local credential stores
|
|
348
|
-
> (env vars / permission-tightened files); keep only a **directory card** in memory (which keys exist,
|
|
349
|
-
> where they live, what they are for, rotation status) and use `secretHint` so the agent remembers to
|
|
350
|
-
> look it up every session. Why values must not be stored: a card's `summary` is just the first 200
|
|
351
|
-
> chars of its body, and **recall sends the first 130 chars by default** (`recallSummaryLen`) — a value
|
|
352
|
-
> in the vault would travel into the model context on every recall, plus into `/export`, backups and
|
|
353
|
-
> multi-machine sync. `dsh-memory setup` registers a `PreToolUse` guard in Claude Code that **denies**
|
|
354
|
-
> direct reads of `.credentials.yaml` / `.env` / `*-token` and points at the directory card instead.
|
|
355
|
-
|
|
356
|
-
> 💰 **To save money**: turn `Distill cards` off, lower `Distill output cap`, raise `Recall min score`.
|
|
357
|
-
|
|
358
|
-
### 🎯 One-click presets
|
|
359
|
-
|
|
360
|
-
Top of the config page: **🟢 A Light / 💰 B Budget / ⭐ C Premium** — click to fill, then Save:
|
|
361
|
-
|
|
362
|
-
| Plan | Scenario | Keep-alive | Watchdog | Distill | Distill cap | Recall min | Memory | LLM cost |
|
|
363
|
-
|---|---|---|---|---|---|---|---|---|
|
|
364
|
-
| 🟢 **A Light** | Personal dev (recommended) | init | off | on | 2000 | 2 | ~47 MB | normal |
|
|
365
|
-
| 💰 **B Budget** | Tight budget / many Agents | init | off | **off** | 500 | 3 | ~47 MB | **~0** |
|
|
366
|
-
| ⭐ **C Premium** | Long projects / teams | interval | **on** | on | 1200 | 1 | ~94 MB | high |
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
## 🔔 Capture Log & Failure Alerts
|
|
371
|
-
|
|
372
|
-
Auto-capture is a silent background pipeline: when it broke, the only symptom used to be "no new cards" with no visible cause. Since v0.9.0 it speaks up.
|
|
373
|
-
|
|
374
|
-
- **Capture log**: Settings → Memory → Usage / Today shows the last 100 run entries: `🚀 boot` / `👂 session attached (which event API + event count)` / `✅ card created` / `➕ update appended` / `⏭ skipped (reason)` / `❌ failed (reason)`.
|
|
375
|
-
- **Active alerts**: any failure turns the strip red at the top of the memory library *and* injects "auto-capture failed + reason" into the agent context, so the agent tells you in its first sentence — no need to dig through logs.
|
|
376
|
-
- **Stall watchdog**: only when this run has received **no turn-stopping event at all** while turns keep starting inside the last 20 minutes (e.g. DSH renamed the event) does a 5-minute sweep record a `warn` — it no longer masquerades as a red auto-capture anomaly, and an in-flight **long turn is never mistaken for a stall** (inside one turn `inbox/claimed` fires repeatedly, while the stop only arrives when the turn really ends).
|
|
377
|
-
- **Self-adapting event API**: the session event API is detected automatically (`ownEvents()` → `snapshotEvents()` → `events`), so a DSH upgrade that changes the API can no longer silently break capture.
|
|
378
|
-
|
|
379
|
-
---
|
|
380
|
-
|
|
381
|
-
## 🛡️ Audit Center & Recycle Bin
|
|
382
|
-
|
|
383
|
-
> **Agents cannot approve on your behalf (since v0.9.1).** Whenever audit is active, the plugin injects a red line into the agent context: new cards stay `pending`, and calling the `approve`/`reject` endpoints (or any equivalent bypass) is forbidden. The decision is always yours.
|
|
384
|
-
|
|
385
|
-
New cards go to the **Audit Center** (`pending`) by default and enter the main vault only after you approve them; rejected ones move to "Rejected", where you can restore or delete to the recycle bin. Cards matching an exemption (audit mode = skip all / exempt agents / exempt kinds) go straight in; recycle-bin cards are recoverable within 30 days, then auto-purged.
|
|
386
|
-
|
|
387
|
-
<p align="center">
|
|
388
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/audit-center.png" width="880" alt="Audit Center" />
|
|
389
|
-
</p>
|
|
390
|
-
|
|
391
|
-
- **Pending / Rejected** tabs, filtered by type / date / agent; select all and approve / reject / delete-to-recycle in one click.
|
|
392
|
-
- Rules in "Memory Config → Auto-audit config": `audit mode` (audit all / skip all) + `exempt agents` + `exempt kinds` + `recycle retention days`.
|
|
393
|
-
|
|
394
|
-
### 🥇 Why the audit system is more reliable (vs other memory products)
|
|
395
|
-
|
|
396
|
-
Most memory products (mem0 / Zep / agentmemory…) auto-ingest **everything** the moment a conversation ends, good or bad — noise, wrong facts, and sensitive content all go in and get recalled later, **polluting context and amplifying hallucinations**.
|
|
397
|
-
|
|
398
|
-
memory-eternal uses **human-in-the-loop auditing** — only trusted content enters the main vault:
|
|
399
|
-
|
|
400
|
-
| Dimension | Other memory products | memory-eternal audit system |
|
|
401
|
-
|---|---|---|
|
|
402
|
-
| Ingest | Auto-ingest all, no gate | New cards first enter the **Audit Center (`pending`)**, approved before entering the vault |
|
|
403
|
-
| Quality | No filtering of noise/errors | Only cards you've confirmed → more accurate recall, less noise |
|
|
404
|
-
| Traceability | No source/audit trail | Every card carries `submittedBy` author + `pending/approved/rejected` state |
|
|
405
|
-
| No-friction | — | Exempt agents / exempt kinds hit → trusted cards go straight in, zero wait |
|
|
406
|
-
| Safety | Delete = gone forever | Recycle-bin soft delete, recoverable within 30 days |
|
|
407
|
-
|
|
408
|
-
---
|
|
409
|
-
|
|
410
|
-
## 🧬 Why Fully Self-built
|
|
411
|
-
|
|
412
|
-
Most memory solutions lean on third-party frameworks / spin up an MCP service / lock memory in a private store. This plugin builds the skeleton itself — **zero third-party runtime deps**, logic readable line by line:
|
|
413
|
-
|
|
414
|
-
| Module | Self-built | Replaces |
|
|
415
|
-
|---|---|---|
|
|
416
|
-
| Dedup | lexical Jaccard bigram (0.62) + semantic dedup | duplicate-card prevention |
|
|
417
|
-
| Retrieval | CJK-aware: Chinese whole-word + char bigram | no full-text search engine needed |
|
|
418
|
-
| Graph | force-directed + `[[wikilink]]`/shared-tag edges | see knowledge links at a glance |
|
|
419
|
-
| Storage | plain `.md` with frontmatter | not locked in, readable, git-able, any tool can read |
|
|
420
|
-
|
|
421
|
-
> Same direction as popular projects (summarize→store→recall), but positioned differently: **local, self-built, zero-dep, readable & controllable**. If you already use mem0/Zep etc., just layer this as a "local persistent memory base".
|
|
422
|
-
|
|
423
|
-
---
|
|
424
|
-
|
|
425
|
-
## 🛠 Development / Test
|
|
426
|
-
|
|
427
|
-
```bash
|
|
428
|
-
npm i
|
|
429
|
-
npm test # unit tests: vault dedup/retrieval/graph + capture pipeline + API shapes
|
|
430
|
-
npm run build # builds lib/client.js (DSH embed) + web/app.js (standalone web bundle)
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
> 📄 **The README ships as two separate files**: `README.md` (Simplified Chinese, the **default** —
|
|
434
|
-
> this is what npm and the plugin marketplace render) and `README.en.md` (English). Heading levels are
|
|
435
|
-
> 1:1 between them, so **a change in one must be mirrored in the other** (add new sections on both sides).
|
|
436
|
-
|
|
437
|
-
---
|
|
438
|
-
|
|
439
|
-
## 🐞 Reporting a bug
|
|
440
|
-
|
|
441
|
-
The left rail has a **🐞 Report a bug** entry: describe the problem, then either
|
|
442
|
-
|
|
443
|
-
- **Open prefilled issue on GitHub** — title, body, diagnostics and the `bug` label are filled in; one more click on **Submit** files it;
|
|
444
|
-
- **Copy AI prompt** — paste it into your AI chat and it will run `gh` to de-duplicate, file the issue and report the URL back.
|
|
445
|
-
|
|
446
|
-
> **Why not one-click submission?** GitHub forbids anonymous issue creation, a token inside the plugin would be extracted by decompiling, and OAuth / a relay needs hosted infrastructure — hence "pre-fill + one click".
|
|
447
|
-
> Diagnostics are **auto-redacted** (home directory → `~`; `sk-` / `ghp_` / `github_pat_` / `api_key:` → `***`), but do skim them before posting publicly.
|
|
448
|
-
|
|
449
|
-
If you cannot reach the UI, paste the prompt below into your AI chat and replace the last placeholder with your problem:
|
|
450
|
-
|
|
451
|
-
```text
|
|
452
|
-
I hit a problem while using memory-eternal (a DeepSeek Harness memory plugin). Please file it on GitHub for me.
|
|
453
|
-
|
|
454
|
-
Repo: https://github.com/EternalNight996/memory-eternal
|
|
455
|
-
|
|
456
|
-
Workflow:
|
|
457
|
-
|
|
458
|
-
1. Run `gh issue list --repo EternalNight996/memory-eternal --state open --limit 50` first: if a matching issue exists, add a comment with `gh issue comment <n> --body-file <file>` instead of opening a duplicate.
|
|
459
|
-
2. Otherwise create it with `gh issue create --repo EternalNight996/memory-eternal --title "[Bug] <one-line summary>" --body-file <file>`, using sections: Symptoms / Steps to reproduce / Expected / Actual / Environment (plugin, DSH, OS, Node) / Diagnostics.
|
|
460
|
-
3. If `gh` is not authenticated or unavailable, do NOT try to submit: output a GitHub new-issue link with the title and body pre-filled instead, and I will click submit myself.
|
|
461
|
-
4. Report the resulting issue URL back to me.
|
|
462
|
-
|
|
463
|
-
Keep my description verbatim - do not rewrite or summarise it. Put the diagnostics under the Diagnostics section as-is.
|
|
464
|
-
|
|
465
|
-
My description:
|
|
466
|
-
<write your problem here>
|
|
467
|
-
|
|
468
|
-
Diagnostics:
|
|
469
|
-
<paste the diagnostics copied from the in-app Report-a-bug dialog>
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
### Self-diagnostics (paste these when reporting — triage gets much faster)
|
|
473
|
-
|
|
474
|
-
```bash
|
|
475
|
-
dsh plugin list # plugin version
|
|
476
|
-
node -v # Node version
|
|
477
|
-
curl http://127.0.0.1:7999/memory-eternal/api/web-info # is the standalone web alive?
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
---
|
|
481
|
-
|
|
482
|
-
## 📋 Changelog
|
|
483
|
-
|
|
484
|
-
> **Withdrawn versions**: **v0.9.16 – v0.9.22 are all withdrawn** (problem-period releases: git tags deleted, npm entries deprecated — use **v0.9.23**). **v0.9.14 is deprecated** (its client-rendering changes hurt the feel and were reverted; its server-side algorithm work shipped in **v0.9.15**, whose rendering is identical to v0.9.13). **v0.9.0 – v0.9.5 are all deprecated on npm, and the release tags for v0.9.1 / v0.9.2 / v0.9.3 / v0.9.4 have been removed from git** (v0.9.5 is merely superseded, so its tag stays) — v0.9.1/v0.9.2 ship the blank-memory-page defect, v0.9.0 false-alarms its capture warnings, v0.9.3 does not support DSH v0.1.7-rc.2 (the plugin stops mounting entirely: the settings service changed and the client-side `settingsScope` is gone), v0.9.4 crashes under the official desktop build (schemastery 3.18.4 volatile live references) with `cfg.vaultDir.trim is not a function`, and **v0.9.5's full-screen overlay covers the desktop shell's window controls (its top-right "×" sits on the window Close button — one click quits the whole shell)**. Use **v0.9.6+** (`npm i memory-eternal@latest`).
|
|
485
|
-
> **DSH compatibility**: `>=0.1.5-alpha.2 <0.3.0-0` (**widened in v0.10.1**: the old `<0.2.0` upper bound excludes DSH `0.2.0-rc.x` under plain semver — DSH's plugin guard uses `includePrerelease: true`, so installs still worked, but the declaration did not match reality. The range now covers the whole 0.1/0.2 line, and `<0.3.0-0` still blocks 0.3.0 and its prereleases. **Verified running on the official desktop build DSH 0.2.0-rc.2**: mounting, memory page, auto-capture, review centre and import/export all work). **v0.9.4 adds support for DSH v0.1.7-rc.2**, which replaced the settings service with a forms-only API and removed `@deepseek-ai/dsh-client-runtime` / `settingsScope`; **v0.9.5 adds support for schemastery ≥3.18.4 volatile live references** (the shape the official desktop profile uses, while the web profile still ships 3.18.1 — both work); **v0.9.6 keeps full-screen overlays clear of the desktop shell's window controls**. The 0.1.5 line still uses the legacy `settings.register` path; both paths are kept.
|
|
486
|
-
|
|
487
|
-
| Version | Date | Highlights |
|
|
488
|
-
|---|---|---|
|
|
489
|
-
| **v0.15.0** | 2026-10-23 | **Live TUI by default**: `dsh-memory tui` no longer needs `--live`; it loads the real library, review queue, graph, runtime and recycle bin by default. `--demo` is opt-in sample data, and `--write` is required for real review. Also fixes empty-queue sample fallback and fake library/graph/status data. |
|
|
490
|
-
| **v0.14.3** | 2026-10-23 | **Live data across the whole TUI**: `--live` now loads the real library (paginated), graph nodes/edges, runtime totals/backups/version and recycle bin; the library/graph/status pages no longer show fake 999 nodes, 0.12.0 or demo logs. Review stays on the real queue, and cards are fetched with their real status. |
|
|
491
|
-
|
|
492
|
-
<details>
|
|
493
|
-
<summary>Older release notes (v0.10.52 and earlier · 79 releases, click to expand)</summary>
|
|
494
|
-
|
|
495
|
-
| Version | Date | Highlights |
|
|
496
|
-
|---|---|---|
|
|
497
|
-
| **v0.14.2** | 2026-10-23 | **Live empty-queue fix**: `dsh-memory tui` no longer falls back to sample data when the real pending queue is empty; it stays in LIVE mode and shows the empty state. The path-free npx entry and the 0.14.1 old-global-CLI upgrade note remain. |
|
|
498
|
-
| **v0.14.1** | 2026-10-23 | **Portable terminal entry**: new `dsh-memory tui` lets any installed user launch the full terminal client without `cd`-ing into the repo or hard-coding a local path; the Web terminal page now shows the same read-only/write commands. |
|
|
499
|
-
| **v0.14.0** | 2026-10-23 | **Web terminal entry + fresh screenshots**: the library rail gains a Terminal page with copy-ready commands, the dsh-tui `/audit` guide and a terminal preview, so terminal-only features are discoverable from the Web UI; README adds real screenshots for the terminal audit, long-form reader and Web terminal page; the native dsh-tui `/audit` scene ships. |
|
|
500
|
-
| **v0.13.0** | 2026-10-23 | **Full TUI + native dsh-tui audit**: the terminal client now covers library, audit, graph, status, recycle, settings, long-form reader and search; audit gains select-all and batch approve/reject; `--live` talks to the local API (read-only by default, `--write` to mutate); a native dsh-tui `/audit` scene replaces the browser-only entry. |
|
|
501
|
-
| **v0.12.0** | 2026-10-22 | **Cross-host launchers**: Orca right-sidebar Memory panel, dsh-tui `/memory`, Codex / Claude `memory-open` skill, and a Cursor / VS Code Webview extension; README gains a platform support table; one Web UI stays the single source of truth — Webview where it works, a thin launcher everywhere else, with no new agent permissions. |
|
|
502
|
-
| **v0.11.0** | 2026-10-22 | **Memory Atlas stable release**: the full V5 UI lands — 4-column library cards with daily resurface and mini connection graphs; review top type bar + queue + right verdict sidebar (no bottom bar, no popups); knowledge graph page head/cluster legend + DPR, grid and edge rendering optimizations; four runtime status cards; reader TOC and related memories; Jinwu brand icon; click-to-zoom QR codes; onboarding starts with Star/Fork; GitHub/Gitee/npm shipped together. |
|
|
503
|
-
| **v0.11.0-rc.1** | 2026-10-21 | **V5 preview RC**: Memory Atlas dark ember theme, 228px rail and topbar; V5 card grid and page head; audit top type bar + queue + verdict preview + select-all batch approve/reject; smaller graph default; first-run guide (👍 emphasised), reader TOC/related memory, and support rail (community/sponsor/legacy/bug report); public demo updated to V5. |
|
|
504
|
-
| **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** |
|
|
505
|
-
| **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** |
|
|
506
|
-
| **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** |
|
|
507
|
-
| **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** |
|
|
508
|
-
| **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** |
|
|
509
|
-
| **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** |
|
|
510
|
-
| **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** |
|
|
511
|
-
| **v0.10.47** | 2026-10-19 | **The panel version probe now refreshes itself, and the "resident not running" wording is fixed (user report: the panel kept saying "resident instance: not running" after a restart)**. ① The instance was in fact alive (status showed 7999 listening and serving v0.10.46), but the probe ran only once when the panel mounted, so a run that happened to land inside the restart window was cached as "not running" and stuck. It now **re-checks silently every 60 seconds** (without lighting up the Check-for-updates button) and **immediately when the tab becomes visible** (visibilitychange); a restart already triggers a re-check six seconds later. ② When the resident really is down, the hint used to interpolate an empty "currently v"; it now uses vcRestartStart: "Starts the resident instance on the current disk version v{disk} (no DSH restart needed)". npm test now runs **408 checks** |
|
|
512
|
-
| **v0.10.46** | 2026-10-19 | **The "restart resident instance" button is now always shown, and the "which one is stale" wording is fixed (second user report of the button disappearing)**. The previous build put the button inside the "resident needs restart" branch, while the real state was "the resident is already current, but the process serving this page (the DSH host) is stale", so it fell into the host-hint branch and the button vanished again; worse, the warning claimed clicking restart-resident would fix it even though restarting the resident never changes the host-served page. Now: ① the button depends only on vc existing; ② each of the three states gets an accurate explanation — resident stale (click and it upgrades without restarting DSH) / host stale (restarting the resident does not affect this page, restart DSH) / everything current (force restart if it looks stuck); ③ the warning text is split into vcHostProcessStale, making clear that the page is served by the host process. npm test now runs **406 checks** |
|
|
513
|
-
| **v0.10.45** | 2026-10-19 | **Fixes the disappearing "restart resident instance" button and the dialog layout / scrollbar anomalies (from user screenshots)**. ① **The button is persistent**: it used to live inside the warning block that only renders when something is wrong, so once versions matched the whole block vanished and the button could not be found (issue #28 complained about the same). The action row now renders independently: it is the repair entry point when something is off, and stays available when everything is fine, with a note that a restart is usually unnecessary but can be forced if the instance looks stuck. ② **Scrollbar anomalies**: the reader can have up to eight header buttons while 0.10.42 made the action group nowrap, so it overflowed horizontally inside a max-width:760px dialog; each button still never shrinks or wraps, but the **group itself may wrap** onto a second row without deforming. ③ **Layout**: .me-dialog and .me-dialog-body use border-box and hide horizontal scrolling in the body (the reader width:100% plus padding no longer breaks out of the container), diagnostic/feedback pre blocks use word-break for long paths, and large-title modals ellipsize their h2. Adds tests/ui-overflow.test.mjs (4 guards). npm test now runs **406 checks** |
|
|
514
|
-
| **v0.10.44** | 2026-10-19 | **UI experience, batch two: click a tag to filter, plus search hit snippets**. ① **Tag filtering**: tag chips in the list and the reader are now clickable and filter immediately (combining with search, kind and status); the toolbar shows a removable "tag x" chip. The server-side /cards?tag= uses an exact SQL match on the quoted complete tag inside the JSON array (instr), so **ab is never matched by a**, and both the countCards and sort=title branches apply it. ② **Search hit snippets**: when the match is in the body, the list shows a short excerpt around the keyword with highlighting (whitespace collapsed, ellipses at both ends), so it is obvious why a card matched. ③ Adds src/client/snippet.js (pure, unit-testable in Node) and tests/tag-filter.test.mjs (3 checks). npm test now runs **402 checks** |
|
|
515
|
-
| **v0.10.43** | 2026-10-19 | **UI experience, batch one: reading a card**. ① **Copy**: the reader gains "copy body" and "copy path", reusing the clipboard helper that has a manual fallback, with a "copied" confirmation for 1.5s (no more silent clicks); ② **Reading progress**: a 2px bar under the header advances as the body scrolls, and short cards read 100% immediately; ③ **j/k paging**: j goes to the next card and k to the previous one (following the current sorted list), while the arrow keys still scroll the body and typing in an input never triggers paging; ④ **Reading width**: narrow/medium/wide (720/900/1180px) cycling with the preference remembered; ⑤ keeps the 0.10.42 header fix. Adds tests/card-reader-ux.test.mjs (4 guards). npm test now runs **399 checks** |
|
|
516
|
-
| **v0.10.42** | 2026-10-19 | **Card headers no longer get squeezed by long titles (from user screenshots)**. The old layout used space-between while h3 had no min-width:0 (so a long title could not shrink) and the action group had no flex:0 0 auto / nowrap, so the buttons were squashed into vertical stacks ("collapse", "close"). Now h3 shrinks and **ellipsizes on one line** (hover shows the full title), the action group is flex:0 0 auto + nowrap (never squashed), and the header may wrap (on very narrow widths the whole group drops to a second row and stays clickable); both multi-button headers (reader and export graph) share the .me-head-actions class. Adds tests/card-head-actions.test.mjs (3 guards). npm test now runs **395 checks** |
|
|
517
|
-
| **v0.10.41** | 2026-10-19 | **The knowledge graph initial view is zoomed out two steps**. The graph is canvas + sim.zoom with one wheel notch = x1.1, so "two steps" means x(1/1.1)^2 ≈ **0.826**: the initial fit() (first paint, after layout settles, and the Reset view button) now starts from the exact-fit scale and backs off those two notches, leaving room to drag and marquee-select around the edges; the wheel and the initial view **share the same ZOOM_STEP constant** so future step changes cannot drift apart; the existing clamps (0.35-1.8) stay, so a tiny graph is never shrunk out of sight. Adds tests/graph-initial-zoom.test.mjs (3 source guards, including the numeric factor). npm test now runs **392 checks** |
|
|
518
|
-
| **v0.10.40** | 2026-10-19 | **Follow-up to #45 (release-blocking): an enumeration timeout is no longer treated as an empty port, plus the macOS traffic-light overlap (#46)**. ① The reporter measured Get-NetTCPConnection at **4.1s to 61.4s** on that machine (huge TCP table) while takeover allows only 2.5s, so it timed out, returned empty output, became null, was read as "port freed", and the new watchdog could not bind 7999 and drifted to 8001 — **the old web survived forever and the 0.10.38 fix could never run**. Fix: on Windows we now try **netstat -ano first** (1.8s there and it already reports the pid; 403ms on this machine, pid and command line included) with PowerShell only as a fallback; portFree became **tri-state** (free / occupied / **unknown**) where a timeout means unknown, which **aborts the takeover** with an explicit log instead of drifting; status separates "response timed out" from "no service"; probe paths no longer leak sockets. ② **#46**: the macOS window buttons are an Electron **native traffic light** (titleBarStyle:hiddenInset) that no z-index or portal can cover, so the plugin now **follows the host safe area** --dsh-frame-overlay-top (68px on macOS, 20px in native fullscreen), keeping the overlay top edge at 68px while the scrim still covers the screen, so the close button no longer sits on top of the plugin UI. npm test now runs **389 checks** |
|
|
519
|
-
| **v0.10.39** | 2026-10-17 | **Wiring self-healing: codex TOML parsed per section, single quotes and escaping supported, plus a read-only startup check (items ②/④ of #44)**. ① Fixed a real bug: setup / align parsed ~/.codex/config.toml **without scoping to [mcp_servers.memory]**, so they picked up the first command = of another server (on this machine it read MCP_DOCKER docker.exe), and only double quotes were understood while codex itself writes single quotes — so the entry was **neither readable nor fixable, yet setup still printed "updated"**. Reading and writing now share pure helpers (section scoping, both quote styles, unescaping), the writer verifies the text really changed, is idempotent (already-aligned entries are not counted as changes) and honestly reports section unparseable when it cannot edit. ② A new read-only startup check staleWiringCheck() logs a warn in the auto-capture log when MCP/hooks reference a **non-existent** node path or an entry that differs from the current version (dsh-memory setup fixes it) — nobody runs align daily, so silent failures must become visible. ③ Verified locally: all three agent wirings are now ✓ in align (codex previously kept reporting "node path differs"). npm test now runs **379 checks** |
|
|
520
|
-
| **v0.10.38** | 2026-10-17 | **The claim is released once a takeover succeeds (fixing a 0.10.35 side effect), plus real-machine A/B numbers for #45**. In 0.10.35 the claim was never released on the path where the helper becomes the resident watchdog (that process never exits), so a restart within 60 seconds of a completed takeover was blocked as in-flight by itself (reproduced locally). bootWatchdog() is now immediately followed by releaseReplaceClaim(), making the mutual-exclusion window exactly "a takeover is in progress". **#45 verified on a real machine (same host, same batch of 20 /version-check calls)**: 0.10.36 gave 9 successes / **11 refusals**, TIME_WAIT 24 to **1976** (+1952, about 100 connections per request, i.e. the recursion depth) and a 2389 ms first frame; 0.10.37 gave **20/20 successes**, TIME_WAIT 1969 to **1971** (+2) and 18-34 ms. npm test now runs **376 checks** |
|
|
521
|
-
| **v0.10.37** | 2026-10-17 | **Fixes #45 (a severe regression I introduced in 0.10.36: the self-probing recursion in /version-check, amplified by retries), #44 (hardcoded node paths / zombie-watchdog misjudgement) and #43 (the official route fields for follow-session)**. ① **#45**: selfPort() is this process own port, yet /version-check called probeServedVersion to **request /version-check from it** — handler to itself to handler, with depth bounded only by timeouts and a new connection per level, which saturated the listen backlog and produced **intermittent ECONNREFUSED (17–37ms)**, 4402 extra TIME_WAIT entries in 40 idle seconds and an empty served value; the 0.10.36 retry multiplied it by three. Self-probes now **answer locally** (probedSelf:true, no HTTP), /restart-self has the same gate, and /version-check accepts ?port= for other ports. ② **#44**: nodeBinary() now **verifies existence** before using a cached path and falls back to PATH when execPath vanished (brew upgrade removing the old directory), so long-lived watchdogs heal themselves; inspectResident gains portServing and decideResidentAction treats "the lock says a watchdog is alive but the port answers nobody" as restart (watchdog-port-dead) instead of delegate; status prints the actual node binary and whether it exists. ③ **#43**: sessionRouteFrom() is async and prefers the official fields, session.requestHeader().config then agent.options, with the old sniffing as a fallback. npm test now runs **375 checks** |
|
|
522
|
-
| **v0.10.36** | 2026-10-16 | **Both new states now surface at every entry point (align / doctor), plus one test-hygiene fix**. ① align gains a finding: **the resident instance is host-managed** (the lock is unwritable, so the watchdog path is bypassed and the host stops/starts the instance itself and keeps it alive) — **warn level**, stating explicitly that "the instance is managed, it is not an orphan, and it returns to the watchdog once the lock becomes writable" (align.ok is unaffected; warn is not a failure); ② dsh-memory doctor prints a new line **resident instance management:** (watchdog / host-managed / cannot replace (environment limit)), so together with journal mode, write probe, lock writability and the backup list you can see at a glance which state a machine is in; ③ **test hygiene**: the /restart-self alreadyCurrent case depended on the **developer machine real lock file** (my local lock held only an endedAt record, so it stopped reporting alreadyCurrent and the check went red) — reading the lock is now injectable (deps.readWatchdogLock) and the case injects a live registration, making it independent of the dev machine. npm test now runs **371 checks** |
|
|
523
|
-
| **v0.10.35** | 2026-10-16 | **Single-flight for takeover helpers (issue #30, sixth retest round: the host tick and the CLI each spawn a --replace and step on each other)**. The reporter observed that during a failed restart **two takeover helpers were alive on the same port without knowing each other** (parent = the DSH host / parent = the CLI): neither wrote the lock and each reaped the other web, making the scene even harder to diagnose. Fix: a new **claim file** (memory-eternal-replace.claim, next to the watchdog lock) — ① **a helper claims it exclusively before starting**; if it cannot, it **exits immediately** (yielding to whoever is already running) and releases it on exit; ② **when restartResident sees a takeover in flight** (holder alive and less than 60s old) it **does not spawn a second helper**, returning stage in-flight with who is running it and how long ago it started; ③ **fail-open**: in restricted environments where the claim cannot be written the takeover still proceeds (permission problems must not block takeover for good), but the reason is surfaced in the result and the log. Tests cover: first-come-first-served / a live holder makes the second yield / stale and dead-pid claims can be taken over / only your own claim is released / the in-flight branch of restartResident. npm test now runs **370 checks** |
|
|
524
|
-
| **v0.10.34** | 2026-10-16 | **Root fix: when the lock is unwritable the HOST manages the resident instance itself (adopting the reporter's fourth point in issue #30)**. Their diagnosis: on that machine the "registration" happens to live in the only process that cannot write the lock, so automatic replacement is permanently refused. Fixes: ① the host's `autoWebMode=interval` keep-alive loop now probes **the version served on the port**, not just liveness — if it lags and the lock is writable the watchdog/drift path still handles it (the host does not compete); if it lags and the **lock is unwritable** the **host takes over**: `ensureWebServer({forceTakeover:true})` stops only **our own** web, starts a new instance on the **same port** and keeps it alive; ② a testable `takeoverOwnWeb()` with a safety gate that only acts on occupants where `looksLikeOurWeb` is true (foreign processes are never touched); ③ "host-managed" is recorded as state (`recordHostManaged`) so `/version-check` reports `resident.managedBy='host'` and the panel says "**host-managed**" instead of "unmanaged / orphan"; the state clears itself once a watchdog registers again. `npm test` now runs **366 checks** |
|
|
525
|
-
| **v0.10.33** | 2026-10-15 | **Distillation provider: dynamic dropdowns, best-effort follow-session, first-candidate-only and a test button (first slice of issue #42)**. ① **Dynamic dropdowns**: a new read-only `/llm-routes` endpoint (backed by the host's listProviders/listModels) turns the "distill provider / model" fields from hand-typed IDs into selects (falling back to text inputs when unavailable); ② **Follow the current session** (their top priority): the new `captureFollowSession` config makes host distillation **try** the session's provider/model first (`sessionRouteFrom()` sniffs `agent / session / session.route / session.options`), and **falls back to the explicit config with a log line** when nothing is exposed (DSH does not guarantee a route on the session object — that log is the basis for a future adapter rather than a guess); background/no-session tasks (CLI, watchdog, hooks) can only use the explicit config; ③ **First-candidate-only + test button**: `captureFirstOnly` makes routing take just the first candidate and stop on failure (no more "hit the official provider first, burn a call, then rotate"); `POST /llm-test` makes one small real call and echoes **provider / model / latency / candidate count / error code**, which also verifies ① and ②; ④ also fixes **probe jitter**: `probeServedVersion()` now retries with backoff three times (the reporter measured "version-check 200 at one moment, then status says no service 2s later"), so single-shot probes are no longer fooled by restart windows or transient refusals. `npm test` now runs **363 checks** |
|
|
526
|
-
| **v0.10.32** | 2026-10-15 | **"The resident instance cannot be replaced" becomes a long-lived state instead of a retry on every boot (issue #30, fifth retest round: the steady state on the A: drive machine)**. The reporter measured 44 "version drift" events over five days, with a mechanism where **every step works as designed yet the instance can never be replaced**: upgrade → the port still serves the previous generation → the host detects drift → it spawns a `--replace` helper → the helper pre-flights and finds `lock.writable=false` → it **correctly refuses** (the 0.10.29 design, zero damage) → the old instance survives → the next boot reports it again. Fixes: ① new `lib/resident-state.js` **records "cannot replace (environment limit)" as state** (cause / lock path / port / versions / count, valid for 7 days); ② the host now **probes lock writability before restarting** and, when unwritable, **stops retrying a doomed replacement**, recording just one line — `warn` the first time, then downgraded to a `boot`-level "known state" on later boots (the reporter's words: they do not want a warn on every boot forever); ③ the wording is explicitly split from "drift": **"the resident instance cannot be replaced (environment limit)"** plus the way out (fix permissions then restart, or run `dsh-memory watchdog --port N` in the foreground); ④ the panel, `/version-check` and `align` all surface the state (`resident.replaceBlocked`; in `align` it is a `warn` finding that clears itself once the environment is fixed). `npm test` now runs **359 checks** |
|
|
527
|
-
| **v0.10.31** | 2026-10-15 | **Root fix for `restart` failing 100% of the time on machines with slow port enumeration (issue #30, third/fourth retest rounds — the reporter traced it to the line)**. Mechanism: the `--replace` helper ran "clear the scene → only then write the lock if the port is free → start the watchdog", while `Get-NetTCPConnection` inside the clear step measured **4–13.6 seconds** and was **not bounded by the 8-second budget** → the helper **never reached the lock write** → the lock stayed empty → `waitForTakeover` (which requires a fresh live watchdog in the lock) **was guaranteed to time out** → on such machines both the CLI and the panel button report "(no response)" **100% of the time, regardless of version**. Fixes: ① **a failed clear step still leaves a registration** (`acquireWatchdogLock({force:true})`), so "a web on the port with an empty lock" can no longer happen; ② **`acquireWatchdogLock` gains `force`** (the whole point of `--replace` is to replace that same-port instance, so it must not yield — otherwise the helper exits before the clear step finishes); ③ 0.10.30 already bounded the enumerator (`defaultExec` kills the child on timeout, `findPortListener({timeoutMs})` threads it through, the clear step uses ≤2.5s); ④ the CLI's `restart` result is now **actually written to the auto-capture log** (the hint always claimed so, but the CLI path never did). They also measured "two competing takeover helpers" (the host tick and the CLI each spawning one) — with registration plus force, that race is far less harmful (the later one takes over the registration instead of both leaving nothing). `npm test` now runs **355 checks** |
|
|
528
|
-
| **v0.10.30** | 2026-10-15 | **Two fixes for "the whole page is unusable": malformed tags causing a black screen (#41) plus two real defects in the takeover path (issue #30, third/fourth retest rounds)**. ① **#41**: a string `tags` value sent to `/write` (e.g. `"[\"a\",\"b\"]"`) was passed straight into `writeCard`'s `JSON.stringify` → **double-encoded** in the database → read back as a string → the client's `card.tags.slice(...).map` threw → **the React root failed to mount and the whole page went black** (restarting DSH, the plugin or the resident instance changed nothing, because the bad data lives in the DB). `normalizeTags()` is now the **single entry point**: arrays, JSON strings, comma strings and **legacy double-encoded rows** all collapse to arrays, used by the write path **and every read path** (existing bad data needs no manual repair); the render layer also guards itself (bad tags cost a few chips, never the tree). ② **#30**: the reporter found two more issues inside my 0.10.29 — `restoreWatchdogLock`'s "live registration" test **missed `!w.endedAt`** (so with a reused pid the write-back was abandoned), and **Windows port enumeration had no timeout** (`Get-NetTCPConnection` measured at 4–7.5s, once 13.6s, while the takeover's 8s budget could not bound it → decisions were made on stale data). `defaultExec` now supports a timeout and actually kills the child, `findPortListener({timeoutMs})` threads it through, and the takeover passes a bounded value (≤2.5s). Adds `tests/tags-normalize.test.mjs` (4 checks) and 2 regressions in `watchdog-restart`. `npm test` now runs **354 checks** |
|
|
529
|
-
| **v0.10.29** | 2026-10-14 | **A failed takeover no longer wrecks the crime scene (key finding from the issue #30 retest)**. The reporter measured on 0.10.26 that after a failed `restart` the **lock file was deleted** while the same web kept listening — the scene then looked as if a resident had never existed, leaving only an orphan. Four fixes: ① **releasing the lock no longer deletes the file** (`releaseWatchdogLock` leaves an "ended" record with `endedAt`; `status` shows "exited (stale record)", and such slots **never block** a new instance, even with pid reuse); ② **a failed restart writes the pre-replacement registration back** (`restoreWatchdogLock`, only when no live registration exists for that port); ③ **failing to enumerate the occupant no longer counts as "port free"** — `clearResidentForTakeover` now **double-checks with TCP** (in restricted environments the process query returns nothing, and the old code concluded the port was free, so the new web could not bind, the self-check reported "(no response)" and the lock was wiped); if something is still listening the takeover aborts and **touches no registration at all**; ④ **refuse to take over when the lock file is unwritable** (an unregisterable instance would only become another orphan — it now errors out and points at running the watchdog in the foreground). The restart failure message also names **who is on the port now** and whether the lock was written back, and `status` now **labels the port probe and the lock registration as separate sections** (the reporter pointed out the old output mixed the two and misled readers). `npm test` now runs **348 checks** |
|
|
530
|
-
| **v0.10.28** | 2026-10-14 | **New "version alignment check": see at a glance what stays stale after a plugin upgrade**. Upgrading only replaces **files on disk**, and two kinds of "old" do not go away by themselves: ① **runtime** — the event subscriptions the DSH host registered in memory (`agent/turn-stopping`, `agent/inbox/claimed`, …) come from **the code it loaded at startup**, so without restarting DSH it keeps using the old interfaces (that "turn-stop event appears broken" alert is almost certainly this: measured here, host **v0.10.8** against disk v0.10.27 — and the alert text itself, with its cumulative "135/75", proves the running code predates 0.10.20); ② **persisted wiring** — the MCP and hook entries the plugin writes into `~/.claude.json`, `~/.claude/settings.json`, `~/.codex/config.toml` and `~/.cursor/mcp.json` carry **old install paths, old node paths and old arguments** that an upgrade never rewrites. Adds `lib/align.js`, `GET /align`, `dsh-memory align [--json] [--fix]` and a panel card (usage/today → 🔧 version alignment check) that lists each finding with an **actionable fix** (`fully quit and reopen DSH` / `dsh-memory restart --port N` / `dsh-memory setup` to rewrite the wiring / update the plugin); `--fix` rewrites only the plugin's own keys (your data and config are untouched). Adds `tests/align.test.mjs` (6 checks, including a wording guard for "same node but stale entry"). `npm test` now runs **345 checks** |
|
|
531
|
-
| **v0.10.27** | 2026-10-13 | **"Rebuild from backup" goes from guidance to something you can run (the practical way out for issue #31)**. Once a WAL header is corrupt, code cannot repair the **data** — the previous version could only tell you which backups exist and in what order to swap files; now `dsh-memory doctor --restore latest\|<name>` does it: **without `--yes` it is a dry run** (not a single file touched, and it tells you how many cards the backup holds and whether it would overwrite a writable database). Execution passes four gates: ① **validate the backup** (open read-only + `PRAGMA quick_check` + count cards); ② require an explicit `--force` when the current database is healthy **right now** (decided by a live probe, not a cached health verdict); ③ **abort** if a resident watchdog is still alive (no swapping while writers are active, unless `--force`); ④ **re-verify** afterwards (reopen + write probe + card count). The original scene is **always renamed to `*.corrupt-<timestamp>` and never deleted** (the DB plus `-wal` and `-shm`). Adds `tests/db-restore.test.mjs` (4 checks: rebuild from a broken DB / healthy DB needs force and a dry run touches nothing / dangerous sources refused / health verdict refreshed). `npm test` now runs **339 checks** |
|
|
532
|
-
| **v0.10.26** | 2026-10-13 | **Lock contention now retries asynchronously at the application level instead of using `busy_timeout` (the measured remedy for issues #31/#33)**. Local controlled experiment: while an external process held a write transaction we immediately got `database is locked`, and it **stayed locked even after that process was killed** — the old code had no waiting at all, so a single collision failed permanently (on restricted or externally-occupied drives like A: this shows up as "the whole memory database is broken"). The review rejected `PRAGMA busy_timeout` (`DatabaseSync` from `node:sqlite` is a **synchronous API**, so busy-waiting blocks the host event loop and freezes all of DSH), so we added `withDbRetry()` instead: on a lock, **yield the event loop, sleep briefly and retry** (linear backoff, up to 3 attempts), only on **write routes**; once headers have been sent retrying is explicitly forbidden (`e.noRetry`, preventing double writes), and non-lock errors (data/syntax) are tried once and rethrown unchanged. Tests cover: retry until success / one attempt for non-busy errors / `noRetry` honoured / exhaustion / a source guard against sneaking `busy_timeout` back in. `npm test` now runs **335 checks** |
|
|
533
|
-
| **v0.10.25** | 2026-10-13 | **"Cannot be determined" no longer masquerades as "unmanaged", plus a unified UX for an unwritable DB (closing layer ③ of issue #33)**. ① **Managed state becomes three-valued**: `/version-check` now also reports `resident.managedKnown`, `lock:{path,writable,error}` and `pid` — in restricted environments (sandbox write bans, ACLs, non-system drives) where the watchdog lock file cannot be written, the panel no longer says "unmanaged" (which made users think the instance was broken and restart it repeatedly) but "**management state unknown**: the lock file cannot be written, so it cannot be determined; keep-alive usually still works", together with the real cause and path; `writeWatchdogLock` failures are no longer fully silent either (one traceable line per process). ② **Unified UX when the DB is unwritable**: write routes (delete / merge / card / refine-apply / optimize-execute / …) now **fail fast at the entrance** (503 + `DB_UNAVAILABLE` + recovery guidance), and any write path not enumerated is caught by the outermost wrapper translating raw SQLite text into the same reply; the panel shows a **cross-tab banner** at the top: "the memory database is currently not writable — card reads/writes will fail, but **settings can still be saved**". ③ `dsh-memory doctor` now reports watchdog lock writability (visible at a glance in restricted environments). `npm test` now runs **334 checks** |
|
|
534
|
-
| **v0.10.24** | 2026-10-13 | **"Same goal, different approach" now clusters too (third and final slice of issue #36)**. The two previous versions covered "merge duplicates" and "judge conflicts", but the last piece — one purpose, several workflows/codebases — is **undetectable by body similarity** ("compress with 7z", "distribute as tar.gz", "slim the npm package" read very differently yet share a goal). Approach: **the model first writes one line per card stating what problem it solves, then we cluster by goal** — goals are cached in `refine-goals.json` (each card extracted once; a second scan makes zero calls) and clustered with union-find; hits are grouped as "one goal, N cards" with two actions: **keep all** (records pairwise rulings so it stops asking) or **merge this group** (via the existing /merge; the new card enters the audit queue). *Read-only throughout — the only write is the goal cache.* Entry points: the panel (organize → 🎯 same goal, different approach) and APIs `/refine-goals`, `/refine-goals-keep`; CLI `dsh-memory refine --goals --keep --path A --path B` (the clustering itself needs a model, run it in the panel). `npm test` now runs **332 checks** |
|
|
535
|
-
| **v0.10.23** | 2026-10-13 | **Existing "injected junk cards" can be cleaned up in one click (second slice of issue #36)**. The previous version stopped such cards from being **created** (skill catalogs / `<system-reminder>` arriving as user messages were captured as knowledge after their tags were stripped), but the **backlog remained**: a scan of a real vault immediately found **32** cards unrelated to any conversation (catalog lines like `- \`code-review\`: Review the changes…`, fragments like `用户system-reminder-you-are-teammate-c…`). Adds `findInjectedCards()` (a **proposal only**, it changes nothing; criteria: injection tags present / catalog lines ≥30% of the body / under 200 characters left after stripping / a fragmentary title) and `purgeInjected()` (**goes to the recycle bin, recoverable**, reporting failures per card). Entry points: the panel (organize → 🧹 injected junk cards: scan, recycle one, recycle all) and the CLI `dsh-memory refine --junk [--purge --path P] [--json]`; API `/refine-junk`, `/refine-junk-purge`. `npm test` now runs **331 checks** |
|
|
536
|
-
| **v0.10.22** | 2026-10-13 | **"Conflict scan": similar cards may be merged, conflicts must be judged by a human (first slice of issue #36)**. Until now `/optimize` merged **blindly** on similarity (≥0.55) — yet two semantically close cards with mutually exclusive conclusions score just as high (0.93 measured), so a blind merge quietly "reconciles" the conflict, and `mergeCards` **permanently deletes** the sources. New `lib/refine.js` plus a "organize → 🧭 Conflict scan" panel section: ① **wide recall** (≥0.30, upper bound lifted to 1.0, deliberately including the near-duplicate band — the most dangerous one for blind merging); ② the model only classifies each pair as "duplicate / complement / conflict / unrelated" and states the conflict in one line plus who to keep; ③ **the human decides**: keep A / keep B / keep both / merge; the **scan never writes**, and `keepA|keepB` only moves the yielding card to the **recycle bin** (recoverable); ④ rulings are recorded in `refine-decisions.json` so the same pair is not raised again; ⑤ **safety interlock**: pairs ruled conflicting are **excluded from one-click optimize** entirely. Also fixes one source of redundancy: skill-catalog injections (`- \`skill-name\`: …`) are no longer captured as junk cards (real examples already existed in the vault). API: `/refine`, `/refine-scan`, `/refine-apply`; CLI: `dsh-memory refine` (lists candidates without calling a model; `--apply` records a ruling). `npm test` now runs **329 checks** |
|
|
537
|
-
| **v0.10.21** | 2026-10-13 | **"DB unavailable" goes from silent to visible (issue #33, scoped by review)**. ① **Health check at open**: the old code ran `PRAGMA journal_mode=WAL` and assumed success (that pragma does not throw on failure — it just returns the actual mode), so a read-only file or unwritable directory let the plugin run on silently; now the real journal mode is **read back** and a **write probe** runs (create then drop a table, in the main DB rather than temp), the verdict lands in `dbStatus()`, and an unwritable DB immediately logs the reason plus where to look. ② **`/overview` degrades instead of 500**: when the DB or directory is unavailable it no longer returns 500 (probes, the panel and scripts read 500 as "this port is broken / not our service", which historically started the restart storm and port drift); it now returns 200 with `degraded:true`, `dbError` and `db:{journalMode,writable,error}` while keeping the `vaultDir` / `version` identity fields — so "this is us, but the DB is broken" can finally be expressed separately. ③ **New `dsh-memory doctor`**: DB health self-check (real journal mode / write probe / backup list / recovery guidance) with `--json` for scripting; when the DB is unwritable or suspected corrupt it prints **guidance that never touches data** (stop writers → copy the scene aside → list backups → swap → re-verify). **Deliberately not done** (review conclusion, now guarded by tests so nobody adds it back casually): `busy_timeout` (`DatabaseSync` is a synchronous API, so waiting on a lock blocks the host event loop) and automatic WAL→DELETE downgrade (it **permanently** changes the DB mode, and a rollback journal has worse concurrency, which could worsen lock contention). `npm test` now runs **322 checks** |
|
|
538
|
-
| **v0.10.20** | 2026-10-12 | **Fixing the three-layer "DB unavailable → the watchdog kills its own web → the port drifts to 8006" scene (issues #31 / #33, measured by AkiMei0721)**. ① **A non-200 `/overview` is not a dead port**: `/overview` touches the database, so a read-only / WAL-corrupted DB makes it 500, and the watchdog declared the port offline → `spawnWeb()` (which first SIGTERMs its own previous child) → **it killed and respawned its own healthy web, drifting the port from 7999 all the way to 8006 (six instances)**; a new pure `decideTickAction()` treats **a reachable TCP port as alive** (`api-degraded-but-listening`, resetting the restart counter), and the readiness check for a new instance likewise accepts "listening but `/overview` non-200". ② **The version probe now asks `/version-check`** (no database access; falls back to `/overview`) — the root symptom was "the port is clearly alive yet no version comes back", so verify reported "(no response)" and `status` even misjudged it as "a pre-v0.10.4 build". ③ **No more crash in restricted sandboxes**: the default `stdio:'pipe'` makes `spawn()` **throw EPERM synchronously** (the old code only guarded the `'error'` event, which never ran), so `dsh-memory watchdog --reap` crashed and process enumeration stayed permanently empty; `spawn` is now wrapped in `try/catch` and stdout goes through a **temp file**, touching no named pipes. ④ The stall check also gained a "minimum uptime" (30 minutes): a freshly restarted process necessarily has zero stops, so it must not report "turn-stop events are broken" (a false alarm was measured 1.5 minutes after a restart). Adds `tests/issue-fixes-8.test.mjs` (4 checks) plus a too-early case in `tests/stall.test.mjs`. `npm test` now runs **318 checks** |
|
|
539
|
-
| **v0.10.19** | 2026-10-12 | **`UNSUPPORTED_REASONING_EFFORT` no longer kills the whole distillation (issue #34)**. The scene (zafir-zhong and others): many **gateways** claim reasoning-effort support in their capability list and only reject `reasoningEffort: 'off'` when it is actually sent (measured: workbuddy/hy3, FallbacksChain/Auto, new-api-resp/gpt-6-luna), so the entire candidate is marked failed and the pipeline moves to the next provider — auto-capture stops dead for those users (that FAIL shows up in the diagnostics of #37 / #38 / #40 too). The existing design only covered "the provider **says** it does not support it → do not send the field" (#15); the runtime half was missing. Fix: an in-process memo `markEffortUnsupported()\/isEffortUnsupported()` — on the first rejection the call **retries once without the field** (same candidate, same provider), remembers that `provider/model`, and from then on the first request for it carries no effort at all; the memo is **keyed by provider/model**, so other providers are untouched. Guards added in `tests/capture-route.test.mjs` (sent → rejected → retried without and got a card → later only one request → other providers unaffected). `npm test` now runs **313 checks** |
|
|
540
|
-
| **v0.10.18** | 2026-10-12 | **"Save config" was being rejected unconditionally (issue #35)**. The scene (Dzifxazy): a fresh install, change nothing, hit save → **HTTP 400 "field must not be empty: secretHint"**, the whole submission rejected, so none of the numbers or toggles the user did change were stored; the wording also pointed troubleshooting at "some numeric box left blank". Root cause: the host-side pre-check treated **any empty string** as "numeric field not filled" and only exempted a hard-coded list (vaultDir / captureProvider / captureModel / recallEmbedding / activeVault) — while `secretHint`'s default **is** an empty string (`z.string().default('')`) and was not on that list; since the panel submits the whole form, every save hit it. Fix: new `emptyNumericFields(patch, Config.dict)` decides which keys were cleared by looking at the schema (`type === 'number'`), and the hard-coded list is gone (future nullable string fields need nobody to remember an exemption). Adds `tests/config-validate.test.mjs` (2 checks: empty strings only count for number fields — including a brand-new string field that appears on no list as a regression nail, plus a source guard against reintroducing the list). `npm test` now runs **312 checks** |
|
|
541
|
-
| **v0.10.17** | 2026-10-12 | **Root fix for the "resident is serving but nobody manages it" self-contradiction (issue #28)**. The scene: after upgrading to 0.10.16 the reporter had no resident instance at all, and the panel showed **neither a restart button nor any hint** (the banner only rendered when `vc.stale`, which is false when the host and the port report the same version), only an "auto-capture anomaly"; reproduced the other half locally: after `stop` the port was **still serving**, the lock was empty, and `status` said "no registered watchdog" — because that web had been spawned directly by the host's `ensureWeb()` (no `webPid` in the lock) and `stop` only killed the watchdog. Five fixes: ① **`/version-check` now reports `resident.managed`** (a live watchdog in the lock is what makes it managed) — "serving" is not "managed"; ② **panel**: both the **not-running** and the **unmanaged** resident show a restart button with dedicated wording ("not running" / "unmanaged") instead of only the version-drift case; ③ **`/restart-self`**: `alreadyCurrent` now requires a version match **and** a live manager, otherwise it replaces as usual (an orphan web does not get to stay just because its version is new); ④ **an explicit `dsh-memory stop` frees the port** (`forcePortOccupant: true`, same gate as ever: only processes `looksLikeOurWeb` recognises are terminated); ⑤ **the self-check now demands a fresh takeover**: `restartResident` no longer accepts "the port serves the expected version" (before and after an upgrade the version is often identical, so the **old** instance fooled the check — measuring "self-check passed (waited 0s)" immediately followed by `status` reporting no registered watchdog); the new `waitForTakeover()` requires a matching version **and** a live watchdog whose pid is not in the pre-replacement set, and failure reasons are split accordingly into "wrong version" vs "nobody took over". Measured on this machine in the same scenario: `41672/17432` before → `28136/31976` after, `restart` waited 13s and passed, and the **very next `status` showed the new watchdog registered** (no contradiction). `npm test` now runs **310 checks** |
|
|
542
|
-
| **v0.10.16** | 2026-10-09 | **`--reap` no longer says "nothing was reaped" when it did reap something** (reported by alario-tang, measured on macOS with 0.10.13). The scene: after `kill -9` on the watchdog leaves a `PPID=1` orphan web, `dsh-memory watchdog --reap --port 7999` really freed the port and really killed the web, yet the screen read "**scanned 1 watchdog process, cleaned 0 orphans**" — because 0.10.13 made `scanned` include webs while `webKilled` was never printed. Fix: a pure `formatReapSummary()` unifies the wording (scan counts split into `watchdog A / web B`, cleaned entries listed per kind with pids), `reapStaleWatchdogs` gains `scannedWatchdogs` / `scannedWebs` (`scanned` stays their sum for older callers), and the CLI uses it. Two checks added (never say "cleaned 0 orphans" when a web was reaped / scan counts must be split), and `tests/watchdog-orphan-web.test.mjs` now records the reproduction note: **such an orphan can only be created with `kill -9` — SIGTERM runs the watchdog's graceful exit, which also stops the web it spawned** (the same coin as #19's "on Windows SIGTERM is unconditional termination"). `npm test` now runs **308 checks** |
|
|
543
|
-
| **v0.10.15** | 2026-10-09 | **Three loose ends left by the upgrades.** ① **Recommended plan A no longer reinstalls the call we just saved**: v0.10.12 raised the default distiller output cap to 4000 (removing the wasted "hit 2000, then re-run the same candidate at 4000"), but the panel one-click preset "A (light)" still hard-coded `captureMaxTokens: 2000` — one click brought the retry back; it now matches the default, guarded by a source check (`tests/csrf-origin.test.mjs` asserts planA cap === default). ② **The README "stall watchdog" description now matches the new rule** (both languages): the old wording said "if no turn-stopping event arrives for 15 minutes", while it is actually "**zero stops since this process started** plus turns still starting inside the last 20 minutes → record a `warn`", and it now states that an in-flight **long turn is never mistaken for a stall**. ③ While at it, the other log wording `轮次收尾事件未触发(agent/turn-stopping 没到)` was verified to have last fired on **2026-09-24** — a historical remnant of the old heuristic with no such code path left today (nothing to fix). `npm test` now runs **306 checks** |
|
|
544
|
-
| **v0.10.14** | 2026-10-09 | **The "auto-capture anomaly: turn-stop event may be broken" false alarm is fixed at the root.** The scene: during a long turn lasting tens of minutes, `agent/inbox/claimed` fires **repeatedly inside one turn**, while `turn-stopping` only arrives when the turn really ends — so any 20-minute window necessarily shows "≥3 starts, 0 stops", and the check mistook an **in-flight** turn for a renamed event, pushing a red alert at the user (the "39/18" line measured in this session came from exactly that). Fix: one hard precondition — "the event was renamed/removed" is a **process-lifetime** property, not a window property: if this run has seen even one stop, the wiring works and 0 stops in the window merely means the current turn is still running (`evaluateStall` gains `everStopped` and reports `reason: wiring-proven | no-stop-ever`); only "zero stops since this process started **and** continuous starts inside the window" alerts. At the same time the alert is downgraded from `fail` to `warn` — an inference no longer paints capture health red nor masquerades as an auto-capture anomaly (real failures still turn it red on their own). Three checks in `tests/stall.test.mjs` lock the old and new behaviour (long turn stays quiet / a real rename alerts / `everStopped` injectable). `npm test` now runs **305 checks** |
|
|
545
|
-
| **v0.10.13** | 2026-10-09 | **Orphan `web.js` processes are finally reachable (#11 item 1, the POSIX branch)**. The scene (alario-tang, macOS arm64): the watchdog died first and the lock file was gone, leaving a stale web on port 7999 with `PPID=1`; `stop`/`restart` read lock slots and found no target, `watchdog --reap` only matched `watchdog.js`, and the panel button rides the same path — so drift could be reported but never healed, and the user had to `kill` by hand. Two concrete gaps: ① **on POSIX the port occupant only came back as a process name** — `lsof -Fpcn` yields `node` for the `c` field rather than a command line, while the "should we reap it" test (`looksLikeOurWeb`) requires `memory-eternal` or this install web.js absolute path in the command line → always false → the fallback loops in `stopWatchdogs` / `clearResidentForTakeover` **never killed anything** (Windows uses `Get-CimInstance`, which returns the full command line, so this box never showed it); now a follow-up `ps -p <pid> -o args=` fills in the command line, falling back to the old behaviour when unavailable (better to miss than to misfire), with an injectable `platform` for tests; ② **`--reap` now covers our own webs**: a `listWebs` branch reaps "same port + our web + **its keeper is gone** (that slot watchdog is not alive) + outside the protected set (keepPid / current pid / parent / a `--reap` sibling)", while **a web whose keeper is alive is never touched**, and other ports or foreign `web.js` processes are never killed. Adds `tests/watchdog-orphan-web.test.mjs` (5 checks) and makes two watchdog-only cases state `listWebs: []` explicitly (otherwise reap enumerates the real process table). `npm test` now runs **302 checks** |
|
|
546
|
-
| **v0.10.12** | 2026-10-09 | **Two quiet things that either burn money or leave a hole every day.** ① **Default distiller output cap: 2000 → 4000 (the hard ceiling)**: the logs kept showing "hit the maxTokens=2000 cap → retry the same candidate at 4000", and each occurrence **wasted one LLM call** (hit the cap, then re-run the same candidate at 4000). `maxTokens` is only a ceiling and billing follows what is actually generated, so the default now sits at the hard ceiling: those turns finish in one call; `maxTokenLadder` stays for users who **explicitly** lower `captureMaxTokens`. ② **A cross-site write gate on the plugin API**: `/memory-eternal/api/*` is unauthenticated, so any local web page could POST `/config`, `/audit/approve`, `/write`… New `crossOriginError()` — state-changing requests (non-GET/HEAD/OPTIONS) must be **same-origin with the Host of the request itself**: browsers always send `Origin` on cross-site requests (a mismatch returns 403 `CROSS_ORIGIN_BLOCKED`), while same-origin pages (DSH settings/memory page, the standalone page) and LAN access stay unaffected, curl / CLI / MCP calls without `Origin` keep working, and desktop-shell schemes like `app://` are allowed so nothing breaks. Both the intercepted host routes (`/config` POST, `/restart-self`, `/mcp/action`…) and the shared api entry carry the gate. Adds `tests/csrf-origin.test.mjs` (4 checks: decision matrix incl. LAN / no Origin / null / app://, a cross-site POST getting 403 without reaching business logic, both entry points present, and the default cap equal to the hard ceiling with no ladder retry). `npm test` now runs **297 checks** |
|
|
547
|
-
| **v0.10.11** | 2026-10-09 | **One more layer of tolerance in distiller-output parsing: unescaped quotes inside string values (`Expected ',' or '}' after property value`)**. The scene: `UNPARSEABLE_OUTPUT` dominates the `fail` log (103 of the last 600 entries); the freshest one carries the full picture — `JSON.parse 原始报错:Expected ',' or '}' after property value in JSON at position 1913`, with an excerpt whose body reads `运行 "npm test" 时…`: the model wrote a quote into the string as content, so the parser took it for the string terminator. That is a **different** class from the bare control characters fixed in #24 (a control character is illegal inside a string, a quote merely needs escaping). Fix: new `repairUnescapedQuotes()` — a `"` only ends the string when the next non-space character is `:`, `,`, `}`, `]` or end-of-text, otherwise it is escaped as content; it participates **only as the last candidate** and the result must still pass the card health check (usable title + body ≥20 chars) — better to fail than to write a bad card. Guarded by `tests/capture-json-repair.test.mjs` (4 checks: identity on valid JSON, the live unescaped-quote case, control chars + quotes combined, truncation still failing honestly). `npm test` now runs **293 checks** |
|
|
548
|
-
| **v0.10.10** | 2026-10-09 | **The review centre can finally show card bodies, and you can approve/reject in place (issue #27)**. The scene (thinrflbtlm): the review centre listed only "title / kind / source / severity / reason" and **never the body**; and since v0.10.0 pending cards live physically quarantined, while the library lists approved cards only — so "no review without manual confirmation" made auditing pointless. ① Every row gains a ▸/▾ toggle (clicking the title works too) that **expands the frontmatter + body in place**, rendered with the **same Markdown pipeline as the card reader** (`renderMd` + `splitFrontmatter`), not raw text; ② the body is fetched **on demand** (`GET /card?path=…&status=pending|rejected`; the server still only allows paths that really sit in the queue, unknown paths keep returning 403 `CARD_NOT_APPROVED`), so the list endpoint never ships a whole store of bodies; ③ the expanded block carries **✓ approve / ✕ reject** directly (the recycle-bin tab uses ✓ restore / 🗑 delete), while bulk selection and the toolbar stay as they were; ④ approving/rejecting collapses and refreshes automatically, with both language dictionaries updated. Adds `tests/issue-fixes-6.test.mjs` (2 checks: a pending body is readable and leaves the queue once approved, plus source guards for on-demand fetching and the deny code). `npm test` now runs **289 checks** |
|
|
549
|
-
| **v0.10.9** | 2026-10-09 | **Root fix for the upgrade-window port drift that turned the memory page into "connection refused" (issue #29)**. The scene: a detached instance left by the old package sat on 7999 without reporting a version → the host `ensureWebServer()` could not recognise it, drifted to 8000 and wrote that into `web-info`; the plugin's self-heal then reaped the instance on 8000 as a stray → `web-info` dangled on a dead port (the page loaded once, then "connection refused 127.0.0.1" after a refresh). ① **The library page now uses a host same-origin shell**: the host gains `/memory-eternal/ui/app` (same index.html as the config page `/ui/config`, where `<script src="app.js">` resolves to `/ui/app.js`), so the iframe address is **decoupled from whichever port the resident instance listens on** (an older host still falls back to `web-info`). ② **`/web-info` converges live**: before answering, it probes "configured port → recorded port", uses whichever is serving, and reports `alive:false` when neither is (no more advertising a dead address); it is also rewritten right after a successful self-heal restart and whenever the interval keeper sees the instance alive. ③ **No more blind port drift**: new `looksLikeOurWeb()` (lenient detection of "ours but not reporting a version": any `ok:true` from /overview, /web-info or /budget) gives self-heal a 6s takeover window before drifting, and logs it when it does drift. ④ **Backoff retry in the health check**: `inspectResident()` no longer concludes "old instance that does not report its version" from a single missed probe (that verdict triggers a needless replacement on every start), it retries with backoff first. Adds `tests/issue-fixes-5.test.mjs` (4 checks). `npm test` now runs **287 checks** |
|
|
550
|
-
| **v0.10.8** | 2026-10-08 | **Saving on the standalone web page now takes effect on click: the pending apply moves from a plugin callback into a host HTTP request (design A, measurement-driven)**. The scene: a standalone-page save ended in a `dropped` patch after five retries, always with `HMR transactions cannot be nested` (0.10.7 only stopped it from masquerading as an auto-capture anomaly and filtered upgrade-window keys — the sync itself was not fixed). **Measured on the same host with the same `settings.update`**: called from a plugin **callback** (`setInterval` / `fs.watch`) → guard error and **nothing was committed at all** (a patch with only host-known keys and a genuinely different value failed the same way); called from a plugin **HTTP request handler** → **it really committed** (`recallSummaryLen` 130→132→130, `revision` 4→6). So the fix is not another API but another **calling context**: ① the host gains `POST /memory-eternal/api/drain-pending`, applying the pending patch inside a **request handler** (two gates: loopback origin plus a per-process token from the heartbeat file, so no other local page can write); ② the host publishes its web server's **listening port + token** in the heartbeat (`webServer.port` is a public getter), and a standalone save now **wakes the host** and reports the real result (new `savedApplied` toast); ③ a failed wake-up (old host without the route → 404, port mismatch, timeout) is **not** an error — it falls back to the 0.10.5 waiting path and the "change it under DSH Settings" hint. Adds `tests/host-drain.test.mjs` (6 checks: loopback, the three gates, the end-to-end wake-up, failure reporting, 404 fallback, source guards) plus `tests/e2e-host-drain.mjs` (2 checks: a **real spawned process** end-to-end "standalone save → wake the host → host applies → reports applied", and the fallback to "queued" when the heartbeat comes from an old host with no port/token). `npm test` now runs **283 checks** |
|
|
551
|
-
| **v0.10.7** | 2026-10-08 | **"Still the old version after starting dsh" fixed at the root: version-drift self-heal + one-click resident restart (the positive answer to #19 / #23).** The scene: an npm upgrade only swaps files on disk, and the **resident web (port 7999 by default) outlives the DSH host** — the process on the port keeps running the code it loaded at startup, while activation policy was "there is already a live watchdog on this port → `delegated` (no spawn, no replacement)", so **no number of DSH restarts ever replaces it** (measured here: the host started at 10:19 on the new build and the same-origin settings panel showed 0.10.6, while the web on 7999 had started at 08:15 on 0.10.0 — `/config` and `/version-check` each reported a different version). ① **Startup self-heal (`autoRestartOnDrift`, default on)**: activation first runs `inspectResident()` — the version is always asked from **the port** (the lock's `pkgVersion` is only the watchdog's self-report, and an old instance wrote an empty string, so drift was invisible) — then `decideResidentAction()` decides: match → `delegate` (keeping #19's honest wording), drift → `restart`, own version unreadable → touch nothing, just replaced → debounce (a config change re-runs the effect). **"Updating the running program" is therefore defined as "restart that resident process on the new code", never as hot-swapping modules in memory**: ESM module cache plus closure state (settings / vault routing / SSE hub / `fs.watch` / the already-bound port) leaves no safe way to hot-swap, and a cache-busting re-import only produces a second module graph (double instances, double listeners, double writes). ② **One implementation for replacement**: new `restartResident()` spawns a detached `watchdog.js --replace` helper (reap the old watchdog / old web → **wait for the port to really free up** → take the lock → pull up the new web), then verifies "the version served on the port equals the local version" (#23 suggestion 2); if the port never frees up it **aborts the takeover** instead of falling back to `port+1` (fallback instances are exactly where orphan webs are born). `dsh-memory restart`, host startup self-heal and the panel button now share it. The helper is detached with `stdio:ignore` (nobody reads its stderr), so the takeover outcome **must** go into the capture log — otherwise "restarted but not in effect" becomes yet another untraceable silent failure. ③ **One-click fix in the panel**: new `POST /memory-eternal/api/restart-self` under three hard rules — this process may well **be** the stale web being replaced, so the response is sent **before** the replacement is scheduled; if the resident instance is already current it answers `alreadyCurrent` (the common case when clicking from the DSH panel — a healthy instance is never needlessly taken down); and only one replacement may run at a time. ④ **Panel semantics disambiguated**: "running" became "**running (process serving this page)**" (the resident web on the standalone page, the host inside DSH Settings — the very reason the two panels' numbers disagreed), plus a new "**resident instance vX**" chip so it is obvious which one to restart; the drift banner offers the button plus the equivalent command, and when the resident instance is already new but this page is not, it says plainly "restart the desktop app / DSH" instead of offering a button that would do nothing. ⑤ **`updateAvailable` now compares versions**: `latest !== onDisk` used to **advise downgrading** when running an unpublished local build (disk 0.10.7 / npm 0.10.6); it now uses `compareVersions` (with prerelease semantics, `rc.2 < rc.10`). ⑥ Also removed a duplicate `saving` key from the client dictionary (esbuild warned on every build). New `tests/watchdog-restart.test.mjs` (17 checks: decision matrix / inspection asking the port / replacement verification / non-lethal takeover cleanup / route responding before replacing + concurrency gate / version comparison). ⑦ **The README now ships as two separate files**: `README.md` is the **default** Simplified Chinese one (what npm and the DSH marketplace render) and `README.en.md` is the English mirror; both gained a language switch plus a "change one, change the other" note at the top, and the English self-diagnostics subsection that was missing; new `tests/readme-i18n.test.mjs` locks **1:1 structure** (heading-level sequence + code-block count + cross links + default-language markers) so future edits cannot land on one side only. ⑧ **`secretHint` credential hint + credential-read guard**: one line of your own convention gets injected into every session's systemPrompt so the agent looks up the **secret directory card** in memory first when it needs an API key / token (memory stores the directory only — names / locations / how to use — while **values stay in local credential stores**); `dsh-memory setup` now also registers the Claude Code `PreToolUse` guard (denies reads of `.credentials.yaml` / `.env` / `*-token` and points at the loader) and refreshes hook paths that still point at an older install. ⑨ **Three more live-site findings fixed before release**: ① the panel's **restart resident instance** button also showed when the *process serving this page* was the old one — clicking it must 404 (the old host has no such route) and the user only saw "unknown interface"; it now appears only when the resident instance on the port is behind the files on disk, and a 404/405 gets explicit "restart the desktop build / DSH, or run dsh-memory restart" guidance. ② When the standalone page (new) submits the whole form to an old host, the host drops the keys it does not know (e.g. `secretHint`) by whitelist → the read-back check can never match → five retries then `dropped`, with the user only seeing "HMR transactions cannot be nested"; the pending patch is now **filtered against the host schema** before it is applied (unknown keys take effect once the host upgrades), and "config sync failed" is kept strictly apart from "auto-capture anomaly" (`config-warn` / `config-fail`, no longer painting the capture health red). ③ `autoRestartOnDrift` now renders as **on** when the host snapshot has no such key (measured: 0.10.6 host + 0.10.7 panel → checkbox empty while the host actually treats it as on). Adds `tests/issue-fixes-4.test.mjs`. `npm test` now runs **274 checks** |
|
|
552
|
-
| **v0.10.6** | 2026-10-07 | **Orphan webs are now reclaimed + a failed shared-config sync is no longer silent.** ① **Orphan webs**: the web child is spawned detached, so when a watchdog is hard-killed (on Windows `process.kill(pid,'SIGTERM')` terminates unconditionally) its exit handler never runs; if that web had **fallen back to port+1…** because the target port was taken, the lock entry naming it disappears together with the watchdog — leaving an unowned web parked on 8001/8002 (two were found on this machine, one of them 20 hours old, and `status` only probes the configured port so it could never see them). Now `startWatchdog` on startup, and `stop`/`restart` (the `checkPort` path), sweep the **fallback window** `[port+1, port+9]` and reap them; three gates must all pass (the port is inside the window / the command line **really is this plugin's web** / the pid is not any watchdog's `pid` or `webPid` in the lock), so **someone else's `node web.js` is never killed**. In addition `spawnWeb` no longer spawns a duplicate when the target port already holds our own web (the main source of fallback instances) — liveness is left to the tick probe. ② **`syncConfigFile()` no longer swallows errors**: it writes the read source shared by the standalone page / hooks / MCP, so a failed write shows up as "config changed but they can't see it" — the old code was `catch { /* silent */ }` (observed once: the shared file's mtime stayed at pre-activation while the host's own value had changed). Failures now reach stderr and the capture log as a `warn` (same reason reported once, no spam); because `logCapture` is defined later inside `apply()`, the log line is deliberately deferred by one tick to avoid the `const` TDZ. New `tests/watchdog-orphans.test.mjs` (7 checks); `npm test` now runs **245 checks** |
|
|
553
|
-
| **v0.10.5** | 2026-10-07 | **Config saves from the standalone web page: no more empty promise, and no DSH install required.** ① **A host-independent write path (#21)**: if nothing consumes the pending file within 800 ms **and** no live DSH host is present (the heartbeat file `memory-eternal-config.host.json` is stale, or its pid is dead), the standalone side now **atomically merges** the change straight into the shared config, clears the pending file and answers `pendingOutcome: applied-direct` — so Codex / Claude Code / Cursor users, and anyone running only `dsh-memory serve`, get their save applied immediately (previously that path just wrote a "pending" file for a host that **does not exist**, while the UI promised "takes effect on next start" forever). When a host *is* alive the standalone side **never** writes directly: the shared file is the host's derived mirror, and a direct write would be overwritten by the next `syncConfigFile()` (a dedicated reverse test guards that boundary). ② **Failures now give directions, not just an error (#21)**: host-guard errors are recognised — `HMR transactions cannot be nested` (Cordis HMR hosts) and `root.events.emit is unavailable from a plugin activation` (dsh-tui and friends) are two phrasings of one defect family — and "this host does not let a plugin write config from its own callback context" plus where to change it instead (**DSH Settings → Memory**, or the profile patch layer) now lands in the give-up error, the diagnostics and the standalone page's hint; `POST /config` also returns `hostGuard` / `hint`. ③ **The UI no longer always says "Saved" (#21)**: the client consumes `pendingOutcome` — `queued` / `failed` use the non-success style and say "not confirmed applied yet", `applied-direct` states "no DSH host here, written straight into the shared config", and only a real apply shows "Saved". ④ Also fixed a cleanup race that could keep a host or test process **alive forever**: the heartbeat timer and the pending-file watcher are created after an async `import()`, so a dispose that happened before it resolved left them uncleaned (the heartbeat interval is additionally `unref()`ed) — this is exactly what hung `tests/settings-compat.test.mjs` when it ran `apply()` against a fake ctx. New `tests/issue-fixes-3.test.mjs` (9 checks); `npm test` now runs **238 checks** |
|
|
554
|
-
| **v0.10.4** | 2026-10-07 | **Four "looks fine, silently wrong" issues fixed at once (#21 / #22 / #23 / #24).** ① **A save misreported by a host guard is no longer treated as failed (#21)**: the capability guard in hosts such as dsh-tui throws `root.events.emit is unavailable from a plugin activation` from the `describe()` call **inside** `settings.update`, even though the write has already been committed — the old code only asked "did it throw", so an **already-applied** change was retried 5 times, marked `dropped` and logged as a failure. New `applyPatchVerified` **reads the config back** after a throw (predicate `patchApplied`, shallow compare) and logs one line while treating it as success; drain and the standalone web share that path, and the standalone web's `POST /config` no longer claims "all good" unconditionally: it waits up to 800 ms for the pending file to be consumed and returns `pendingOutcome: applied / failed / queued` with matching wording (`waitPendingOutcome`). ② **`/cards` status no longer silently misaligns (#22)**: `status=deleted` now really lists the **recycle bin** (the numbers match `/recycle/list`), and an unrecognized value returns `unknownStatus: true` plus `appliedStatus` / `requestedStatus` echoes (the fallback to approved stays for old callers, but it is no longer silent). ③ **After an upgrade, trust the version actually served on the port (#23)**: `/overview` gains the `version` loaded by the **serving process**; `dsh-memory status` reports the **port's actual served version** and says "upgrade not in effect" when it differs (the lock file's version is only the watchdog's self-report; when the port holds a **pre-0.10.4** web nothing can be probed because its `/overview` has no `version` field yet, and the tool then says exactly that — "this plugin's web, but it does not report a version, i.e. an old build" — instead of wrongly claiming no service is responding); `stop` re-checks the port occupant afterwards and warns if it is still taken; `restart` no longer trusts only the webPid recorded in the lock — it falls back to the **port occupant** to reap an old web the lock never registered (only after confirming from the command line that it really is **this plugin's** web — the `memory-eternal` package name, or the absolute path of this plugin's own `lib/web.js`; someone else's `node web.js` is merely warned about, since killing an unrelated process that happens to hold that port is worse than not killing at all), aborts if the port never frees up, and self-checks "port-served version = local version" after startup, exiting non-zero on a mismatch (no more "started" and then silence). ④ **Distillation no longer misreads bare control characters inside strings as unparseable (#24)**: when the model writes a markdown body with real U+000A newlines (braces balanced, `looksTruncatedJson` false), the old code always failed `JSON.parse` and dropped the whole card into a raw-text fallback. New `repairJsonControlChars` escapes bare control characters **only inside strings** (`\n` `\r` `\t` `\b` `\f`, otherwise `\u00XX`; newlines outside strings are legal JSON whitespace and stay untouched), and candidates are tried as "as-is → sliced `{ … }` → each one's repaired form"; parse failures no longer swallow the raw `JSON.parse` error, and the excerpt now keeps **both head and tail** (`describeOutputExcerpt`; the old `slice(0,160)` made output cut mid-`"body":` look like a broken stream); the max-tokens retry went from "double once" to `maxTokenLadder` **doubling all the way to the schema ceiling** (1200 → 2400 → 4000, no longer stopping at 2400). New `tests/issue-fixes-2.test.mjs` (21 checks); `npm test` now runs **229 checks** |
|
|
555
|
-
| **v0.10.3** | 2026-10-06 | **Fixed the counting semantics of `audit list` (#20).** ① The total is now **decoupled from `--limit`**: the reported count is the **matching total** (via `countCards`) while `--limit` caps output only, and truncation is stated explicitly (`显示前 M 张 … 还有 X 张未显示`). ② **No more silent truncation by default** (omit `--limit`, or `--limit 0`, to list everything). ③ `--json` now exposes `total` / `returned` / `count` (a backward-compatible alias of total; in 0.10.2 it wrongly equalled returned) / `limit` (null = all) / `hasMore`. ④ The meaning of `--status all` is documented in the usage text and README: the audit queue (pending + rejected), **excluding** dequeued approved and recycle-bin deleted; `approved` remains queryable explicitly. ⑤ **Same defect fixed in the MCP `memory_audit_list` tool**: it also silently capped at 20 and reported the truncated length as the pending count; it now reports the matching total plus how many are hidden, cap 100 → 500. |
|
|
556
|
-
| **v0.10.2** | 2026-10-06 | **Five long-standing issues fixed at once (#15 / #16 / #17 / #18 / #19).** ① **Per-project vaults finally work in the host (#15-1)**: the host matched `vaultProfiles[].match.workspace` against the process cwd (the directory dsh was launched from) — it now uses the **session's own workspace** from `session.header.cwd` (both capture and `memory_recall`), while CLI/MCP/hooks keep their existing cwd behaviour. ② **Distillation stops burning doomed candidates (#15-2)**: providers declaring `supportsReasoningEffort:false` no longer receive a `reasoningEffort` field (unknown = supported), and they move behind supporting candidates instead of being dropped. ③ **Truncated output is no longer misreported as a parse failure (#18)**: a dedicated `MAX_TOKENS` code (with the actual cap in the message), one automatic retry with a doubled cap, failure reporting that **always keeps the first candidate's error as the primary cause** plus a per-code summary of the rest, default `captureMaxTokens` 900 → **2000**, and a `distill-failed` tag on fallback raw cards. ④ **Standalone-web config saves are no longer lost silently (#16)**: drain errors are no longer swallowed (stderr + capture log, throttled), and the shared `memory-eternal-config.json` is now written **atomically (tmp + rename)** — the old non-atomic write could be read mid-truncation by the standalone web / MCP, fail JSON.parse and silently fall back to defaults, the pending file is **kept** with `dropped:true` + `lastError` after retries are exhausted, and the state shows up in `/diagnostics` and `/config`. ⑤ **Audit CLI (#17)**: `dsh-memory audit list/approve/reject` (multiple paths, `--json` for jq) reuses the same `setCardStatus` guard and `audit_log`; MCP gains only a **read-only** `memory_audit_list`. ⑥ **Watchdog lifecycle (#19)**: new `dsh-memory status/stop/restart`, `pkgVersion` recorded in the lock (status warns when the resident instance is outdated), truthful `spawned` vs `delegated to existing pid` host logs, a clear notice when `watchdogAutoSpawn` is off but an instance is still running, `dsh-memory stop` also stops the **web server the watchdog spawned** (on Windows `process.kill(pid,'SIGTERM')` terminates unconditionally, so the watchdog's own exit handler never runs — the old behaviour left an orphan web holding the port; the web pid is recorded in the lock and confirmed against the process command line before killing, avoiding pid-reuse mistakes), plus README fixes for the contradictory default and the "restart turns it off" promise. |
|
|
557
|
-
| **v0.10.1** | 2026-10-02 | **Fixes "export works, import brings in 0 cards".** Root cause: "Export JSON" wrote a **bare array**, while `/import` only read `payload.cards` — a bare array was silently treated as an empty backup (`ok:true` / `imported:0` / `skipped:0`), so the UI only ever said "Import complete: 0". ① `/import` now accepts both a **bare array** (backups from v0.10.0 and earlier) and an **envelope object** `{format, formatVersion, exportedAt, count, cards}`; an unrecognized shape returns **400 with a readable reason**, an empty file returns a `warning`. ② Export now writes the envelope (older builds only read `payload.cards`, so the envelope stays compatible with them) and keeps each card's `status` / `store`. ③ The import dedup baseline is now a **pre-import snapshot** (new `dedupAgainst` option on `writeCard`) — otherwise near-identical cards inside one backup dedup against each other and swallow part of the batch (a backup is the source of truth). ④ The import response adds `total` / `skipped` / `quarantined` / `failed[]` (with a per-card reason), and the toast reads "Import complete: N / of M · pending review K · skipped D"; a run that imports nothing is shown as a failure. ⑤ New `tests/import-roundtrip.test.mjs` (6 checks). ⑥ **A broken install now speaks plainly (issue #14)**: a copy installed from the plugin marketplace can be missing its `web/` assets, and the sidebar then showed a raw `{"ok":false,"error":"ENOENT …"}` page. Now a missing `index.html` with `app.js` still present is covered by a **built-in shell** that boots the UI anyway (self-healing), while a missing `app.js` returns a JS snippet that paints the diagnosis into `#root` plus a page stating which file is gone, its absolute path, the package version and how to reinstall. Both cases land in the auto-capture log (`app.js` → `fail`, health turns red; `index.html` only → `warn`), and the host self-checks the same way at startup (shared predicates and wording in the new `lib/web-assets.js`, with 6 checks in `tests/web-assets.test.mjs`). ⑦ `appendCaptureLog` no longer fails silently when the `DSH_HOME` directory does not exist. ⑧ **DSH compatibility widened to `>=0.1.5-alpha.2 <0.3.0-0`**: the old `<0.2.0` upper bound excludes DSH `0.2.0-rc.x` under plain semver (DSH's guard uses `includePrerelease:true`, so installs still worked, but the declaration did not match reality) — the range now covers the whole 0.1/0.2 line while `<0.3.0-0` still blocks 0.3.0 and its prereleases; `dsh.compatibility` gains `"0.2.0-rc.2": "compatible"` (verified on the official desktop build 0.2.0-rc.2). ⑨ **Fixed three third-party plugin manifests that were invalid JSON**: the closing quote of `description` in `.claude-plugin` / `.codex-plugin` / `.cursor-plugin` had been swallowed by a double-encoding accident (last touched in v0.9.23); DSH never reads those files, so nothing surfaced, but installing through Claude Code / Codex / Cursor **always failed to parse**. They are now valid UTF-8 JSON with `version` bumped to 0.10.1, plus a new **shipped-files health guard** `tests/manifests.test.mjs` (3 checks: every shipped JSON parses / the three manifests' `name`+`version` match package.json / no encoding-accident mojibake in shipped text). Measured on a real **661-card bare-array backup (3.9 MB): 0 cards before the fix → 661 after** (654 into the main store + 7 into quarantine). `npm test` now runs **187 checks** |
|
|
558
|
-
| **v0.10.0** | 2026-09-30 | **Two stores: unreviewed content is physically quarantined.** ① `cards` (the main store) holds `approved` only, while a new `quarantine` store (with `quarantined_at` / `quarantine_reason`) holds `pending` / `rejected` / `deleted`; ② read paths (recall, search, graph, dedup, export) no longer need a status condition — querying the main store is safe by construction — which closes the three review-bypass holes audited in v0.9 (the dedup pool writing new knowledge into pending cards, `readCard` reading unreviewed bodies, `/card` skipping the status check); ③ review transitions became **cross-store moves** (inside a transaction, ids issued by the new `card_sequence` allocator, `card_updates` rebound with the card); ④ existing non-approved cards are **migrated automatically** on first start (nothing deleted, idempotent); ⑤ a new `checkMainStoreInvariant()` health check plus `tests/two-store.test.mjs`; ⑥ an intake noise gate that strips DSH runtime injections (environment snapshots, team broadcasts, teammate transcripts, tool manuals) so fragment cards and mushy titles never get in; ⑦ fixed the native crash of `settings-compat` on Windows (`fs.watch` on the parent directory → watch the file instead) |
|
|
559
|
-
| **v0.9.23** | 2026-09-30 | **No more save flicker.** After a successful save the client immediately re-fetches `/config`, but the host volatile echo lags, so inputs visibly reverted to the old values and then jumped back. The client now keeps a **local overlay** for fields that were saved but not yet echoed (`src/client/config-merge.js`, pure function + 4 checks), dropping each key once the host echoes it, and following the host value afterwards; resetting the form clears the overlay. `npm test` now runs **139 checks** |
|
|
560
|
-
| **v0.9.22** | 2026-09-30 | **Near-instant config sync (SSE push + file watching).** ① A new SSE hub (`lib/sse.js`): both hosts (DSH-embedded and standalone web) expose `/memory-eternal/api/events`, so a config change is pushed to every open page and the client reloads automatically — **without overwriting what you are currently typing** (dirty flag). ② Standalone save → DSH apply now triggers via `fs.watch` in **milliseconds** instead of a 5-second poll (the poll remains as a fallback). ③ The standalone page watches the shared config file, so a DSH-side save shows up there immediately. Adds `tests/sse.test.mjs` (5 checks); `npm test` now runs **135 checks** |
|
|
561
|
-
| **v0.9.21** | 2026-09-30 | **A successful save is no longer shown as a failure + config layout fix.** ① Success always uses the success style (the "written, host has not echoed yet" case used to pop up in the red failure style, which read as "save failed"), reworded to "saved to the config file; restart DSH to make it effective"; ② config field grid widened from 150/160px to **200/220px** with larger gaps and wrapping long labels, checkbox and text top-aligned — fields no longer crowd each other. |
|
|
562
|
-
| **v0.9.20** | 2026-09-30 | Standalone web page gains **range validation**: `/config` mirrors the Config schema min/max (`recycleRetentionDays` 1–3650, `webPort` 1–65535), returning **400** with "needs 1–3650, now 0" for out-of-range values. The most common save-failure path (cleared/out-of-range number) is now blocked with a field-level reason at all three layers: client, standalone page and host. |
|
|
563
|
-
| **v0.9.19** | 2026-09-30 | **"Save failed" now says why (root cause: clearing a number box yields 0, the schema rejects it, and the host answered with an empty 500).** Reproduced against the live host: out-of-range value / cleared number box / wrong type → **HTTP 500 with an empty body** → the client `r.json()` throws → only the generic "Save failed" is shown. Fixes: ① clearing a number input **no longer silently becomes 0** (an empty string coerced to a number is 0, while `recycleRetentionDays` requires `min(1)`); ② pre-save **per-field range validation** that names the field ("recycleRetentionDays must be a number between 1 and 3650, now 0"); ③ defensive response parsing: on a non-JSON or empty body the panel shows the HTTP status and the raw snippet instead of "Save failed"; ④ the host POST is wrapped so it **never returns an empty body**, and returns 400 plus the field names for empty numeric values; ⑤ the standalone `/config` validates types and ranges so invalid pending values cannot be written; ⑥ `drainPendingConfig` counts failures and gives up (deleting the file) after 5 tries instead of retrying forever. `npm test` now runs **130 checks** |
|
|
564
|
-
| **v0.9.18** | 2026-09-30 | **The standalone web page can finally save config — and a read-only page no longer feels dead.** Root cause (the other half of issue #12): the standalone web server has **no DSH settings service**, so its `/config` always returned `writable:false`; the client `save()` starts with `if (readonly) return` and the button was `disabled` — clicking Save did **nothing at all**, independent of the plugin version. Fixes: ① new shared-file protocol in `lib/config-sync.js`: the standalone page writes `memory-eternal-config.pending.json` atomically, and the **DSH host applies it on activation and every 5 seconds** via `settings.update(patch, undefined)` (skipping the optimistic-concurrency check), deleting the file on success and retrying on failure; if DSH is not running the change waits in the file for the next start. ② The standalone `/config` now reports `writable:true, viaPending:true` and the page shows a blue note explaining the sync. ③ **The button is no longer disabled when read-only** — clicking always explains why (`🔒 Read-only` + "desktop app → Settings → Memory"), so "no reaction" can no longer happen. Adds `tests/config-sync.test.mjs` (4 checks). `npm test` now runs **125 checks** |
|
|
565
|
-
| **v0.9.17** | 2026-09-30 | **"I updated it but the UI is still old" — fixed for good.** ① The build now injects the plugin version into the client bundle and the settings page shows a "**Page script vX**" chip; ② when the page-script version differs from the on-disk version it says so loudly: "⚠ This page is still running an older script (page v{page} / disk v{disk}): **press Ctrl+F5**"; the running-vs-disk hint now also leads with "refresh first, restart the desktop app / DSH only if the host half is old". Root cause of issue #12 ("saving shows no feedback"): the page was still executing the JS loaded before the update — the host sends `Cache-Control: no-store`, but **an already-open page never reloads its scripts**, so without a refresh you keep seeing the old UI. The standalone web service on `:7999` (an old process started 9/29) was restarted and now reports `loaded=onDisk=latest` |
|
|
566
|
-
| **v0.9.16** | 2026-09-30 | **Fixed config saves silently not persisting (issue #12) + full config coverage.** ① **Save path**: the host `dsh-settings.write()` requires the revision held by the panel to match `describe()` **exactly**; once stale it throws `SettingsConflictError` and the save fails — the plugin now re-reads the latest revision and **retries once** (marked ♻ in the message). ② **Late volatile echo**: after a successful write the patch is overlaid on the local view (reopening the panel shows the new value immediately) and the overlay is dropped once the host snapshot catches up; the host POST now **re-reads after writing** and returns `pending` with an honest note ("written; restart DSH to apply from the config file") when the echo has not landed. ③ **Immediate save feedback** (requested earlier): ⏳ Saving / ✅ Saved / ⚠ error now appears right next to the Save button, including whether a retry or an echo was involved, and the button label no longer wrongly says "Exporting…". ④ **Full config coverage**: seven fields that previously could only be edited by hand are now in the panel — the **master switch `enabled`**, single-vault `vaultDir`, distillation route `captureProvider` / `captureModel`, cooldown `captureCooldownMs`, semantic recall `recallEmbedding` and `sessionBudgetChars`; the host `/config` gained 6 keys and the standalone web `/config` gained 9. ⑤ New **permanent guard tests**: `tests/config-coverage.test.mjs` asserts every Config field is covered by the host API, the standalone web API and the settings panel (missing one turns the suite red), and `tests/settings-save.test.mjs` (5 checks) pins revision retry, overlay fallback and "never swallow errors". `npm test` now runs **121 checks** |
|
|
567
|
-
| **v0.9.15** | 2026-09-29 | **Server-side graph speed-ups only — rendering is identical to v0.9.13.** ① **On-disk graph cache** keyed by fingerprint at `<vault>/memory-eternal-graph-cache.json` (atomic write, invalidated by fingerprint, skipped above 8MB, failures only cost a rebuild): measured cold start **422ms → 11ms** at 596 cards; ② **hashed bigrams + counting-sorted rare-gram index**: `bigramHashSet()` packs each bigram into a 32-bit integer (`Set<number>`; **capture de-duplication still uses the string version, so semantics are unchanged**), and the rare-gram index is built with a counting sort (202 → **54ms**); ③ **MinHash + LSH** candidate generation (48-dim signatures, 24 bands, unioned with the rare-gram index and then **verified exactly**): Jaccard calls 15,148 → 6,797 and the similarity stage 210 → 130ms. Full cold build **600 → 422ms**, edges unchanged from v0.9.13 (tag 2777 / similar 4). **Zero client changes** — no sprites, no static layer, no skeleton degradation (the v0.9.14 client work was reverted and deprecated because it hurt the feel). Adds `lib/minhash.js` + `tests/minhash.test.mjs` (4 checks); `npm test` runs **108 checks** |
|
|
568
|
-
| **v0.9.13** | 2026-09-28 | **Seven fixes from user feedback.** ① **Version tracking**: Plugin info now shows **running (loaded by host) / on disk / npm latest** side by side, with an amber warning when disk is newer ("X is installed on disk but the running process still has Y — restart to apply") plus a "Check for updates" button (server-side npm lookup, 10-minute cache, 4s timeout). ② **Copy failure**: the memory dialog runs inside an iframe without clipboard-write permission, so `navigator.clipboard.writeText` is always rejected — it now tries the Clipboard API, falls back to `document.execCommand('copy')`, and finally shows the text in a read-only box with a Select-all button; the iframe also gets `allow="clipboard-write; clipboard-read"`. ③ **Silent save**: the result now appears right next to the Save button (⏳ Saving… / ✅ Saved / ⚠ error) and the button label changed from the wrong "Exporting…" to "Saving…". ④ **Recycle bin**: restore / purge / empty-all now report success and failure, and the empty state explains itself and disables the buttons. ⑤ **Button feedback everywhere**: the audit centre's approve / reject / delete used to swallow everything in `catch {}`; it now counts successes/failures and reports them (and warns when nothing is selected). ⑥ **Graph zoom jank**: new `src/client/graph-lod.js` — **viewport culling** (off-screen nodes/edges are skipped) + a **label budget** (≤120 nodes draw all; ≤300 draw 160; ≤600 draw 90; more draw 60, prioritising focus, hover, search hits and highest degree) + **text-width caching** + **shadows off for large graphs**. ⑦ Adds `tests/graph-lod.test.mjs` (5 checks); `npm test` now runs **108 checks** |
|
|
569
|
-
| **v0.9.12** | 2026-09-28 | **New "Report a bug" entry (🐞 in the left rail)**: the dialog takes your description and then either ① **opens a pre-filled GitHub issue** (title, body, diagnostics and the `bug` label already filled — one more click on Submit files it), or ② **copies a ready-made AI prompt** (paste it into your chat and the AI runs `gh issue list` to de-duplicate, files the issue and reports the URL back). **Why not one-click submission?** GitHub forbids anonymous issue creation, a token shipped inside the plugin would be extracted by decompiling, and OAuth/relay needs hosted infrastructure — hence "pre-fill + one click". Diagnostics come from the new `/memory-eternal/api/diagnostics` endpoint and are **auto-redacted** (home directory → `~`; `sk-` / `ghp_` / `github_pat_` / `api_key:` → `***`; truncated to 2400 chars), and the issue URL is truncated by **binary search** so it can never exceed the length limit (`URLSearchParams` inflates CJK by roughly 9x). Adds `lib/feedback.js` (pure functions) and `tests/feedback.test.mjs` (4 checks: redaction, diagnostic fields and length cap, pre-filled URL encoding/truncation, bilingual prompt); `npm test` now runs **103 checks** |
|
|
570
|
-
| **v0.9.11** | 2026-09-28 | **Fixed `watchdog --reap` killing itself**: when you run `node lib/watchdog.js --reap` directly, the caller's own command line also contains `watchdog.js` on the same port and is not in the lock — so the reaper SIGTERMed **itself**, showing up as "no output at all + exit code 1" (going through `dsh-memory watchdog --reap` was unaffected because its command line lacks `watchdog.js`, which is why it only showed up on the direct path). The protected set now always includes `keepPid`, the current pid and the parent pid, and any other process whose command line carries `--reap` is skipped; output now uses `fs.writeSync(2, …)` so `process.exit` cannot truncate it. Regression test added for "self and parent must never be killed" (using a real idle child process as the target instead of a fake pid). `npm test` runs **98 checks** |
|
|
571
|
-
| **v0.9.10** | 2026-09-28 | **Multi-vault + watchdog wrap-up, plus graph frame-time work.** ① **Multi-vault (#10)**: `vaultProfiles` / `activeVault` finally have a settings-page form (add/remove vaults, set directories, pick the active one, and see the effective directory / source / workspace); vault resolution moved into `lib/vault-resolve.js` and is **shared by the host and CLI / MCP / hooks / standalone web / sweep**, with new `vaultProfiles[].match.workspace` **auto-selection by project directory** (longest prefix wins, opt-in, still single-vault by default); `memory_recall` gained a `scope` argument (vault name / path prefix / `all` for cross-vault aggregation) and cross-vault results are labelled with their source vault. ② **Watchdog (#11)**: the lock is now **per-port** (several ports/vaults no longer overwrite each other) and `dsh-memory watchdog --reap` collects orphans left by earlier versions (only same-port processes that are not in the lock; the live registered instance and user-started watchdogs are left alone). ③ **Graph frame time**: edges are bucketed by colour + alpha + width and stroked with a single **Path2D** per bucket (2.7k edges: 2.7k draw calls → a handful), and node radial gradients are **cached per colour\|radius and reused via translate** (593/frame → essentially none after the first frame). ④ New `tests/vault-resolve.test.mjs` (7 checks), watchdog tests grown to 7; `npm test` now runs **98 checks** |
|
|
572
|
-
| **v0.9.9** | 2026-09-28 | **Fixed the sidebar "Memory" entry being pushed out of view (issue #2)**: the slot host comes in two shapes — the shell's own horizontal row (`.footerActions`) and a `div[data-slot="sidebar.footer.action"]` that other plugins (`dsh-diff-approval`, `dsh-footer-order`, …) turn into `flex-direction: column`. The old CSS applied `flex-wrap: wrap` plus `.me-footer { flex: 1 1 100% }` to both: inside a column container `wrap` means "new column" and `basis: 100%` means **100% height**, so the button was pushed into an overflow column and clipped to a sliver. `flex-wrap` is now scoped to the horizontal `.footerActions` only and `.me-footer` uses the direction-agnostic `flex: 0 0 auto`, guarded by a new SSR assertion. Also: bilingual READMEs gained a "Community & Support" section (group QR + WeChat pay/reward codes), plus `SPONSOR.md` and `.github/FUNDING.yml` |
|
|
573
|
-
| **v0.9.8** | 2026-09-28 | **Backlog burn-down release.** ① **Capture reliability** (issues #3 / #4): distillation no longer hardcodes `providers[0]` — new `captureProvider` / `captureModel` config plus ordered fallback across providers; adapter errors carried by the stream's terminal `finish` block (missing credential, rate limit, …) are now **visible failures** (code/message in the capture log, red health state, raw-card fallback annotated with the cause) instead of a vague "distillation produced nothing"; the stall detector moved from cumulative-counter deltas to a **sliding window** (alerts only when turns keep starting with zero finishes inside 20 min), fixing both false alarms and misses. ② **Independent-process vault consistency** (dsh-memory-eternal#5): CLI / MCP / hooks / standalone web / sweep now share the host's resolution order (`MEMORY_VAULT_DIR` → `activeVault` → `vaultDir` → default), so switching vaults no longer splits your memory in two. ③ **Watchdog singleton lock** (dsh-memory-eternal#6): a new process exits when a live watchdog already owns the port, and stale pid records are cleaned, ending the orphan pile-up on every restart. ④ **Graph performance**: tag edges became "Top-K by shared tags" (measured **24,193 → 2,763** edges at 593 cards), similarity switched to a **rare-bigram inverted index** (cold **948 → ~520ms**, warm 2-4ms), payload **3,531KB → 579KB**, and the client dim pass now uses an **adjacency set** (**52ms → <1ms** per frame). ⑤ Five new test files (routing/finish, stall window, vault resolution, watchdog lock, graph perf baseline); `npm test` now covers **85 checks** |
|
|
574
|
-
| **v0.9.7** | 2026-09-28 | **Cards open fully expanded (and can be collapsed) + new body typography**: the reader no longer folds to 300px by default — it **opens showing the whole card**, with a one-click collapse for long ones (and an "Expand ⌄" hint when folded). Frontmatter is lifted out of the body into **colored chips** (kind / audit status / tags / dates / source; kind reuses the graph palette) instead of leaking raw YAML. Body rendering is rewritten (`src/client/markdown.js`, shared by the host and the standalone web page): headings get an **emoji + color group by meaning** (conclusion ✅green / risk ⚠️amber / root cause 🔍blue / plan 🛠️purple / example 💡pink), consecutive `- ` lines become a real `<ul>`, `1.` becomes an `<ol>` with numbered circles, `- [x]` becomes a checkbox, `>` becomes an emoji callout, `---` becomes a divider, short `key: value` lines get an aligned style, and `**bold**` gets a highlighter underline; fenced code is protected so `#` / `-` inside it are no longer re-typeset. **Adds 7 typography unit tests** (XSS escaping, link-protocol allowlist, prose colons must not be misread as key/value). Also carries the PR #8 port: Electron-host subprocess node resolution (`lib/node-bin.js`, fixing "standalone web server never starts → sidebar popup white screen") and API routes registered after `webServer` is ready (fixing "settings page stuck loading") |
|
|
575
|
-
| **v0.9.6** | 2026-09-28 | **Fixed the full-screen overlay covering the desktop shell's window controls (clicking Close quit the whole shell)**: the desktop shell draws minimize / maximize / close at the top of the page and, per the DSH convention, marks `<html>` with `data-windows-titlebar` and publishes `--dsh-windows-titlebar-height` (both confirmed present inside the official desktop build's `app.asar`); the memory overlay used `inset:0` plus an inline `100vh`, covering that whole strip, and our own top-right "×" landed exactly on the shell's window Close — one click quit the entire desktop shell. The full-screen state now uses the `.me-modal-full` class (no inline `100vh`, which CSS cannot override), and under `[data-windows-titlebar]` the `.me-overlay-top` / `.me-overlay` / `.me-modal-full` surfaces are pushed down by `--dsh-windows-titlebar-height` with height `calc(100vh - that)`. Browsers without the attribute get a 0px offset, so behaviour is unchanged there. The export-graph full-screen preview also moved from `100vh` to `100%` so it cannot overflow the shortened overlay. Adds 1 render regression asserting all three avoidance rules ship in the bundle |
|
|
576
|
-
| **v0.9.5** | 2026-09-28 | **Fixed the plugin failing to mount under the official desktop build (schemastery 3.18.4)**: the desktop profile resolves **schemastery 3.18.4**, which turns every `volatile` Config field into a cosmokit **live reference** (`{ get(): snapshot }` branded with `Symbol.for('cosmokit.volatile.write')`), while 0.9.4 treated those fields as plain values — `cfg.vaultDir.trim()` threw `TypeError: cfg.vaultDir.trim is not a function` and the plugin died inside `apply` (the exact desktop error). The plugin now **deep-unwraps** references per the cosmokit protocol (snapshot each reference, recurse through arrays/objects), so `settings.get()` only ever exposes plain values; each read unwraps afresh, so in-place volatile hot updates stay immediately visible, and `JSON.stringify` no longer writes `{}` into the shared config file. Adds 2 regressions (the real 3.18.4 reference shape + nested arrays/objects) and verifies `apply` under the **desktop build's real module-resolution layout** (schemastery 3.18.4 + real cosmokit) |
|
|
577
|
-
| **v0.9.4** | 2026-09-28 | **DSH v0.1.7-rc.2 support (fixes "the plugin goes dark after the upgrade")**: (1) **The settings service was replaced** — in 0.1.7 `ctx.settings` is a forms-only API (`describe/update/replace/mutate/configure`) with no `register()`; the old `ctx.settings.register(...)` call throws a `TypeError`, so the plugin never mounts (memory page gone, auto-capture dead). A cross-version shim keeps `register` for older hosts while new hosts read the **live config reference** the loader passes in, subscribe to `loader/volatile-update` for hot updates, and write back through `settings.update(entry id, patch, revision)` (revision conflicts map to HTTP 409); it also registers `configure({ auto:false })` so the host does not generate a duplicate form page. (2) **Volatile field marking moved after construction**: no more chained `.volatile()` — that method only exists in schemastery ≥3.18.4, while the plugin actually resolves **3.18.1** at the profile level, so the chain threw `volatile is not a function` at import time and killed the plugin. (3) **Dead client references removed**: `inject` no longer lists `settingsScope`, which disappeared together with `@deepseek-ai/dsh-client-runtime` (0.1.7 has no such service, so the client plugin waited forever and never activated — taking the memory page and the sidebar button with it); `dsh.client.inject` dropped its non-existent package names. (4) New **`tests/settings-compat.test.mjs`** (8 cases: 0.1.7-shaped mount / live-reference reads / volatile hot update / write-back and revision conflict / legacy `register` path / field volatile markers) wired into `npm test`. Verified by booting a **real DSH v0.1.7-rc.2**: the host plugin mounts (config written to `memory-eternal-config.json`) and the client bundle composes into the boot graph (`plugins/??memory-eternal/client.js`) |
|
|
578
|
-
| **v0.9.3** | 2026-09-12 | **Fixed the blank memory page ("no UI")**: v0.9.1 placed the infinite-scroll `useEffect` **before** the `loadCards` definition — React evaluates the dependency array during render, hitting a TDZ error (`Cannot access 'loadCards' before initialization`) that **crashed the whole memory page on first render**. It now comes after the definition. Added a **render smoke test** (`tests/ssr.test.mjs`: renders the built bundle through all four entry points — cards / settings / usage / graph — wired into `npm test`) so "green build, blank page" bugs cannot ship again |
|
|
579
|
-
| **v0.9.2** | 2026-09-12 | **Log visibility + false-alarm fix**: the capture log is now **persisted to disk** (`~/.dsh/memory-eternal-capture.jsonl`, last 500 entries) — the standalone web page no longer says "log only available inside the DSH host" but shows the records, and history survives a host restart (read back on boot); fixed **stall-detector false alarms flooding the panel** (mismatched turn counters are normal: sub-agent turns and cancelled turns emit no `turn-stopping`; it now requires "turns running **and** no capture activity for 15 minutes" to alert once, and the alarm clears as soon as activity resumes — no more one `fail` line every 10 minutes permanently turning health red); when distillation returns nothing / unparseable JSON the content is **saved as a raw card instead of being dropped** |
|
|
580
|
-
| **v0.9.1** | 2026-09-12 | **Performance & UX batch**: memory page loads lazily per view (opening Settings no longer drags in 250KB of cards + 890KB of graph; first paint needs ~0.5KB); graph generation **3232ms → 43ms** (bigram sets computed once + lossless mathematical-bound pruning + tag inverted index) plus a server-side cache with **fingerprint invalidation** (write/audit/delete rebuilds immediately, no stale reads); site-wide **brotli q5** (graph 877KB → **54KB**, 34% smaller than gzip; cards 246KB → 83KB → 20KB with br) and large responses compressed with both br and gzip keeping the smaller, so no response is ever worse than gzip; `/cards` gained **real pagination** (offset/limit/total + server-side title sort, globally ordered across pages) loading 100 cards first and fetching more on scroll; the "Agent cards" panel in Usage/Today filters by agent server-side (30 rows); **audit red line**: the plugin injects "never call approve/reject on the user's behalf" into the agent context; the watchdog now **reaps redundant instances on fallback ports** (a duplicate web process holding 76MB was reaped in testing) |
|
|
581
|
-
| **v0.9.0** | 2026-09-12 | **Fixed silent auto-capture death (6 days without a single card)**: after a DSH upgrade the session event API moved from `events` to `ownEvents()/snapshotEvents()`; the listener read nothing, returned silently and left no trace — now it tries `ownEvents()` → `snapshotEvents()` → `events` and logs a per-session attach entry. Daily quota now counts **real writes only** (previously every "not worth saving" verdict burned quota, exhausting the 24h budget after 40 attempts); `captureCooldownMs` actually applies; a single capture input is tail-capped at 20k chars. New **capture log** panel and **active failure alerts** (red strip + agent-context injection + turn-stopping stall sweep). Removed the automatic "Daily review" digest (timer / `/todayBrief` route / UI button). |
|
|
582
|
-
| **v0.8.0** | 2026-09-06 | **Full UI i18n**: follows the DSH system language live (zh/en) across cards, graph, audit center, config panel and new-card templates; SQLite EBUSY fix for the Windows test suite |
|
|
583
|
-
| **v0.3.1** | 2026-09-03 | Knowledge graph loads on demand (fetch on open, unregister on close) |
|
|
584
|
-
| **v0.3.0** | 2026-09-03 | **SQLite storage layer** (`node:sqlite`, zero deps) + DB-level audit guard `enforceAudit()` + `audit_log` table + automatic `.md` migration |
|
|
585
|
-
|
|
586
|
-
</details>
|
|
587
|
-
| v0.2.0 | 2026-09-03 | Recall with body text (`search()` returns the `excerpt` field) |
|
|
588
|
-
| v0.1.2 | 2026-09-03 | Audit guard hardening (`writeCard` defaults to `pending`, `mergeCards` goes through audit) |
|
|
589
|
-
| v0.1.1 | 2026-09-03 | Recall body fix (`excerpt` field + body logic) |
|
|
590
|
-
| v0.1.0 | 2026-08-31 | First release: auto capture + auto recall + graphical knowledge base + knowledge graph + audit center + recycle bin |
|
|
591
|
-
|
|
592
|
-
</details>
|
|
593
|
-
|
|
594
|
-
---
|
|
595
|
-
|
|
596
|
-
## 💬 Community & Support
|
|
597
|
-
|
|
598
|
-
**Questions, feature requests, or just want to talk about DSH plugin development — join the group chat:**
|
|
599
|
-
|
|
600
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/support/group-qr.jpg" width="260" alt="DeepSeek Harness community group QR code" />
|
|
601
|
-
|
|
602
|
-
**If this plugin saved you some time, you can buy the author a coffee (either code works):**
|
|
603
|
-
|
|
604
|
-
| WeChat Pay | WeChat Reward |
|
|
605
|
-
|---|---|
|
|
606
|
-
| <img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/support/wechat-pay.jpg" width="230" alt="WeChat Pay QR code" /> | <img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/support/wechat-reward.jpg" width="230" alt="WeChat reward QR code" /> |
|
|
607
|
-
|
|
608
|
-
> Donations are entirely optional and change nothing about the plugin; it stays open source and free (MIT).
|
|
609
|
-
> Picking the first custom entry in the repo's **Sponsor** dropdown lands on [SPONSOR.md](SPONSOR.md), which shows the same codes.
|
|
610
|
-
|
|
611
|
-
---
|
|
612
|
-
|
|
613
|
-
## 📄 License
|
|
614
|
-
|
|
615
|
-
MIT
|
|
616
|
-
|
|
617
|
-
---
|
|
618
|
-
|
|
619
|
-
> **Make your AI truly remember: dialogue auto-captured, knowledge at your fingertips.** ⭐ Star it if you like, Let's make AI not forget.
|
|
620
|
-
>
|
|
621
|
-
> 中文 README: [README.md](README.md)
|