@modusensus/dsh-mneme 0.7.32 → 0.8.1
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.md +44 -20
- package/lib/api.js +100 -12
- package/lib/client.js +276 -4
- package/lib/commands.js +30 -3
- package/lib/config.js +21 -0
- package/lib/dream/sleep.js +43 -12
- package/lib/dream.js +43 -2
- package/lib/inject.js +39 -5
- package/lib/lang.js +23 -0
- package/lib/scope.js +118 -0
- package/lib/service.js +374 -62
- package/lib/settings.js +7 -0
- package/lib/store.js +245 -7
- package/lib/tools.js +138 -17
- package/lib/version-check.js +122 -0
- package/package.json +1 -1
- package/src/api.js +100 -12
- package/src/commands.js +30 -3
- package/src/config.js +21 -0
- package/src/dream/sleep.js +43 -12
- package/src/dream.js +43 -2
- package/src/inject.js +39 -5
- package/src/lang.js +23 -0
- package/src/scope.js +118 -0
- package/src/service.js +374 -62
- package/src/settings.js +7 -0
- package/src/store.js +245 -7
- package/src/tools.js +138 -17
- package/src/version-check.js +122 -0
- package/test/api.test.js +90 -4
- package/test/client.test.js +58 -0
- package/test/commands.test.js +40 -3
- package/test/conflict-freeze.test.js +15 -5
- package/test/inject.test.js +93 -0
- package/test/scope-candidates.test.js +239 -0
- package/test/scope-explicit.test.js +452 -0
- package/test/scope-retrieval.test.js +197 -0
- package/test/scope-storage.test.js +255 -0
- package/test/scope-strict.test.js +231 -0
- package/test/service.test.js +76 -0
- package/test/version-check.test.js +152 -0
package/lib/client.js
CHANGED
|
@@ -105,6 +105,31 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
h("span", { className: "mneme-heatpct" }, `${pct}%`));
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* v0.8.0 A4(issue #17):scope 标注徽章——条目的归属来源一行摘要。
|
|
110
|
+
*
|
|
111
|
+
* 仅在条目带任一 scope 标注(agent_scope / workspace_scope / sensitivity)
|
|
112
|
+
* 时渲染;字段缺省返回 null(未标注条目视觉零变化,前端不感知开关状态,
|
|
113
|
+
* 与 HeatBadge 的「数据缺省即不渲染」同一模式)。tooltip 展示完整归属,
|
|
114
|
+
* 标签复用详情抽屉的 i18n 键(云端复验观察点:tooltip 曾硬编码英文)。
|
|
115
|
+
*
|
|
116
|
+
* @param {{m: object, t: (key: string) => string}} props
|
|
117
|
+
* @param {object} props.m 记忆条目(/list、/search 透出的 toApiList 形状,
|
|
118
|
+
* scope 四字段条件存在)
|
|
119
|
+
* @param {(key: string) => string} props.t i18n 取词函数(调用点闭包提供)
|
|
120
|
+
* @returns {object|null} 徽章 vnode;无任何标注时为 null
|
|
121
|
+
*/
|
|
122
|
+
const ScopeBadge = ({ m, t }) => {
|
|
123
|
+
if (!m || (!m.agent_scope && !m.workspace_scope && !m.sensitivity)) return null;
|
|
124
|
+
const tip = [
|
|
125
|
+
m.agent_scope ? `${t("memory.explorer.detail.agentScope")}: ${m.agent_scope}` : null,
|
|
126
|
+
m.workspace_scope ? `${t("memory.explorer.detail.workspaceScope")}: ${m.workspace_scope}` : null,
|
|
127
|
+
m.sensitivity ? `${t("memory.explorer.detail.sensitivity")}: ${m.sensitivity}` : null
|
|
128
|
+
].filter(Boolean).join(" · ");
|
|
129
|
+
return h("span", { className: "mneme-badge mneme-badge--scope", title: tip },
|
|
130
|
+
t("memory.explorer.scopeBadge"));
|
|
131
|
+
};
|
|
132
|
+
|
|
108
133
|
// Unified API fetcher: attaches the optional apiToken (set in the settings
|
|
109
134
|
// view, persisted in localStorage) as a Bearer header. When no token has
|
|
110
135
|
// been configured the header is omitted and the API stays open (default).
|
|
@@ -245,6 +270,8 @@ window.__ModuleLoader__.load({
|
|
|
245
270
|
"memory.settings.feedback.email": "邮件反馈(work@modusensus.space)",
|
|
246
271
|
"memory.settings.feedback.browse": "浏览仓库已知问题",
|
|
247
272
|
"memory.settings.feedback.hint": "反馈前先搜搜是否已有相同问题,能省一份重复的 issue~",
|
|
273
|
+
"memory.settings.version.outdated": "🆕 有新版本 {v}(当前运行 {c})",
|
|
274
|
+
"memory.settings.version.outdatedHint": "若安装时指定过版本号,常规升级不会跨大/小版本(pnpm 钉子)——请重新安装或在升级命令加 --latest;npm 已发布而市场暂未收录属正常(约 1 天延迟)。",
|
|
248
275
|
"memory.settings.mode.title": "运行模式",
|
|
249
276
|
"memory.settings.mode.desc": "轻量模式只保留核心的记忆读写与自动注入(关闭 autoDream 巩固、实体抽取、语义搜索等高级功能),适合只想「记住偏好」的轻量使用;标准模式开启全部功能。",
|
|
250
277
|
"memory.settings.mode.light": "轻量",
|
|
@@ -270,6 +297,7 @@ window.__ModuleLoader__.load({
|
|
|
270
297
|
"memory.features.group.core": "核心",
|
|
271
298
|
"memory.features.group.enhance": "记忆增强",
|
|
272
299
|
"memory.features.group.dream": "巩固与睡眠",
|
|
300
|
+
"memory.features.group.scope": "作用域隔离",
|
|
273
301
|
"memory.features.group.advanced": "高级",
|
|
274
302
|
"memory.features.advancedToggle": "高级(注入策略 · 反思 · 实验项)",
|
|
275
303
|
"memory.features.restartHint": "重启 DSH 后生效",
|
|
@@ -322,6 +350,10 @@ window.__ModuleLoader__.load({
|
|
|
322
350
|
"memory.features.bm25SearchEnabled.hint": "传统关键词打分检索,与向量召回互补",
|
|
323
351
|
"memory.features.conflictFreezeEnabled": "冲突冻结",
|
|
324
352
|
"memory.features.conflictFreezeEnabled.hint": "巩固发现互相矛盾的记忆时先冻结待确认",
|
|
353
|
+
"memory.features.scopeEnabled": "作用域标注",
|
|
354
|
+
"memory.features.scopeEnabled.hint": "按会话身份(agent/工作区)给新记忆打归属标注;重启 DSH 后生效",
|
|
355
|
+
"memory.features.strictScope": "严格隔离",
|
|
356
|
+
"memory.features.strictScope.hint": "检索与注入硬过滤,只对显式声明的归属生效:显式收窄到他者作用域的记忆不可见;载体自动标注只降权保留可见(真正的物理隔离请用 sensitivity)。重启 DSH 后生效",
|
|
325
357
|
"memory.features.trustEpistemicWeighting": "可信度加权",
|
|
326
358
|
"memory.features.trustEpistemicWeighting.hint": "按来源可信度调整召回排序(实验性)",
|
|
327
359
|
"memory.features.reflectionFailureTracking": "反思失败追踪",
|
|
@@ -374,6 +406,17 @@ window.__ModuleLoader__.load({
|
|
|
374
406
|
"memory.explorer.exportFailed": "导出失败",
|
|
375
407
|
"memory.explorer.conflictBadge": "冲突",
|
|
376
408
|
"memory.explorer.archivedBadge": "已归档",
|
|
409
|
+
"memory.explorer.scopeBadge": "作用域",
|
|
410
|
+
"memory.explorer.detail.agentScope": "Agent 作用域",
|
|
411
|
+
"memory.explorer.detail.workspaceScope": "工作区作用域",
|
|
412
|
+
"memory.explorer.detail.scopeGlobal": "全局(所有会话可见)",
|
|
413
|
+
"memory.explorer.detail.scopeSourceAuto": "自动",
|
|
414
|
+
"memory.explorer.detail.scopeSourceExplicit": "显式",
|
|
415
|
+
"memory.explorer.detail.scopeEditHint": "留空 = 全局;也可填写具体标签收窄归属",
|
|
416
|
+
"memory.explorer.detail.scopeWidenConfirm": "确认放宽可见性?",
|
|
417
|
+
"memory.explorer.detail.scopeWidenHint": "放宽后该记忆将对所有会话可见",
|
|
418
|
+
"memory.explorer.detail.sensitivity": "敏感度",
|
|
419
|
+
"memory.explorer.detail.occurred": "发生时间",
|
|
377
420
|
"memory.explorer.detail.edit": "编辑",
|
|
378
421
|
"memory.explorer.detail.save": "保存",
|
|
379
422
|
"memory.explorer.detail.cancel": "取消",
|
|
@@ -394,6 +437,15 @@ window.__ModuleLoader__.load({
|
|
|
394
437
|
"memory.status.dreamNever": "尚未运行",
|
|
395
438
|
"memory.status.conflicts": "待确认冲突",
|
|
396
439
|
"memory.status.conflictsHint": "冻结的矛盾记忆,等待人工确认",
|
|
440
|
+
"memory.status.conflictQueue.reason": "原因",
|
|
441
|
+
"memory.status.conflictQueue.sideA": "A 方",
|
|
442
|
+
"memory.status.conflictQueue.sideB": "B 方",
|
|
443
|
+
"memory.status.conflictQueue.keepA": "保留 A",
|
|
444
|
+
"memory.status.conflictQueue.keepB": "保留 B",
|
|
445
|
+
"memory.status.conflictQueue.markReviewed": "仅标记已处理",
|
|
446
|
+
"memory.status.conflictQueue.applyHint": "确认后:保留方正文追加已否决注记,另一方归档。",
|
|
447
|
+
"memory.status.conflictQueue.missing": "(该记忆已不存在)",
|
|
448
|
+
"memory.status.conflictQueue.refresh": "刷新队列",
|
|
397
449
|
"memory.status.workbench": "工作动态",
|
|
398
450
|
"memory.status.dreamConsolidate": "记忆巩固",
|
|
399
451
|
"memory.status.summarize": "总结提炼",
|
|
@@ -567,6 +619,8 @@ window.__ModuleLoader__.load({
|
|
|
567
619
|
"memory.settings.feedback.email": "Email feedback (work@modusensus.space)",
|
|
568
620
|
"memory.settings.feedback.browse": "Browse known issues",
|
|
569
621
|
"memory.settings.feedback.hint": "Search for an existing issue first — it saves a duplicate.",
|
|
622
|
+
"memory.settings.version.outdated": "🆕 New version {v} available (running {c})",
|
|
623
|
+
"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.",
|
|
570
624
|
"memory.settings.mode.title": "Runtime mode",
|
|
571
625
|
"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.",
|
|
572
626
|
"memory.settings.mode.light": "Light",
|
|
@@ -592,6 +646,7 @@ window.__ModuleLoader__.load({
|
|
|
592
646
|
"memory.features.group.core": "Core",
|
|
593
647
|
"memory.features.group.enhance": "Enhancement",
|
|
594
648
|
"memory.features.group.dream": "Consolidation",
|
|
649
|
+
"memory.features.group.scope": "Scope isolation",
|
|
595
650
|
"memory.features.group.advanced": "Advanced",
|
|
596
651
|
"memory.features.advancedToggle": "Advanced (injection · reflection · experimental)",
|
|
597
652
|
"memory.features.restartHint": "Takes effect after restarting DSH",
|
|
@@ -644,6 +699,10 @@ window.__ModuleLoader__.load({
|
|
|
644
699
|
"memory.features.bm25SearchEnabled.hint": "Classic keyword scoring, complementary to vector recall",
|
|
645
700
|
"memory.features.conflictFreezeEnabled": "Conflict freezing",
|
|
646
701
|
"memory.features.conflictFreezeEnabled.hint": "Freeze contradictory memories for confirmation during consolidation",
|
|
702
|
+
"memory.features.scopeEnabled": "Scope tagging",
|
|
703
|
+
"memory.features.scopeEnabled.hint": "Stamp new memories with the writing session's identity (agent/workspace); takes effect after restarting DSH",
|
|
704
|
+
"memory.features.strictScope": "Strict isolation",
|
|
705
|
+
"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",
|
|
647
706
|
"memory.features.trustEpistemicWeighting": "Credibility weighting",
|
|
648
707
|
"memory.features.trustEpistemicWeighting.hint": "Adjust recall ranking by source credibility (experimental)",
|
|
649
708
|
"memory.features.reflectionFailureTracking": "Reflection failure tracking",
|
|
@@ -696,6 +755,17 @@ window.__ModuleLoader__.load({
|
|
|
696
755
|
"memory.explorer.exportFailed": "Export failed",
|
|
697
756
|
"memory.explorer.conflictBadge": "Conflict",
|
|
698
757
|
"memory.explorer.archivedBadge": "Archived",
|
|
758
|
+
"memory.explorer.scopeBadge": "Scoped",
|
|
759
|
+
"memory.explorer.detail.agentScope": "Agent scope",
|
|
760
|
+
"memory.explorer.detail.workspaceScope": "Workspace scope",
|
|
761
|
+
"memory.explorer.detail.scopeGlobal": "Global (visible to all sessions)",
|
|
762
|
+
"memory.explorer.detail.scopeSourceAuto": "auto",
|
|
763
|
+
"memory.explorer.detail.scopeSourceExplicit": "explicit",
|
|
764
|
+
"memory.explorer.detail.scopeEditHint": "Empty = global; or enter a specific label to narrow the scope",
|
|
765
|
+
"memory.explorer.detail.scopeWidenConfirm": "Widen visibility?",
|
|
766
|
+
"memory.explorer.detail.scopeWidenHint": "Widening makes this memory visible to all sessions",
|
|
767
|
+
"memory.explorer.detail.sensitivity": "Sensitivity",
|
|
768
|
+
"memory.explorer.detail.occurred": "Occurred",
|
|
699
769
|
"memory.explorer.detail.edit": "Edit",
|
|
700
770
|
"memory.explorer.detail.save": "Save",
|
|
701
771
|
"memory.explorer.detail.cancel": "Cancel",
|
|
@@ -716,6 +786,15 @@ window.__ModuleLoader__.load({
|
|
|
716
786
|
"memory.status.dreamNever": "Not yet run",
|
|
717
787
|
"memory.status.conflicts": "Pending conflicts",
|
|
718
788
|
"memory.status.conflictsHint": "Frozen contradictory memories awaiting confirmation",
|
|
789
|
+
"memory.status.conflictQueue.reason": "Reason",
|
|
790
|
+
"memory.status.conflictQueue.sideA": "Side A",
|
|
791
|
+
"memory.status.conflictQueue.sideB": "Side B",
|
|
792
|
+
"memory.status.conflictQueue.keepA": "Keep A",
|
|
793
|
+
"memory.status.conflictQueue.keepB": "Keep B",
|
|
794
|
+
"memory.status.conflictQueue.markReviewed": "Mark reviewed only",
|
|
795
|
+
"memory.status.conflictQueue.applyHint": "On confirm: a veto note is appended to the kept side and the other side is archived.",
|
|
796
|
+
"memory.status.conflictQueue.missing": "(memory no longer exists)",
|
|
797
|
+
"memory.status.conflictQueue.refresh": "Refresh queue",
|
|
719
798
|
"memory.status.workbench": "Activity",
|
|
720
799
|
"memory.status.dreamConsolidate": "Consolidation",
|
|
721
800
|
"memory.status.summarize": "Summarization",
|
|
@@ -1043,6 +1122,20 @@ window.__ModuleLoader__.load({
|
|
|
1043
1122
|
".mneme-badge{flex:none;display:inline-flex;align-items:center;gap:4px;height:18px;padding:0 7px;border-radius:6px;font-size:11px;line-height:14px}",
|
|
1044
1123
|
".mneme-badge--conflict{color:var(--dsw-alias-state-error,#c33);background:color-mix(in srgb,var(--dsw-alias-state-error,#c33) 10%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-error,#c33) 30%,transparent)}",
|
|
1045
1124
|
".mneme-badge--archived{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
1125
|
+
".mneme-badge--scope{color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-business-primary) 30%,transparent)}",
|
|
1126
|
+
// 冲突集中处理队列(v0.8.0):状态页的待确认冲突列表
|
|
1127
|
+
".mneme-conflictq{display:flex;flex-direction:column;gap:10px}",
|
|
1128
|
+
".mneme-conflictq-head{display:flex;align-items:center;gap:8px}",
|
|
1129
|
+
".mneme-conflict-item{border:1px solid var(--dsw-alias-border,var(--dsw-alias-interactive-bg-hover));border-radius:10px;padding:10px;display:flex;flex-direction:column;gap:8px}",
|
|
1130
|
+
".mneme-conflict-reason{font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary)}",
|
|
1131
|
+
".mneme-conflict-pair{display:grid;grid-template-columns:1fr 1fr;gap:8px}",
|
|
1132
|
+
".mneme-conflict-side{border:1px solid var(--dsw-alias-border,var(--dsw-alias-interactive-bg-hover));border-radius:8px;padding:8px;display:flex;flex-direction:column;gap:4px;min-width:0}",
|
|
1133
|
+
".mneme-conflict-sidelabel{font-size:11px;color:var(--dsw-alias-label-tertiary)}",
|
|
1134
|
+
".mneme-conflict-sidetitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
1135
|
+
".mneme-conflict-snippet{font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}",
|
|
1136
|
+
".mneme-conflict-missing{font-size:12px;color:var(--dsw-alias-label-tertiary)}",
|
|
1137
|
+
".mneme-conflict-actions{display:flex;align-items:center;gap:6px;flex-wrap:wrap}",
|
|
1138
|
+
".mneme-conflict-hint{font-size:11px;color:var(--dsw-alias-label-tertiary);margin-right:auto}",
|
|
1046
1139
|
// 详情抽屉:sheet 内右侧滑出,覆盖在浏览区之上
|
|
1047
1140
|
".mneme-xmain{position:relative}",
|
|
1048
1141
|
"@keyframes mneme-slidein{from{opacity:0;transform:translateX(16px)}to{opacity:1;transform:translateX(0)}}",
|
|
@@ -1655,7 +1748,9 @@ window.__ModuleLoader__.load({
|
|
|
1655
1748
|
const FEATURE_GROUPS = [
|
|
1656
1749
|
{ key: "group.core", items: ["autoInject", "autoSummarize", "hotMemoryEnabled", "memoryQualityFilter.enabled", "llmAudit.enabled"] },
|
|
1657
1750
|
{ key: "group.enhance", items: ["entityExtractionEnabled", "codingRetrospect", "rerankEnabled", "searchSemanticDedup", "bm25SearchEnabled"] },
|
|
1658
|
-
{ key: "group.dream", items: ["autoDream", "sleepModeEnabled"] }
|
|
1751
|
+
{ key: "group.dream", items: ["autoDream", "sleepModeEnabled"] },
|
|
1752
|
+
// v0.8.0 A4(issue #17):作用域隔离组——标注总开关 + 严格硬过滤。
|
|
1753
|
+
{ key: "group.scope", items: ["scopeEnabled", "strictScope"] }
|
|
1659
1754
|
];
|
|
1660
1755
|
const FEATURE_ADVANCED_BOOLS = ["hybridInject", "selectiveInjectEnabled", "adaptiveThresholdEnabled", "reflectionUpdateEnabled", "reflectionFailureTracking", "conflictFreezeEnabled", "trustEpistemicWeighting"];
|
|
1661
1756
|
// 字符串键(blur/Enter 提交,空串合法 = 跟随默认):巩固模型与语义
|
|
@@ -1989,6 +2084,9 @@ window.__ModuleLoader__.load({
|
|
|
1989
2084
|
const [apiTokenSaved, setApiTokenSaved] = react.useState(false);
|
|
1990
2085
|
// 反馈入口的插件版本(GET /info)。失败保持 "unknown",链接仍可用。
|
|
1991
2086
|
const [pkgVersion, setPkgVersion] = react.useState("unknown");
|
|
2087
|
+
// 版本自检(#174 后续):运行版本 vs registry latest。仅 outdated 渲染
|
|
2088
|
+
// 横幅,其余状态(up-to-date / ahead / unknown / 查询失败)零渲染零打扰。
|
|
2089
|
+
const [updateInfo, setUpdateInfo] = react.useState(null);
|
|
1992
2090
|
// 运行模式 — light vs standard; null = still loading. The card keeps
|
|
1993
2091
|
// its own busy/saved/error state so it never blocks the others.
|
|
1994
2092
|
const [mode, setMode] = react.useState(null);
|
|
@@ -2034,6 +2132,21 @@ window.__ModuleLoader__.load({
|
|
|
2034
2132
|
return () => { cancelled = true; };
|
|
2035
2133
|
}, []);
|
|
2036
2134
|
|
|
2135
|
+
// 版本自检:只读 /version-check(宿主围栏鉴权)。非 outdated 一律归
|
|
2136
|
+
// null——横幅不出现即是「无需处理」;接口失败也静默,不新增故障面。
|
|
2137
|
+
react.useEffect(() => {
|
|
2138
|
+
let cancelled = false;
|
|
2139
|
+
apiFetch("/api/dsh-mneme/version-check")
|
|
2140
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
2141
|
+
.then((j) => {
|
|
2142
|
+
if (!cancelled && j && j.status === "outdated" && typeof j.latest === "string" && typeof j.version === "string") {
|
|
2143
|
+
setUpdateInfo({ latest: j.latest, current: j.version });
|
|
2144
|
+
}
|
|
2145
|
+
})
|
|
2146
|
+
.catch(() => {});
|
|
2147
|
+
return () => { cancelled = true; };
|
|
2148
|
+
}, []);
|
|
2149
|
+
|
|
2037
2150
|
// 反馈链接预填:环境信息(插件版本 + 平台)+ 问题描述骨架。邮件主题带
|
|
2038
2151
|
// 版本/平台方便归档,正文同款模板;纯链接零后端成本。
|
|
2039
2152
|
const feedbackEnv = `**插件版本**: ${pkgVersion}\n**平台**: ${platformLabel()}\n`;
|
|
@@ -2244,6 +2357,17 @@ window.__ModuleLoader__.load({
|
|
|
2244
2357
|
}
|
|
2245
2358
|
|
|
2246
2359
|
return h("div", null,
|
|
2360
|
+
// 版本自检横幅 — 仅 outdated 时渲染(up-to-date/ahead/unknown/失败
|
|
2361
|
+
// 全部零渲染)。钉子警示:安装时指定过版本号的 profile 会被 pnpm
|
|
2362
|
+
// 挡住常规升级(#174 报障者的实际成因);市场收录新发布约有 1 天延迟。
|
|
2363
|
+
updateInfo && h("section", { className: "mneme-set-sec" },
|
|
2364
|
+
h("div", { className: "mneme-set-title" },
|
|
2365
|
+
t("memory.settings.version.outdated")
|
|
2366
|
+
.replace("{v}", updateInfo.latest)
|
|
2367
|
+
.replace("{c}", updateInfo.current)
|
|
2368
|
+
),
|
|
2369
|
+
h("div", { className: "mneme-featsubhint" }, t("memory.settings.version.outdatedHint"))
|
|
2370
|
+
),
|
|
2247
2371
|
// 用户画像 — who the agent is talking to
|
|
2248
2372
|
h("section", { className: "mneme-set-sec" },
|
|
2249
2373
|
h("div", { className: "mneme-set-title" }, t("memory.settings.profile")),
|
|
@@ -2856,6 +2980,62 @@ window.__ModuleLoader__.load({
|
|
|
2856
2980
|
);
|
|
2857
2981
|
}
|
|
2858
2982
|
|
|
2983
|
+
// --- 冲突集中处理队列(v0.8.0,状态页)---------------------------------
|
|
2984
|
+
// 此前冻结冲突只有计数与散落徽章,resolveConflictPending 无任何调用方——
|
|
2985
|
+
// 这里是第一处理入口:并排展示双方内容 + reason,人工选保留方后走
|
|
2986
|
+
// POST /conflicts/resolve(service 端按 dream 非冻结 conflict 同款处置)。
|
|
2987
|
+
// 队列为空时整块不渲染(不打扰无冲突实例)。
|
|
2988
|
+
function ConflictsQueue({ t }) {
|
|
2989
|
+
const [items, setItems] = useState(null);
|
|
2990
|
+
const [busy, setBusy] = useState(false);
|
|
2991
|
+
const load = () => {
|
|
2992
|
+
apiFetch("/api/dsh-mneme/conflicts")
|
|
2993
|
+
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
2994
|
+
.then((d) => setItems(d.items || []))
|
|
2995
|
+
.catch(() => setItems([]));
|
|
2996
|
+
};
|
|
2997
|
+
useEffect(() => { load(); }, []);
|
|
2998
|
+
if (!items || items.length === 0) return null;
|
|
2999
|
+
const resolve = (id, winner) => {
|
|
3000
|
+
setBusy(true);
|
|
3001
|
+
apiFetch("/api/dsh-mneme/conflicts/resolve", {
|
|
3002
|
+
method: "POST",
|
|
3003
|
+
headers: { "Content-Type": "application/json" },
|
|
3004
|
+
body: JSON.stringify({ id, winner, apply: winner !== null })
|
|
3005
|
+
})
|
|
3006
|
+
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
3007
|
+
.then(() => load())
|
|
3008
|
+
.catch(() => {})
|
|
3009
|
+
.finally(() => setBusy(false));
|
|
3010
|
+
};
|
|
3011
|
+
const side = (s, label) => h("div", { className: "mneme-conflict-side" },
|
|
3012
|
+
h("div", { className: "mneme-conflict-sidelabel" }, label),
|
|
3013
|
+
s.missing
|
|
3014
|
+
? h("div", { className: "mneme-conflict-missing" }, t("memory.status.conflictQueue.missing"))
|
|
3015
|
+
: h(react.Fragment, null,
|
|
3016
|
+
h("div", { className: "mneme-conflict-sidetitle", title: s.title }, s.title || "…"),
|
|
3017
|
+
h("div", { className: "mneme-conflict-snippet" }, (s.content || "").slice(0, 140)),
|
|
3018
|
+
s.archived && h("span", { className: "mneme-badge mneme-badge--archived" }, t("memory.explorer.archivedBadge")))
|
|
3019
|
+
);
|
|
3020
|
+
return h("div", { className: "mneme-conflictq" },
|
|
3021
|
+
h("div", { className: "mneme-conflictq-head" },
|
|
3022
|
+
h("span", { className: "mneme-xcount" }, t("memory.status.conflicts")),
|
|
3023
|
+
h("button", { className: "mneme-footbtn", onClick: () => load() },
|
|
3024
|
+
h(Icon, { name: "refresh", size: 12 }), t("memory.status.conflictQueue.refresh"))),
|
|
3025
|
+
items.map((it) => h("div", { key: it.id, className: "mneme-conflict-item" },
|
|
3026
|
+
it.reason && h("div", { className: "mneme-conflict-reason" },
|
|
3027
|
+
`${t("memory.status.conflictQueue.reason")}: ${it.reason}`),
|
|
3028
|
+
h("div", { className: "mneme-conflict-pair" },
|
|
3029
|
+
side(it.memory_a, t("memory.status.conflictQueue.sideA")),
|
|
3030
|
+
side(it.memory_b, t("memory.status.conflictQueue.sideB"))),
|
|
3031
|
+
h("div", { className: "mneme-conflict-actions" },
|
|
3032
|
+
h("span", { className: "mneme-conflict-hint" }, t("memory.status.conflictQueue.applyHint")),
|
|
3033
|
+
h("button", { className: "mneme-footbtn", disabled: busy, onClick: () => resolve(it.id, "a") }, t("memory.status.conflictQueue.keepA")),
|
|
3034
|
+
h("button", { className: "mneme-footbtn", disabled: busy, onClick: () => resolve(it.id, "b") }, t("memory.status.conflictQueue.keepB")),
|
|
3035
|
+
h("button", { className: "mneme-footbtn", disabled: busy, onClick: () => resolve(it.id, null) }, t("memory.status.conflictQueue.markReviewed"))))
|
|
3036
|
+
));
|
|
3037
|
+
}
|
|
3038
|
+
|
|
2859
3039
|
// --- 状态页工作台:让用户切实看见插件在干活 ---
|
|
2860
3040
|
// 活动流合并 llm_audit 里 autoDream/autoSummarize 的后台调用(状态、
|
|
2861
3041
|
// token、沉淀条数);沉淀记忆列表用 audit 行携带的 related_memory_ids
|
|
@@ -3046,6 +3226,7 @@ window.__ModuleLoader__.load({
|
|
|
3046
3226
|
h(DreamStatusCards, { t }),
|
|
3047
3227
|
h(HeatStatusCard, { t })
|
|
3048
3228
|
),
|
|
3229
|
+
h(ConflictsQueue, { t }),
|
|
3049
3230
|
h(WorkbenchSection, { t, onBrowse })
|
|
3050
3231
|
);
|
|
3051
3232
|
}
|
|
@@ -3055,11 +3236,30 @@ window.__ModuleLoader__.load({
|
|
|
3055
3236
|
// 右侧滑出:全文、来源、质量分、关联实体与手动操作(编辑/归档/删除)。
|
|
3056
3237
|
// 编辑态本地暂存草稿,保存经 POST /update 落库;后端成功后重渲染镜像,
|
|
3057
3238
|
// 前端只做本地视图同步。两步删除确认在此完成(红钮武装 → 实心红提交)。
|
|
3239
|
+
// v0.8.1 底座(issue #170):scope 行的来源徽注(显式/自动)。存量行无来源
|
|
3240
|
+
// 时不加后缀——与 A4 的「未标注零视觉变化」同款克制。
|
|
3241
|
+
const scopeSourceSuffix = (t, source) =>
|
|
3242
|
+
source === "explicit" ? ` · ${t("memory.explorer.detail.scopeSourceExplicit")}`
|
|
3243
|
+
: source === "auto" ? ` · ${t("memory.explorer.detail.scopeSourceAuto")}`
|
|
3244
|
+
: "";
|
|
3245
|
+
const scopeProvenanceTitle = (t, source, decidedAt) =>
|
|
3246
|
+
[
|
|
3247
|
+
source === "explicit" ? t("memory.explorer.detail.scopeSourceExplicit")
|
|
3248
|
+
: source === "auto" ? t("memory.explorer.detail.scopeSourceAuto") : null,
|
|
3249
|
+
decidedAt ?? null
|
|
3250
|
+
].filter(Boolean).join(" · ");
|
|
3251
|
+
|
|
3058
3252
|
function MemoryDrawer({ t, memory, conflict, deleting, deleteError, onClose, onDelete, onSaved }) {
|
|
3059
3253
|
const [editing, setEditing] = useState(false);
|
|
3060
3254
|
const [title, setTitle] = useState(memory.title || "");
|
|
3061
3255
|
const [content, setContent] = useState(memory.content || "");
|
|
3062
3256
|
const [importance, setImportance] = useState(memory.importance || 3);
|
|
3257
|
+
// v0.8.1 底座(issue #170):scope 归属的人工修正入口。空串=放宽到全局
|
|
3258
|
+
// (发送 null),非空=收窄到该标签;只有真的改动才会随 patch 发送并盖
|
|
3259
|
+
// explicit 章——普通编辑不得污染归属来源。
|
|
3260
|
+
const [agentScope, setAgentScope] = useState(memory.agent_scope || "");
|
|
3261
|
+
const [workspaceScope, setWorkspaceScope] = useState(memory.workspace_scope || "");
|
|
3262
|
+
const [confirmWiden, setConfirmWiden] = useState(false);
|
|
3063
3263
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
3064
3264
|
const [busy, setBusy] = useState(false);
|
|
3065
3265
|
const [savedTick, setSavedTick] = useState(false);
|
|
@@ -3098,7 +3298,25 @@ window.__ModuleLoader__.load({
|
|
|
3098
3298
|
const save = () => {
|
|
3099
3299
|
const t2 = title.trim();
|
|
3100
3300
|
if (!t2) return;
|
|
3101
|
-
|
|
3301
|
+
const patch = { title: t2, content, importance };
|
|
3302
|
+
const curAgent = memory.agent_scope ?? "";
|
|
3303
|
+
const curWorkspace = memory.workspace_scope ?? "";
|
|
3304
|
+
const nextAgent = agentScope.trim();
|
|
3305
|
+
const nextWorkspace = workspaceScope.trim();
|
|
3306
|
+
const agentChanged = nextAgent !== curAgent;
|
|
3307
|
+
const workspaceChanged = nextWorkspace !== curWorkspace;
|
|
3308
|
+
if (agentChanged) patch.agent_scope = nextAgent || null;
|
|
3309
|
+
if (workspaceChanged) patch.workspace_scope = nextWorkspace || null;
|
|
3310
|
+
// 放宽可见性(标注→全局)必须显式确认(issue #170 的安全阀):
|
|
3311
|
+
// 第一次点保存只弹确认,再次点「确认放宽」才真正落库。
|
|
3312
|
+
const widening = (curAgent && agentChanged && !nextAgent)
|
|
3313
|
+
|| (curWorkspace && workspaceChanged && !nextWorkspace);
|
|
3314
|
+
if (widening && !confirmWiden) {
|
|
3315
|
+
setConfirmWiden(true);
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
setConfirmWiden(false);
|
|
3319
|
+
postUpdate(patch, {});
|
|
3102
3320
|
};
|
|
3103
3321
|
|
|
3104
3322
|
return h("aside", { className: "mneme-drawer", "aria-label": t("memory.explorer.detail") },
|
|
@@ -3139,6 +3357,46 @@ window.__ModuleLoader__.load({
|
|
|
3139
3357
|
memory.source && h(react.Fragment, null,
|
|
3140
3358
|
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.source")),
|
|
3141
3359
|
h("span", { className: "mneme-dmetaval", title: memory.source }, memory.source)),
|
|
3360
|
+
memory.occurred_at && h(react.Fragment, null,
|
|
3361
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.occurred")),
|
|
3362
|
+
h("span", { className: "mneme-dmetaval", title: memory.occurred_at }, formatDateShort(memory.occurred_at))),
|
|
3363
|
+
// v0.8.1 底座:scope 行双态——查看态展示归属 + 来源徽注(显式声明的
|
|
3364
|
+
// 「全局」也渲染,不再因 NULL 而隐身);编辑态为文本框(留空=全局)。
|
|
3365
|
+
(editing || memory.agent_scope || memory.agent_scope_source === "explicit") && h(react.Fragment, null,
|
|
3366
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.agentScope")),
|
|
3367
|
+
editing
|
|
3368
|
+
? h("input", {
|
|
3369
|
+
type: "text",
|
|
3370
|
+
className: "mneme-select",
|
|
3371
|
+
style: { height: 26, fontSize: 12, justifySelf: "start", width: "100%" },
|
|
3372
|
+
value: agentScope,
|
|
3373
|
+
placeholder: t("memory.explorer.detail.scopeGlobal"),
|
|
3374
|
+
title: t("memory.explorer.detail.scopeEditHint"),
|
|
3375
|
+
onChange: (e) => setAgentScope(e.target.value)
|
|
3376
|
+
})
|
|
3377
|
+
: h("span", {
|
|
3378
|
+
className: "mneme-dmetaval",
|
|
3379
|
+
title: scopeProvenanceTitle(t, memory.agent_scope_source, memory.scope_decided_at) || memory.agent_scope
|
|
3380
|
+
}, (memory.agent_scope ?? t("memory.explorer.detail.scopeGlobal")) + scopeSourceSuffix(t, memory.agent_scope_source))),
|
|
3381
|
+
(editing || memory.workspace_scope || memory.workspace_scope_source === "explicit") && h(react.Fragment, null,
|
|
3382
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.workspaceScope")),
|
|
3383
|
+
editing
|
|
3384
|
+
? h("input", {
|
|
3385
|
+
type: "text",
|
|
3386
|
+
className: "mneme-select",
|
|
3387
|
+
style: { height: 26, fontSize: 12, justifySelf: "start", width: "100%" },
|
|
3388
|
+
value: workspaceScope,
|
|
3389
|
+
placeholder: t("memory.explorer.detail.scopeGlobal"),
|
|
3390
|
+
title: t("memory.explorer.detail.scopeEditHint"),
|
|
3391
|
+
onChange: (e) => setWorkspaceScope(e.target.value)
|
|
3392
|
+
})
|
|
3393
|
+
: h("span", {
|
|
3394
|
+
className: "mneme-dmetaval",
|
|
3395
|
+
title: scopeProvenanceTitle(t, memory.workspace_scope_source, memory.scope_decided_at) || memory.workspace_scope
|
|
3396
|
+
}, (memory.workspace_scope ?? t("memory.explorer.detail.scopeGlobal")) + scopeSourceSuffix(t, memory.workspace_scope_source))),
|
|
3397
|
+
memory.sensitivity && h(react.Fragment, null,
|
|
3398
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.sensitivity")),
|
|
3399
|
+
h("span", { className: "mneme-dmetaval", title: memory.sensitivity }, memory.sensitivity)),
|
|
3142
3400
|
memory.quality_score != null && h(react.Fragment, null,
|
|
3143
3401
|
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.quality")),
|
|
3144
3402
|
h("span", { className: "mneme-dmetaval" }, String(memory.quality_score))),
|
|
@@ -3168,7 +3426,16 @@ window.__ModuleLoader__.load({
|
|
|
3168
3426
|
h("div", { className: "mneme-dactions" },
|
|
3169
3427
|
editing
|
|
3170
3428
|
? h(react.Fragment, null,
|
|
3171
|
-
|
|
3429
|
+
confirmWiden
|
|
3430
|
+
? h(react.Fragment, null,
|
|
3431
|
+
h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-warn,#b8860b)" } },
|
|
3432
|
+
t("memory.explorer.detail.scopeWidenHint")),
|
|
3433
|
+
h("button", { type: "button", className: "mneme-btn mneme-btndangerconfirm", disabled: busy, onClick: save },
|
|
3434
|
+
t("memory.explorer.detail.scopeWidenConfirm")),
|
|
3435
|
+
h("button", { type: "button", className: "mneme-btn", disabled: busy, onClick: () => setConfirmWiden(false) },
|
|
3436
|
+
t("memory.explorer.cancel"))
|
|
3437
|
+
)
|
|
3438
|
+
: h("button", { type: "button", className: "mneme-btn", disabled: busy, onClick: save }, t("memory.explorer.detail.save")),
|
|
3172
3439
|
h("button", {
|
|
3173
3440
|
type: "button",
|
|
3174
3441
|
className: "mneme-btn",
|
|
@@ -3178,6 +3445,9 @@ window.__ModuleLoader__.load({
|
|
|
3178
3445
|
setTitle(memory.title || "");
|
|
3179
3446
|
setContent(memory.content || "");
|
|
3180
3447
|
setImportance(memory.importance || 3);
|
|
3448
|
+
setAgentScope(memory.agent_scope || "");
|
|
3449
|
+
setWorkspaceScope(memory.workspace_scope || "");
|
|
3450
|
+
setConfirmWiden(false);
|
|
3181
3451
|
}
|
|
3182
3452
|
}, t("memory.explorer.detail.cancel")),
|
|
3183
3453
|
savedTick && h("span", { className: "mneme-saved" }, t("memory.explorer.detail.saved")),
|
|
@@ -3800,6 +4070,7 @@ window.__ModuleLoader__.load({
|
|
|
3800
4070
|
h("div", { className: "mneme-cardfoot" },
|
|
3801
4071
|
h(ImportanceStars, { value: m.importance || 0, size: 12 }),
|
|
3802
4072
|
h(HeatBadge, { value: m.heat }),
|
|
4073
|
+
h(ScopeBadge, { m, t }),
|
|
3803
4074
|
m.source && h("span", { className: "mneme-cardsrc", title: m.source }, m.source)
|
|
3804
4075
|
)
|
|
3805
4076
|
)),
|
|
@@ -3861,7 +4132,8 @@ window.__ModuleLoader__.load({
|
|
|
3861
4132
|
},
|
|
3862
4133
|
h("span", { className: "mneme-xdot", style: { color: memoryTypeColor(m.type) }, title: typeLabel(t, m.type), "aria-hidden": "true" }),
|
|
3863
4134
|
h("span", { className: "mneme-xtime" }, time),
|
|
3864
|
-
h("span", { className: "mneme-xname" }, m.title || m.content?.slice(0, 40))
|
|
4135
|
+
h("span", { className: "mneme-xname" }, m.title || m.content?.slice(0, 40)),
|
|
4136
|
+
h(ScopeBadge, { m, t })
|
|
3865
4137
|
);
|
|
3866
4138
|
})
|
|
3867
4139
|
))
|
package/lib/commands.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
1
2
|
import { STR } from "./lang.js";
|
|
2
3
|
|
|
3
4
|
// Custom slash-command manager: keeps the DSH command registry in sync with
|
|
4
5
|
// user-defined commands persisted in SQLite. Commands are registered on boot
|
|
5
6
|
// and (re)registered on add/remove through the API.
|
|
6
7
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
8
|
+
// Per the DSH command contract, a handler's CommandResult text is only a UI
|
|
9
|
+
// settlement notice and never reaches the model. To actually drive the agent,
|
|
10
|
+
// the handler submits the user-authored instruction itself as a user followup
|
|
11
|
+
// message on the receiving agent (same pattern as the official /goal command).
|
|
12
|
+
// The notice just acknowledges the submission; an optional rawInput suffix
|
|
13
|
+
// typed after the command is appended for the model.
|
|
9
14
|
|
|
10
15
|
export function createCommandManager({ ctx, settings, logger, language = "zh" }) {
|
|
11
16
|
const registered = new Map(); // name -> disposer
|
|
@@ -18,7 +23,8 @@ export function createCommandManager({ ctx, settings, logger, language = "zh" })
|
|
|
18
23
|
name: command.name,
|
|
19
24
|
// 缺省描述随实例语言(命令描述进命令注册表,对 LLM 可见)。
|
|
20
25
|
description: command.description || STR.commandFallbackDesc[language](command.name),
|
|
21
|
-
|
|
26
|
+
input: { hint: STR.commandInputHint[language] },
|
|
27
|
+
handler: (invocation) => submit(command, invocation)
|
|
22
28
|
});
|
|
23
29
|
} catch (error) {
|
|
24
30
|
logger?.warn?.(`dsh-mneme: failed to register command /${command.name}: ${String(error)}`);
|
|
@@ -27,6 +33,27 @@ export function createCommandManager({ ctx, settings, logger, language = "zh" })
|
|
|
27
33
|
registered.set(command.name, dispose);
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
/** Submit the instruction (plus optional rawInput suffix) to the receiving agent. */
|
|
37
|
+
function submit(command, invocation) {
|
|
38
|
+
const suffix = typeof invocation?.rawInput === "string" ? invocation.rawInput.trim() : "";
|
|
39
|
+
const text = suffix ? `${command.instruction}\n\n${suffix}` : command.instruction;
|
|
40
|
+
const agent = invocation?.agent;
|
|
41
|
+
if (typeof agent?.followup !== "function") {
|
|
42
|
+
// 兜底:宿主未提供接收 agent 时退回旧行为,至少把指令展示在 notice 里。
|
|
43
|
+
return { kind: "success", text };
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
agent.followup(createUserMessage({
|
|
47
|
+
content: [{ type: "text", text }],
|
|
48
|
+
source: { kind: "user" }
|
|
49
|
+
}));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger?.warn?.(`dsh-mneme: failed to submit /${command.name} to agent: ${String(error)}`);
|
|
52
|
+
return { kind: "error", text: STR.commandSubmitFailed[language](command.name) };
|
|
53
|
+
}
|
|
54
|
+
return { kind: "success", text: STR.commandSubmitted[language](command.name) };
|
|
55
|
+
}
|
|
56
|
+
|
|
30
57
|
function unregisterOne(name) {
|
|
31
58
|
const dispose = registered.get(name);
|
|
32
59
|
if (dispose) {
|
package/lib/config.js
CHANGED
|
@@ -58,6 +58,11 @@ export const Config = z.object({
|
|
|
58
58
|
// 编码记忆注入加权系数:编码任务时对 rejected_solution/pitfall/constraint
|
|
59
59
|
// 记忆的 importance 乘以该系数排序,让编码记忆在编码场景更靠前。
|
|
60
60
|
codingBoostFactor: z.number().min(1).max(5).default(2),
|
|
61
|
+
// 注入边界花括号转义(issue #162,v0.7.4 曾修后被 v0.7.11 重构误删):默认开启,
|
|
62
|
+
// 在 memory / 短期上下文 / 用户设置三处注入出口对连续 2+ 个花括号插 `\`
|
|
63
|
+
// (`{{a}}`→`{\{a\}\}`),让 DSH 的 interpolate() 不再扫到非法变量名而 throw。
|
|
64
|
+
// 关闭时按原样透传。
|
|
65
|
+
escapePromptVariables: z.boolean().default(true),
|
|
61
66
|
autoDream: z.boolean().default(true),
|
|
62
67
|
dreamThresholdCount: z.natural().min(1).max(1000).default(10),
|
|
63
68
|
dreamThresholdChars: z.natural().min(100).max(100000).default(5000),
|
|
@@ -438,6 +443,22 @@ export const Config = z.object({
|
|
|
438
443
|
recallRecordDefault: z.boolean().default(true),
|
|
439
444
|
// recall_runs 滚动清理保留天数。
|
|
440
445
|
recallRetentionDays: z.natural().min(1).max(3650).default(90),
|
|
446
|
+
|
|
447
|
+
// --- scope: v0.8.0 A1 存储层(issue #17)--------------------------------
|
|
448
|
+
// 总开关默认关:关闭时写入不标注 scope、去重维持 (type, title) 现状,行为
|
|
449
|
+
// 逐字节不变。开启后 memory_save 写入 agent_scope(session header 的
|
|
450
|
+
// agentPreset)与 workspace_scope(registry 反查 canonical path,header.cwd
|
|
451
|
+
// 兜底,取不到 NULL——解析绝不阻塞写入),去重键扩展为 (type, title,
|
|
452
|
+
// agent_scope, workspace_scope, sensitivity)。检索侧加权/过滤在 A2/A3 落地。
|
|
453
|
+
// 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
|
|
454
|
+
scopeEnabled: z.boolean().default(false),
|
|
455
|
+
// strictScope(A3):硬过滤模式。关闭=A2 软隔离(他 scope 降权保留可见);
|
|
456
|
+
// 开启后检索/注入/list/get 按 issue #17 四象限可见性公式硬过滤——带他 scope
|
|
457
|
+
// 的记忆完全不可见,未标注(NULL)恒可见;当前会话某维度解析不到时该维度
|
|
458
|
+
// 带标注的记忆一律不可见(fail-closed:身份不明只见全局)。依赖 scopeEnabled
|
|
459
|
+
// 打开才有意义(没有写入标注就无可过滤维度),但独立成键:可先开标注积累
|
|
460
|
+
// 数据、观察 A2 加权质量后再切硬过滤。
|
|
461
|
+
strictScope: z.boolean().default(false),
|
|
441
462
|
});
|
|
442
463
|
|
|
443
464
|
// Fields forced to false by the light-mode preset. Everything not listed here
|
package/lib/dream/sleep.js
CHANGED
|
@@ -4,6 +4,8 @@ import { STR, langOf } from "../lang.js";
|
|
|
4
4
|
// 1. conflict resolution — high-similarity same-type pairs are either parked
|
|
5
5
|
// for review (freeze mode) or adjudicated by the LLM (winner kept / loser
|
|
6
6
|
// archived), reusing the dream conflict machinery. Strictness-graded.
|
|
7
|
+
// v0.8.1 (issue #170): cross-scope pairs are ALWAYS parked for human
|
|
8
|
+
// ownership review with a dedicated reason — never auto-adjudicated.
|
|
7
9
|
// 2. archival demotion — memories unreferenced past sleepArchiveDays shrink
|
|
8
10
|
// to a one-line summary with the full body moved to _full_content; past
|
|
9
11
|
// sleepCompressDays they are archived outright.
|
|
@@ -18,6 +20,7 @@ import { STR, langOf } from "../lang.js";
|
|
|
18
20
|
import { randomUUID, createHash } from "node:crypto";
|
|
19
21
|
import { validateDecisions, applyDecisions, ACTIONS } from "./decisions.js";
|
|
20
22
|
import { findPotentialConflicts, cosineSimilarity } from "./clustering.js";
|
|
23
|
+
import { scopeKeyOf } from "../scope.js";
|
|
21
24
|
import { buildReceipt, describeStreamFailure, resolveDreamEffort, withEffortFallback, maintainIndexAfterDream } from "../dream.js";
|
|
22
25
|
import { computeHeat } from "../heat.js";
|
|
23
26
|
|
|
@@ -169,29 +172,57 @@ async function phaseConflicts(ctx, service, config, logger, runId, semantic = nu
|
|
|
169
172
|
selected.push(p);
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
//
|
|
175
|
+
// v0.8.1(issue #170 第 2 步):跨 scope 相似对分流。同一内容落在两个归属下
|
|
176
|
+
// (任一维标注键不等即算,NULL=未标注=全局也参与比较——「全局副本 + 专属副本
|
|
177
|
+
// 并存」正是收窄/提升裁决的典型候选)不能自动裁决:归档败者可能销毁该内容在
|
|
178
|
+
// 某个归属下的唯一副本,LLM 也无从替用户决定归属。一律停车到冲突待确认队列
|
|
179
|
+
// (专属 reason),同 scope 对维持既有路径(freeze 停车 / LLM 裁决)。
|
|
180
|
+
const isCrossScopePair = (p) =>
|
|
181
|
+
scopeKeyOf(p.a.agent_scope) !== scopeKeyOf(p.b.agent_scope)
|
|
182
|
+
|| scopeKeyOf(p.a.workspace_scope) !== scopeKeyOf(p.b.workspace_scope);
|
|
183
|
+
const crossPairs = selected.filter(isCrossScopePair);
|
|
184
|
+
const samePairs = selected.filter((p) => !isCrossScopePair(p));
|
|
185
|
+
const parkPair = (p, reason) => {
|
|
186
|
+
try {
|
|
187
|
+
// 返回 undefined = 该对已被人工裁决且两侧内容未变(复核项 4)——不算停车。
|
|
188
|
+
return service.saveConflictPending({ run_id: runId, memory_a: p.a.id, memory_b: p.b.id, reason }) != null;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
logger?.warn?.(`dsh-mneme sleep: failed to park conflict ${p.a.id}/${p.b.id}: ${String(error)}`);
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const scopeReason = (p) => STR.scopeCandidateReason[language](p.similarity.toFixed(2));
|
|
195
|
+
const simReason = (p) => STR.similarityReason[language](p.similarity.toFixed(2));
|
|
196
|
+
|
|
197
|
+
// Freeze mode: park pairs for manual review, no LLM required. Cross-scope
|
|
198
|
+
// pairs carry the dedicated scope-candidate reason so the queue reads them
|
|
199
|
+
// as ownership decisions rather than plain duplicates.
|
|
173
200
|
if (config.conflictFreezeEnabled === true) {
|
|
174
201
|
let frozen = 0;
|
|
175
202
|
for (const p of selected) {
|
|
176
|
-
|
|
177
|
-
service.saveConflictPending({ run_id: runId, memory_a: p.a.id, memory_b: p.b.id, reason: STR.similarityReason[language](p.similarity.toFixed(2)) });
|
|
178
|
-
frozen++;
|
|
179
|
-
} catch (error) {
|
|
180
|
-
logger?.warn?.(`dsh-mneme sleep: failed to freeze conflict ${p.a.id}/${p.b.id}: ${String(error)}`);
|
|
181
|
-
}
|
|
203
|
+
if (parkPair(p, isCrossScopePair(p) ? scopeReason(p) : simReason(p))) frozen++;
|
|
182
204
|
}
|
|
183
|
-
return { status: frozen > 0 ? "ok" : "noop", frozen, pairs: selected.length };
|
|
205
|
+
return { status: frozen > 0 ? "ok" : "noop", frozen, scopeCandidates: crossPairs.length, pairs: selected.length };
|
|
184
206
|
}
|
|
185
207
|
|
|
186
|
-
// LLM
|
|
208
|
+
// 非 freeze:跨 scope 对先停车(绝不进 LLM 裁决),同 scope 对照常走 LLM。
|
|
209
|
+
let scopeParked = 0;
|
|
210
|
+
for (const p of crossPairs) {
|
|
211
|
+
if (parkPair(p, scopeReason(p))) scopeParked++;
|
|
212
|
+
}
|
|
213
|
+
if (samePairs.length === 0) {
|
|
214
|
+
return { status: scopeParked > 0 ? "ok" : "noop", frozen: 0, scopeCandidates: scopeParked, pairs: selected.length };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// LLM adjudication(仅同 scope 对)。
|
|
187
218
|
const route = resolveSleepRoute(ctx, config, logger);
|
|
188
|
-
if (!route) return { status: "skipped", reason: "no llm route" };
|
|
219
|
+
if (!route) return { status: "skipped", reason: "no llm route", scopeCandidates: scopeParked, pairs: selected.length };
|
|
189
220
|
const snapshot = new Map();
|
|
190
|
-
for (const p of
|
|
221
|
+
for (const p of samePairs) {
|
|
191
222
|
snapshot.set(p.a.id, p.a);
|
|
192
223
|
snapshot.set(p.b.id, p.b);
|
|
193
224
|
}
|
|
194
|
-
const listText =
|
|
225
|
+
const listText = samePairs.map((p) =>
|
|
195
226
|
STR.candidateConflicts[language](p)
|
|
196
227
|
).join("\n\n");
|
|
197
228
|
const sleepEffort = await resolveDreamEffort(ctx, route, config.sleepReasoningEffort, logger);
|