@modusensus/dsh-mneme 0.7.17 → 0.7.18
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/README.en.md +3 -2
- package/README.md +2 -1
- package/lib/api.js +6 -2
- package/lib/client.js +159 -38
- package/lib/store.js +16 -2
- package/package.json +10 -3
- package/src/api.js +6 -2
- package/src/store.js +16 -2
- package/test/api.test.js +55 -0
- package/test/client.test.js +77 -0
package/README.en.md
CHANGED
|
@@ -7,7 +7,7 @@ English | [简体中文](README.md)
|
|
|
7
7
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
10
|
-
[](https://github.com/modusensus/dsh-mneme)
|
|
11
11
|
|
|
12
12
|
> A cross-session memory plugin for DeepSeek Harness: it lets the Agent remember you, remember your projects, and organize memories automatically. **Mneme** (Μνήμη) — named after Mnemosyne, the Greek goddess of memory who presides over memory and dreams, just as autoDream consolidates memories in the background.
|
|
13
13
|
|
|
@@ -166,6 +166,7 @@ Every **background LLM call** (autoDream consolidation + summary, autoSummarize
|
|
|
166
166
|
|
|
167
167
|
| Version | Highlights |
|
|
168
168
|
|------|------|
|
|
169
|
+
| **v0.7.18** | Ecosystem step one + query convergence: better-sidebar soft integration (inject declaration + optional peer `dsh-better-sidebar` + registerTab reusing the four views, safe skip when absent; narrow-container `@container` adaptation) + `/list?deposited=only` view (receipt_chain ∪ source=dream) + deposited/archived filter chips in the library + status-page dashboard (server totals + view-all jumps) + drawer restore for archived memories; 667 tests green |
|
|
169
170
|
| **v0.7.17** | Panel polish: sidebar entry continuously aligned with the host (MutationObserver syncs the New-Session class + `width:100%` + native centering restored, resilient to async skin rewrites) + importance rendered as Lucide star glyphs (`ImportanceStars` filled/hollow component replacing text ★) + toolbar dropdown stacking fix (z-index moved to the container; export/import menu no longer painted under the sticky month header); 664 tests green |
|
|
170
171
|
| **v0.7.16** | Fixed autoDream empty-body failures on thinking models (`no json array in llm output`): restored config-first routing (settings "consolidation model" wins, Issue #25) + reasoningEffort auto-retry without effort on rejection + honest `llm_audit` error on parse failure; backfilled API-route tests (/delete, /entities, /external-api) + lib runtime smoke; 662 tests green |
|
|
171
172
|
| **v0.7.15** | Desktop adaptation: library panel redesign + 30-key feature-flag UI (features API) + status dashboard + import/export (mirror-isomorphic md golden loop) + token masking by default; 645 tests green |
|
|
@@ -210,7 +211,7 @@ Every **background LLM call** (autoDream consolidation + summary, autoSummarize
|
|
|
210
211
|
| v0.7.1–0.7.8 | ✅ Done | Issue fixes + graph backfill | tags↔entity_attrs bridge, inline-confirm delete, sidebar trigger toggle, brace escaping, user/fact layered types + stats endpoint (later removed), prefix-id resolution (later removed), sleep batch entity extraction, snapshotEvents() DSH compat; 764→812 tests green |
|
|
211
212
|
| v0.7.9 | ✅ Done | lib-sync gate | Issue #65: src-only shim silently killed the shipped lib — synced lib + pre-publish src↔lib consistency check + lib smoke tests; 815 tests green |
|
|
212
213
|
| v0.7.10–0.7.12 | ✅ Done | Panel redesign + near-rewrite | UX upgrades (color dots, pan/zoom, pagination, global search), then near-rewrite: inlined panel → pure HTTP API + standalone CLI + lightMode; TYPES narrowed 8→6 |
|
|
213
|
-
| v0.7.13–0.7.
|
|
214
|
+
| v0.7.13–0.7.18 | ✅ Done | Distillation, security, desktop, dream fix, panel polish, ecosystem | codingRetrospect + 429 governor; private reasoning blocks dropped (CWE-200); desktop panel redesign + feature flags; autoDream thinking-model empty-body fix + backfilled tests; sidebar entry alignment + star glyphs + menu stacking polish; better-sidebar soft integration + deposited/archived library views; 667 tests green |
|
|
214
215
|
| **v0.8.0** | 🚧 Planned (late Sep) | Graph enhancement | Interest-drift visualization + scope isolation (issue #17) + cross-workspace sharing |
|
|
215
216
|
|
|
216
217
|
> All new capabilities ship as **toggleable features** (enabled/disabled via configuration), conservatively on by default and never breaking existing behavior. The `failure_memories` table and the autoDream decision engine have already paved the way for future reflective growth.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
8
|
-
[](https://github.com/modusensus/dsh-mneme)
|
|
9
9
|
[](https://github.com/modusensus/dsh-mneme/actions)
|
|
10
10
|
[](https://nodejs.org)
|
|
11
11
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
@@ -216,6 +216,7 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
216
216
|
|
|
217
217
|
| 版本 | 亮点 |
|
|
218
218
|
|------|------|
|
|
219
|
+
| **v0.7.18** | 生态第一步 + 查询收敛:better-sidebar 软集成(inject 声明 + optional peer `dsh-better-sidebar` + registerTab 复用四视图,未装安全跳过;窄容器 `@container` 适配)+ `/list?deposited=only` 沉淀视图(receipt_chain ∪ source=dream)+ 记忆库沉淀/已归档筛选 chip + 状态页仪表盘化(统计 + 查看全部跳转预置筛选)+ 抽屉归档记忆「恢复」;667 测试全绿 |
|
|
219
220
|
| **v0.7.17** | 面板体验细化:侧边栏入口持续对齐宿主(MutationObserver 同步「新会话」类名 + `width:100%` + 交还原生居中,皮肤异步改写不再失配)+ 重要性星级换 Lucide 星形(`ImportanceStars` 实心/空心组件,替换文本 ★)+ 工具栏下拉层级修复(z-index 提到容器,导出/导入菜单不再被吸顶月份头遮挡);664 测试全绿 |
|
|
220
221
|
| **v0.7.16** | 修复 autoDream 在 thinking 模型上空体 failed(`no json array in llm output`):恢复 config-first 路由(设置面板「巩固模型」生效,Issue #25)+ reasoningEffort 被拒自动去掉重试一次 + 解析失败如实记 llm_audit error 并带原始输出头日志;补测 API 路由空白(/delete、/entities、/external-api)+ lib 运行时冒烟(src↔lib 一致性);662 测试全绿 |
|
|
221
222
|
| **v0.7.15** | 桌面端适配:记忆库面板重设计(撤对话 tab → 居中非全屏 sheet、卡片网格/时间线双视图、右侧详情抽屉、编辑/归档/关联实体)+ 侧边栏入口上移工作区上方(借用宿主原生类名对齐、收起态零位移)+ 功能开关 30 键上 UI(features API,巩固模型与 embedding 提供方可配)+ 状态页工作台(巩固卡/工作动态流/沉淀记忆/归档恢复)+ 导入导出(镜像同构 md 黄金闭环)+ Token 面板默认遮蔽;645 测试全绿 |
|
package/lib/api.js
CHANGED
|
@@ -178,7 +178,11 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
178
178
|
// archived=only:只看归档(状态页的归档列表用)。归档行不进默认列表,
|
|
179
179
|
// 所以这是独立的视图开关,而不是 includeArchived 的混看模式。
|
|
180
180
|
const onlyArchived = url.searchParams.get("archived") === "only";
|
|
181
|
-
|
|
181
|
+
// deposited=only:只看 autoDream 巩固过的记忆(receipt_chain 的
|
|
182
|
+
// merge/update verdict ∪ source=dream 直写)——状态页「查看全部」
|
|
183
|
+
// 与记忆库的「沉淀」筛选 chip 共用这个视图。
|
|
184
|
+
const depositedOnly = url.searchParams.get("deposited") === "only";
|
|
185
|
+
const rows = service.list({ type, limit, offset, order, minImportance, source, updatedFrom, updatedTo, onlyArchived, depositedOnly });
|
|
182
186
|
// 面板行在 wire DTO 之上补 archived/quality_score——模型工具的输出
|
|
183
187
|
// schema 严格复用 toApiList,扩展只发生在 HTTP 层。
|
|
184
188
|
const items = service.toApiList(rows).map((m, i) => ({
|
|
@@ -189,7 +193,7 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
189
193
|
// Total honors the same filters as the rows, or the pager's
|
|
190
194
|
// has-more math breaks whenever minImportance/source/updated-at
|
|
191
195
|
// bounds are active.
|
|
192
|
-
sendJson(res, 200, { items, total: service.count(type, { minImportance, source, updatedFrom, updatedTo, onlyArchived }) });
|
|
196
|
+
sendJson(res, 200, { items, total: service.count(type, { minImportance, source, updatedFrom, updatedTo, onlyArchived, depositedOnly }) });
|
|
193
197
|
} catch {
|
|
194
198
|
sendJson(res, 500, { error: "internal" });
|
|
195
199
|
}
|
package/lib/client.js
CHANGED
|
@@ -105,7 +105,7 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
return fetch(path, { ...opts, headers });
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
const inject = ["slots", "locale"];
|
|
108
|
+
const inject = ["slots", "locale", "betterSidebar"];
|
|
109
109
|
|
|
110
110
|
const NS = "memory";
|
|
111
111
|
|
|
@@ -174,6 +174,11 @@ window.__ModuleLoader__.load({
|
|
|
174
174
|
"memory.explorer.updated": "更新",
|
|
175
175
|
"memory.explorer.tags": "标签",
|
|
176
176
|
"memory.explorer.importance": "重要性",
|
|
177
|
+
"memory.explorer.sourceFilter": "来源",
|
|
178
|
+
"memory.explorer.filterDeposited": "沉淀",
|
|
179
|
+
"memory.explorer.filterDepositedHint": "只看 autoDream 巩固/更新过的记忆",
|
|
180
|
+
"memory.explorer.filterArchived": "已归档",
|
|
181
|
+
"memory.explorer.filterArchivedHint": "只看已归档的记忆(默认列表不含它们)",
|
|
177
182
|
"memory.explorer.topK": "返回数量",
|
|
178
183
|
"memory.explorer.topKOption": "返回 {n} 条",
|
|
179
184
|
"memory.card.open": "在记忆库中查看全文",
|
|
@@ -329,6 +334,8 @@ window.__ModuleLoader__.load({
|
|
|
329
334
|
"memory.explorer.detail.save": "保存",
|
|
330
335
|
"memory.explorer.detail.cancel": "取消",
|
|
331
336
|
"memory.explorer.detail.archive": "归档",
|
|
337
|
+
"memory.explorer.detail.restore": "恢复",
|
|
338
|
+
"memory.explorer.detail.restored": "已恢复到主列表",
|
|
332
339
|
"memory.explorer.detail.archived": "已归档",
|
|
333
340
|
"memory.explorer.detail.archiveFailed": "操作失败",
|
|
334
341
|
"memory.explorer.detail.saved": "已保存,镜像同步更新",
|
|
@@ -355,6 +362,9 @@ window.__ModuleLoader__.load({
|
|
|
355
362
|
"memory.status.consolidated": "沉淀的记忆",
|
|
356
363
|
"memory.status.consolidatedEmpty": "autoDream / autoSummarize 沉淀的记忆会出现在这里",
|
|
357
364
|
"memory.status.archivedMemories": "已归档的记忆",
|
|
365
|
+
"memory.status.viewAll": "查看全部",
|
|
366
|
+
"memory.status.depositedCount": "沉淀的记忆({n})",
|
|
367
|
+
"memory.status.archivedCount": "已归档的记忆({n})",
|
|
358
368
|
"memory.status.archivedEmpty": "没有归档的记忆",
|
|
359
369
|
"memory.status.restore": "恢复",
|
|
360
370
|
"memory.settings.extapi.reveal": "显示",
|
|
@@ -438,6 +448,11 @@ window.__ModuleLoader__.load({
|
|
|
438
448
|
"memory.explorer.updated": "Updated",
|
|
439
449
|
"memory.explorer.tags": "Tags",
|
|
440
450
|
"memory.explorer.importance": "Importance",
|
|
451
|
+
"memory.explorer.sourceFilter": "Source",
|
|
452
|
+
"memory.explorer.filterDeposited": "Deposited",
|
|
453
|
+
"memory.explorer.filterDepositedHint": "Only memories autoDream consolidated or updated",
|
|
454
|
+
"memory.explorer.filterArchived": "Archived",
|
|
455
|
+
"memory.explorer.filterArchivedHint": "Only archived memories (excluded from the default list)",
|
|
441
456
|
"memory.explorer.topK": "Results limit",
|
|
442
457
|
"memory.explorer.topKOption": "Return {n}",
|
|
443
458
|
"memory.card.open": "Open full text in the memory library",
|
|
@@ -593,6 +608,8 @@ window.__ModuleLoader__.load({
|
|
|
593
608
|
"memory.explorer.detail.save": "Save",
|
|
594
609
|
"memory.explorer.detail.cancel": "Cancel",
|
|
595
610
|
"memory.explorer.detail.archive": "Archive",
|
|
611
|
+
"memory.explorer.detail.restore": "Restore",
|
|
612
|
+
"memory.explorer.detail.restored": "Restored to the main list",
|
|
596
613
|
"memory.explorer.detail.archived": "Archived",
|
|
597
614
|
"memory.explorer.detail.archiveFailed": "Action failed",
|
|
598
615
|
"memory.explorer.detail.saved": "Saved; mirrors re-rendered",
|
|
@@ -619,6 +636,9 @@ window.__ModuleLoader__.load({
|
|
|
619
636
|
"memory.status.consolidated": "Deposited memories",
|
|
620
637
|
"memory.status.consolidatedEmpty": "Memories deposited by autoDream / autoSummarize appear here",
|
|
621
638
|
"memory.status.archivedMemories": "Archived memories",
|
|
639
|
+
"memory.status.viewAll": "View all",
|
|
640
|
+
"memory.status.depositedCount": "Deposited memories ({n})",
|
|
641
|
+
"memory.status.archivedCount": "Archived memories ({n})",
|
|
622
642
|
"memory.status.archivedEmpty": "Nothing archived",
|
|
623
643
|
"memory.status.restore": "Restore",
|
|
624
644
|
"memory.settings.extapi.reveal": "Reveal",
|
|
@@ -686,6 +706,8 @@ window.__ModuleLoader__.load({
|
|
|
686
706
|
// text-turns-brand-blue active states — no boxed panels).
|
|
687
707
|
const CSS_TAG = "@modusensus/dsh-mneme/drawer.css";
|
|
688
708
|
const css = [
|
|
709
|
+
// --- explorer shell: container queries let the toolbar adapt to the
|
|
710
|
+
// better-sidebar workbench pane (~500px) as well as the 1240px sheet ---
|
|
689
711
|
// --- sidebar foot trigger (wide row / collapsed rail icon) ---
|
|
690
712
|
".mneme-trigger{box-sizing:border-box;cursor:pointer;width:calc(100% + 4px);height:42px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:12px;flex:none;align-items:center;gap:8px;margin:4px -2px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}",
|
|
691
713
|
".mneme-trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
@@ -705,7 +727,11 @@ window.__ModuleLoader__.load({
|
|
|
705
727
|
".mneme-entitychip{flex:none;height:26px;padding:0 10px;border-radius:8px;border:none;background:none;color:var(--dsw-alias-state-business-primary);cursor:pointer;font-family:inherit;font-size:12px;line-height:16px;display:inline-flex;align-items:center}",
|
|
706
728
|
".mneme-entitychip:hover{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent)}",
|
|
707
729
|
// --- main-area memory library page ---
|
|
708
|
-
".mneme-x{flex:1;min-height:0;height:100%;width:100%;box-sizing:border-box;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2)}",
|
|
730
|
+
".mneme-x{flex:1;min-height:0;height:100%;width:100%;box-sizing:border-box;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);container-type:inline-size}",
|
|
731
|
+
// 窄容器(better-sidebar 工作台 ~500px)下中央 vtabs 会与右缘悬浮的
|
|
732
|
+
// 卡片/时间线切换器重叠:容器查询收窄时隐藏切换器,视图模式沿用
|
|
733
|
+
// localStorage 记忆,宽容器(sheet)不受影响。
|
|
734
|
+
"@container (max-width: 640px){.mneme-xtools .mneme-seg{display:none}}",
|
|
709
735
|
".mneme-xbar{position:relative;flex:none;display:flex;align-items:center;justify-content:center;gap:6px;border-bottom:1px solid var(--dsw-alias-border-l2);padding:0 16px;min-height:52px}",
|
|
710
736
|
".mneme-filterbar{flex:none;display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 16px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
|
|
711
737
|
".mneme-vtabs{display:flex;align-items:stretch;height:46px}",
|
|
@@ -938,7 +964,7 @@ window.__ModuleLoader__.load({
|
|
|
938
964
|
".mneme-statusgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;max-width:1000px;margin:0 auto}",
|
|
939
965
|
".mneme-statuscard{min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:14px}",
|
|
940
966
|
// --- 状态页工作台:让用户看见插件在干活(动态/沉淀/归档) ---
|
|
941
|
-
".mneme-wbhead{margin:28px auto 10px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);max-width:1000px}",
|
|
967
|
+
".mneme-wbhead{margin:28px auto 10px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);max-width:1000px;display:flex;align-items:baseline;justify-content:space-between;gap:8px}",
|
|
942
968
|
".mneme-wblist{max-width:1000px;display:flex;flex-direction:column;gap:6px}",
|
|
943
969
|
".mneme-wbrow{display:flex;gap:10px;align-items:flex-start;padding:10px 14px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px}",
|
|
944
970
|
".mneme-wbdot{flex:none;width:8px;height:8px;border-radius:50%;margin-top:6px;background:var(--dsw-alias-state-success,#2a9d6a)}",
|
|
@@ -2379,11 +2405,14 @@ window.__ModuleLoader__.load({
|
|
|
2379
2405
|
return [];
|
|
2380
2406
|
}
|
|
2381
2407
|
|
|
2382
|
-
function WorkbenchSection({ t }) {
|
|
2408
|
+
function WorkbenchSection({ t, onBrowse }) {
|
|
2383
2409
|
const [feed, setFeed] = useState(null); // audit rows(已过滤巩固/总结)
|
|
2384
|
-
const [deposited, setDeposited] = useState(null); //
|
|
2385
|
-
const [
|
|
2410
|
+
const [deposited, setDeposited] = useState(null); // 沉淀记忆首页(服务端 deposited 视图)
|
|
2411
|
+
const [depositedTotal, setDepositedTotal] = useState(0);
|
|
2412
|
+
const [archived, setArchived] = useState(null); // 归档记忆首页
|
|
2413
|
+
const [archivedTotal, setArchivedTotal] = useState(0);
|
|
2386
2414
|
const [restoring, setRestoring] = useState("");
|
|
2415
|
+
const [wbReload, setWbReload] = useState(0);
|
|
2387
2416
|
|
|
2388
2417
|
const load = useCallback(() => {
|
|
2389
2418
|
let cancelled = false;
|
|
@@ -2391,30 +2420,31 @@ window.__ModuleLoader__.load({
|
|
|
2391
2420
|
.then((r) => (r.ok ? r.json() : { items: [] }))
|
|
2392
2421
|
.then((d) => {
|
|
2393
2422
|
if (cancelled) return;
|
|
2394
|
-
|
|
2395
|
-
r.trigger_source === "autoDream" || r.trigger_source === "autoSummarize");
|
|
2396
|
-
setFeed(rows);
|
|
2397
|
-
const ids = [];
|
|
2398
|
-
for (const row of rows) {
|
|
2399
|
-
for (const id of parseRelatedIds(row.related_memory_ids)) {
|
|
2400
|
-
if (!ids.includes(id)) ids.push(id);
|
|
2401
|
-
}
|
|
2402
|
-
}
|
|
2403
|
-
return apiFetch("/api/dsh-mneme/list?limit=300&order=chrono")
|
|
2404
|
-
.then((r2) => (r2.ok ? r2.json() : { items: [] }))
|
|
2405
|
-
.then((d2) => {
|
|
2406
|
-
if (cancelled) return;
|
|
2407
|
-
const byId = new Map((d2.items || []).map((m) => [m.id, m]));
|
|
2408
|
-
setDeposited(ids.map((id) => byId.get(id)).filter(Boolean).slice(0, 8));
|
|
2409
|
-
});
|
|
2423
|
+
setFeed((d.items || []).filter((r) =>
|
|
2424
|
+
r.trigger_source === "autoDream" || r.trigger_source === "autoSummarize"));
|
|
2410
2425
|
})
|
|
2411
2426
|
.catch(() => { if (!cancelled) setFeed([]); });
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2427
|
+
// 仪表盘数据源:沉淀/归档各取一小页 + 服务端 total(/list 的 total
|
|
2428
|
+
// 与行同过滤)。长列表不再进状态页——「查看全部」跳记忆库的筛选
|
|
2429
|
+
// 视图,查询收敛到有搜索/分页的地方。
|
|
2430
|
+
apiFetch("/api/dsh-mneme/list?deposited=only&limit=8&order=chrono")
|
|
2431
|
+
.then((r) => (r.ok ? r.json() : { items: [], total: 0 }))
|
|
2432
|
+
.then((d) => {
|
|
2433
|
+
if (cancelled) return;
|
|
2434
|
+
setDeposited(d.items || []);
|
|
2435
|
+
setDepositedTotal(d.total || 0);
|
|
2436
|
+
})
|
|
2437
|
+
.catch(() => { if (!cancelled) setDeposited([]); });
|
|
2438
|
+
apiFetch("/api/dsh-mneme/list?archived=only&limit=3&order=chrono")
|
|
2439
|
+
.then((r) => (r.ok ? r.json() : { items: [], total: 0 }))
|
|
2440
|
+
.then((d) => {
|
|
2441
|
+
if (cancelled) return;
|
|
2442
|
+
setArchived(d.items || []);
|
|
2443
|
+
setArchivedTotal(d.total || 0);
|
|
2444
|
+
})
|
|
2415
2445
|
.catch(() => { if (!cancelled) setArchived([]); });
|
|
2416
2446
|
return () => { cancelled = true; };
|
|
2417
|
-
}, []);
|
|
2447
|
+
}, [wbReload]);
|
|
2418
2448
|
useEffect(() => { load(); }, [load]);
|
|
2419
2449
|
|
|
2420
2450
|
const restore = (id) => {
|
|
@@ -2425,7 +2455,10 @@ window.__ModuleLoader__.load({
|
|
|
2425
2455
|
body: JSON.stringify({ id, archived: false })
|
|
2426
2456
|
})
|
|
2427
2457
|
.then((r) => { if (!r.ok) throw new Error("http"); })
|
|
2428
|
-
.then(() =>
|
|
2458
|
+
.then(() => {
|
|
2459
|
+
setArchived((cur) => (cur || []).filter((m) => m.id !== id));
|
|
2460
|
+
setArchivedTotal((n) => Math.max(0, n - 1));
|
|
2461
|
+
})
|
|
2429
2462
|
.catch(() => {})
|
|
2430
2463
|
.finally(() => setRestoring(""));
|
|
2431
2464
|
};
|
|
@@ -2457,8 +2490,14 @@ window.__ModuleLoader__.load({
|
|
|
2457
2490
|
row.error_message ? ` · ${row.error_message}` : "")
|
|
2458
2491
|
));
|
|
2459
2492
|
})),
|
|
2460
|
-
deposited !== null &&
|
|
2461
|
-
h("div", { className: "mneme-wbhead" },
|
|
2493
|
+
deposited !== null && depositedTotal > 0 && h(react.Fragment, null,
|
|
2494
|
+
h("div", { className: "mneme-wbhead" },
|
|
2495
|
+
h("span", null, t("memory.status.depositedCount").replace("{n}", String(depositedTotal))),
|
|
2496
|
+
onBrowse && depositedTotal > deposited.length && h("button", {
|
|
2497
|
+
type: "button",
|
|
2498
|
+
className: "mneme-footbtn",
|
|
2499
|
+
onClick: () => onBrowse({ deposited: true })
|
|
2500
|
+
}, t("memory.status.viewAll"))),
|
|
2462
2501
|
h("div", { className: "mneme-wblist" },
|
|
2463
2502
|
deposited.map((m) => h("div", { className: "mneme-wbmemo", key: m.id },
|
|
2464
2503
|
h("span", { className: "mneme-xdot", style: { color: memoryTypeColor(m.type) }, "aria-hidden": "true" }),
|
|
@@ -2468,7 +2507,13 @@ window.__ModuleLoader__.load({
|
|
|
2468
2507
|
`${typeLabel(t, m.type)} · ${formatRelativeTime(m.updated_at || m.created_at, t)}`))
|
|
2469
2508
|
)))),
|
|
2470
2509
|
archived !== null && h(react.Fragment, null,
|
|
2471
|
-
h("div", { className: "mneme-wbhead" },
|
|
2510
|
+
h("div", { className: "mneme-wbhead" },
|
|
2511
|
+
h("span", null, t("memory.status.archivedCount").replace("{n}", String(archivedTotal))),
|
|
2512
|
+
onBrowse && archivedTotal > archived.length && h("button", {
|
|
2513
|
+
type: "button",
|
|
2514
|
+
className: "mneme-footbtn",
|
|
2515
|
+
onClick: () => onBrowse({ archived: true })
|
|
2516
|
+
}, t("memory.status.viewAll"))),
|
|
2472
2517
|
archived.length === 0
|
|
2473
2518
|
? h("div", { className: "mneme-featsubhint" }, t("memory.status.archivedEmpty"))
|
|
2474
2519
|
: h("div", { className: "mneme-wblist" },
|
|
@@ -2488,7 +2533,7 @@ window.__ModuleLoader__.load({
|
|
|
2488
2533
|
);
|
|
2489
2534
|
}
|
|
2490
2535
|
|
|
2491
|
-
function StatusPanel({ t }) {
|
|
2536
|
+
function StatusPanel({ t, onBrowse }) {
|
|
2492
2537
|
return h("div", { className: "mneme-status" },
|
|
2493
2538
|
h("div", { className: "mneme-statusgrid" },
|
|
2494
2539
|
h(MemoriesStatusCard, { t }),
|
|
@@ -2497,7 +2542,7 @@ window.__ModuleLoader__.load({
|
|
|
2497
2542
|
h(LlmStatusCard, { t }),
|
|
2498
2543
|
h(DreamStatusCards, { t })
|
|
2499
2544
|
),
|
|
2500
|
-
h(WorkbenchSection, { t })
|
|
2545
|
+
h(WorkbenchSection, { t, onBrowse })
|
|
2501
2546
|
);
|
|
2502
2547
|
}
|
|
2503
2548
|
|
|
@@ -2633,8 +2678,11 @@ window.__ModuleLoader__.load({
|
|
|
2633
2678
|
)
|
|
2634
2679
|
: h(react.Fragment, null,
|
|
2635
2680
|
h("button", { type: "button", className: "mneme-btn", onClick: () => setEditing(true) }, t("memory.explorer.detail.edit")),
|
|
2636
|
-
|
|
2637
|
-
|
|
2681
|
+
memory.archived
|
|
2682
|
+
? h("button", { type: "button", className: "mneme-btn", disabled: busy, onClick: () => postUpdate({ archived: false }, { restored: true }) },
|
|
2683
|
+
t("memory.explorer.detail.restore"))
|
|
2684
|
+
: h("button", { type: "button", className: "mneme-btn", disabled: busy, onClick: () => postUpdate({ archived: true }, { archived: true }) },
|
|
2685
|
+
t("memory.explorer.detail.archive")),
|
|
2638
2686
|
h("button", {
|
|
2639
2687
|
type: "button",
|
|
2640
2688
|
className: "mneme-btn",
|
|
@@ -2747,6 +2795,9 @@ window.__ModuleLoader__.load({
|
|
|
2747
2795
|
catch { return "timeline"; }
|
|
2748
2796
|
});
|
|
2749
2797
|
const [dateRange, setDateRange] = useState("all"); // all | 7d | 30d | 90d
|
|
2798
|
+
// 方案 A:沉淀/归档筛选 chip——状态页「查看全部」也会带着它们跳转过来。
|
|
2799
|
+
const [depositedOnly, setDepositedOnly] = useState(false); // 只看 autoDream 巩固
|
|
2800
|
+
const [archivedOnly, setArchivedOnly] = useState(false); // 只看已归档
|
|
2750
2801
|
const [pendingIds, setPendingIds] = useState(() => new Set());
|
|
2751
2802
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
2752
2803
|
const [importOpen, setImportOpen] = useState(false);
|
|
@@ -2785,7 +2836,9 @@ window.__ModuleLoader__.load({
|
|
|
2785
2836
|
).toISOString();
|
|
2786
2837
|
const filterQS = (type === "all" ? "" : `&type=${encodeURIComponent(type)}`)
|
|
2787
2838
|
+ (minImp ? `&minImportance=${minImp}` : "")
|
|
2788
|
-
+ (dateFromIso ? `&updatedFrom=${encodeURIComponent(dateFromIso)}` : "")
|
|
2839
|
+
+ (dateFromIso ? `&updatedFrom=${encodeURIComponent(dateFromIso)}` : "")
|
|
2840
|
+
+ (depositedOnly ? "&deposited=only" : "")
|
|
2841
|
+
+ (archivedOnly ? "&archived=only" : "");
|
|
2789
2842
|
const listUrl = (offset) => `/api/dsh-mneme/list?limit=${PAGE_SIZE}&offset=${offset}${filterQS}&order=chrono`;
|
|
2790
2843
|
|
|
2791
2844
|
// Browse = paged, pure-chronological pages (order=chrono). The default
|
|
@@ -2971,7 +3024,7 @@ window.__ModuleLoader__.load({
|
|
|
2971
3024
|
|
|
2972
3025
|
// 编辑保存 / 归档的本地落账:替换或移除对应行;镜像由后端重渲染。
|
|
2973
3026
|
// 归档从所有视图移除(列表默认只看未归档),并清空选中。
|
|
2974
|
-
const applyMemoryUpdate = (updated, { archived = false } = {}) => {
|
|
3027
|
+
const applyMemoryUpdate = (updated, { archived = false, restored = false } = {}) => {
|
|
2975
3028
|
if (archived) {
|
|
2976
3029
|
setItems((cur) => cur.filter((m) => m.id !== updated.id));
|
|
2977
3030
|
setRemoteItems((cur) => (cur ? cur.filter((m) => m.id !== updated.id) : cur));
|
|
@@ -2981,6 +3034,17 @@ window.__ModuleLoader__.load({
|
|
|
2981
3034
|
setTimeout(() => setToastMsg(""), 2500);
|
|
2982
3035
|
return;
|
|
2983
3036
|
}
|
|
3037
|
+
if (restored) {
|
|
3038
|
+
// 恢复:行离开当前视图(抽屉只能从归档浏览列表打开它),回主列表
|
|
3039
|
+
// 由下一次筛选切换/刷新自然带出。
|
|
3040
|
+
setItems((cur) => cur.filter((m) => m.id !== updated.id));
|
|
3041
|
+
setRemoteItems((cur) => (cur ? cur.filter((m) => m.id !== updated.id) : cur));
|
|
3042
|
+
setTotal((n) => Math.max(0, n - 1));
|
|
3043
|
+
setSelectedId(null);
|
|
3044
|
+
setToastMsg(t("memory.explorer.detail.restored"));
|
|
3045
|
+
setTimeout(() => setToastMsg(""), 2500);
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
2984
3048
|
const replace = (m) => (m.id === updated.id ? updated : m);
|
|
2985
3049
|
setItems((cur) => cur.map(replace));
|
|
2986
3050
|
setRemoteItems((cur) => (cur ? cur.map(replace) : cur));
|
|
@@ -3026,6 +3090,16 @@ window.__ModuleLoader__.load({
|
|
|
3026
3090
|
setView("entity");
|
|
3027
3091
|
};
|
|
3028
3092
|
|
|
3093
|
+
// 状态页「查看全部」入口:跳回记忆库并预置对应筛选(清搜索态,避免
|
|
3094
|
+
// remoteItems 盖过浏览列表)。
|
|
3095
|
+
const browseWithFilter = (patch) => {
|
|
3096
|
+
setDepositedOnly(!!patch.deposited);
|
|
3097
|
+
setArchivedOnly(!!patch.archived);
|
|
3098
|
+
setQuery("");
|
|
3099
|
+
setRemoteItems(null);
|
|
3100
|
+
setView("memory");
|
|
3101
|
+
};
|
|
3102
|
+
|
|
3029
3103
|
const subviews = [
|
|
3030
3104
|
{ key: "memory", label: t("memory.explorer.tabMemory") },
|
|
3031
3105
|
{ key: "entity", label: t("memory.explorer.tabEntities") },
|
|
@@ -3144,6 +3218,19 @@ window.__ModuleLoader__.load({
|
|
|
3144
3218
|
onClick: () => setMinImp(v)
|
|
3145
3219
|
}, v === 0 ? t("memory.tab.all") : `★${v}+`))
|
|
3146
3220
|
),
|
|
3221
|
+
h("div", { className: "mneme-xcolhead" }, t("memory.explorer.sourceFilter")),
|
|
3222
|
+
h("div", { className: "mneme-xrow" },
|
|
3223
|
+
h("button", {
|
|
3224
|
+
className: depositedOnly ? "mneme-chip mneme-active" : "mneme-chip",
|
|
3225
|
+
title: t("memory.explorer.filterDepositedHint"),
|
|
3226
|
+
onClick: () => setDepositedOnly(!depositedOnly)
|
|
3227
|
+
}, t("memory.explorer.filterDeposited")),
|
|
3228
|
+
h("button", {
|
|
3229
|
+
className: archivedOnly ? "mneme-chip mneme-active" : "mneme-chip",
|
|
3230
|
+
title: t("memory.explorer.filterArchivedHint"),
|
|
3231
|
+
onClick: () => setArchivedOnly(!archivedOnly)
|
|
3232
|
+
}, t("memory.explorer.filterArchived"))
|
|
3233
|
+
),
|
|
3147
3234
|
h("div", { className: "mneme-xcolhead" }, t("memory.explorer.types")),
|
|
3148
3235
|
h("button", {
|
|
3149
3236
|
className: type === "all" ? "mneme-xtype mneme-active" : "mneme-xtype",
|
|
@@ -3276,7 +3363,7 @@ window.__ModuleLoader__.load({
|
|
|
3276
3363
|
})
|
|
3277
3364
|
),
|
|
3278
3365
|
view === "entity" && h(EntityPanel, { t, focusEntity: graphFocus, onJumpMemory: jumpToMemory }),
|
|
3279
|
-
view === "status" && h(StatusPanel, { t }),
|
|
3366
|
+
view === "status" && h(StatusPanel, { t, onBrowse: browseWithFilter }),
|
|
3280
3367
|
view === "settings" && h("div", { className: "mneme-set" },
|
|
3281
3368
|
h("div", { className: "mneme-set-inner" }, h(SettingsContent, { t }))
|
|
3282
3369
|
),
|
|
@@ -3369,6 +3456,7 @@ window.__ModuleLoader__.load({
|
|
|
3369
3456
|
}
|
|
3370
3457
|
|
|
3371
3458
|
function apply(ctx) {
|
|
3459
|
+
const t = ctx.locale.bind(NS);
|
|
3372
3460
|
ctx.effect(() => ctx.locale.register(NS, dictionaries), "dsh-mneme: dictionaries");
|
|
3373
3461
|
|
|
3374
3462
|
// 记忆库唯一入口:sidebar.footer.action 注册作为锚点与回退;真实按钮
|
|
@@ -3377,7 +3465,6 @@ window.__ModuleLoader__.load({
|
|
|
3377
3465
|
// v0.7.15 起不再注册 conversation.view tab——对话内嵌的面板被悬浮
|
|
3378
3466
|
// 输入框遮挡、挤压会话布局,sheet 页是更舒服的承载方式。
|
|
3379
3467
|
ctx.slots.inject("sidebar.footer.action", () => {
|
|
3380
|
-
const t = ctx.locale.bind(NS);
|
|
3381
3468
|
return ctx.slots.register({
|
|
3382
3469
|
name: "sidebar.footer.action",
|
|
3383
3470
|
id: "dsh-mneme",
|
|
@@ -3392,6 +3479,40 @@ window.__ModuleLoader__.load({
|
|
|
3392
3479
|
h(MemoryOverlay, { t })
|
|
3393
3480
|
));
|
|
3394
3481
|
});
|
|
3482
|
+
|
|
3483
|
+
// --- better-sidebar 生态 tab(可选软依赖,官方指南 §2.2 契约)---
|
|
3484
|
+
// 'betterSidebar' 声明进 inject + package.json optional peer:未安装
|
|
3485
|
+
// better-sidebar 时服务解析为 undefined,探测条件不成立即静默跳过,
|
|
3486
|
+
// 现有 footer 锚点 + 顶部入口 + sheet 完全不受影响(实测:DSH 运行时
|
|
3487
|
+
// 对 ctx 属性访问有 inject 闸门,未声明就取值会直接 fail 整个 loader
|
|
3488
|
+
// entry,§15 的「不声明纯探测」模式在 DSH 上不可用)。
|
|
3489
|
+
// 服务提供时序仍无保证(better-sidebar 可能晚于本插件加载),短重试
|
|
3490
|
+
// 兜底;注册包在 ctx.effect 里,HMR/禁用时随作用域自动清理。
|
|
3491
|
+
ctx.effect(() => {
|
|
3492
|
+
let tries = 0, timer = null;
|
|
3493
|
+
const attempt = () => {
|
|
3494
|
+
const bs = ctx.betterSidebar;
|
|
3495
|
+
if (bs && typeof bs.registerTab === "function") {
|
|
3496
|
+
try {
|
|
3497
|
+
bs.registerTab({
|
|
3498
|
+
id: "dsh-mneme:memory",
|
|
3499
|
+
title: () => t("memory.view.label"),
|
|
3500
|
+
icon: (size) => h(IconArchiveOutline20, { size }),
|
|
3501
|
+
order: 60,
|
|
3502
|
+
component: () => h(MemoryExplorer, { t })
|
|
3503
|
+
});
|
|
3504
|
+
return;
|
|
3505
|
+
} catch (err) {
|
|
3506
|
+
// id 重复等注册失败不应拖垮其余功能,留一条线索即可
|
|
3507
|
+
console.error("[dsh-mneme] better-sidebar registerTab failed:", err);
|
|
3508
|
+
return;
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
if (++tries <= 10) timer = setTimeout(attempt, 1000);
|
|
3512
|
+
};
|
|
3513
|
+
attempt();
|
|
3514
|
+
return () => clearTimeout(timer);
|
|
3515
|
+
}, "dsh-mneme: better-sidebar tab");
|
|
3395
3516
|
}
|
|
3396
3517
|
|
|
3397
3518
|
exports.apply = apply;
|
package/lib/store.js
CHANGED
|
@@ -637,7 +637,7 @@ export function createStore(path) {
|
|
|
637
637
|
return ts;
|
|
638
638
|
}
|
|
639
639
|
|
|
640
|
-
function count(type, { minImportance = null, source = null, includeForgotten = false, includeArchived = false, onlyArchived = false, updatedFrom = null, updatedTo = null } = {}) {
|
|
640
|
+
function count(type, { minImportance = null, source = null, includeForgotten = false, includeArchived = false, onlyArchived = false, depositedOnly = false, updatedFrom = null, updatedTo = null } = {}) {
|
|
641
641
|
const clauses = [];
|
|
642
642
|
const params = [];
|
|
643
643
|
if (type !== undefined) {
|
|
@@ -673,6 +673,12 @@ export function createStore(path) {
|
|
|
673
673
|
} else if (!includeArchived) {
|
|
674
674
|
clauses.push("archived = 0");
|
|
675
675
|
}
|
|
676
|
+
// 与 list() 同过滤:deposited 视图的 total 才能和行保持一致。
|
|
677
|
+
if (depositedOnly) {
|
|
678
|
+
clauses.push(
|
|
679
|
+
"(id IN (SELECT record_id FROM receipt_chain WHERE kind IN ('merge', 'update') AND verdict = 'live') OR source = 'dream')"
|
|
680
|
+
);
|
|
681
|
+
}
|
|
676
682
|
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
677
683
|
return db.prepare(`SELECT count(*) AS c FROM memories ${where}`).get(...params).c;
|
|
678
684
|
}
|
|
@@ -924,7 +930,7 @@ export function createStore(path) {
|
|
|
924
930
|
return rows.map(toRow);
|
|
925
931
|
}
|
|
926
932
|
|
|
927
|
-
function list({ type, limit = 50, offset = 0, order = "importance", includeForgotten = false, includeArchived = false, onlyArchived = false, minImportance = null, source = null, updatedFrom = null, updatedTo = null } = {}) {
|
|
933
|
+
function list({ type, limit = 50, offset = 0, order = "importance", includeForgotten = false, includeArchived = false, onlyArchived = false, depositedOnly = false, minImportance = null, source = null, updatedFrom = null, updatedTo = null } = {}) {
|
|
928
934
|
const clauses = [];
|
|
929
935
|
const params = [];
|
|
930
936
|
if (type) {
|
|
@@ -962,6 +968,14 @@ export function createStore(path) {
|
|
|
962
968
|
} else if (!includeArchived) {
|
|
963
969
|
clauses.push("archived = 0");
|
|
964
970
|
}
|
|
971
|
+
// depositedOnly:只看 autoDream 巩固过的记忆——receipt_chain 的 merge /
|
|
972
|
+
// update live verdict(record_id 即保留/更新目标)∪ source="dream" 的
|
|
973
|
+
// 直写沉淀(记忆库总览)。conflict 不算沉淀:两侧只被仲裁,内容未落。
|
|
974
|
+
if (depositedOnly) {
|
|
975
|
+
clauses.push(
|
|
976
|
+
"(id IN (SELECT record_id FROM receipt_chain WHERE kind IN ('merge', 'update') AND verdict = 'live') OR source = 'dream')"
|
|
977
|
+
);
|
|
978
|
+
}
|
|
965
979
|
const { limit: lim, offset: off } = sanitizePage(limit, offset, 50);
|
|
966
980
|
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
967
981
|
// "chrono" is pure newest-first — the stable order paged browsing (month
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modusensus/dsh-mneme",
|
|
3
3
|
"description": "Cross-session memory plugin for DeepSeek Harness with autoDream consolidation: SQLite store, Markdown mirrors, 7 model tools, automatic injection, session summarization, user profile/rules, custom slash commands, vector (semantic) search, and a Web GUI panel",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.18",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"slots",
|
|
40
40
|
"locale",
|
|
41
41
|
"layout",
|
|
42
|
-
"connection"
|
|
42
|
+
"connection",
|
|
43
|
+
"betterSidebar"
|
|
43
44
|
],
|
|
44
45
|
"platform": "web"
|
|
45
46
|
},
|
|
@@ -53,7 +54,13 @@
|
|
|
53
54
|
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
54
55
|
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
|
|
55
56
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
56
|
-
"@deepseek-ai/schemastery": "^3.18.1"
|
|
57
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
58
|
+
"dsh-better-sidebar": "*"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"dsh-better-sidebar": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
57
64
|
},
|
|
58
65
|
"devDependencies": {
|
|
59
66
|
"@deepseek-ai/cordis": "^4.0.1",
|
package/src/api.js
CHANGED
|
@@ -178,7 +178,11 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
178
178
|
// archived=only:只看归档(状态页的归档列表用)。归档行不进默认列表,
|
|
179
179
|
// 所以这是独立的视图开关,而不是 includeArchived 的混看模式。
|
|
180
180
|
const onlyArchived = url.searchParams.get("archived") === "only";
|
|
181
|
-
|
|
181
|
+
// deposited=only:只看 autoDream 巩固过的记忆(receipt_chain 的
|
|
182
|
+
// merge/update verdict ∪ source=dream 直写)——状态页「查看全部」
|
|
183
|
+
// 与记忆库的「沉淀」筛选 chip 共用这个视图。
|
|
184
|
+
const depositedOnly = url.searchParams.get("deposited") === "only";
|
|
185
|
+
const rows = service.list({ type, limit, offset, order, minImportance, source, updatedFrom, updatedTo, onlyArchived, depositedOnly });
|
|
182
186
|
// 面板行在 wire DTO 之上补 archived/quality_score——模型工具的输出
|
|
183
187
|
// schema 严格复用 toApiList,扩展只发生在 HTTP 层。
|
|
184
188
|
const items = service.toApiList(rows).map((m, i) => ({
|
|
@@ -189,7 +193,7 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
189
193
|
// Total honors the same filters as the rows, or the pager's
|
|
190
194
|
// has-more math breaks whenever minImportance/source/updated-at
|
|
191
195
|
// bounds are active.
|
|
192
|
-
sendJson(res, 200, { items, total: service.count(type, { minImportance, source, updatedFrom, updatedTo, onlyArchived }) });
|
|
196
|
+
sendJson(res, 200, { items, total: service.count(type, { minImportance, source, updatedFrom, updatedTo, onlyArchived, depositedOnly }) });
|
|
193
197
|
} catch {
|
|
194
198
|
sendJson(res, 500, { error: "internal" });
|
|
195
199
|
}
|
package/src/store.js
CHANGED
|
@@ -637,7 +637,7 @@ export function createStore(path) {
|
|
|
637
637
|
return ts;
|
|
638
638
|
}
|
|
639
639
|
|
|
640
|
-
function count(type, { minImportance = null, source = null, includeForgotten = false, includeArchived = false, onlyArchived = false, updatedFrom = null, updatedTo = null } = {}) {
|
|
640
|
+
function count(type, { minImportance = null, source = null, includeForgotten = false, includeArchived = false, onlyArchived = false, depositedOnly = false, updatedFrom = null, updatedTo = null } = {}) {
|
|
641
641
|
const clauses = [];
|
|
642
642
|
const params = [];
|
|
643
643
|
if (type !== undefined) {
|
|
@@ -673,6 +673,12 @@ export function createStore(path) {
|
|
|
673
673
|
} else if (!includeArchived) {
|
|
674
674
|
clauses.push("archived = 0");
|
|
675
675
|
}
|
|
676
|
+
// 与 list() 同过滤:deposited 视图的 total 才能和行保持一致。
|
|
677
|
+
if (depositedOnly) {
|
|
678
|
+
clauses.push(
|
|
679
|
+
"(id IN (SELECT record_id FROM receipt_chain WHERE kind IN ('merge', 'update') AND verdict = 'live') OR source = 'dream')"
|
|
680
|
+
);
|
|
681
|
+
}
|
|
676
682
|
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
677
683
|
return db.prepare(`SELECT count(*) AS c FROM memories ${where}`).get(...params).c;
|
|
678
684
|
}
|
|
@@ -924,7 +930,7 @@ export function createStore(path) {
|
|
|
924
930
|
return rows.map(toRow);
|
|
925
931
|
}
|
|
926
932
|
|
|
927
|
-
function list({ type, limit = 50, offset = 0, order = "importance", includeForgotten = false, includeArchived = false, onlyArchived = false, minImportance = null, source = null, updatedFrom = null, updatedTo = null } = {}) {
|
|
933
|
+
function list({ type, limit = 50, offset = 0, order = "importance", includeForgotten = false, includeArchived = false, onlyArchived = false, depositedOnly = false, minImportance = null, source = null, updatedFrom = null, updatedTo = null } = {}) {
|
|
928
934
|
const clauses = [];
|
|
929
935
|
const params = [];
|
|
930
936
|
if (type) {
|
|
@@ -962,6 +968,14 @@ export function createStore(path) {
|
|
|
962
968
|
} else if (!includeArchived) {
|
|
963
969
|
clauses.push("archived = 0");
|
|
964
970
|
}
|
|
971
|
+
// depositedOnly:只看 autoDream 巩固过的记忆——receipt_chain 的 merge /
|
|
972
|
+
// update live verdict(record_id 即保留/更新目标)∪ source="dream" 的
|
|
973
|
+
// 直写沉淀(记忆库总览)。conflict 不算沉淀:两侧只被仲裁,内容未落。
|
|
974
|
+
if (depositedOnly) {
|
|
975
|
+
clauses.push(
|
|
976
|
+
"(id IN (SELECT record_id FROM receipt_chain WHERE kind IN ('merge', 'update') AND verdict = 'live') OR source = 'dream')"
|
|
977
|
+
);
|
|
978
|
+
}
|
|
965
979
|
const { limit: lim, offset: off } = sanitizePage(limit, offset, 50);
|
|
966
980
|
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
967
981
|
// "chrono" is pure newest-first — the stable order paged browsing (month
|
package/test/api.test.js
CHANGED
|
@@ -994,3 +994,58 @@ test("GET /api/dsh-mneme/list?archived=only lists just archived rows; default li
|
|
|
994
994
|
await route.handler(req("/api/dsh-mneme/list"), back);
|
|
995
995
|
assert.equal(JSON.parse(back.body).total, 2);
|
|
996
996
|
});
|
|
997
|
+
|
|
998
|
+
test("GET /api/dsh-mneme/list?deposited=only lists dream-touched memories (receipts ∪ source=dream)", async () => {
|
|
999
|
+
const { routes, service } = setup();
|
|
1000
|
+
const plain = service.saveWithDedupe({ type: "preference", title: "无关", content: "plain" });
|
|
1001
|
+
const merged = service.saveWithDedupe({ type: "project", title: "被巩固", content: "merged" });
|
|
1002
|
+
const updated = service.saveWithDedupe({ type: "decision", title: "被更新", content: "updated" });
|
|
1003
|
+
service.saveWithDedupe({ type: "summary", title: "总览", content: "overview", source: "dream" });
|
|
1004
|
+
const ids = [plain, merged, updated].map((r) => r.memory.id);
|
|
1005
|
+
|
|
1006
|
+
// 巩固账本:merge 落在 keepSource、update 落在目标;conflict 只仲裁不落
|
|
1007
|
+
// 内容,不算沉淀。verdict='live' 才有效。
|
|
1008
|
+
service.saveReceipt({
|
|
1009
|
+
receipt_id: "r-merge", run_id: "run-1", record_id: merged.memory.id, kind: "merge",
|
|
1010
|
+
input_digest: "d1", keep_source: merged.memory.id, sources: [merged.memory.id, ids[0]],
|
|
1011
|
+
verdict: "live", count_before: 2, count_after: 1, policy_epoch: 0,
|
|
1012
|
+
created_at: new Date().toISOString()
|
|
1013
|
+
});
|
|
1014
|
+
service.saveReceipt({
|
|
1015
|
+
receipt_id: "r-update", run_id: "run-1", record_id: updated.memory.id, kind: "update",
|
|
1016
|
+
input_digest: "d2", verdict: "live", count_before: 1, count_after: 1,
|
|
1017
|
+
policy_epoch: 0, created_at: new Date().toISOString()
|
|
1018
|
+
});
|
|
1019
|
+
service.saveReceipt({
|
|
1020
|
+
receipt_id: "r-conflict", run_id: "run-1", record_id: "winner-x", kind: "conflict",
|
|
1021
|
+
input_digest: "d3", winner_id: "winner-x", loser_id: "loser-y", verdict: "live",
|
|
1022
|
+
count_before: 2, count_after: 2, policy_epoch: 0, created_at: new Date().toISOString()
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
const route = routes.find((r) => r.path === "/api/dsh-mneme/list");
|
|
1026
|
+
const res = new FakeRes();
|
|
1027
|
+
await route.handler(req("/api/dsh-mneme/list?deposited=only"), res);
|
|
1028
|
+
const data = JSON.parse(res.body);
|
|
1029
|
+
assert.deepEqual(
|
|
1030
|
+
data.items.map((m) => m.title).sort(),
|
|
1031
|
+
["总览", "被巩固", "被更新"],
|
|
1032
|
+
"deposited view = receipt merge/update records ∪ source=dream writes"
|
|
1033
|
+
);
|
|
1034
|
+
assert.equal(data.total, 3, "total honors the deposited filter");
|
|
1035
|
+
|
|
1036
|
+
// 默认列表不受 deposited 过滤影响
|
|
1037
|
+
const def = new FakeRes();
|
|
1038
|
+
await route.handler(req("/api/dsh-mneme/list"), def);
|
|
1039
|
+
assert.equal(JSON.parse(def.body).total, 4);
|
|
1040
|
+
|
|
1041
|
+
// 与 archived=only 可叠加:归档的沉淀记忆才出现在交集视图里
|
|
1042
|
+
const upd = routes.find((r) => r.path === "/api/dsh-mneme/update");
|
|
1043
|
+
const arch = new FakeRes();
|
|
1044
|
+
await upd.handler(req("/api/dsh-mneme/update", "POST", { id: merged.memory.id, archived: true }), arch);
|
|
1045
|
+
assert.equal(arch.statusCode, 200);
|
|
1046
|
+
const both = new FakeRes();
|
|
1047
|
+
await route.handler(req("/api/dsh-mneme/list?deposited=only&archived=only"), both);
|
|
1048
|
+
const bothData = JSON.parse(both.body);
|
|
1049
|
+
assert.deepEqual(bothData.items.map((m) => m.title), ["被巩固"]);
|
|
1050
|
+
assert.equal(bothData.total, 1);
|
|
1051
|
+
});
|
package/test/client.test.js
CHANGED
|
@@ -219,6 +219,45 @@ test("importance renders as star glyphs, not raw text stars", () => {
|
|
|
219
219
|
);
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
+
// better-sidebar ecosystem integration is an optional capability (official
|
|
223
|
+
// external-plugin-guide §2.2): 'betterSidebar' IS declared in inject (DSH's
|
|
224
|
+
// runtime gates ctx property access on the inject declaration — probing
|
|
225
|
+
// without declaring fails the whole loader entry, verified in the field) and
|
|
226
|
+
// marked as an optional peer dependency, so when it is absent the service
|
|
227
|
+
// resolves to undefined and registration skips gracefully. Registration
|
|
228
|
+
// probes at runtime inside a named ctx.effect (scope cleanup on HMR/disable)
|
|
229
|
+
// with a bounded retry for service-provision ordering.
|
|
230
|
+
test("better-sidebar tab declares optional inject, probes, and skips gracefully", () => {
|
|
231
|
+
assert.ok(
|
|
232
|
+
/const inject = \["slots", "locale", "betterSidebar"\];/.test(clientSource),
|
|
233
|
+
"module inject must declare betterSidebar — DSH gates ctx property access on it"
|
|
234
|
+
);
|
|
235
|
+
assert.ok(
|
|
236
|
+
/const bs = ctx\.betterSidebar;[\s\S]{0,60}typeof bs\.registerTab === "function"/.test(clientSource),
|
|
237
|
+
"the tab registration must still probe ctx.betterSidebar at runtime (undefined when absent)"
|
|
238
|
+
);
|
|
239
|
+
assert.ok(
|
|
240
|
+
/id: "dsh-mneme:memory"/.test(clientSource),
|
|
241
|
+
"the registered tab id must be package-prefixed"
|
|
242
|
+
);
|
|
243
|
+
assert.ok(
|
|
244
|
+
/title: \(\) => t\("memory\.view\.label"\)/.test(clientSource),
|
|
245
|
+
"the tab title must reuse the localized 记忆库 label"
|
|
246
|
+
);
|
|
247
|
+
assert.ok(
|
|
248
|
+
/component: \(\) => h\(MemoryExplorer, \{ t \}\)/.test(clientSource),
|
|
249
|
+
"the tab must reuse the MemoryExplorer views"
|
|
250
|
+
);
|
|
251
|
+
assert.ok(
|
|
252
|
+
clientSource.includes('"dsh-mneme: better-sidebar tab"'),
|
|
253
|
+
"the registration effect must carry a named label for scope cleanup"
|
|
254
|
+
);
|
|
255
|
+
assert.ok(
|
|
256
|
+
/if \(\+\+tries <= 10\) timer = setTimeout\(attempt, 1000\);/.test(clientSource),
|
|
257
|
+
"the service probe must retry with a bound (10 × 1s), not loop forever"
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
222
261
|
// The graph toggle must not read as "share": the primitives share icon is
|
|
223
262
|
// banned and a custom node-graph glyph takes its place.
|
|
224
263
|
test("graph toggle uses a node-graph glyph, not the share icon", () => {
|
|
@@ -233,6 +272,44 @@ test("graph toggle uses a node-graph glyph, not the share icon", () => {
|
|
|
233
272
|
);
|
|
234
273
|
});
|
|
235
274
|
|
|
275
|
+
// 方案 A:查询收敛。状态页只做仪表盘(小页预览 + 服务端 total + 查看全部),
|
|
276
|
+
// 沉淀/归档的完整浏览走记忆库的 deposited/archived 筛选视图(chip 预置 +
|
|
277
|
+
// 状态页入口跳转),详情抽屉给归档记忆一个反向的「恢复」。
|
|
278
|
+
test("status dashboard links into deposited/archived library views", () => {
|
|
279
|
+
assert.ok(
|
|
280
|
+
clientSource.includes('"/api/dsh-mneme/list?deposited=only&limit=8&order=chrono"'),
|
|
281
|
+
"the workbench deposited preview must read the server-side deposited view"
|
|
282
|
+
);
|
|
283
|
+
assert.ok(
|
|
284
|
+
clientSource.includes('"/api/dsh-mneme/list?archived=only&limit=3&order=chrono"'),
|
|
285
|
+
"the workbench archived preview must cap at 3 rows backed by a server total"
|
|
286
|
+
);
|
|
287
|
+
assert.ok(
|
|
288
|
+
clientSource.includes("browseWithFilter"),
|
|
289
|
+
"the status page must jump into the library with preset filters"
|
|
290
|
+
);
|
|
291
|
+
assert.ok(
|
|
292
|
+
/view === "status" && h\(StatusPanel, \{ t, onBrowse: browseWithFilter \}\)/.test(clientSource),
|
|
293
|
+
"the status panel must receive the browse-jump callback"
|
|
294
|
+
);
|
|
295
|
+
assert.ok(
|
|
296
|
+
clientSource.includes('(depositedOnly ? "&deposited=only" : "")'),
|
|
297
|
+
"the library filterQS must carry the deposited chip"
|
|
298
|
+
);
|
|
299
|
+
assert.ok(
|
|
300
|
+
clientSource.includes('(archivedOnly ? "&archived=only" : "")'),
|
|
301
|
+
"the library filterQS must carry the archived chip"
|
|
302
|
+
);
|
|
303
|
+
assert.ok(
|
|
304
|
+
clientSource.includes('postUpdate({ archived: false }, { restored: true })'),
|
|
305
|
+
"the drawer must offer restore for archived memories"
|
|
306
|
+
);
|
|
307
|
+
assert.ok(
|
|
308
|
+
clientSource.includes('"memory.status.viewAll"'),
|
|
309
|
+
"the view-all entries must come from the dictionary"
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
|
|
236
313
|
// Every memory feature lives in the main-area library now: the explorer
|
|
237
314
|
// hosts three sub-views (browse / entities / settings) switched by tabs whose
|
|
238
315
|
// labels come from dedicated dictionary keys.
|