@modusensus/dsh-mneme 0.8.8 → 0.8.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/lib/client.js CHANGED
@@ -129,7 +129,10 @@ window.__ModuleLoader__.load({
129
129
  check: [["path", { d: "M20 6 9 17l-5-5" }]],
130
130
  inbox: [["polyline", { points: "22 12 16 12 14 15 10 15 8 12 2 12" }], ["path", { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" }]],
131
131
  activity: [["path", { d: "M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2" }]],
132
- flame: [["path", { d: "M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z" }]]
132
+ flame: [["path", { d: "M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z" }]],
133
+ // 意见与反馈入口的行首图标(Lucide 数据,同上 ISC 惯例内联)
134
+ bug: [["path", { d: "m8 2 1.88 1.88" }], ["path", { d: "M14.12 3.88 16 2" }], ["path", { d: "M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" }], ["path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" }], ["path", { d: "M12 20v-9" }], ["path", { d: "M6.53 9C4.6 8.8 3 7.1 3 5" }], ["path", { d: "M6 13H2" }], ["path", { d: "M3 21c0-2.1 1.7-3.9 3.8-4" }], ["path", { d: "M20.97 5c0 2.1-1.6 3.8-3.5 4" }], ["path", { d: "M22 13h-4" }], ["path", { d: "M17.2 17c2.1.1 3.8 1.9 3.8 4" }]],
135
+ mail: [["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }], ["path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" }]]
133
136
  };
134
137
  const Icon = ({ name, size = 16, className }) => {
135
138
  const parts = ICON_PATHS[name];
@@ -166,6 +169,21 @@ window.__ModuleLoader__.load({
166
169
  }))));
167
170
  };
168
171
 
172
+ // 单颗星(与 ImportanceStars 同一份 Lucide 路径):筛选 chip、实体提及数、
173
+ // 注入预览这类行内小指标用它渲染,替换此前的文本星号——文本星号角更尖锐,
174
+ // 与卡片/抽屉的 SVG 星并排时明显不同形(用户反馈后全线统一)。
175
+ const StarGlyph = ({ size = 11, filled = true }) => h("svg", {
176
+ width: size,
177
+ height: size,
178
+ viewBox: "0 0 24 24",
179
+ "aria-hidden": "true",
180
+ fill: filled ? "currentColor" : "none",
181
+ stroke: "currentColor",
182
+ strokeWidth: 1.6,
183
+ strokeLinejoin: "round",
184
+ style: { verticalAlign: "-1px" }
185
+ }, h("path", { d: STAR_PATH_D, opacity: filled ? 1 : 0.35 }));
186
+
169
187
  // 热度徽章(阶段二):flame 图标 + 整数百分比,三档配色(热/温/冷)。
170
188
  // /list 仅在 heatEnabled=true 时下发 heat 字段——缺省即不渲染,开关关闭
171
189
  // 时徽章全站自动消失,前端无需感知开关状态。
@@ -233,7 +251,13 @@ window.__ModuleLoader__.load({
233
251
  "memory.tab.project": "项目",
234
252
  "memory.tab.decision": "决策",
235
253
  "memory.tab.history": "历史",
254
+ "memory.tab.summary": "小结",
236
255
  "memory.settings.title": "记忆库设置",
256
+ "memory.settings.group.personal": "个性化",
257
+ "memory.settings.group.engine": "记忆引擎",
258
+ "memory.settings.group.search": "搜索",
259
+ "memory.settings.group.connect": "连接与安全",
260
+ "memory.settings.group.help": "帮助",
237
261
  "memory.settings.profile": "用户画像",
238
262
  "memory.settings.profileHint": "写一段自我介绍(角色、背景、偏好),Agent 每轮对话都会自动带上。",
239
263
  "memory.settings.profileSave": "保存画像",
@@ -338,22 +362,25 @@ window.__ModuleLoader__.load({
338
362
  "memory.settings.apiTokenPlaceholder": "留空 = 不鉴权(默认)",
339
363
  "memory.settings.apiTokenSave": "保存 Token",
340
364
  "memory.settings.apiTokenSaved": "Token 已保存",
341
- "memory.settings.feedback.title": "帮助与反馈",
342
- "memory.settings.feedback.desc": "遇到问题?先看看仓库已有的 issue,或直接把情况反馈过来:",
343
- "memory.settings.feedback.newIssue": "GitHub 新建 issue(自动带上环境信息)",
344
- "memory.settings.feedback.email": "邮件反馈(work@modusensus.space)",
345
- "memory.settings.feedback.browse": "浏览仓库已知问题",
365
+ "memory.settings.feedback.title": "意见与反馈",
366
+ "memory.settings.feedback.desc": "遇到问题?直接告诉我们,或先看看有没有人遇到过:",
367
+ "memory.settings.feedback.newIssue": "在 GitHub 上反馈问题",
368
+ "memory.settings.feedback.newIssue.sub": "自动带上插件版本与环境信息",
369
+ "memory.settings.feedback.email": "邮件反馈",
370
+ "memory.settings.feedback.email.sub": "work@modusensus.space",
371
+ "memory.settings.feedback.browse": "浏览已知问题",
372
+ "memory.settings.feedback.browse.sub": "先搜搜,省一份重复 issue",
346
373
  "memory.settings.feedback.hint": "反馈前先搜搜是否已有相同问题,能省一份重复的 issue~",
347
374
  "memory.settings.version.outdated": "🆕 有新版本 {v}(当前运行 {c})",
348
375
  "memory.settings.version.outdatedHint": "若安装时指定过版本号,常规升级不会跨大/小版本(pnpm 钉子)——请重新安装或在升级命令加 --latest;npm 已发布而市场暂未收录属正常(约 1 天延迟)。",
349
376
  "memory.settings.mode.title": "运行模式",
350
- "memory.settings.mode.desc": "轻量模式只保留核心的记忆读写与自动注入(关闭 autoDream 巩固、实体抽取、语义搜索等高级功能),适合只想「记住偏好」的轻量使用;标准模式开启全部功能。",
377
+ "memory.settings.mode.desc": "轻量模式只保留核心能力:记忆读写、自动注入、热记忆;巩固、实体抽取、语义搜索等全部关闭,适合低配机器或只想「记住偏好」的场景。标准模式开启全部功能。",
351
378
  "memory.settings.mode.light": "轻量",
352
379
  "memory.settings.mode.standard": "标准",
353
380
  "memory.settings.mode.savedHint": "已保存,重启 DSH 后生效",
354
381
  "memory.settings.mode.offList": "已关闭:巩固(autoDream)· 实体抽取 · 语义搜索",
355
382
  "memory.settings.extapi.title": "外部访问 API",
356
- "memory.settings.extapi.desc": "独立 HTTP 服务,供其他插件 / CLI / 桌面工具读写记忆,默认绑定 127.0.0.1。重启 DSH 后生效。",
383
+ "memory.settings.extapi.desc": "开放一个本机接口,让其他工具(插件、命令行、桌面应用)也能读写这份记忆库,默认只允许本机连接。改动保存后重启 DSH 生效。",
357
384
  "memory.settings.extapi.enabled": "启用",
358
385
  "memory.settings.extapi.disabled": "停用",
359
386
  "memory.settings.extapi.address": "地址",
@@ -368,11 +395,11 @@ window.__ModuleLoader__.load({
368
395
  "memory.tab.constraint": "工程约束",
369
396
  "memory.tab.document": "文档",
370
397
  "memory.features.title": "功能开关",
371
- "memory.features.desc": "按需启停后端能力,改动保存后重启 DSH 生效。",
372
- "memory.features.group.core": "核心",
373
- "memory.features.group.enhance": "记忆增强",
398
+ "memory.features.desc": "控制插件在后台做哪些事。改动即时保存,重启 DSH 后生效。",
399
+ "memory.features.group.core": "基础",
400
+ "memory.features.group.enhance": "检索与增强",
374
401
  "memory.features.group.dream": "巩固与睡眠",
375
- "memory.features.group.scope": "作用域隔离",
402
+ "memory.features.group.scope": "多会话隔离",
376
403
  "memory.features.group.advanced": "高级",
377
404
  "memory.features.advancedToggle": "高级(注入策略 · 反思 · 实验项)",
378
405
  "memory.features.restartHint": "重启 DSH 后生效",
@@ -380,14 +407,16 @@ window.__ModuleLoader__.load({
380
407
  "memory.features.autoInject": "自动注入",
381
408
  "memory.features.autoInject.hint": "每轮对话自动携带相关记忆",
382
409
  "memory.features.injectGuidanceEnabled": "能力说明",
383
- "memory.features.injectGuidanceEnabled.hint": "在工具描述与一次性提示段里说明怎么用记忆(何时查、何时写、拿不准就不做)",
410
+ "memory.features.injectGuidanceEnabled.hint": "教 Agent 怎么用记忆:什么时候该查、什么时候该写、拿不准就不动",
384
411
  "memory.features.continuityRescueEnabled": "压缩边缘抢救",
385
- "memory.features.continuityRescueEnabled.hint": "上下文即将被精简前,落一条连续性快照(正在做什么 / 下一步 / 未决问题)并追加到对话末尾,让它活过这次压缩",
412
+ "memory.features.continuityRescueEnabled.hint": "长对话被压缩前,先存一份「正在做什么 / 下一步 / 未决问题」的快照,让关键上下文活过压缩",
386
413
  "memory.features.parentOff": "父开关关闭时不生效",
387
414
  "memory.features.autoSummarize": "自动总结",
388
415
  "memory.features.autoSummarize.hint": "对话结束自动提炼记忆条目",
389
416
  "memory.features.hotMemoryEnabled": "热记忆",
390
417
  "memory.features.hotMemoryEnabled.hint": "最近几轮对话原文随注入携带,不写入长期记忆",
418
+ "memory.features.injectTimePrefix": "对话开始注入当前时间",
419
+ "memory.features.injectTimePrefix.hint": "每次新会话的首轮注入一次日期与星期(默认关)",
391
420
  "memory.features.entityExtractionEnabled": "实体抽取",
392
421
  "memory.features.entityExtractionEnabled.hint": "从记忆中提取人物/项目/概念,图谱随之生长",
393
422
  "memory.features.entityExtractionProvider": "实体抽取 Provider",
@@ -403,18 +432,18 @@ window.__ModuleLoader__.load({
403
432
  "memory.features.summarizeReasoningEffort.low": "低",
404
433
  "memory.features.summarizeReasoningEffort.medium": "中",
405
434
  "memory.features.summarizeReasoningEffort.high": "高",
406
- "memory.features.summarizeReasoningEffort.hint": "思考型模型建议选低档或关闭思考,避免推理烧光输出预算导致总结失败;改后重启 DSH 生效",
407
- "memory.features.entityExtractionModelHint": "Provider/模型留空 = 跟随主对话模型;思考强度 none = 服务商默认",
435
+ "memory.features.summarizeReasoningEffort.hint": "思考型模型建议选「低」或「关闭思考」,避免推理占满输出导致总结失败",
436
+ "memory.features.entityExtractionModelHint": "Provider / 模型留空 = 跟随主对话模型;思考强度选「跟随默认」= 服务商默认",
408
437
  "memory.features.codingRetrospect": "编码记忆蒸馏",
409
- "memory.features.codingRetrospect.hint": "用完整转录(含工具调用与报错)提炼踩坑、约束与被否决方案",
438
+ "memory.features.codingRetrospect.hint": "回顾写代码的完整过程(含报错与工具调用),沉淀踩坑、约束和被否决的方案",
410
439
  "memory.features.rerankEnabled": "结果重排",
411
440
  "memory.features.rerankEnabled.hint": "本地重排模型对召回结果精排,更慢更准",
412
441
  "memory.features.heatEnabled": "热度衰减",
413
- "memory.features.heatEnabled.hint": "按遗忘曲线给记忆降温:久未访问热度越低,参与注入排序与睡眠降级判定",
442
+ "memory.features.heatEnabled.hint": "像遗忘曲线一样给记忆降温:越久没用到排得越靠后,不占注入名额",
414
443
  "memory.features.documentMemoryEnabled": "document 型记忆",
415
- "memory.features.documentMemoryEnabled.hint": "agent 产长文档入库为指针行:注册校验(文件存在 + evidence 求交)、摘要 + 路径落库、去重与 supersede 记账;全文归 agent 按需读,注入只带摘要行(默认关)",
444
+ "memory.features.documentMemoryEnabled.hint": "Agent 写的长文档只在记忆里放一张「摘要卡」:正文仍由 Agent 保管、按需读取,注入时只带摘要,不占上下文",
416
445
  "memory.features.resilientModelDownload": "模型下载断点续传",
417
- "memory.features.resilientModelDownload.hint": "模型文件下载中断后从已下载部分续传并自动重试,失败会记录中断位置;关闭后回到一次性下载",
446
+ "memory.features.resilientModelDownload.hint": "模型下载中断后从断点续传并自动重试;关闭则回到一次性下载",
418
447
  "memory.features.searchSemanticDedup": "语义去重",
419
448
  "memory.features.searchSemanticDedup.hint": "搜索结果中意思相近的条目只保留一条",
420
449
  "memory.features.autoDream": "记忆巩固",
@@ -439,18 +468,25 @@ window.__ModuleLoader__.load({
439
468
  "memory.features.memoryQualityFilter.enabled.hint": "低价值记忆自动归档,注入按质量降权",
440
469
  "memory.features.llmAudit.enabled": "后台调用审计",
441
470
  "memory.features.llmAudit.enabled.hint": "记录巩固/总结的后台模型调用与 token 消耗",
471
+ // #254:写入准入两层。检测关时整条路径不碰;检测开、拦截关 = 仅告警 +
472
+ // 留审计;两个都开才真拦。文案要写清「只留审计」与「真拦下」的区别,
473
+ // 否则操作者会以为打开检测就已经在拦。
474
+ "memory.features.writeAdmission.enabled": "写入准入判定",
475
+ "memory.features.writeAdmission.enabled.hint": "写入前判空白/噪声,命中留审计(不拦)",
476
+ "memory.features.writeAdmission.enforce": "写入准入拦截",
477
+ "memory.features.writeAdmission.enforce.hint": "真拦下命中的写入;关闭时只留审计不拦",
442
478
  "memory.features.bm25SearchEnabled": "BM25 关键词检索",
443
479
  "memory.features.bm25SearchEnabled.hint": "传统关键词打分检索,与向量召回互补",
444
480
  "memory.features.conflictFreezeEnabled": "冲突冻结",
445
481
  "memory.features.conflictFreezeEnabled.hint": "巩固发现互相矛盾的记忆时先冻结待确认",
446
482
  "memory.features.scopeEnabled": "作用域标注",
447
- "memory.features.scopeEnabled.hint": "按会话身份(agent/工作区)给新记忆打归属标注;重启 DSH 后生效",
483
+ "memory.features.scopeEnabled.hint": "新记忆自动记下来自哪个会话(Agent / 工作区),供隔离与检索过滤使用",
448
484
  "memory.features.strictScope": "严格隔离",
449
- "memory.features.strictScope.hint": "检索与注入硬过滤,只对显式声明的归属生效:显式收窄到他者作用域的记忆不可见;载体自动标注只降权保留可见(真正的物理隔离请用 sensitivity)。重启 DSH 后生效",
485
+ "memory.features.strictScope.hint": "开启后,其他会话明确设为私有的记忆对你完全不可见;关闭时仍可见、只是排到后面。要做到彻底隔离,请用条目的「敏感度」字段",
450
486
  "memory.features.trustEpistemicWeighting": "可信度加权",
451
487
  "memory.features.trustEpistemicWeighting.hint": "按来源可信度调整召回排序(实验性)",
452
488
  "memory.features.reflectionFailureTracking": "反思失败追踪",
453
- "memory.features.reflectionFailureTracking.hint": "记录巩固决策的失败样本供后续改进",
489
+ "memory.features.reflectionFailureTracking.hint": "记录巩固失败的案例供后续改进(诊断用)",
454
490
  "memory.features.embedRoute": "语义检索路线",
455
491
  "memory.features.embedProvider": "Embedding 提供方",
456
492
  "memory.features.embedProvider.openai": "OpenAI 兼容接口",
@@ -466,9 +502,9 @@ window.__ModuleLoader__.load({
466
502
  "memory.features.dreamModel": "巩固用模型名",
467
503
  "memory.features.dreamModelHint": "留空 = 跟随主对话模型;只影响记忆巩固(autoDream)用的模型",
468
504
  "memory.features.dreamPeakHours": "高峰时段(不做梦)",
469
- "memory.features.dreamPeakHours.hint": "空 = 关闭。逗号分隔、可带星期前缀、支持跨零点,如 09:00-18:00 或 mon-fri 08:00-12:00,14:00-18:00。命中时段不调模型,顺延到最近的高峰结束时刻补跑(最多顺延 dreamPeakMaxDeferMinutes 分钟,避免长高峰把巩固饿死)",
505
+ "memory.features.dreamPeakHours.hint": "设置忙碌时段,巩固会避开这些时间、事后补跑。写法:09:00-18:00,多段用逗号分隔,可带星期(如 mon-fri 08:00-12:00),支持跨零点;留空 = 不限制",
470
506
  "memory.features.summarizePeakHours": "高峰时段(不蒸馏)",
471
- "memory.features.summarizePeakHours.hint": "空 = 关闭。与上方巩固侧同一份时段语法;命中时蒸馏顺延到非高峰、窗口累积后一次蒸",
507
+ "memory.features.summarizePeakHours.hint": "与巩固侧同一份写法;蒸馏会避开这些时段,窗口先攒着、非高峰一次蒸完",
472
508
  "memory.features.sleepProvider": "睡眠 Provider",
473
509
  "memory.features.sleepModel": "睡眠模型",
474
510
  "memory.features.sleepModelHint": "留空 = 用巩固模型或当前模型;建议选非思考模型",
@@ -479,7 +515,7 @@ window.__ModuleLoader__.load({
479
515
  "memory.features.modelTestFail": "测试失败",
480
516
  "memory.features.modelTestHint": "真实发起一次最小巩固调用,验证 Provider/模型连通与 effort 支持",
481
517
  "memory.features.routeStaleMark": "(不在可用列表)",
482
- "memory.features.routeStaleHint": "不在当前可用列表,多为切换 Provider 后的旧值:请改选,或点「测试连通性」当场验证;改动保存后重启 DSH 生效。",
518
+ "memory.features.routeStaleHint": "该值不在当前可用列表(常见于切换过 Provider):请重新选择,或点「测试连通性」当场验证",
483
519
  "memory.explorer.viewCards": "卡片",
484
520
  "memory.explorer.viewTimeline": "时间线",
485
521
  "memory.explorer.viewAria": "视图切换",
@@ -532,16 +568,20 @@ window.__ModuleLoader__.load({
532
568
  "memory.explorer.detail.editTitle": "标题",
533
569
  "memory.explorer.detail.editContent": "内容",
534
570
  "memory.explorer.detail.editImportance": "重要性",
571
+ "memory.explorer.detail.starLabel": "设为 {n} 星",
535
572
  "memory.status.dream": "最近巩固",
536
573
  "memory.status.dreamNever": "尚未运行",
537
574
  "memory.status.conflicts": "待确认冲突",
538
575
  "memory.status.conflictsHint": "冻结的矛盾记忆,等待人工确认",
539
576
  "memory.status.injectSuppressed": "极简模式下注入按宿主设计关闭",
540
- "memory.status.injectSuppressedHint": "当前会话使用 minimal 预设,记忆注入 / 用户画像 / hot memory 不送达模型(宿主设计,非插件缺陷)。解法:切换标准模式,或在 ~/.dsh/settings.yaml 设 agent-presets.default: standard;过渡方案:把画像与规则写入 AGENTS.md",
577
+ "memory.status.injectSuppressedHint": "当前会话使用 minimal 预设:记忆注入 / 用户画像 / 热记忆不会送达模型(宿主设计,非插件缺陷)。切到标准模式,或在 ~/.dsh/settings.yaml 设 agent-presets.default: standard;过渡方案:把画像与规则写进 AGENTS.md",
541
578
  "memory.status.injectPreview": "注入预览",
542
579
  "memory.status.injectPreviewNone": "暂无预览——尚未发生注入(新会话或注入已关闭)",
543
- "memory.status.injectPreview.chars": "总体积 {chars} 字符",
580
+ "memory.status.injectPreview.chars": "总体积",
544
581
  "memory.status.injectPreview.charsUnit": " 字符",
582
+ "memory.status.injectPreview.maxItems": "注入上限",
583
+ "memory.status.injectPreview.threshold": "相关性门槛",
584
+ "memory.status.injectPreview.scope": "会话归属",
545
585
  "memory.status.injectPreview.query": "查询「{query}…」",
546
586
  "memory.status.injectPreview.hot": "hot memory",
547
587
  "memory.status.injectPreview.adaptiveOn": "自适应条数",
@@ -580,11 +620,19 @@ window.__ModuleLoader__.load({
580
620
  "memory.status.consolidatedEmpty": "autoDream / autoSummarize 沉淀的记忆会出现在这里",
581
621
  "memory.status.archivedMemories": "已归档的记忆",
582
622
  "memory.status.heatDistribution": "热度分布",
583
- "memory.status.heatHint": "热门(≥66%){hot} · 温热(33–66%){warm} · 冷却(<33%){cold},样本为最近 {sample} 条",
623
+ "memory.status.heat.hot": "热门(≥66%)",
624
+ "memory.status.heat.warm": "温热(33–66%)",
625
+ "memory.status.heat.cold": "冷却(<33%)",
626
+ "memory.status.heat.sample": "按最近 {sample} 条采样",
584
627
  "memory.status.recallStats": "记忆复用",
585
- "memory.status.recallHint": "复用率 {rate} · 僵尸 {zombie}/{active}(豁免 {exempt})· 30 天回执 {runs} 次 · Top:{top}",
586
- "memory.status.recallInject": "注入 {runs} 轮 {count} 条,槽位填充 {fill}%",
587
- "memory.status.recallArchive": "归档 {total} 行({add}/天),精确重复可压掉 {compress} 行",
628
+ "memory.status.recall.zombie": "僵尸记忆",
629
+ "memory.status.recall.exempt": "豁免",
630
+ "memory.status.recall.runs": "30 天回执",
631
+ "memory.status.recall.top": "常被召回",
632
+ "memory.status.recall.inject": "注入",
633
+ "memory.status.recall.archive": "归档",
634
+ "memory.status.recallInject": "{runs} 轮 · {count} 条 · 槽位 {fill}%",
635
+ "memory.status.recallArchive": "{total} 行 · +{add}/天 · 可压掉 {compress}",
588
636
  "memory.status.viewAll": "查看全部",
589
637
  "memory.status.depositedCount": "沉淀的记忆({n})",
590
638
  "memory.status.archivedCount": "已归档的记忆({n})",
@@ -606,7 +654,7 @@ window.__ModuleLoader__.load({
606
654
  "memory.status.vectorInitHint": "embedder 不可达,正在重试",
607
655
  "memory.status.vectorRuntimeMissing": "缺少本地推理运行时",
608
656
  "memory.status.vectorRuntimeHint": "本地推理运行时未就绪({status})",
609
- "memory.status.vectorRuntimeCost": "本地向量化要额外一份本地推理运行时(transformers + onnxruntime 闭包,解包后数百 MB)。下面的按钮会先尝试收编本机已有的那份(同盘则硬链接,不占额外空间),没有再按固定清单从 npm 取回(逐个校验 sha512)。也可以直接让 agent 处理,或在终端里自己跑:node scripts/mneme-runtime.mjs status / adopt [--from <node_modules>] / verify(详见 docs/LOCAL_MODEL.md §2.5)。",
657
+ "memory.status.vectorRuntimeCost": "本地向量化需要额外一份本地推理运行时(解包后数百 MB)。下面的按钮会优先收编本机已有的那份(同盘硬链接、不占额外空间),没有再从 npm 下载并逐个校验。也可以让 Agent 代劳,或在终端自己跑:node scripts/mneme-runtime.mjs status / adopt / verify(见 docs/LOCAL_MODEL.md)",
610
658
  "memory.runtime.title": "本地推理运行时",
611
659
  "memory.runtime.available": "已就绪",
612
660
  "memory.runtime.missing": "未就绪 —— 只有用本地嵌入(embedProvider: local)时才需要它",
@@ -622,6 +670,8 @@ window.__ModuleLoader__.load({
622
670
  "memory.status.vectorUnconfigured": "未配置",
623
671
  "memory.status.vectorUnconfiguredHint": "未填 embedding 端点/模型或未启用,语义召回不可用",
624
672
  "memory.status.vectorDegradedHint": "已索引 0 / {m} 条,语义召回实际不可用",
673
+ "memory.status.sec.overview": "库内一览",
674
+ "memory.status.sec.engine": "后台运转",
625
675
  "memory.status.llm": "LLM 消耗",
626
676
  "memory.status.llmCalls": "近 7 天 · {n} 次调用",
627
677
  "memory.status.error": "加载失败"
@@ -634,7 +684,13 @@ window.__ModuleLoader__.load({
634
684
  "memory.tab.project": "Projects",
635
685
  "memory.tab.decision": "Decisions",
636
686
  "memory.tab.history": "History",
687
+ "memory.tab.summary": "Summaries",
637
688
  "memory.settings.title": "Memory Settings",
689
+ "memory.settings.group.personal": "Personalization",
690
+ "memory.settings.group.engine": "Memory engine",
691
+ "memory.settings.group.search": "Search",
692
+ "memory.settings.group.connect": "Connections & safety",
693
+ "memory.settings.group.help": "Help",
638
694
  "memory.settings.profile": "User Profile",
639
695
  "memory.settings.profileHint": "Describe yourself once — the agent reads it every turn.",
640
696
  "memory.settings.profileSave": "Save Profile",
@@ -739,22 +795,25 @@ window.__ModuleLoader__.load({
739
795
  "memory.settings.apiTokenPlaceholder": "Empty = no auth (default)",
740
796
  "memory.settings.apiTokenSave": "Save Token",
741
797
  "memory.settings.apiTokenSaved": "Token saved",
742
- "memory.settings.feedback.title": "Help & feedback",
743
- "memory.settings.feedback.desc": "Stuck? Search the existing issues, or tell us what happened:",
744
- "memory.settings.feedback.newIssue": "Open a GitHub issue (environment info prefilled)",
745
- "memory.settings.feedback.email": "Email feedback (work@modusensus.space)",
798
+ "memory.settings.feedback.title": "Feedback",
799
+ "memory.settings.feedback.desc": "Something off? Tell us directly, or check if someone hit it first:",
800
+ "memory.settings.feedback.newIssue": "Report on GitHub",
801
+ "memory.settings.feedback.newIssue.sub": "Plugin version & environment prefilled",
802
+ "memory.settings.feedback.email": "Email us",
803
+ "memory.settings.feedback.email.sub": "work@modusensus.space",
746
804
  "memory.settings.feedback.browse": "Browse known issues",
805
+ "memory.settings.feedback.browse.sub": "Check if it's already reported",
747
806
  "memory.settings.feedback.hint": "Search for an existing issue first — it saves a duplicate.",
748
807
  "memory.settings.version.outdated": "🆕 New version {v} available (running {c})",
749
808
  "memory.settings.version.outdatedHint": "If the plugin was installed with a pinned version, regular upgrades never cross minor/major lines — reinstall or pass --latest. A fresh npm release may take about a day to appear in the market.",
750
809
  "memory.settings.mode.title": "Runtime mode",
751
- "memory.settings.mode.desc": "Light mode keeps only the core memory read/write and auto-injection (autoDream consolidation, entity extraction and semantic search are off) — for light use where you just want preferences remembered. Standard mode enables everything.",
810
+ "memory.settings.mode.desc": "Light mode keeps the core loop only: memory read/write, auto-injection and hot memory — consolidation, entity extraction and semantic search are all off. For modest machines or when you just want preferences remembered. Standard mode enables everything.",
752
811
  "memory.settings.mode.light": "Light",
753
812
  "memory.settings.mode.standard": "Standard",
754
813
  "memory.settings.mode.savedHint": "Saved. Takes effect after restarting DSH",
755
814
  "memory.settings.mode.offList": "Off: consolidation (autoDream) · entity extraction · semantic search",
756
815
  "memory.settings.extapi.title": "External API",
757
- "memory.settings.extapi.desc": "A standalone HTTP service for other plugins / CLIs / desktop tools to read and write memories, bound to 127.0.0.1 by default. Takes effect after restarting DSH.",
816
+ "memory.settings.extapi.desc": "Opens a local API so other tools (plugins, CLIs, desktop apps) can also read and write this memory store; binds to localhost only by default. Changes take effect after restarting DSH.",
758
817
  "memory.settings.extapi.enabled": "Enable",
759
818
  "memory.settings.extapi.disabled": "Disable",
760
819
  "memory.settings.extapi.address": "Address",
@@ -769,11 +828,11 @@ window.__ModuleLoader__.load({
769
828
  "memory.tab.constraint": "Constraints",
770
829
  "memory.tab.document": "Documents",
771
830
  "memory.features.title": "Features",
772
- "memory.features.desc": "Toggle backend capabilities. Changes take effect after restarting DSH.",
773
- "memory.features.group.core": "Core",
774
- "memory.features.group.enhance": "Enhancement",
775
- "memory.features.group.dream": "Consolidation",
776
- "memory.features.group.scope": "Scope isolation",
831
+ "memory.features.desc": "Choose what the plugin does in the background. Changes are saved immediately and take effect after restarting DSH.",
832
+ "memory.features.group.core": "Basics",
833
+ "memory.features.group.enhance": "Search & enrichment",
834
+ "memory.features.group.dream": "Consolidation & sleep",
835
+ "memory.features.group.scope": "Multi-session isolation",
777
836
  "memory.features.group.advanced": "Advanced",
778
837
  "memory.features.advancedToggle": "Advanced (injection · reflection · experimental)",
779
838
  "memory.features.restartHint": "Takes effect after restarting DSH",
@@ -781,14 +840,16 @@ window.__ModuleLoader__.load({
781
840
  "memory.features.autoInject": "Auto injection",
782
841
  "memory.features.autoInject.hint": "Carry relevant memories into every turn",
783
842
  "memory.features.injectGuidanceEnabled": "Capability guide",
784
- "memory.features.injectGuidanceEnabled.hint": "Explain how to use memory (when to search, when to save, when to do nothing) in tool descriptions plus a one-time prompt section",
843
+ "memory.features.injectGuidanceEnabled.hint": "Teach the agent how to use memory: when to search, when to save, when to leave things alone",
785
844
  "memory.features.continuityRescueEnabled": "Compaction-edge rescue",
786
- "memory.features.continuityRescueEnabled.hint": "Before context compaction, record a continuity snapshot (current work / next step / open questions) and append it near the end of the conversation so it survives the compaction",
845
+ "memory.features.continuityRescueEnabled.hint": "Before a long conversation gets compacted, store a snapshot of what's in progress / next steps / open questions so key context survives the compaction",
787
846
  "memory.features.parentOff": "Inactive while auto injection is off",
788
847
  "memory.features.autoSummarize": "Auto summarization",
789
848
  "memory.features.autoSummarize.hint": "Distill memory entries when a conversation ends",
790
849
  "memory.features.hotMemoryEnabled": "Hot memory",
791
850
  "memory.features.hotMemoryEnabled.hint": "Carry the last few turns verbatim; never written to long-term memory",
851
+ "memory.features.injectTimePrefix": "Inject current time at conversation start",
852
+ "memory.features.injectTimePrefix.hint": "Adds the date and weekday once at each new session's first turn (off by default)",
792
853
  "memory.features.entityExtractionEnabled": "Entity extraction",
793
854
  "memory.features.entityExtractionEnabled.hint": "Extract people / projects / concepts so the graph grows by itself",
794
855
  "memory.features.entityExtractionProvider": "Entity extraction provider",
@@ -804,18 +865,18 @@ window.__ModuleLoader__.load({
804
865
  "memory.features.summarizeReasoningEffort.low": "Low",
805
866
  "memory.features.summarizeReasoningEffort.medium": "Medium",
806
867
  "memory.features.summarizeReasoningEffort.high": "High",
807
- "memory.features.summarizeReasoningEffort.hint": "Prefer low or No reasoning for thinking models so reasoning cannot drain the output budget; takes effect after a DSH restart",
808
- "memory.features.entityExtractionModelHint": "Provider / model empty = follow the main conversation model; reasoning none = provider default",
868
+ "memory.features.summarizeReasoningEffort.hint": "For thinking models, prefer Low or No reasoning so thinking cannot fill the output budget and break summaries",
869
+ "memory.features.entityExtractionModelHint": "Provider / model empty = follow the main conversation model; reasoning \"Follow default\" = provider default",
809
870
  "memory.features.codingRetrospect": "Coding retrospection",
810
- "memory.features.codingRetrospect.hint": "Distill pitfalls, constraints and rejected solutions from full transcripts (tools and errors included)",
871
+ "memory.features.codingRetrospect.hint": "Review the full coding session (tools and errors included) and distill pitfalls, constraints and rejected solutions",
811
872
  "memory.features.rerankEnabled": "Reranking",
812
873
  "memory.features.rerankEnabled.hint": "Rerank recalled results with a local model — slower, more precise",
813
874
  "memory.features.heatEnabled": "Heat decay",
814
- "memory.features.heatEnabled.hint": "Decay memory heat since last access; feeds injection ranking and sleep demotion",
875
+ "memory.features.heatEnabled.hint": "Cool memories down like a forgetting curve: the longer unused, the lower they rank — they stop taking injection slots",
815
876
  "memory.features.documentMemoryEnabled": "Document memory",
816
- "memory.features.documentMemoryEnabled.hint": "Register agent-authored documents as pointer rows: existence + evidence validation, summary + path stored, dedupe + supersede bookkeeping; the full text stays agent-owned and is read on demand; injection carries the summary row only (off by default)",
877
+ "memory.features.documentMemoryEnabled.hint": "Long agent-authored documents are stored as a \"summary card\" only: the full text stays agent-owned and is read on demand, so injection carries the summary without filling the context",
817
878
  "memory.features.resilientModelDownload": "Resumable model downloads",
818
- "memory.features.resilientModelDownload.hint": "Resume model file downloads from received bytes and retry on failure; failures log where they stopped",
879
+ "memory.features.resilientModelDownload.hint": "Resume model downloads from where they stopped and retry automatically; off = one-shot downloads",
819
880
  "memory.features.searchSemanticDedup": "Semantic dedup",
820
881
  "memory.features.searchSemanticDedup.hint": "Keep one entry when search hits near-duplicates",
821
882
  "memory.features.autoDream": "Memory consolidation",
@@ -840,18 +901,24 @@ window.__ModuleLoader__.load({
840
901
  "memory.features.memoryQualityFilter.enabled.hint": "Archive low-value memories automatically; injection downranks by quality",
841
902
  "memory.features.llmAudit.enabled": "Background call audit",
842
903
  "memory.features.llmAudit.enabled.hint": "Log background model calls and token usage from consolidation / summaries",
904
+ // #254 — two layers: enabled runs the check (audit only), enforce actually
905
+ // blocks. The hints must keep that difference explicit.
906
+ "memory.features.writeAdmission.enabled": "Write admission checks",
907
+ "memory.features.writeAdmission.enabled.hint": "Flag blank / noise writes before storing, with an audit row (never blocks)",
908
+ "memory.features.writeAdmission.enforce": "Write admission enforcement",
909
+ "memory.features.writeAdmission.enforce.hint": "Actually reject flagged writes; off keeps the audit row only",
843
910
  "memory.features.bm25SearchEnabled": "BM25 keyword search",
844
911
  "memory.features.bm25SearchEnabled.hint": "Classic keyword scoring, complementary to vector recall",
845
912
  "memory.features.conflictFreezeEnabled": "Conflict freezing",
846
913
  "memory.features.conflictFreezeEnabled.hint": "Freeze contradictory memories for confirmation during consolidation",
847
914
  "memory.features.scopeEnabled": "Scope tagging",
848
- "memory.features.scopeEnabled.hint": "Stamp new memories with the writing session's identity (agent/workspace); takes effect after restarting DSH",
915
+ "memory.features.scopeEnabled.hint": "New memories record which session (agent / workspace) they came from, for isolation and filtered recall",
849
916
  "memory.features.strictScope": "Strict isolation",
850
- "memory.features.strictScope.hint": "Hard-filter search and injection for EXPLICITLY declared scopes only: memories explicitly narrowed to other agents/workspaces become invisible; auto carrier labels are demoted but stay visible (use sensitivity for true physical isolation). Takes effect after restarting DSH",
917
+ "memory.features.strictScope.hint": "When on, memories explicitly made private by other sessions become fully invisible; when off they stay visible but rank lower. For true isolation use a memory's sensitivity field",
851
918
  "memory.features.trustEpistemicWeighting": "Credibility weighting",
852
919
  "memory.features.trustEpistemicWeighting.hint": "Adjust recall ranking by source credibility (experimental)",
853
920
  "memory.features.reflectionFailureTracking": "Reflection failure tracking",
854
- "memory.features.reflectionFailureTracking.hint": "Record failed consolidation decisions for later improvement",
921
+ "memory.features.reflectionFailureTracking.hint": "Record failed consolidation cases for later improvement (diagnostics)",
855
922
  "memory.features.embedRoute": "Semantic search route",
856
923
  "memory.features.embedProvider": "Embedding provider",
857
924
  "memory.features.embedProvider.openai": "OpenAI-compatible API",
@@ -867,9 +934,9 @@ window.__ModuleLoader__.load({
867
934
  "memory.features.dreamModel": "Consolidation model",
868
935
  "memory.features.dreamModelHint": "Leave empty to follow the main conversation model; only affects autoDream consolidation",
869
936
  "memory.features.dreamPeakHours": "Peak hours (no dreaming)",
870
- "memory.features.dreamPeakHours.hint": "Empty = off. Comma-separated windows, optional weekday prefix, midnight-crossing allowed — e.g. 09:00-18:00 or mon-fri 08:00-12:00,14:00-18:00. Inside these windows no LLM call is made; the run is deferred to the nearest peak-end (capped by dreamPeakMaxDeferMinutes so an all-day peak cannot starve consolidation)",
937
+ "memory.features.dreamPeakHours.hint": "Busy hours for the machine; consolidation avoids them and catches up afterwards. Syntax: 09:00-18:00, several windows comma-separated, weekday prefix allowed (mon-fri 08:00-12:00), midnight-crossing allowed; empty = no limit",
871
938
  "memory.features.summarizePeakHours": "Peak hours (no distillation)",
872
- "memory.features.summarizePeakHours.hint": "Empty = off. Same window syntax as the consolidation side above; inside a peak, distillation is deferred and the window accumulates for one bigger run",
939
+ "memory.features.summarizePeakHours.hint": "Same syntax as the consolidation side above; distillation avoids these windows and accumulates the material for one run off-peak",
873
940
  "memory.features.sleepProvider": "Sleep provider",
874
941
  "memory.features.sleepModel": "Sleep model",
875
942
  "memory.features.sleepModelHint": "Leave empty to reuse the consolidation model; a non-reasoning model is recommended",
@@ -880,7 +947,7 @@ window.__ModuleLoader__.load({
880
947
  "memory.features.modelTestFail": "Test failed",
881
948
  "memory.features.modelTestHint": "Fires one minimal consolidation call to verify provider/model connectivity and effort support",
882
949
  "memory.features.routeStaleMark": " (not in list)",
883
- "memory.features.routeStaleHint": "Not in the currently available list, often a leftover from a provider switch: reselect, or run a connectivity test to verify now; changes take effect after restarting DSH.",
950
+ "memory.features.routeStaleHint": "This value is not in the currently available list (often a leftover from switching providers): reselect, or run a connectivity test to verify now",
884
951
  "memory.explorer.viewCards": "Cards",
885
952
  "memory.explorer.viewTimeline": "Timeline",
886
953
  "memory.explorer.viewAria": "Switch view",
@@ -933,16 +1000,20 @@ window.__ModuleLoader__.load({
933
1000
  "memory.explorer.detail.editTitle": "Title",
934
1001
  "memory.explorer.detail.editContent": "Content",
935
1002
  "memory.explorer.detail.editImportance": "Importance",
1003
+ "memory.explorer.detail.starLabel": "Set importance to {n} stars",
936
1004
  "memory.status.dream": "Last consolidation",
937
1005
  "memory.status.dreamNever": "Not yet run",
938
1006
  "memory.status.conflicts": "Pending conflicts",
939
1007
  "memory.status.conflictsHint": "Frozen contradictory memories awaiting confirmation",
940
1008
  "memory.status.injectSuppressed": "Injection disabled by host minimal preset",
941
- "memory.status.injectSuppressedHint": "This session uses the minimal preset: memory injection / user profile / hot memory never reach the model (by host design, not a plugin defect). Fix: switch to standard mode, or set agent-presets.default: standard in ~/.dsh/settings.yaml. Interim: put profile and rules in AGENTS.md",
1009
+ "memory.status.injectSuppressedHint": "This session uses the minimal preset: memory injection / profile / hot memory never reach the model (by host design, not a plugin defect). Switch to standard mode, or set agent-presets.default: standard in ~/.dsh/settings.yaml. Interim: put profile and rules in AGENTS.md",
942
1010
  "memory.status.injectPreview": "Injection preview",
943
1011
  "memory.status.injectPreviewNone": "No preview yet — no injection has happened (new session or injection off)",
944
- "memory.status.injectPreview.chars": "total {chars} chars",
1012
+ "memory.status.injectPreview.chars": "Total",
945
1013
  "memory.status.injectPreview.charsUnit": " chars",
1014
+ "memory.status.injectPreview.maxItems": "Max items",
1015
+ "memory.status.injectPreview.threshold": "Relevance floor",
1016
+ "memory.status.injectPreview.scope": "Session scope",
946
1017
  "memory.status.injectPreview.query": "query \"{query}…\"",
947
1018
  "memory.status.injectPreview.hot": "hot memory",
948
1019
  "memory.status.injectPreview.adaptiveOn": "adaptive budget",
@@ -981,11 +1052,19 @@ window.__ModuleLoader__.load({
981
1052
  "memory.status.consolidatedEmpty": "Memories deposited by autoDream / autoSummarize appear here",
982
1053
  "memory.status.archivedMemories": "Archived memories",
983
1054
  "memory.status.heatDistribution": "Heat distribution",
984
- "memory.status.heatHint": "hot (≥66%) {hot} · warm (33–66%) {warm} · cooled (<33%) {cold}, sampled from the latest {sample}",
1055
+ "memory.status.heat.hot": "Hot (≥66%)",
1056
+ "memory.status.heat.warm": "Warm (33–66%)",
1057
+ "memory.status.heat.cold": "Cooled (<33%)",
1058
+ "memory.status.heat.sample": "Sampled from the latest {sample}",
985
1059
  "memory.status.recallStats": "Recall reuse",
986
- "memory.status.recallHint": "reuse {rate} · zombie {zombie}/{active} (exempt {exempt}) · {runs} runs in 30d · top: {top}",
987
- "memory.status.recallInject": "injected {runs} turns · {count} items ({fill}% slots)",
988
- "memory.status.recallArchive": "archived {total} rows (+{add}/day) · {compress} exactly-duplicate rows compressible",
1060
+ "memory.status.recall.zombie": "Zombie memories",
1061
+ "memory.status.recall.exempt": "exempt",
1062
+ "memory.status.recall.runs": "Receipts (30d)",
1063
+ "memory.status.recall.top": "Most recalled",
1064
+ "memory.status.recall.inject": "Injected",
1065
+ "memory.status.recall.archive": "Archived",
1066
+ "memory.status.recallInject": "{runs} turns · {count} items · {fill}% slots",
1067
+ "memory.status.recallArchive": "{total} rows · +{add}/day · {compress} compressible",
989
1068
  "memory.status.viewAll": "View all",
990
1069
  "memory.status.depositedCount": "Deposited memories ({n})",
991
1070
  "memory.status.archivedCount": "Archived memories ({n})",
@@ -1007,7 +1086,7 @@ window.__ModuleLoader__.load({
1007
1086
  "memory.status.vectorInitHint": "embedder unreachable, retrying",
1008
1087
  "memory.status.vectorRuntimeMissing": "Local inference runtime missing",
1009
1088
  "memory.status.vectorRuntimeHint": "Local inference runtime not ready ({status})",
1010
- "memory.status.vectorRuntimeCost": "Local vectorization needs an extra local inference runtime (the transformers + onnxruntime closure, hundreds of MB unpacked). The button below first tries to adopt an existing copy on this machine (hardlinked when on the same volume, so no extra disk), and otherwise fetches it from npm against a pinned manifest (every tarball checked against its sha512). You can also just ask the agent, or run it yourself: node scripts/mneme-runtime.mjs status / adopt [--from <node_modules>] / verify (see docs/LOCAL_MODEL.md §2.5).",
1089
+ "memory.status.vectorRuntimeCost": "Local vectorization needs an extra local inference runtime (hundreds of MB unpacked). The button below first adopts an existing copy on this machine (hardlinked on the same volume, no extra disk) and otherwise fetches it from npm with per-file checksums. You can also just ask the agent, or run it yourself: node scripts/mneme-runtime.mjs status / adopt / verify (see docs/LOCAL_MODEL.md)",
1011
1090
  "memory.runtime.title": "Local inference runtime",
1012
1091
  "memory.runtime.available": "ready",
1013
1092
  "memory.runtime.missing": "not ready — only needed when you use local embedding (embedProvider: local)",
@@ -1023,6 +1102,8 @@ window.__ModuleLoader__.load({
1023
1102
  "memory.status.vectorUnconfigured": "Not configured",
1024
1103
  "memory.status.vectorUnconfiguredHint": "No embedding endpoint/model configured — semantic recall is off",
1025
1104
  "memory.status.vectorDegradedHint": "Indexed 0 / {m} items — semantic recall is effectively unavailable",
1105
+ "memory.status.sec.overview": "Library",
1106
+ "memory.status.sec.engine": "Background activity",
1026
1107
  "memory.status.llm": "LLM Usage",
1027
1108
  "memory.status.llmCalls": "Last 7 days · {n} calls",
1028
1109
  "memory.status.error": "Failed to load"
@@ -1202,8 +1283,8 @@ window.__ModuleLoader__.load({
1202
1283
  // --- settings: quiet stacked sections, one concern per section ---
1203
1284
  ".mneme-set{flex:1;min-height:0;overflow-y:auto;padding:8px 24px 48px;box-sizing:border-box}",
1204
1285
  ".mneme-set-inner{max-width:720px;margin:0 auto}",
1205
- ".mneme-set-sec{padding:20px 0 24px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
1206
- ".mneme-set-sec:last-child{border-bottom:none}",
1286
+ // 设置页各节一律用 boxed 卡(.mneme-set-card)+ 分组标题(.mneme-set-grouphead):
1287
+ // 旧的「无框 set-sec + 有框 set-card」混排是版面杂乱的一半来源
1207
1288
  ".mneme-set-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);margin-bottom:4px}",
1208
1289
  ".mneme-set-desc{font-size:13px;line-height:19px;color:var(--dsw-alias-label-tertiary);margin-bottom:14px}",
1209
1290
  ".mneme-set-input{box-sizing:border-box;width:100%;height:34px;padding:0 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13px;outline:none;margin-bottom:10px;transition:border-color .12s,box-shadow .12s}",
@@ -1271,7 +1352,6 @@ window.__ModuleLoader__.load({
1271
1352
  ".mneme-numinput:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 15%,transparent)}",
1272
1353
  // 字符串开关的输入框(provider/model 等)与其子块容器
1273
1354
  ".mneme-strinput{box-sizing:border-box;width:240px;max-width:60%;height:30px;padding:0 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13px;outline:none;text-align:left;transition:border-color .12s,box-shadow .12s}",
1274
- ".mneme-feed-link{display:block;color:var(--dsw-alias-brand,var(--dsw-alias-label-primary));text-decoration:none;font-size:13px;line-height:20px;padding:4px 2px;border-radius:6px;transition:opacity .12s}.mneme-feed-link:hover{opacity:.8;text-decoration:underline}",
1275
1355
  ".mneme-strinput:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 15%,transparent)}",
1276
1356
  ".mneme-featsub{margin:2px 0 8px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;display:flex;flex-direction:column;gap:6px}",
1277
1357
  ".mneme-featsub .mneme-featnum{padding:6px 0;border-bottom:none}",
@@ -1378,8 +1458,36 @@ window.__ModuleLoader__.load({
1378
1458
  ".mneme-xemptyico{display:block;margin:0 auto 6px;opacity:.7}",
1379
1459
  // --- status sub-view: responsive stat-card grid (auto-fill, ~220px min) ---
1380
1460
  ".mneme-status{flex:1;min-height:0;overflow-y:auto;padding:20px 24px 40px;box-sizing:border-box}",
1381
- ".mneme-statusgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;max-width:1000px;margin:0 auto}",
1382
- ".mneme-statuscard{min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:14px}",
1461
+ ".mneme-statusgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;max-width:1000px;margin:0 auto 12px}",
1462
+ ".mneme-statusgrid:last-of-type{margin-bottom:0}",
1463
+ // 概览区是单行三卡:auto-fit 让不满的轨道塌缩,三张卡恒等宽铺满、行尾不留洞
1464
+ ".mneme-statusgrid--overview{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}",
1465
+ // 跨列只在容器够放下 ≥2 列时生效(220px 最小轨 + 12px 间隙推出来的界);
1466
+ // 单列容器里 span 2 会撑出隐式轨道把布局撑爆,必须撤掉
1467
+ "@container (min-width:452px) and (max-width:683px){.mneme-statuscard--vectorwide{grid-column:span 2}}",
1468
+ "@container (min-width:452px){.mneme-statuscard--wide{grid-column:span 2}}",
1469
+ // 状态页分组标题(库内一览 / 后台运转):只做扫读锚点,压得比卡片标题低
1470
+ ".mneme-statushead{max-width:1000px;margin:0 auto 10px;font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-tertiary)}",
1471
+ ".mneme-statusgrid+.mneme-statushead{margin-top:26px}",
1472
+ ".mneme-statuscard{min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:16px 16px 14px}",
1473
+ // 指标行:一行一指标(label 左 / 数值右),取代「·」串联的说明长句——
1474
+ // 拥挤感的根源是把多指标压进一段会换行的散文
1475
+ ".mneme-statrows{margin-top:10px;display:flex;flex-direction:column;gap:4px}",
1476
+ ".mneme-statrow{display:flex;align-items:baseline;justify-content:space-between;gap:12px;font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary)}",
1477
+ ".mneme-statrow>span:first-child{flex:none;white-space:nowrap}",
1478
+ ".mneme-statrow .mneme-heatdot{align-self:center}",
1479
+ ".mneme-statrowval{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;text-align:right}",
1480
+ ".mneme-statfoot{margin-top:8px;font-size:11px;line-height:16px;color:var(--dsw-alias-label-dimmed)}",
1481
+ // 热度分布条:三档占比一图看清,行标只补数字(类名带双横线,与 heatdot 同款)
1482
+ ".mneme-heatbar{display:flex;height:6px;border-radius:3px;overflow:hidden;margin-top:12px;background:var(--dsw-alias-interactive-bg-hover)}",
1483
+ ".mneme-heatbar-seg{min-width:2px}",
1484
+ ".mneme-heatbar--hot{background:var(--dsw-alias-state-warning,#d97706)}",
1485
+ ".mneme-heatbar--warm{background:var(--dsw-alias-label-tertiary)}",
1486
+ ".mneme-heatbar--cold{background:var(--dsw-alias-label-dimmed)}",
1487
+ ".mneme-heatdot{flex:none;width:8px;height:8px;border-radius:50%}",
1488
+ ".mneme-heatdot--hot{background:var(--dsw-alias-state-warning,#d97706)}",
1489
+ ".mneme-heatdot--warm{background:var(--dsw-alias-label-tertiary)}",
1490
+ ".mneme-heatdot--cold{background:var(--dsw-alias-label-dimmed)}",
1383
1491
  // --- 状态页工作台:让用户看见插件在干活(动态/沉淀/归档) ---
1384
1492
  ".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}",
1385
1493
  ".mneme-wblist{max-width:1000px;display:flex;flex-direction:column;gap:6px}",
@@ -1402,9 +1510,29 @@ window.__ModuleLoader__.load({
1402
1510
  // --- settings cards: boxed cards for the runtime-mode and external-API
1403
1511
  // sections — each card owns its fetch/PUT state, so it renders as a
1404
1512
  // self-contained unit inside the stacked settings view ---
1405
- ".mneme-set-card{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:14px;margin-bottom:12px}",
1513
+ ".mneme-set-card{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:16px;margin-bottom:10px}",
1514
+ // 设置页分组标题(个性化 / 记忆引擎 / 搜索 / 连接与安全 / 帮助):
1515
+ // 先给「这一段在管什么」,卡片才是具体选项
1516
+ ".mneme-set-grouphead{font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-tertiary);margin:24px 0 10px}",
1517
+ ".mneme-set-grouphead:first-child{margin-top:8px}",
1406
1518
  ".mneme-set-token{font-family:monospace;font-size:12px;padding:7px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary);word-break:break-all;user-select:all}",
1407
- ".mneme-set-hint{font-size:12px;color:var(--dsw-alias-label-tertiary)}"
1519
+ ".mneme-set-hint{font-size:12px;color:var(--dsw-alias-label-tertiary)}",
1520
+ // --- 重要性编辑行:与查看态同一份 Lucide 星形(此前是文本星号下拉,角更尖锐且与其他星不统一) ---
1521
+ ".mneme-staredit{display:inline-flex;align-items:center;gap:1px;justify-self:start}",
1522
+ ".mneme-starbtn{border:none;background:none;cursor:pointer;padding:2px 1px;display:inline-flex;color:var(--dsw-alias-label-secondary);transition:transform .12s}",
1523
+ ".mneme-starbtn--on{color:var(--dsw-alias-state-warning,#d97706)}",
1524
+ ".mneme-starbtn:hover{transform:scale(1.12)}",
1525
+ ".mneme-starbtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px;border-radius:4px}",
1526
+ // --- 意见与反馈:图标行 + 版本 footer,收在设置页末尾当「关于」用 ---
1527
+ ".mneme-feedlist{display:flex;flex-direction:column;gap:4px}",
1528
+ ".mneme-feedrow{display:flex;align-items:flex-start;gap:10px;padding:9px 10px;border-radius:10px;text-decoration:none;transition:background .12s}",
1529
+ ".mneme-feedrow:hover{background:var(--dsw-alias-interactive-bg-hover)}",
1530
+ ".mneme-feedico{flex:none;width:28px;height:28px;border-radius:8px;display:inline-flex;align-items:center;justify-content:center;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}",
1531
+ ".mneme-feedrow:hover .mneme-feedico{background:var(--dsw-alias-interactive-bg-active)}",
1532
+ ".mneme-feedtext{display:flex;flex-direction:column;gap:1px;min-width:0}",
1533
+ ".mneme-feedname{font-size:13px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-primary)}",
1534
+ ".mneme-feedsub{font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary)}",
1535
+ ".mneme-set-foot{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-top:12px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l1);font-size:12px;color:var(--dsw-alias-label-tertiary)}"
1408
1536
  ].join("\n");
1409
1537
  if (typeof document !== "undefined" && document.querySelector(`style[data-plugin-css="${CSS_TAG}"]`) === null) {
1410
1538
  const tag = document.createElement("style");
@@ -1777,7 +1905,9 @@ window.__ModuleLoader__.load({
1777
1905
  h("div", { className: "mneme-gs-title" },
1778
1906
  h("span", null, selected.node.name),
1779
1907
  h("span", { className: "mneme-gs-meta" },
1780
- `${entityTypeLabel(t, selected.node.type || "concept")} · ★${selected.node.mention_count ?? 1}`)
1908
+ `${entityTypeLabel(t, selected.node.type || "concept")} · `,
1909
+ h(StarGlyph, { size: 11 }),
1910
+ ` ${selected.node.mention_count ?? 1}`)
1781
1911
  ),
1782
1912
  selected.node.name !== entityName && h("button", {
1783
1913
  className: "mneme-footbtn",
@@ -1959,7 +2089,7 @@ window.__ModuleLoader__.load({
1959
2089
  // test/inject-parent-gate.test.js 钉住。
1960
2090
  const FEATURE_CHILDREN = { autoInject: ["injectGuidanceEnabled", "continuityRescueEnabled"] };
1961
2091
  const FEATURE_GROUPS = [
1962
- { key: "group.core", items: ["autoInject", "autoSummarize", "hotMemoryEnabled", "memoryQualityFilter.enabled", "llmAudit.enabled"] },
2092
+ { key: "group.core", items: ["autoInject", "autoSummarize", "hotMemoryEnabled", "injectTimePrefix", "memoryQualityFilter.enabled", "llmAudit.enabled", "writeAdmission.enabled", "writeAdmission.enforce"] },
1963
2093
  { key: "group.enhance", items: ["entityExtractionEnabled", "codingRetrospect", "rerankEnabled", "resilientModelDownload", "searchSemanticDedup", "bm25SearchEnabled", "heatEnabled", "documentMemoryEnabled"] },
1964
2094
  { key: "group.dream", items: ["autoDream", "sleepModeEnabled"] },
1965
2095
  // v0.8.0 A4(issue #17):作用域隔离组——标注总开关 + 严格硬过滤。
@@ -2435,6 +2565,14 @@ window.__ModuleLoader__.load({
2435
2565
  const mailHref = "mailto:work@modusensus.space?subject="
2436
2566
  + encodeURIComponent(`[dsh-mneme 反馈] ${pkgVersion} / ${platformLabel()}`)
2437
2567
  + "&body=" + encodeURIComponent(feedbackBody);
2568
+ // 反馈行:图标 + 主标题 + 一句副标;锚点属性由调用点给(保持 href 字面量可被测试锁住)
2569
+ const feedRow = (iconName, label, sub, anchorProps) => h("a", {
2570
+ className: "mneme-feedrow", ...anchorProps
2571
+ },
2572
+ h("span", { className: "mneme-feedico", "aria-hidden": "true" }, h(Icon, { name: iconName, size: 14 })),
2573
+ h("span", { className: "mneme-feedtext" },
2574
+ h("span", { className: "mneme-feedname" }, label),
2575
+ h("span", { className: "mneme-feedsub" }, sub)));
2438
2576
 
2439
2577
  // Runtime mode + external API — two independent fetches: one failing
2440
2578
  // endpoint only errors its own card, never the other one.
@@ -2643,7 +2781,7 @@ window.__ModuleLoader__.load({
2643
2781
  // 版本自检横幅 — 仅 outdated 时渲染(up-to-date/ahead/unknown/失败
2644
2782
  // 全部零渲染)。钉子警示:安装时指定过版本号的 profile 会被 pnpm
2645
2783
  // 挡住常规升级(#174 报障者的实际成因);市场收录新发布约有 1 天延迟。
2646
- updateInfo && h("section", { className: "mneme-set-sec" },
2784
+ updateInfo && h("section", { className: "mneme-set-card" },
2647
2785
  h("div", { className: "mneme-set-title" },
2648
2786
  t("memory.settings.version.outdated")
2649
2787
  .replace("{v}", updateInfo.latest)
@@ -2651,8 +2789,10 @@ window.__ModuleLoader__.load({
2651
2789
  ),
2652
2790
  h("div", { className: "mneme-featsubhint" }, t("memory.settings.version.outdatedHint"))
2653
2791
  ),
2792
+ // —— 个性化:画像 / 规则 / 自定义指令(Agent 怎么对你) ——
2793
+ h("div", { className: "mneme-set-grouphead" }, t("memory.settings.group.personal")),
2654
2794
  // 用户画像 — who the agent is talking to
2655
- h("section", { className: "mneme-set-sec" },
2795
+ h("section", { className: "mneme-set-card" },
2656
2796
  h("div", { className: "mneme-set-title" }, t("memory.settings.profile")),
2657
2797
  h("div", { className: "mneme-set-desc" }, t("memory.settings.profileHint")),
2658
2798
  h("textarea", {
@@ -2667,7 +2807,7 @@ window.__ModuleLoader__.load({
2667
2807
  )
2668
2808
  ),
2669
2809
  // 规则 — numbered rows, hover reveals the delete affordance
2670
- h("section", { className: "mneme-set-sec" },
2810
+ h("section", { className: "mneme-set-card" },
2671
2811
  h("div", { className: "mneme-set-title" }, t("memory.settings.rules")),
2672
2812
  h("div", { className: "mneme-set-desc" }, t("memory.settings.rulesHint")),
2673
2813
  rules.length === 0 && h("div", { className: "mneme-xempty" }, t("memory.settings.empty")),
@@ -2696,7 +2836,7 @@ window.__ModuleLoader__.load({
2696
2836
  )
2697
2837
  ),
2698
2838
  // 自定义指令 — slash commands as titled rows
2699
- h("section", { className: "mneme-set-sec" },
2839
+ h("section", { className: "mneme-set-card" },
2700
2840
  h("div", { className: "mneme-set-title" }, t("memory.settings.commands")),
2701
2841
  h("div", { className: "mneme-set-desc" }, t("memory.settings.commandsHint")),
2702
2842
  commands.length === 0 && h("div", { className: "mneme-xempty" }, t("memory.settings.empty")),
@@ -2726,9 +2866,8 @@ window.__ModuleLoader__.load({
2726
2866
  )
2727
2867
  )
2728
2868
  ),
2729
- // 功能开关 — 0.7.13/0.7.14 后端能力的前端总闸(codingRetrospect、
2730
- // 智能调速器参数、实体抽取、巩固等);重启 DSH 后生效。
2731
- h(FeaturesCard, { t }),
2869
+ // —— 记忆引擎:运行模式(粗粒度)在前,功能开关(细粒度)紧随 ——
2870
+ h("div", { className: "mneme-set-grouphead" }, t("memory.settings.group.engine")),
2732
2871
  // 运行模式 — light vs standard chip radios; each click PUTs and the
2733
2872
  // change only lands after a DSH restart (green saved hint says so).
2734
2873
  h("section", { className: "mneme-set-card" },
@@ -2755,6 +2894,30 @@ window.__ModuleLoader__.load({
2755
2894
  t("memory.settings.mode.offList"))
2756
2895
  )
2757
2896
  ),
2897
+ // 功能开关 — 后端能力的细粒度闸门;改动即时保存,重启 DSH 后生效。
2898
+ h(FeaturesCard, { t }),
2899
+ // —— 搜索:语义召回 ——
2900
+ h("div", { className: "mneme-set-grouphead" }, t("memory.settings.group.search")),
2901
+ // 向量搜索 — semantic recall over an embeddings API
2902
+ h("section", { className: "mneme-set-card" },
2903
+ h("div", { className: "mneme-set-title" }, t("memory.settings.vectorTitle")),
2904
+ h("div", { className: "mneme-set-desc" }, t("memory.settings.vectorHint")),
2905
+ h("label", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 10, fontSize: 13, color: "var(--dsw-alias-label-primary)" } },
2906
+ h("input", { type: "checkbox", checked: !!vector.enabled, onChange: (e) => setVector({ ...vector, enabled: e.target.checked }) }),
2907
+ h("span", null, t("memory.settings.vectorEnabled"))
2908
+ ),
2909
+ h("input", { className: "mneme-set-input", value: vector.baseUrl, placeholder: t("memory.settings.vectorBaseUrl"), onChange: (e) => setVector({ ...vector, baseUrl: e.target.value }) }),
2910
+ h("input", { className: "mneme-set-input", type: "password", value: vector.apiKey, placeholder: t("memory.settings.vectorApiKey"), onChange: (e) => setVector({ ...vector, apiKey: e.target.value }) }),
2911
+ h("input", { className: "mneme-set-input", value: vector.model, placeholder: t("memory.settings.vectorModel"), onChange: (e) => setVector({ ...vector, model: e.target.value }) }),
2912
+ h("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" } },
2913
+ h("button", { className: "mneme-btn", onClick: saveVector }, t("memory.settings.vectorSave")),
2914
+ vectorSaved && h("span", { className: "mneme-saved" }, t("memory.settings.vectorSaved")),
2915
+ h("button", { className: "mneme-btn", onClick: reindex, disabled: reindexing }, reindexing ? t("memory.settings.vectorReindexing") : t("memory.settings.vectorReindex")),
2916
+ reindexMsg && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #666)" } }, reindexMsg)
2917
+ )
2918
+ ),
2919
+ // —— 连接与安全:外部访问 API / 面板令牌 ——
2920
+ h("div", { className: "mneme-set-grouphead" }, t("memory.settings.group.connect")),
2758
2921
  // 外部访问 API — standalone HTTP service for plugins/CLI/desktop tools;
2759
2922
  // the token is generated and kept by the backend, so it is read-only
2760
2923
  // here with a copy affordance. Changes need a DSH restart.
@@ -2818,26 +2981,10 @@ window.__ModuleLoader__.load({
2818
2981
  )
2819
2982
  )
2820
2983
  ),
2821
- // 向量搜索 — semantic recall over an embeddings API
2822
- h("section", { className: "mneme-set-sec" },
2823
- h("div", { className: "mneme-set-title" }, t("memory.settings.vectorTitle")),
2824
- h("div", { className: "mneme-set-desc" }, t("memory.settings.vectorHint")),
2825
- h("label", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 10, fontSize: 13, color: "var(--dsw-alias-label-primary)" } },
2826
- h("input", { type: "checkbox", checked: !!vector.enabled, onChange: (e) => setVector({ ...vector, enabled: e.target.checked }) }),
2827
- h("span", null, t("memory.settings.vectorEnabled"))
2828
- ),
2829
- h("input", { className: "mneme-set-input", value: vector.baseUrl, placeholder: t("memory.settings.vectorBaseUrl"), onChange: (e) => setVector({ ...vector, baseUrl: e.target.value }) }),
2830
- h("input", { className: "mneme-set-input", type: "password", value: vector.apiKey, placeholder: t("memory.settings.vectorApiKey"), onChange: (e) => setVector({ ...vector, apiKey: e.target.value }) }),
2831
- h("input", { className: "mneme-set-input", value: vector.model, placeholder: t("memory.settings.vectorModel"), onChange: (e) => setVector({ ...vector, model: e.target.value }) }),
2832
- h("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" } },
2833
- h("button", { className: "mneme-btn", onClick: saveVector }, t("memory.settings.vectorSave")),
2834
- vectorSaved && h("span", { className: "mneme-saved" }, t("memory.settings.vectorSaved")),
2835
- h("button", { className: "mneme-btn", onClick: reindex, disabled: reindexing }, reindexing ? t("memory.settings.vectorReindexing") : t("memory.settings.vectorReindex")),
2836
- reindexMsg && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #666)" } }, reindexMsg)
2837
- )
2838
- ),
2984
+ // 向量搜索卡片已上移到「搜索」分组(与功能开关里的语义检索路线相邻),
2985
+ // 这里不再重复渲染。
2839
2986
  // API Token — advanced, last
2840
- h("section", { className: "mneme-set-sec" },
2987
+ h("section", { className: "mneme-set-card" },
2841
2988
  h("div", { className: "mneme-set-title" }, t("memory.settings.apiTokenTitle")),
2842
2989
  h("div", { className: "mneme-set-desc" }, t("memory.settings.apiTokenHint")),
2843
2990
  h("div", { style: { display: "flex", gap: 8 } },
@@ -2853,18 +3000,25 @@ window.__ModuleLoader__.load({
2853
3000
  ),
2854
3001
  apiTokenSaved && h("div", { style: { marginTop: 8 } }, h("span", { className: "mneme-saved" }, t("memory.settings.apiTokenSaved")))
2855
3002
  ),
2856
- // 帮助与反馈 — 反馈问题三入口(新建 issue 预填 / 邮件 / 浏览已知问题)。
3003
+ // —— 帮助:意见与反馈(图标行 + 版本 footer,兼当「关于」收尾) ——
3004
+ h("div", { className: "mneme-set-grouphead" }, t("memory.settings.group.help")),
3005
+ // 反馈三入口:新建 issue 预填(版本+平台)/ 邮件 / 浏览已知问题。
2857
3006
  // 纯前端链接零后端成本;插件版本来自 /info,平台取 userAgent。GitHub
2858
3007
  // 仓库当前没有 issue 模板,故用 issues/new?title=&body= 直接预填。
2859
- h("section", { className: "mneme-set-sec" },
3008
+ h("section", { className: "mneme-set-card" },
2860
3009
  h("div", { className: "mneme-set-title" }, t("memory.settings.feedback.title")),
2861
3010
  h("div", { className: "mneme-set-desc" }, t("memory.settings.feedback.desc")),
2862
- h("div", { style: { display: "flex", flexDirection: "column", gap: 2 } },
2863
- h("a", { className: "mneme-feed-link", href: issueHref, target: "_blank", rel: "noopener noreferrer" }, t("memory.settings.feedback.newIssue")),
2864
- h("a", { className: "mneme-feed-link", href: mailHref }, t("memory.settings.feedback.email")),
2865
- h("a", { className: "mneme-feed-link", href: "https://github.com/slow-stack/mneme/issues", target: "_blank", rel: "noopener noreferrer" }, t("memory.settings.feedback.browse"))
3011
+ h("div", { className: "mneme-feedlist" },
3012
+ feedRow("bug", t("memory.settings.feedback.newIssue"), t("memory.settings.feedback.newIssue.sub"),
3013
+ { href: issueHref, target: "_blank", rel: "noopener noreferrer" }),
3014
+ feedRow("mail", t("memory.settings.feedback.email"), t("memory.settings.feedback.email.sub"),
3015
+ { href: mailHref }),
3016
+ feedRow("search", t("memory.settings.feedback.browse"), t("memory.settings.feedback.browse.sub"),
3017
+ { href: "https://github.com/slow-stack/mneme/issues", target: "_blank", rel: "noopener noreferrer" })
2866
3018
  ),
2867
- h("div", { className: "mneme-featsubhint", style: { marginTop: 8 } }, t("memory.settings.feedback.hint"))
3019
+ h("div", { className: "mneme-set-foot" },
3020
+ h("span", null, `dsh-mneme v${pkgVersion}`)
3021
+ )
2868
3022
  )
2869
3023
  );
2870
3024
  }
@@ -2968,9 +3122,11 @@ window.__ModuleLoader__.load({
2968
3122
 
2969
3123
  // --- Status sub-view: a responsive grid of stat cards. Every card owns
2970
3124
  // its fetch, loading ("…") and error state, so one failing endpoint
2971
- // never blanks or blocks the others. ---
2972
- function StatusCard({ t, title, loading, error, num, cap }) {
2973
- return h("div", { className: "mneme-statuscard" },
3125
+ // never blanks or blocks the others.
3126
+ // 卡片解剖:标题(h3)→ 大数字 → 指标行(rows,一行一指标)→ 脚注
3127
+ // (foot,采样口径这类背景信息)。cap 仍保留给单句说明型卡片。
3128
+ function StatusCard({ t, title, loading, error, num, cap, rows, foot, className }) {
3129
+ return h("div", { className: className ? `mneme-statuscard ${className}` : "mneme-statuscard" },
2974
3130
  h("h3", { className: "mneme-xcolhead" }, title),
2975
3131
  loading
2976
3132
  ? h("div", { className: "mneme-statusnum" }, "…")
@@ -2978,7 +3134,14 @@ window.__ModuleLoader__.load({
2978
3134
  ? h("div", { className: "mneme-statuscap", style: { color: "var(--dsw-alias-state-error,#c33)" } }, t("memory.status.error"))
2979
3135
  : h(react.Fragment, null,
2980
3136
  h("div", { className: "mneme-statusnum" }, num),
2981
- cap ? h("div", { className: "mneme-statuscap" }, cap) : null
3137
+ rows && rows.length
3138
+ ? h("div", { className: "mneme-statrows" },
3139
+ rows.map((r, i) => h("div", { className: "mneme-statrow", key: r.key ?? i },
3140
+ h("span", null, r.label),
3141
+ h("span", { className: "mneme-statrowval", title: String(r.value) }, String(r.value))))
3142
+ )
3143
+ : cap ? h("div", { className: "mneme-statuscap" }, cap) : null,
3144
+ foot ? h("div", { className: "mneme-statfoot" }, foot) : null
2982
3145
  )
2983
3146
  );
2984
3147
  }
@@ -3005,16 +3168,15 @@ window.__ModuleLoader__.load({
3005
3168
  .catch(() => { if (!cancelled) setState({ loading: false, error: true, total: 0, byType: [] }); });
3006
3169
  return () => { cancelled = true; };
3007
3170
  }, []);
3008
- const cap = state.byType
3009
- .map(([ty, n]) => `${typeLabel(t, ty)} ${n === null ? "—" : n}`)
3010
- .join(" · ");
3171
+ const rows = state.byType
3172
+ .map(([ty, n]) => ({ label: typeLabel(t, ty), value: n === null ? "—" : String(n) }));
3011
3173
  return h(StatusCard, {
3012
3174
  t,
3013
3175
  title: t("memory.status.memories"),
3014
3176
  loading: state.loading,
3015
3177
  error: state.error,
3016
3178
  num: state.total.toLocaleString(),
3017
- cap
3179
+ rows
3018
3180
  });
3019
3181
  }
3020
3182
 
@@ -3042,14 +3204,14 @@ window.__ModuleLoader__.load({
3042
3204
  .catch(() => { if (!cancelled) setState({ loading: false, error: true, total: 0, byType: [] }); });
3043
3205
  return () => { cancelled = true; };
3044
3206
  }, []);
3045
- const cap = state.byType.map(([ty, n]) => `${entityTypeLabel(t, ty)} ${n}`).join(" · ");
3207
+ const rows = state.byType.map(([ty, n]) => ({ label: entityTypeLabel(t, ty), value: String(n) }));
3046
3208
  return h(StatusCard, {
3047
3209
  t,
3048
3210
  title: t("memory.status.entities"),
3049
3211
  loading: state.loading,
3050
3212
  error: state.error,
3051
3213
  num: state.total.toLocaleString(),
3052
- cap
3214
+ rows
3053
3215
  });
3054
3216
  }
3055
3217
 
@@ -3216,7 +3378,9 @@ window.__ModuleLoader__.load({
3216
3378
  loading: state.loading,
3217
3379
  error: state.error,
3218
3380
  num,
3219
- cap
3381
+ cap,
3382
+ // 概览区三张卡在 2 列档位会剩下半行空洞:向量卡在该档位跨满整行
3383
+ className: "mneme-statuscard--vectorwide"
3220
3384
  }),
3221
3385
  !state.loading && !state.error && localBlocked
3222
3386
  ? h("div", { className: "mneme-statuscap", style: { marginTop: "6px" } },
@@ -3351,29 +3515,40 @@ window.__ModuleLoader__.load({
3351
3515
  h("div", { className: "mneme-statuscap" }, t("memory.status.injectPreviewNone"))
3352
3516
  );
3353
3517
  }
3354
- const params = [
3355
- `maxItems=${snap.maxItems}`,
3356
- `threshold=${snap.threshold}`,
3357
- snap.adaptive ? t("memory.status.injectPreview.adaptiveOn") : null,
3358
- snap.scoped ? `scope=${snap.scoped.agent_scope || snap.scoped.workspace_scope}` : null,
3359
- snap.rotated > 0 ? `${t("memory.status.injectPreview.rotated")} ${snap.rotated}` : null
3360
- ].filter(Boolean).join(" · ");
3361
- return h("div", { className: "mneme-statuscard", style: { gridColumn: "1 / -1" } },
3518
+ // 参数改用指标行呈现:maxItems/threshold 这类裸键名换成白话标签,
3519
+ // 查询串这类背景信息放脚注。
3520
+ const rows = [
3521
+ { label: t("memory.status.injectPreview.maxItems"), value: String(snap.maxItems) },
3522
+ { label: t("memory.status.injectPreview.threshold"), value: String(snap.threshold) },
3523
+ snap.adaptive ? { label: t("memory.status.injectPreview.adaptiveOn"), value: "✓" } : null,
3524
+ snap.scoped
3525
+ ? { label: t("memory.status.injectPreview.scope"), value: snap.scoped.agent_scope || snap.scoped.workspace_scope }
3526
+ : null,
3527
+ snap.rotated > 0 ? { label: t("memory.status.injectPreview.rotated"), value: String(snap.rotated) } : null,
3528
+ snap.hotChars > 0
3529
+ ? { label: t("memory.status.injectPreview.hot"), value: `${snap.hotChars}${t("memory.status.injectPreview.charsUnit")}` }
3530
+ : null,
3531
+ { label: t("memory.status.injectPreview.chars"), value: `${snap.totalChars}${t("memory.status.injectPreview.charsUnit")}` }
3532
+ ].filter(Boolean);
3533
+ const foot = snap.query
3534
+ ? t("memory.status.injectPreview.query").replace("{query}", snap.query.slice(0, 24))
3535
+ : null;
3536
+ return h("div", { className: "mneme-statuscard mneme-statuscard--wide" },
3362
3537
  h("h3", { className: "mneme-xcolhead" }, t("memory.status.injectPreview")),
3363
- h("div", { className: "mneme-statuscap" },
3364
- params,
3365
- ` · ${t("memory.status.injectPreview.chars").replace("{chars}", String(snap.totalChars))}`,
3366
- snap.query ? ` · ${t("memory.status.injectPreview.query").replace("{query}", snap.query.slice(0, 24))}` : ""
3367
- ),
3368
- snap.hotChars > 0 && h("div", { className: "mneme-statuscap", style: { marginTop: 4 } },
3369
- `${t("memory.status.injectPreview.hot")} · ${snap.hotChars}${t("memory.status.injectPreview.charsUnit")}`),
3370
- h("div", { style: { marginTop: 6 } },
3538
+ h("div", { className: "mneme-statrows" },
3539
+ rows.map((r, i) => h("div", { className: "mneme-statrow", key: i },
3540
+ h("span", null, r.label),
3541
+ h("span", { className: "mneme-statrowval", title: String(r.value) }, String(r.value))))),
3542
+ foot ? h("div", { className: "mneme-statfoot" }, foot) : null,
3543
+ h("div", { style: { marginTop: 10 } },
3371
3544
  (snap.entries || []).length === 0
3372
3545
  ? h("div", { className: "mneme-statuscap" }, t("memory.status.injectPreview.empty"))
3373
3546
  : (snap.entries || []).map((m) => h("div", { key: m.id, style: { display: "flex", gap: 8, alignItems: "baseline", fontSize: 12, padding: "2px 0" } },
3374
3547
  h("span", { className: "mneme-xcolhead" }, typeLabel(t, m.type)),
3375
3548
  h("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, m.title || "—"),
3376
- h("span", { className: "mneme-xcolhead" }, `★${m.importance ?? "—"} · ${m.chars}${t("memory.status.injectPreview.charsUnit")}`))))
3549
+ h("span", { className: "mneme-xcolhead" },
3550
+ h(StarGlyph, { size: 11 }),
3551
+ ` ${m.importance ?? "—"} · ${m.chars}${t("memory.status.injectPreview.charsUnit")}`))))
3377
3552
  );
3378
3553
  }
3379
3554
 
@@ -3666,18 +3841,34 @@ window.__ModuleLoader__.load({
3666
3841
  return () => { cancelled = true; };
3667
3842
  }, []);
3668
3843
  if (state.off) return null;
3669
- return h(StatusCard, {
3670
- t,
3671
- title: t("memory.status.heatDistribution"),
3672
- loading: state.loading,
3673
- error: false,
3674
- num: `${state.hot}`,
3675
- cap: t("memory.status.heatHint")
3676
- .replace("{hot}", String(state.hot))
3677
- .replace("{warm}", String(state.warm))
3678
- .replace("{cold}", String(state.cold))
3679
- .replace("{sample}", String(state.sample))
3680
- });
3844
+ // 三档占比用堆叠条直接画出来(一眼读出分布),数字退到指标行;大数字
3845
+ // 不再放「热门数」——它曾被误读成总数。
3846
+ const total = state.hot + state.warm + state.cold;
3847
+ const tiers = [
3848
+ ["--hot", state.hot, t("memory.status.heat.hot")],
3849
+ ["--warm", state.warm, t("memory.status.heat.warm")],
3850
+ ["--cold", state.cold, t("memory.status.heat.cold")]
3851
+ ];
3852
+ return h("div", { className: "mneme-statuscard" },
3853
+ h("h3", { className: "mneme-xcolhead" }, t("memory.status.heatDistribution")),
3854
+ state.loading
3855
+ ? h("div", { className: "mneme-statusnum" }, "…")
3856
+ : h(react.Fragment, null,
3857
+ total > 0 && h("div", { className: "mneme-heatbar" },
3858
+ tiers.map(([tier, n]) => h("span", {
3859
+ key: tier,
3860
+ className: `mneme-heatbar-seg mneme-heatbar${tier}`,
3861
+ style: { flexGrow: n }
3862
+ }))),
3863
+ h("div", { className: "mneme-statrows" },
3864
+ tiers.map(([tier, n, label]) => h("div", { className: "mneme-statrow", key: tier },
3865
+ h("span", { style: { display: "flex", alignItems: "center", gap: 6 } },
3866
+ h("span", { className: `mneme-heatdot mneme-heatdot${tier}`, "aria-hidden": "true" }),
3867
+ label),
3868
+ h("span", { className: "mneme-statrowval" }, String(n))))),
3869
+ h("div", { className: "mneme-statfoot" },
3870
+ t("memory.status.heat.sample").replace("{sample}", String(state.sample)))
3871
+ ));
3681
3872
  }
3682
3873
 
3683
3874
  // 记忆复用卡(#217):只读聚合 /recall-stats(Top-N 召回 + 僵尸率 +
@@ -3733,19 +3924,26 @@ window.__ModuleLoader__.load({
3733
3924
  return () => { cancelled = true; };
3734
3925
  }, []);
3735
3926
  if (state.off) return null;
3927
+ // 大数字保留复用率;其余指标拆成一行一条,不再用「·」串成散文。
3928
+ const rows = [
3929
+ {
3930
+ label: t("memory.status.recall.zombie"),
3931
+ value: `${state.zombie}/${state.active}(${t("memory.status.recall.exempt")} ${state.exempt})`
3932
+ },
3933
+ { label: t("memory.status.recall.runs"), value: String(state.runs) },
3934
+ state.top && state.top !== "—" ? { label: t("memory.status.recall.top"), value: state.top } : null,
3935
+ state.inject ? { label: t("memory.status.recall.inject"), value: state.inject } : null,
3936
+ state.archive ? { label: t("memory.status.recall.archive"), value: state.archive } : null
3937
+ ].filter(Boolean);
3736
3938
  return h(StatusCard, {
3737
3939
  t,
3738
3940
  title: t("memory.status.recallStats"),
3739
3941
  loading: state.loading,
3740
3942
  error: false,
3741
3943
  num: state.rate ?? "—",
3742
- cap: t("memory.status.recallHint")
3743
- .replace("{rate}", state.rate ?? "—")
3744
- .replace("{zombie}", String(state.zombie))
3745
- .replace("{active}", String(state.active))
3746
- .replace("{exempt}", String(state.exempt))
3747
- .replace("{runs}", String(state.runs))
3748
- .replace("{top}", state.top || "—") + (state.inject ? " · " + state.inject : "") + (state.archive ? " · " + state.archive : "")
3944
+ rows,
3945
+ // 引擎区五张卡是奇数:复用卡信息量最大,宽容器下跨两列补齐行尾空洞
3946
+ className: "mneme-statuscard--wide"
3749
3947
  });
3750
3948
  }
3751
3949
 
@@ -3759,12 +3957,17 @@ window.__ModuleLoader__.load({
3759
3957
  if (queueRef.current.scrollIntoView) queueRef.current.scrollIntoView({ block: "start", behavior: "smooth" });
3760
3958
  };
3761
3959
  return h("div", { className: "mneme-status" },
3762
- h("div", { className: "mneme-statusgrid" },
3960
+ // 两个分组标题把九张卡拆成「库内一览 / 后台运转」两段扫读单元
3961
+ h("div", { className: "mneme-statushead" }, t("memory.status.sec.overview")),
3962
+ h("div", { className: "mneme-statusgrid mneme-statusgrid--overview" },
3763
3963
  h(MemoriesStatusCard, { t }),
3764
3964
  h(EntitiesStatusCard, { t }),
3765
- h(VectorStatusCard, { t }),
3766
- h(LlmStatusCard, { t }),
3965
+ h(VectorStatusCard, { t })
3966
+ ),
3967
+ h("div", { className: "mneme-statushead" }, t("memory.status.sec.engine")),
3968
+ h("div", { className: "mneme-statusgrid" },
3767
3969
  h(DreamStatusCards, { t, onGotoQueue: gotoQueue }),
3970
+ h(LlmStatusCard, { t }),
3768
3971
  h(HeatStatusCard, { t }),
3769
3972
  h(RecallStatsCard, { t }),
3770
3973
  h(InjectStatusCard, { t }),
@@ -3889,12 +4092,16 @@ window.__ModuleLoader__.load({
3889
4092
  h("div", { className: "mneme-dmeta" },
3890
4093
  h("span", { className: "mneme-dmetakey" }, t("memory.explorer.importance")),
3891
4094
  editing
3892
- ? h("select", {
3893
- className: "mneme-select",
3894
- style: { height: 26, fontSize: 12, justifySelf: "start" },
3895
- value: importance,
3896
- onChange: (e) => setImportance(Number(e.target.value))
3897
- }, [1, 2, 3, 4, 5].map((n) => h("option", { key: n, value: n }, "★".repeat(n))))
4095
+ ? h("div", { className: "mneme-staredit", role: "radiogroup", "aria-label": t("memory.explorer.detail.editImportance") },
4096
+ [1, 2, 3, 4, 5].map((n) => h("button", {
4097
+ key: n,
4098
+ type: "button",
4099
+ role: "radio",
4100
+ "aria-checked": String(importance === n),
4101
+ "aria-label": t("memory.explorer.detail.starLabel").replace("{n}", String(n)),
4102
+ className: importance >= n ? "mneme-starbtn mneme-starbtn--on" : "mneme-starbtn",
4103
+ onClick: () => setImportance(n)
4104
+ }, h(StarGlyph, { size: 15, filled: importance >= n }))))
3898
4105
  : h(ImportanceStars, { className: "mneme-dmetaval", value: memory.importance || 0 }),
3899
4106
  memory.heat != null && h(react.Fragment, null,
3900
4107
  h("span", { className: "mneme-dmetakey" }, t("memory.explorer.heat")),
@@ -4546,7 +4753,7 @@ window.__ModuleLoader__.load({
4546
4753
  key: v,
4547
4754
  className: minImp === v ? "mneme-chip mneme-active" : "mneme-chip",
4548
4755
  onClick: () => setMinImp(v)
4549
- }, v === 0 ? t("memory.tab.all") : `★${v}+`))
4756
+ }, v === 0 ? t("memory.tab.all") : h(react.Fragment, null, h(StarGlyph, { size: 11 }), ` ${v}+`)))
4550
4757
  ),
4551
4758
  h("div", { className: "mneme-xcolhead" }, t("memory.explorer.sourceFilter")),
4552
4759
  h("div", { className: "mneme-xrow" },