memory-eternal 0.9.8 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.en.md +19 -0
- package/README.md +19 -0
- package/bin/dsh-memory.mjs +6 -1
- package/index.js +54 -14
- package/lib/api.js +1 -0
- package/lib/capture-run.js +9 -7
- package/lib/client.js +20 -15
- package/lib/vault-resolve.js +84 -0
- package/lib/watchdog.js +113 -16
- package/package.json +2 -2
- package/web/app.js +23 -18
|
@@ -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.
|
|
4
|
+
"version": "0.9.10",
|
|
5
5
|
"description": "璁板繂鏍稿績锛圡emory Eternal锛夛細鐭ヨ瘑鍗¤嚜鍔ㄦ矇娣€/鍙洖锛岀粺涓€ ~/.dsh/memory-vault锛屼細璇濈粨鏉熻嚜鍔ㄥ叆搴撹繘寰呭鏍搞€?,
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "EternalNight996",
|
package/README.en.md
CHANGED
|
@@ -277,6 +277,8 @@ npm run build # builds lib/client.js (DSH embed) + web/app.js (standalone web
|
|
|
277
277
|
|
|
278
278
|
| Version | Date | Highlights |
|
|
279
279
|
|---|---|---|
|
|
280
|
+
| **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** |
|
|
281
|
+
| **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` |
|
|
280
282
|
| **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** |
|
|
281
283
|
| **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") |
|
|
282
284
|
| **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 |
|
|
@@ -296,6 +298,23 @@ npm run build # builds lib/client.js (DSH embed) + web/app.js (standalone web
|
|
|
296
298
|
|
|
297
299
|
---
|
|
298
300
|
|
|
301
|
+
## 💬 Community & Support
|
|
302
|
+
|
|
303
|
+
**Questions, feature requests, or just want to talk about DSH plugin development — join the group chat:**
|
|
304
|
+
|
|
305
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/support/group-qr.jpg" width="260" alt="DeepSeek Harness community group QR code" />
|
|
306
|
+
|
|
307
|
+
**If this plugin saved you some time, you can buy the author a coffee (either code works):**
|
|
308
|
+
|
|
309
|
+
| WeChat Pay | WeChat Reward |
|
|
310
|
+
|---|---|
|
|
311
|
+
| <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" /> |
|
|
312
|
+
|
|
313
|
+
> Donations are entirely optional and change nothing about the plugin; it stays open source and free (MIT).
|
|
314
|
+
> Picking the first custom entry in the repo's **Sponsor** dropdown lands on [SPONSOR.md](SPONSOR.md), which shows the same codes.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
299
318
|
## 📄 License
|
|
300
319
|
|
|
301
320
|
MIT
|
package/README.md
CHANGED
|
@@ -290,6 +290,8 @@ npm run build # 构建 lib/client.js(DSH 内嵌)+ web/app.js(独立 web
|
|
|
290
290
|
|
|
291
291
|
| 版本 | 日期 | 关键改动 |
|
|
292
292
|
|---|---|---|
|
|
293
|
+
| **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 项** |
|
|
294
|
+
| **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 页面直接展示二维码) |
|
|
293
295
|
| **v0.9.8** | 2026-09-28 | **集中解决存量问题**。① **采集可信性**(issue #3 / #4):蒸馏路由不再写死 `providers[0]` —— 新增 `captureProvider` / `captureModel` 配置,并按注册顺序**依次重试**;**流末尾 `finish` 块里的适配器错误**(缺凭证 `MISSING_CREDENTIAL`、限流等)现在成为**可见失败**(沉淀日志写明 code/message + 健康态亮红 + 兜底原文卡注明原因),不再笼统记成「蒸馏无输出」;停滞探测从「累积计数差额」改为**滑动窗口**(20 分钟内持续开始且零收尾才告警),误报与漏报一并修掉。② **独立进程 vault 一致性**(dsh-memory-eternal#5):CLI / MCP / hooks / 独立 web / sweep 与宿主共用同一套优先级(`MEMORY_VAULT_DIR` → `activeVault` → `vaultDir` → 默认库),切库后不再数据分裂。③ **看门狗单例锁**(dsh-memory-eternal#6):同端口已有活着的 watchdog 时新进程直接退出并清理陈旧 pid 记录,止住「每次重启多一个孤儿进程」。④ **图谱性能**:标签边改「共享标签数 Top-K」(593 卡实测 **24,193 → 2,763** 条边)、相似度改**稀有 bigram 倒排候选**(冷启动 **948 → ~520ms**,warm 2-4ms)、payload **3,531KB → 579KB**;客户端 dim 判定改**邻接 Set**(每帧 **52ms → <1ms**)。⑤ 新增 5 个测试文件(路由/finish、停滞窗口、vault 解析、看门狗锁、图谱性能基线),`npm test` **85 项** |
|
|
294
296
|
| **v0.9.7** | 2026-09-28 | **打开卡片默认全部展开(可收起)+ 正文排版重做**:卡片阅读器不再默认折成 300px,而是**打开即读全文**,长卡可一键收起(折叠后底部给「展开 ⌄」提示)。frontmatter 从正文里抽出来,改成 kind / 审核状态 / 标签 / 时间 / 来源的**彩色 chips**(kind 沿用图谱配色),不再把一段 YAML 当正文露出来。正文排版换成新方案(`src/client/markdown.js`,宿主与独立 web 共用同一套):小标题按语义自动配 **emoji + 颜色分组**(结论 ✅绿 / 风险 ⚠️琥珀 / 根因 🔍蓝 / 方案 🛠️紫 / 示例 💡粉),连续 `- ` 合成真正的 `<ul>`、`1.` 合成带圆形序号的 `<ol>`、`- [x]` 变复选框、`>` 引用升级成带 emoji 的 callout、`---` 变分隔线、行首「短键:值」渲染成对齐样式、`**加粗**` 加荧光笔下划线;围栏代码整段保护,内部 `#` / `-` 不再被误排版。**新增 7 项排版单测**(含 XSS 转义、链接协议白名单、散文冒号不得误判成 key/value)。另含 PR #8 移植:Electron 宿主下的子进程 node 解析(`lib/node-bin.js`,修「独立 web server 拉不起来 → 侧边栏弹窗白屏」)与 `webServer` 就绪后再注册 API 路由(修「设置页永远加载中」) |
|
|
295
297
|
| **v0.9.6** | 2026-09-28 | **修复官方桌面版全屏浮层挡住窗口控制面板(点「关闭」会退出整个桌面壳)**:桌面壳把「最小化 / 最大化 / 关闭」画在页面顶部,并按 DSH 约定在 `<html>` 上打 `data-windows-titlebar` + 给出 `--dsh-windows-titlebar-height`(已在官方桌面版 `app.asar` 里确认这两个标记确实存在);记忆库全屏浮层原本是 `inset:0` + 内联 `100vh`,整条盖住标题栏,而我们右上角的「×」恰好压在窗口「关闭」上 —— 用户一点就直接退出整个桌面壳。现全屏态改用 `.me-modal-full` 类(不再写死内联 `100vh`:内联样式无法被 CSS 覆盖),并在 `[data-windows-titlebar]` 下把 `.me-overlay-top` / `.me-overlay` / `.me-modal-full` 一律下移 `--dsh-windows-titlebar-height`、高度改为 `calc(100vh - 该高度)`;浏览器里没有该属性时偏移 0px,行为与之前完全一致。导出图谱的全屏预览同步从 `100vh` 改为 `100%`,避免在缩短后的浮层里溢出。新增 1 项渲染回归断言(三处让位规则必须出现在打包产物中) |
|
|
@@ -309,6 +311,23 @@ npm run build # 构建 lib/client.js(DSH 内嵌)+ web/app.js(独立 web
|
|
|
309
311
|
|
|
310
312
|
---
|
|
311
313
|
|
|
314
|
+
## 💬 交流群 & 打赏
|
|
315
|
+
|
|
316
|
+
**遇到问题、想提需求、或者想聊聊 DSH 插件开发 —— 欢迎进群:**
|
|
317
|
+
|
|
318
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/support/group-qr.jpg" width="260" alt="DeepSeek Harness 交流群二维码" />
|
|
319
|
+
|
|
320
|
+
**如果这个插件帮你省了时间,可以请作者喝杯咖啡(两个码任选其一):**
|
|
321
|
+
|
|
322
|
+
| 微信收款码 | 微信赞赏码 |
|
|
323
|
+
|---|---|
|
|
324
|
+
| <img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/support/wechat-pay.jpg" width="230" alt="微信收款码" /> | <img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/support/wechat-reward.jpg" width="230" alt="微信赞赏码" /> |
|
|
325
|
+
|
|
326
|
+
> 打赏完全自愿,不影响任何功能;插件本体始终开源免费(MIT)。
|
|
327
|
+
> 仓库页的 **Sponsor** 按钮下拉里选 custom 第一项,会落到 [SPONSOR.md](SPONSOR.md)(同样是这一页的码)。
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
312
331
|
## 📄 License
|
|
313
332
|
|
|
314
333
|
MIT
|
package/bin/dsh-memory.mjs
CHANGED
|
@@ -109,9 +109,14 @@ async function main() {
|
|
|
109
109
|
return
|
|
110
110
|
}
|
|
111
111
|
case 'watchdog': {
|
|
112
|
-
const { startWatchdog } = await import('../lib/watchdog.js')
|
|
112
|
+
const { startWatchdog, reapStaleWatchdogs } = await import('../lib/watchdog.js')
|
|
113
113
|
const { defaultVaultDir } = await import('../lib/capture-run.js')
|
|
114
114
|
const port = Number(argOf('--port')) || 7999
|
|
115
|
+
if (has('--reap')) {
|
|
116
|
+
const out = await reapStaleWatchdogs({ port, keepPid: 0 })
|
|
117
|
+
console.log(`扫描到 ${out.scanned} 个 watchdog 进程,清理孤儿 ${out.killed.length} 个${out.killed.length ? '(pid ' + out.killed.join(', ') + ')' : ''},保留 ${out.skipped.length} 个(锁中登记的活跃实例 / 其它端口不会被动)`)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
115
120
|
const interval = Number(argOf('--interval')) || 5000
|
|
116
121
|
const maxRestart = Number(argOf('--max-restart')) || 10
|
|
117
122
|
const vaultRoot = argOf('--vault') ? path.resolve(argOf('--vault')) : defaultVaultDir()
|
package/index.js
CHANGED
|
@@ -28,6 +28,7 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
|
28
28
|
import { ensureVault, search } from './lib/vault.js'
|
|
29
29
|
import { migrateFromMarkdown, setAuditConfig, backupDb } from './lib/db.js'
|
|
30
30
|
import { evaluateStall, MAX_STAMPS } from './lib/stall.js'
|
|
31
|
+
import { resolveVaultDir, currentWorkspace } from './lib/vault-resolve.js'
|
|
31
32
|
import { summarizeTurn, summarizeTurnDetailed, routeCandidates, extractLastTurn, sliceNewEvents, sessionEvents, sessionEventApi, createCaptureHealth, resolveRoute, captureCard, captureUpdate, pickNeighbors } from './lib/capture.js'
|
|
32
33
|
import { createApi, json, encodeBody } from './lib/api.js'
|
|
33
34
|
import { appendCaptureLog, readCaptureLog, rotateCaptureLog } from './lib/capture-log.js'
|
|
@@ -211,17 +212,18 @@ export function apply(ctx, config) {
|
|
|
211
212
|
const settings = bindSettings(ctx, Config, config)
|
|
212
213
|
|
|
213
214
|
// 首次激活:自动从 .md 文件迁移到 SQLite(幂等,已有数据则跳过)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
215
|
+
// vault 解析与所有独立进程共用同一套优先级(见 lib/vault-resolve.js):
|
|
216
|
+
// MEMORY_VAULT_DIR → activeVault → vaultProfiles[].match.workspace → vaultDir → 默认库。
|
|
217
|
+
const vaultDir = () => resolveVaultDir({
|
|
218
|
+
profiles: (settings.get() ?? {}).vaultProfiles,
|
|
219
|
+
activeVault: (settings.get() ?? {}).activeVault,
|
|
220
|
+
configured: (settings.get() ?? {}).vaultDir,
|
|
221
|
+
}).root
|
|
222
|
+
const vaultInfo = () => resolveVaultDir({
|
|
223
|
+
profiles: (settings.get() ?? {}).vaultProfiles,
|
|
224
|
+
activeVault: (settings.get() ?? {}).activeVault,
|
|
225
|
+
configured: (settings.get() ?? {}).vaultDir,
|
|
226
|
+
})
|
|
225
227
|
|
|
226
228
|
// 自动迁移:从 .md 文件导入 SQLite(幂等,DB 有数据则跳过)
|
|
227
229
|
try { migrateFromMarkdown(vaultDir()).catch(() => {}) } catch {}
|
|
@@ -589,6 +591,7 @@ export function apply(ctx, config) {
|
|
|
589
591
|
parameters: {
|
|
590
592
|
query: { type: 'string', required: true, description: '检索关键词或自然语言描述,如「数据库选型」「用户偏好」' },
|
|
591
593
|
limit: { type: 'number', description: '返回卡片数上限,默认 5' },
|
|
594
|
+
scope: { type: 'string', description: '可选作用域:库名(vaultProfiles 里的 name)、路径前缀,或 all 跨全部库聚合;留空 = 当前激活库' },
|
|
592
595
|
},
|
|
593
596
|
output: {
|
|
594
597
|
schema: { type: 'string' },
|
|
@@ -605,15 +608,46 @@ export function apply(ctx, config) {
|
|
|
605
608
|
const defLen = Number(cfg2.recallSummaryLen) || 130
|
|
606
609
|
const includeBody = cfg2.recallIncludeBody === true
|
|
607
610
|
const limit = Math.min(Math.max(Number(args.limit) || defLimit, 1), 20)
|
|
608
|
-
|
|
611
|
+
// 作用域(#10):留空 = 当前激活库;all = 跨库聚合;库名 = 指定 profile;路径前缀 = 命中多个库
|
|
612
|
+
const scope = String(args.scope || '').trim()
|
|
613
|
+
const roots = vaultRoots()
|
|
614
|
+
let targets = [{ name: '', root: vaultDir() }]
|
|
615
|
+
let scoped = false
|
|
616
|
+
if (scope) {
|
|
617
|
+
const want = scope.toLowerCase()
|
|
618
|
+
if (want === 'all') {
|
|
619
|
+
targets = roots
|
|
620
|
+
scoped = true
|
|
621
|
+
} else {
|
|
622
|
+
const byName = roots.filter((r) => String(r.name || '').toLowerCase() === want)
|
|
623
|
+
const prefix = scope.replace(/\\/g, '/').toLowerCase()
|
|
624
|
+
const byPath = roots.filter((r) => r.root.replace(/\\/g, '/').toLowerCase().startsWith(prefix))
|
|
625
|
+
targets = byName.length ? byName : byPath
|
|
626
|
+
if (!targets.length) {
|
|
627
|
+
return `未找到匹配「${scope}」的记忆库。可用:${roots.map((r) => r.name || r.root).join('、')}(也可用 scope=\"all\" 跨库检索)`
|
|
628
|
+
}
|
|
629
|
+
scoped = true
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
const multi = scoped
|
|
633
|
+
let hits = []
|
|
634
|
+
for (const target of targets) {
|
|
635
|
+
try {
|
|
636
|
+
const part = await search(target.root, query, { limit, minScore: 2 })
|
|
637
|
+
for (const h of part) hits.push(multi ? { ...h, vault: target.name || target.root } : h)
|
|
638
|
+
} catch { /* 单个库失败不影响其它库 */ }
|
|
639
|
+
}
|
|
640
|
+
if (multi) hits = hits.slice(0, limit)
|
|
609
641
|
if (hits.length === 0) return `记忆库中没有与「${query}」相关的内容。`
|
|
610
642
|
const lines = hits.map((h, i) => {
|
|
611
643
|
const tags = h.tags.length ? ` [${h.tags.join(', ')}]` : ''
|
|
612
644
|
const snippet = String(h.summary || '').replace(/\s+/g, ' ').trim().slice(0, defLen)
|
|
613
645
|
const body = includeBody ? `\n${String(h.excerpt || '').slice(0, 800)}` : ''
|
|
614
|
-
|
|
646
|
+
const from = h.vault ? `(库:${h.vault})` : ''
|
|
647
|
+
return `### ${i + 1}. ${h.title}${tags}${from}\n路径:${h.path}\n${snippet}${body}`
|
|
615
648
|
})
|
|
616
|
-
|
|
649
|
+
const where = multi ? `(作用域 ${scope || 'all'},共 ${targets.length} 个库)` : ''
|
|
650
|
+
return `从记忆核心检索到 ${hits.length} 条相关卡片${where}:\n\n${lines.join('\n\n')}`
|
|
617
651
|
},
|
|
618
652
|
}))
|
|
619
653
|
}
|
|
@@ -729,6 +763,9 @@ export function apply(ctx, config) {
|
|
|
729
763
|
distillEnabled: cfg.distillEnabled, dedupByLLM: cfg.dedupByLLM, captureMaxTokens: cfg.captureMaxTokens, recallMinScore: cfg.recallMinScore,
|
|
730
764
|
autoWeb: cfg.autoWeb, autoWebMode: cfg.autoWebMode, webPort: cfg.webPort, webCheckIntervalMs: cfg.webCheckIntervalMs, webMaxRestart: cfg.webMaxRestart, watchdogAutoSpawn: cfg.watchdogAutoSpawn, autoMcpSetup: cfg.autoMcpSetup,
|
|
731
765
|
auditMode: cfg.auditMode ?? 'all', auditExemptAgents: cfg.auditExemptAgents || [], auditExemptKinds: cfg.auditExemptKinds || [], recycleRetentionDays: cfg.recycleRetentionDays ?? 30,
|
|
766
|
+
// 多库(#10):配置页可直接编辑
|
|
767
|
+
vaultProfiles: Array.isArray(cfg.vaultProfiles) ? cfg.vaultProfiles : [],
|
|
768
|
+
activeVault: cfg.activeVault || '',
|
|
732
769
|
}
|
|
733
770
|
const descriptor = (ctx.get('settings') ?? {}).describe?.({ redactSecrets: true }) ?? []
|
|
734
771
|
const me = descriptor.find((d) => d.ns === 'memory-eternal')
|
|
@@ -740,6 +777,9 @@ export function apply(ctx, config) {
|
|
|
740
777
|
autoCapture: cfg.autoCapture !== false,
|
|
741
778
|
autoRecall: cfg.autoRecall !== false,
|
|
742
779
|
vaultDir: vaultDir(),
|
|
780
|
+
vaultName: vaultInfo().name,
|
|
781
|
+
vaultSource: vaultInfo().source,
|
|
782
|
+
workspace: vaultInfo().workspace,
|
|
743
783
|
version: versionRef,
|
|
744
784
|
}
|
|
745
785
|
return json(res, 200, { ok: true, config: safe, revision: me?.revision ?? 0, writable: true, readonly: false, schema: me?.schema ?? null, dsh: dshInfo, version: versionRef })
|
package/lib/api.js
CHANGED
|
@@ -299,6 +299,7 @@ export function createApi(deps = {}) {
|
|
|
299
299
|
distillEnabled: cfg.distillEnabled ?? true, dedupByLLM: cfg.dedupByLLM ?? true, captureMaxTokens: cfg.captureMaxTokens ?? 900, recallMinScore: cfg.recallMinScore ?? 2,
|
|
300
300
|
autoWeb: cfg.autoWeb ?? env.MEMORY_AUTO_WEB !== '0', autoWebMode: cfg.autoWebMode || 'init', webPort: Number(cfg.webPort) || Number(env.MEMORY_WEB_PORT) || 7999, webCheckIntervalMs: Number(cfg.webCheckIntervalMs) || 5000, webMaxRestart: Number(cfg.webMaxRestart) || 10, watchdogAutoSpawn: cfg.watchdogAutoSpawn ?? env.MEMORY_WATCHDOG === '1', autoMcpSetup: cfg.autoMcpSetup ?? false,
|
|
301
301
|
auditMode: cfg.auditMode ?? 'all', auditExemptAgents: cfg.auditExemptAgents ?? [], auditExemptKinds: cfg.auditExemptKinds ?? [], recycleRetentionDays: cfg.recycleRetentionDays ?? 30,
|
|
302
|
+
vaultProfiles: Array.isArray(cfg.vaultProfiles) ? cfg.vaultProfiles : [], activeVault: cfg.activeVault || '',
|
|
302
303
|
}
|
|
303
304
|
json(res, 200, { ok: true, config: safe, revision: 0, writable: false, readonly: true, version: env.MEMORY_ETERNAL_VERSION || '' })
|
|
304
305
|
return
|
package/lib/capture-run.js
CHANGED
|
@@ -9,6 +9,7 @@ import path from 'node:path'
|
|
|
9
9
|
import os from 'node:os'
|
|
10
10
|
import fs from 'node:fs'
|
|
11
11
|
import { captureCard, captureUpdate, pickNeighbors, summarizeTurnDetailed, routeCandidates } from './capture.js'
|
|
12
|
+
import { resolveVaultDir, defaultVaultRoot } from './vault-resolve.js'
|
|
12
13
|
import { createLlmFromEnv } from './llm-openai.js'
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -24,16 +25,17 @@ import { createLlmFromEnv } from './llm-openai.js'
|
|
|
24
25
|
*/
|
|
25
26
|
export function defaultVaultDir(env = process.env) {
|
|
26
27
|
if (env.MEMORY_VAULT_DIR && env.MEMORY_VAULT_DIR.trim()) return path.resolve(env.MEMORY_VAULT_DIR.trim())
|
|
27
|
-
const home = env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
|
28
28
|
try {
|
|
29
29
|
const cfg = readConfig(env) || {}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
// 与宿主共用同一套解析(含 match.workspace 的按项目选库)
|
|
31
|
+
return resolveVaultDir({
|
|
32
|
+
env,
|
|
33
|
+
profiles: cfg.vaultProfiles,
|
|
34
|
+
activeVault: cfg.activeVault,
|
|
35
|
+
configured: cfg.vaultDir,
|
|
36
|
+
}).root
|
|
35
37
|
} catch { /* 读不到共享配置就回落默认库 */ }
|
|
36
|
-
return
|
|
38
|
+
return defaultVaultRoot(env)
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
// 读取 DSH 设置的完整配置(写入的共享文件),使独立 web / MCP hook 捕获与 DSH 设置同步。
|