memory-eternal 0.9.11 → 0.9.13

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memory-eternal",
4
- "version": "0.9.11",
4
+ "version": "0.9.13",
5
5
  "description": "璁板繂鏍稿績锛圡emory Eternal锛夛細鐭ヨ瘑鍗¤嚜鍔ㄦ矇娣€/鍙洖锛岀粺涓€ ~/.dsh/memory-vault锛屼細璇濈粨鏉熻嚜鍔ㄥ叆搴撹繘寰呭鏍搞€?,
6
6
  "author": {
7
7
  "name": "EternalNight996",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-eternal",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
4
4
  "description": "璁板繂鏍稿績锛圡emory Eternal锛夛細鐭ヨ瘑鍗¤嚜鍔ㄦ矇娣€/鍙洖锛岀粺涓€ ~/.dsh/memory-vault锛屼細璇濈粨鏉熻嚜鍔ㄥ叆搴撹繘寰呭鏍搞€?,
5
5
  "author": {
6
6
  "name": "EternalNight996",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-eternal",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
4
4
  "description": "璁板繂鏍稿績锛圡emory Eternal锛夛細鐭ヨ瘑鍗¤嚜鍔ㄦ矇娣€/鍙洖锛岀粺涓€ ~/.dsh/memory-vault锛屼細璇濈粨鏉熻嚜鍔ㄥ叆搴撹繘寰呭鏍搞€?,
5
5
  "author": {
6
6
  "name": "EternalNight996",
package/README.en.md CHANGED
@@ -269,6 +269,41 @@ npm run build # builds lib/client.js (DSH embed) + web/app.js (standalone web
269
269
 
270
270
  ---
271
271
 
272
+ ## 🐞 Reporting a bug
273
+
274
+ The left rail has a **🐞 Report a bug** entry: describe the problem, then either
275
+
276
+ - **Open prefilled issue on GitHub** — title, body, diagnostics and the `bug` label are filled in; one more click on **Submit** files it;
277
+ - **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.
278
+
279
+ > **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".
280
+ > Diagnostics are **auto-redacted** (home directory → `~`; `sk-` / `ghp_` / `github_pat_` / `api_key:` → `***`), but do skim them before posting publicly.
281
+
282
+ If you cannot reach the UI, paste the prompt below into your AI chat and replace the last placeholder with your problem:
283
+
284
+ ```text
285
+ I hit a problem while using memory-eternal (a DeepSeek Harness memory plugin). Please file it on GitHub for me.
286
+
287
+ Repo: https://github.com/EternalNight996/memory-eternal
288
+
289
+ Workflow:
290
+
291
+ 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.
292
+ 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.
293
+ 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.
294
+ 4. Report the resulting issue URL back to me.
295
+
296
+ Keep my description verbatim - do not rewrite or summarise it. Put the diagnostics under the Diagnostics section as-is.
297
+
298
+ My description:
299
+ <write your problem here>
300
+
301
+ Diagnostics:
302
+ <paste the diagnostics copied from the in-app Report-a-bug dialog>
303
+ ```
304
+
305
+ ---
306
+
272
307
  ## 📋 Changelog
273
308
 
274
309
  > **Withdrawn versions**: **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`).
@@ -277,6 +312,8 @@ npm run build # builds lib/client.js (DSH embed) + web/app.js (standalone web
277
312
 
278
313
  | Version | Date | Highlights |
279
314
  |---|---|---|
315
+ | **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** |
316
+ | **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** |
280
317
  | **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** |
281
318
  | **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** |
282
319
  | **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` |
package/README.md CHANGED
@@ -282,6 +282,52 @@ npm run build # 构建 lib/client.js(DSH 内嵌)+ web/app.js(独立 web
282
282
 
283
283
  ---
284
284
 
285
+ ## 🐞 反馈问题
286
+
287
+ 插件左栏有 **🐞 反馈异常** 入口:填完问题描述后可以
288
+
289
+ - **在 GitHub 提交(已预填)** —— 标题 / 正文 / 诊断信息 / `bug` 标签都填好,再点一次 **Submit** 即提交;
290
+ - **复制给 AI 的提示词** —— 粘进 AI 对话框,让它用 `gh` 查重后建 issue 并回报链接。
291
+
292
+ > **为什么不能「点一下自动提交」**:GitHub 不允许匿名创建 issue;插件里内置 token 会被反编译提取;OAuth / 自建中转需要服务器。所以实现为「预填 + 一次点击」。
293
+ > 诊断信息会**自动脱敏**(home 目录 → `~`,`sk-` / `ghp_` / `github_pat_` / `api_key:` 等 → `***`),但公开提交前仍建议自己扫一眼。
294
+
295
+ 如果你拿不到插件界面,也可以直接把下面这段贴给 AI(把「我的问题描述」换成你的问题即可):
296
+
297
+ ```text
298
+ 我在使用 memory-eternal(DeepSeek Harness 的记忆插件)时遇到了问题,请你帮我把这个问题提交到 GitHub。
299
+
300
+ 仓库:https://github.com/EternalNight996/memory-eternal
301
+
302
+ 请按这个流程做:
303
+
304
+ 1. 先跑 `gh issue list --repo EternalNight996/memory-eternal --state open --limit 50`,看是否已有相同问题:
305
+ - 已有 → 用 `gh issue comment <编号> --body-file <文件>` 补充现象,不要重复开新 issue;
306
+ - 没有 → 继续第 2 步。
307
+ 2. 把正文写成文件,然后 `gh issue create --repo EternalNight996/memory-eternal --title "[Bug] <一句话概括>" --body-file <文件>`;正文用这个结构:
308
+ 现象 / 复现步骤 / 期望行为 / 实际行为 / 环境(插件版本 / DSH 版本 / 操作系统 / Node)/ 诊断信息。
309
+ 3. 如果 `gh` 没登录或不可用,不要尝试提交,改为输出一条「标题与正文都已预填」的 GitHub 新建 issue 链接给我,我自己点提交。
310
+ 4. 提交成功后,把 issue 链接发给我。
311
+
312
+ 注意:我的问题描述请原样照录,不要替我美化或省略细节;诊断信息原样放进「诊断信息」一节。
313
+
314
+ 我的问题描述:
315
+ <在这里写你的问题>
316
+
317
+ 诊断信息:
318
+ <把「反馈异常」弹窗里复制的诊断信息粘到这里>
319
+ ```
320
+
321
+ ### 环境自检(贴问题时附上更快定位)
322
+
323
+ ```bash
324
+ dsh plugin list # 插件版本
325
+ node -v # Node 版本
326
+ curl http://127.0.0.1:7999/memory-eternal/api/web-info # 独立 web 是否活着
327
+ ```
328
+
329
+ ---
330
+
285
331
  ## 📋 更新日志
286
332
 
287
333
  > **版本撤回说明**:**v0.9.0 – v0.9.5 均已在 npm 标记 deprecated;v0.9.1 / v0.9.2 / v0.9.3 / v0.9.4 的 release tag 已从 git 移除**(v0.9.5 只是被取代、tag 保留)—— v0.9.1/v0.9.2 带记忆页白屏缺陷,v0.9.0 沉淀告警误报刷屏,v0.9.3 不支持 DSH v0.1.7-rc.2(升级后插件整体挂不上:设置服务换血 + 客户端 `settingsScope` 消失),v0.9.4 在官方桌面版(schemastery 3.18.4 的 volatile 活引用)下抛 `cfg.vaultDir.trim is not a function`、插件整体挂不上,**v0.9.5 的全屏浮层会盖住桌面版的窗口控制面板(右上角「×」压在窗口「关闭」上,点一下会退出整个桌面壳)**。请一律使用 **v0.9.6+**(`npm i memory-eternal@latest`)。
@@ -290,6 +336,8 @@ npm run build # 构建 lib/client.js(DSH 内嵌)+ web/app.js(独立 web
290
336
 
291
337
  | 版本 | 日期 | 关键改动 |
292
338
  |---|---|---|
339
+ | **v0.9.13** | 2026-09-28 | **按用户反馈修 7 项**。① **版本跟踪**:配置页「插件信息」同时显示**运行中(宿主加载)/ 磁盘安装 / npm 最新**三个版本;磁盘比运行中新时给橙色警告(「磁盘已安装 X,但当前进程仍加载 Y —— 重启后才生效」),并新增「🔄 检查更新」(服务端查 npm,10 分钟缓存 + 4s 超时)。② **复制失败**:记忆弹窗在 iframe 里、默认没有 clipboard-write 权限,`navigator.clipboard.writeText` 必然被拒 —— 现在先试 Clipboard API,失败退 `document.execCommand('copy')`,再失败就把内容摊进只读文本框并给「全选」;iframe 加 `allow="clipboard-write; clipboard-read"`。③ **保存没有提示**:保存结果显示在保存按钮旁(⏳ 保存中… / ✅ 已保存 / ⚠ 错误),文案从错误的「导出中…」改为「保存中…」。④ **回收中心**:恢复 / 彻底删除 / 清空都有成功与失败提示,空库给说明与禁用态。⑤ **通用按键反馈**:审核中心的批准 / 驳回 / 删除原先 `catch{}` 吞掉一切,现逐条统计成功失败并提示;未选卡片也提示。⑥ **图谱缩放卡顿**:新增 `src/client/graph-lod.js` —— **视口剔除**(屏幕外节点/边不画)+ **标签预算**(≤120 全画 / ≤300 画 160 / ≤600 画 90 / 更多画 60,优先焦点、悬停、搜索命中、高度数)+ **文本宽度缓存** + **大图关阴影**。⑦ 新增 `tests/graph-lod.test.mjs`(5 项),`npm test` 共 **108 项** |
340
+ | **v0.9.12** | 2026-09-28 | **新增「反馈异常」入口(左栏 🐞)**:弹窗里填完问题描述后可 ① **一键打开 GitHub 预填 issue**(标题 / 正文 / 诊断信息 / `bug` 标签都已填好,再点一次 Submit 即提交),或 ② **复制一段给 AI 的提示词**(贴进对话框,让 AI 先用 `gh issue list` 查重、再建 issue 并回报链接)。**为什么不做「点一下直接提交」**:GitHub 不允许匿名创建 issue;插件内置 token 会被反编译提取;OAuth / 中转需要自建服务 —— 所以走「预填 + 一次点击」。诊断信息由新接口 `/memory-eternal/api/diagnostics` 生成并**自动脱敏**(home 目录 → `~`,`sk-` / `ghp_` / `github_pat_` / `api_key:` 等 → `***`,截断到 2400 字符),issue 链接用**二分截断**保证不超长(`URLSearchParams` 会把中文膨胀约 9 倍)。新增 `lib/feedback.js`(纯函数层)与 `tests/feedback.test.mjs`(4 项:脱敏 / 诊断字段与限长 / 预填链接编码与截断 / 中英双语提示词),`npm test` 共 **103 项** |
293
341
  | **v0.9.11** | 2026-09-28 | **修复 `watchdog --reap` 把自己杀掉**:直接运行 `node lib/watchdog.js --reap` 时,调用者自己的命令行同样含 `watchdog.js` + 同端口、且不在锁里 —— reap 于是把**自己** SIGTERM 了,表现为「一条输出都没有 + 退出码 1」(走 `dsh-memory watchdog --reap` 时因命令行不含 `watchdog.js` 而侥幸没事,所以只在直跑时暴露)。现在保护集合固定包含 `keepPid` / 当前 pid / 父进程 pid,并跳过命令行带 `--reap` 的其它进程;输出改成 `fs.writeSync(2, …)` 同步写,不再被 `process.exit` 截断。回归测试补「自己与父进程都不得被杀」(用真实空转子进程当靶子,不再用假 pid)。`npm test` **98 项** |
294
342
  | **v0.9.10** | 2026-09-28 | **多库与看门狗收尾 + 图谱帧优化**。① **多库(#10)**:`vaultProfiles` / `activeVault` 终于有了配置页表单(增删库、填目录、选激活库,并显示当前生效目录 / 来源 / workspace);vault 解析抽成 `lib/vault-resolve.js`,宿主与 CLI / MCP / hooks / 独立 web / sweep **共用同一份**,新增 `vaultProfiles[].match.workspace` **按项目目录自动选库**(最长前缀优先、opt-in,默认仍是单库);`memory_recall` 增加 `scope` 参数(库名 / 路径前缀 / `all` 跨库聚合),跨库结果标注来源库。② **watchdog(#11)**:锁改为**按端口分槽**(多端口 / 多 vault 并存不再互相覆盖),新增 `dsh-memory watchdog --reap` 回收升级前遗留的孤儿进程(只清同端口、且不在锁里的;锁中活跃实例与用户手动起的都不动)。③ **图谱帧时间**:边改为按「颜色 + 透明度 + 线宽」分桶后用 **Path2D 一次描边**(2.7k 条边从 2.7k 次绘制调用降到几个桶),节点径向渐变改为**按 颜色\|半径 缓存 + translate 复用**(593 次/帧 → 首帧之后基本不再创建)。④ 新增 `tests/vault-resolve.test.mjs`(7 项)、看门狗测试扩到 7 项;`npm test` 共 **98 项** |
295
343
  | **v0.9.9** | 2026-09-28 | **修复侧边栏「记忆」入口被挤出可视区(issue #2)**:槽位宿主有两种形态 —— 壳自己的横向行容器 `.footerActions`,以及被其他插件(`dsh-diff-approval` / `dsh-footer-order` 等)改成 `flex-direction: column` 的 `div[data-slot="sidebar.footer.action"]`。旧 CSS 对两者都套了 `flex-wrap: wrap` + `.me-footer { flex: 1 1 100% }`:纵向容器里 wrap 的语义是「换列」、`basis: 100%` 指的是**高度 100%**,叠加后按钮被挤进右侧溢出列、裁切到只剩右边缘一条缝。现在 `flex-wrap` 只留给横向 `.footerActions`,`.me-footer` 改用方向无关的 `flex: 0 0 auto`,并新增 SSR 回归断言。另:README 双语新增「交流群 & 打赏」(群二维码 + 微信收款码 / 赞赏码),新增 `SPONSOR.md` 与 `.github/FUNDING.yml`(FUNDING 只支持文字链接,故 custom 第一项指向 SPONSOR.md 页面直接展示二维码) |
package/lib/api.js CHANGED
@@ -12,13 +12,19 @@
12
12
  // - getCaptureLog() → 自动沉淀运行轨迹(DSH host 注入;独立 web server 无此数据)
13
13
 
14
14
  import { gzipSync, brotliCompressSync, constants as zlibConstants } from 'node:zlib'
15
+ import os from 'node:os'
16
+ import { readFileSync } from 'node:fs'
15
17
  import { listCards, countCards, readCard, search, searchAll, graph, graphAll, overview, exportCards, deleteCard, writeCard, parseCard, stats, optimizeCandidates, optimizeApply, addFeedback, dailyCounts, mergeCards, ensureVault, auditQueue, recycleList, purgeExpired, setCardStatus, restoreCard } from './vault.js'
16
18
  import { compressExcerpt } from './capture.js'
19
+ import { buildDiagnostics } from './feedback.js'
17
20
  import { getSetupStatus, runSetup, mcpAgentAction } from './setup.js'
18
21
  import { backupDb } from './db.js'
19
22
 
20
23
  export const API_PREFIX = '/memory-eternal/api'
21
24
 
25
+ // npm 最新版本缓存(避免每次点「检查更新」都打 registry)
26
+ const versionCache = { at: 0, latest: '' }
27
+
22
28
  export function createApi(deps = {}) {
23
29
  const vaultDir = deps.vaultDir ?? (() => '')
24
30
  const vaultRoots = deps.vaultRoots ?? (() => [{ name: '', root: vaultDir() }])
@@ -84,6 +90,76 @@ export function createApi(deps = {}) {
84
90
  json(res, 200, { ok: true, hits })
85
91
  return
86
92
  }
93
+ case '/version-check': {
94
+ // 版本跟踪:运行中(宿主启动时加载) vs 磁盘安装 vs npm 最新。
95
+ // 典型场景:磁盘已更新,但 DSH 进程仍加载旧版 → UI 显示旧版本号、新接口 404。
96
+ const loaded = (getDshInfo() || {}).version || ''
97
+ let onDisk = ''
98
+ try {
99
+ const pj = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
100
+ onDisk = String(pj.version || '')
101
+ } catch { onDisk = '' }
102
+ let latest = ''
103
+ let checkError = ''
104
+ const force = query.get('force') === '1'
105
+ try {
106
+ if (force || !versionCache.at || Date.now() - versionCache.at > 10 * 60 * 1000) {
107
+ const ctl = new AbortController()
108
+ const timer = setTimeout(() => ctl.abort(), 4000)
109
+ const resp = await fetch('https://registry.npmjs.org/memory-eternal', { signal: ctl.signal })
110
+ clearTimeout(timer)
111
+ const data = await resp.json()
112
+ latest = String((data && data['dist-tags'] && data['dist-tags'].latest) || '')
113
+ versionCache.at = Date.now()
114
+ versionCache.latest = latest
115
+ } else {
116
+ latest = versionCache.latest
117
+ }
118
+ } catch (e) { checkError = String((e && e.message) || e).slice(0, 120) }
119
+ json(res, 200, {
120
+ ok: true,
121
+ loaded,
122
+ onDisk,
123
+ latest,
124
+ checkError,
125
+ stale: !!(loaded && onDisk && loaded !== onDisk),
126
+ updateAvailable: !!(latest && onDisk && latest !== onDisk),
127
+ updateCommand: 'dsh plugin --profile <你的 profile> add memory-eternal@latest',
128
+ })
129
+ return
130
+ }
131
+ case '/diagnostics': {
132
+ // 「反馈异常」用:返回一段可直接贴进 issue 的**已脱敏**诊断文本。
133
+ // 宿主与独立 web 共用这个实现;home 目录、各类 key/token 全都会被替换掉。
134
+ const cfgD = getSettings() ?? {}
135
+ const dshD = getDshInfo() ?? null
136
+ const envD = typeof process !== 'undefined' ? process.env : {}
137
+ let countsD = null
138
+ try { countsD = await overview(vaultRoot) } catch { countsD = null }
139
+ let healthD = null
140
+ try { healthD = getCaptureHealth() } catch { healthD = null }
141
+ let logD = []
142
+ try {
143
+ const raw = getCaptureLog()
144
+ logD = Array.isArray(raw) ? raw : (raw && Array.isArray(raw.entries) ? raw.entries : [])
145
+ } catch { logD = [] }
146
+ const homeD = (() => { try { return os.homedir() } catch { return '' } })()
147
+ const text = buildDiagnostics({
148
+ version: (dshD && dshD.version) || envD.MEMORY_ETERNAL_VERSION || '',
149
+ dshVersion: (dshD && dshD.version) || '',
150
+ host: dshD ? 'deepseek-harness' : 'standalone web',
151
+ os: process.platform + ' ' + process.arch + ' / node ' + process.version,
152
+ vaultDir: vaultRoot,
153
+ vaultSource: dshD && dshD.vaultSource,
154
+ workspace: dshD && dshD.workspace,
155
+ cards: countsD ? countsD.total : undefined,
156
+ health: healthD,
157
+ captureLog: logD,
158
+ maxTokens: undefined,
159
+ }, homeD)
160
+ json(res, 200, { ok: true, diagnostics: text, repo: 'EternalNight996/memory-eternal', version: (dshD && dshD.version) || envD.MEMORY_ETERNAL_VERSION || '' })
161
+ return
162
+ }
87
163
  case '/graph': {
88
164
  await ensureVault(vaultRoot)
89
165
  const all = query.get('all') === '1'