@modusensus/dsh-mneme 0.9.5 → 0.10.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 +10 -5
- package/docs/CONFIGURATION.md +21 -0
- package/docs/DAEMON.md +6 -2
- package/docs/DISTILL.md +90 -6
- package/docs/SLEEP.md +20 -6
- package/docs/STORAGE.md +1 -0
- package/lib/api-standalone.js +11 -3
- package/lib/api.js +76 -6
- package/lib/client.js +259 -11
- package/lib/config.js +33 -1
- package/lib/continuity.js +11 -3
- package/lib/dream/decisions.js +88 -27
- package/lib/dream/sleep.js +467 -81
- package/lib/dream.js +110 -13
- package/lib/fence.js +24 -0
- package/lib/graph/anchoring.js +7 -2
- package/lib/index.js +15 -6
- package/lib/inject.js +9 -4
- package/lib/lang.js +17 -12
- package/lib/pipeline-nudge-auto.js +3 -0
- package/lib/pipeline-nudge.js +41 -8
- package/lib/pipeline-runner.js +15 -0
- package/lib/pre-inject-gate.js +14 -0
- package/lib/profile-extract.js +4 -1
- package/lib/provenance-http.js +162 -37
- package/lib/service.js +99 -8
- package/lib/session-gate.js +91 -0
- package/lib/settings.js +13 -1
- package/lib/store.js +681 -18
- package/lib/summarize.js +152 -15
- package/lib/tools.js +26 -6
- package/package.json +4 -2
- package/src/api-standalone.js +11 -3
- package/src/api.js +76 -6
- package/src/config.js +33 -1
- package/src/continuity.js +11 -3
- package/src/dream/decisions.js +88 -27
- package/src/dream/sleep.js +467 -81
- package/src/dream.js +110 -13
- package/src/fence.js +24 -0
- package/src/graph/anchoring.js +7 -2
- package/src/index.js +15 -6
- package/src/inject.js +9 -4
- package/src/lang.js +17 -12
- package/src/pipeline-nudge-auto.js +3 -0
- package/src/pipeline-nudge.js +41 -8
- package/src/pipeline-runner.js +15 -0
- package/src/pre-inject-gate.js +14 -0
- package/src/profile-extract.js +4 -1
- package/src/provenance-http.js +162 -37
- package/src/service.js +99 -8
- package/src/session-gate.js +91 -0
- package/src/settings.js +13 -1
- package/src/store.js +681 -18
- package/src/summarize.js +152 -15
- package/src/tools.js +26 -6
- package/test/api.test.js +153 -4
- package/test/client.test.js +380 -0
- package/test/config.test.js +8 -0
- package/test/conflict-reviewed.test.js +79 -0
- package/test/continuity.test.js +29 -1
- package/test/dependency-overrides.test.js +78 -0
- package/test/differentiate-feedback.test.js +248 -0
- package/test/dream-decision-evidence.test.js +314 -0
- package/test/dream-fence.test.js +43 -0
- package/test/graph-boosted-relations.test.js +189 -0
- package/test/graph-multipath.test.js +269 -0
- package/test/inject-pools.test.js +2 -1
- package/test/mint-source.test.js +83 -0
- package/test/pipeline-nudge-auto.test.js +26 -2
- package/test/pipeline-nudge.test.js +87 -0
- package/test/pipeline-runner.test.js +26 -0
- package/test/prompt-stance.test.js +128 -0
- package/test/provenance-http.test.js +200 -17
- package/test/release-docs.test.js +7 -1
- package/test/security-doc.test.js +43 -4
- package/test/session-gate.test.js +264 -0
- package/test/settings.test.js +4 -0
- package/test/sleep-action-set.test.js +16 -11
- package/test/sleep-catchup.test.js +315 -0
- package/test/sleep-failure-backoff.test.js +156 -0
- package/test/sleep.test.js +293 -0
- package/test/standalone-api.test.js +50 -0
- package/test/store.test.js +220 -0
- package/test/summarize.test.js +172 -0
package/lib/client.js
CHANGED
|
@@ -452,6 +452,23 @@ window.__ModuleLoader__.load({
|
|
|
452
452
|
"memory.graph.viewInGraph": "在实体视图中打开",
|
|
453
453
|
"memory.graph.loading": "加载中…",
|
|
454
454
|
"memory.graph.distance": "距中心 {n} 跳",
|
|
455
|
+
// issue #24 块4:被动确认的复核面。边侧卡显示权重/来源,复核区列「抬过的边」。
|
|
456
|
+
"memory.graph.weight": "权重 {w}",
|
|
457
|
+
"memory.graph.source": "来源 {s}",
|
|
458
|
+
"memory.graph.boosted.title": "被用出来的边",
|
|
459
|
+
"memory.graph.boosted.hint": "正常触达会自动抬高边的权重,这里列出被抬过的那些——抬升是唯一会改变检索形状的动作。",
|
|
460
|
+
"memory.graph.boosted.empty": "当前筛选下没有边(只列权重已高于其来源基线的边)",
|
|
461
|
+
"memory.graph.boosted.loadFailed": "这一屏没取到数据(接口不可用),不是「没有边」——稍后重试。",
|
|
462
|
+
"memory.graph.boosted.gateOff": "被动确认当前是关的,今后不会再自动抬权;下面列的是此前已经抬过的边。",
|
|
463
|
+
"memory.graph.boosted.show": "查看被用出来的边",
|
|
464
|
+
"memory.graph.boosted.hide": "收起",
|
|
465
|
+
"memory.graph.boosted.gain": "+{g}",
|
|
466
|
+
// 边的来源(#24 块2 的 RELATION_SOURCE_DEFAULTS):决定权重的起点,
|
|
467
|
+
// 所以复核时必须和权重一起显示,否则「0.5 比 1.0 弱」这种误读无可避免。
|
|
468
|
+
"memory.graph.src.manual": "人工建立",
|
|
469
|
+
"memory.graph.src.confirmed": "人工确认",
|
|
470
|
+
"memory.graph.src.tag": "标签共现",
|
|
471
|
+
"memory.graph.src.llm": "模型抽取",
|
|
455
472
|
"memory.settings.vectorTitle": "向量搜索",
|
|
456
473
|
"memory.settings.vectorHint": "搜索不再只认字面:接入任意 OpenAI 兼容的 embedding 接口后,意思相近的记忆也能被召回。",
|
|
457
474
|
"memory.settings.vectorEnabled": "启用向量搜索",
|
|
@@ -892,6 +909,12 @@ window.__ModuleLoader__.load({
|
|
|
892
909
|
"memory.explorer.detail.quality": "质量分",
|
|
893
910
|
"memory.explorer.detail.entities": "关联实体",
|
|
894
911
|
"memory.explorer.detail.entitiesEmpty": "开启实体抽取后,相关人物/项目会出现在这里",
|
|
912
|
+
"memory.explorer.detail.provenance": "蒸发归因",
|
|
913
|
+
"memory.explorer.detail.provenanceEmpty": "这条记忆没有归因:早于改造的历史行不补、也不猜",
|
|
914
|
+
"memory.explorer.detail.provenanceMint": "铸造自",
|
|
915
|
+
"memory.explorer.detail.provenanceWindow": "蒸馏窗口",
|
|
916
|
+
"memory.explorer.detail.provenanceNoRun": "无运行归因(历史行)",
|
|
917
|
+
"memory.explorer.detail.provenanceRecords": "{n} 条原文",
|
|
895
918
|
"memory.explorer.detail.closeAria": "关闭详情",
|
|
896
919
|
"memory.explorer.detail.editTitle": "标题",
|
|
897
920
|
"memory.explorer.detail.editContent": "内容",
|
|
@@ -899,6 +922,7 @@ window.__ModuleLoader__.load({
|
|
|
899
922
|
"memory.explorer.detail.starLabel": "设为 {n} 星",
|
|
900
923
|
"memory.status.dream": "最近巩固",
|
|
901
924
|
"memory.status.dreamNever": "尚未运行",
|
|
925
|
+
"memory.status.dreamOrganizeOnly": "最近一次后台运行是 agent 主动整理({time}),不计入巩固",
|
|
902
926
|
"memory.status.conflicts": "待确认冲突",
|
|
903
927
|
"memory.status.conflictsHint": "冻结的矛盾记忆,等待人工确认",
|
|
904
928
|
"memory.status.skippedTitle": "本轮跳过 {n} 条决定",
|
|
@@ -1235,6 +1259,23 @@ window.__ModuleLoader__.load({
|
|
|
1235
1259
|
"memory.graph.viewInGraph": "Open in entity explorer",
|
|
1236
1260
|
"memory.graph.loading": "Loading…",
|
|
1237
1261
|
"memory.graph.distance": "{n} hop(s) from root",
|
|
1262
|
+
// issue #24 块4:被动确认的复核面。
|
|
1263
|
+
"memory.graph.weight": "weight {w}",
|
|
1264
|
+
"memory.graph.source": "via {s}",
|
|
1265
|
+
"memory.graph.boosted.title": "Edges put to use",
|
|
1266
|
+
"memory.graph.boosted.hint": "A normal touch raises an edge's weight automatically. These are the ones that rose — raising is the only action that changes what gets recalled.",
|
|
1267
|
+
"memory.graph.boosted.empty": "No edges match this filter (only edges already above their source baseline are listed)",
|
|
1268
|
+
"memory.graph.boosted.loadFailed": "Couldn't load this panel (endpoint unavailable) — that is not the same as \"no edges\". Try again later.",
|
|
1269
|
+
"memory.graph.boosted.gateOff": "Passive confirmation is off, so nothing more will be raised; the edges below were raised earlier.",
|
|
1270
|
+
"memory.graph.boosted.show": "Show edges put to use",
|
|
1271
|
+
"memory.graph.boosted.hide": "Hide",
|
|
1272
|
+
"memory.graph.boosted.gain": "+{g}",
|
|
1273
|
+
// 边的来源(#24 块2 的 RELATION_SOURCE_DEFAULTS):决定权重的起点,
|
|
1274
|
+
// 所以复核时必须和权重一起显示,否则「0.5 比 1.0 弱」这种误读无可避免。
|
|
1275
|
+
"memory.graph.src.manual": "manual",
|
|
1276
|
+
"memory.graph.src.confirmed": "confirmed",
|
|
1277
|
+
"memory.graph.src.tag": "tag overlap",
|
|
1278
|
+
"memory.graph.src.llm": "model-extracted",
|
|
1238
1279
|
"memory.settings.vectorTitle": "Vector Search",
|
|
1239
1280
|
"memory.settings.vectorHint": "Match by meaning, not just wording — connect any OpenAI-compatible embeddings API.",
|
|
1240
1281
|
"memory.settings.vectorEnabled": "Enable vector search",
|
|
@@ -1654,6 +1695,12 @@ window.__ModuleLoader__.load({
|
|
|
1654
1695
|
"memory.explorer.detail.quality": "Quality",
|
|
1655
1696
|
"memory.explorer.detail.entities": "Related entities",
|
|
1656
1697
|
"memory.explorer.detail.entitiesEmpty": "Turn on entity extraction and related people / projects appear here",
|
|
1698
|
+
"memory.explorer.detail.provenance": "Distillation provenance",
|
|
1699
|
+
"memory.explorer.detail.provenanceEmpty": "No attribution for this memory: rows older than the change are left blank, never guessed",
|
|
1700
|
+
"memory.explorer.detail.provenanceMint": "Minted by",
|
|
1701
|
+
"memory.explorer.detail.provenanceWindow": "Distill window",
|
|
1702
|
+
"memory.explorer.detail.provenanceNoRun": "No run attribution (legacy row)",
|
|
1703
|
+
"memory.explorer.detail.provenanceRecords": "{n} records",
|
|
1657
1704
|
"memory.explorer.detail.closeAria": "Close details",
|
|
1658
1705
|
"memory.explorer.detail.editTitle": "Title",
|
|
1659
1706
|
"memory.explorer.detail.editContent": "Content",
|
|
@@ -1661,6 +1708,7 @@ window.__ModuleLoader__.load({
|
|
|
1661
1708
|
"memory.explorer.detail.starLabel": "Set importance to {n} stars",
|
|
1662
1709
|
"memory.status.dream": "Last consolidation",
|
|
1663
1710
|
"memory.status.dreamNever": "Not yet run",
|
|
1711
|
+
"memory.status.dreamOrganizeOnly": "The last background run was an agent-initiated organize ({time}), not a consolidation",
|
|
1664
1712
|
"memory.status.conflicts": "Pending conflicts",
|
|
1665
1713
|
"memory.status.conflictsHint": "Frozen contradictory memories awaiting confirmation",
|
|
1666
1714
|
"memory.status.skippedTitle": "{n} decisions skipped this run",
|
|
@@ -2015,6 +2063,13 @@ window.__ModuleLoader__.load({
|
|
|
2015
2063
|
// --- graph sub-view (fills the content area under the tabs) ---
|
|
2016
2064
|
".mneme-graph{flex:1;min-height:0;width:100%;display:flex;flex-direction:column;padding:12px 16px 16px;box-sizing:border-box}",
|
|
2017
2065
|
".mneme-graphbar{display:flex;gap:8px;align-items:center;flex:none;margin-bottom:10px}",
|
|
2066
|
+
// touch-action 保持 none:图谱的拖拽与缩放只挂了 mousedown/mousemove/wheel,
|
|
2067
|
+
// 全仓 onPointerDown/onTouchStart 命中 0,所以在触屏上它本来就不是一个可交手势的
|
|
2068
|
+
// 面。改成 pan-y 会让纵向拖拽退回页面滚动——而浏览器在 touch-action:none 下会派发
|
|
2069
|
+
// 兼容鼠标事件,触屏纵向拖拽究竟走哪条路,没有真机无法定论;这一版不做无法复验的
|
|
2070
|
+
// 行为变更,留给出指针事件重写的那条车道。高度也不在这里限:480 主断点已压到
|
|
2071
|
+
// min(320px,52vh),把 52vh 写进基础规则会在常见窗口高度(≈680px)下把桌面也压成
|
|
2072
|
+
// 353px,等于顺手改了大屏观感。
|
|
2018
2073
|
".mneme-graphsvg{flex:1;min-height:180px;width:100%;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-button-elevated-fill);cursor:grab;touch-action:none}",
|
|
2019
2074
|
".mneme-gnode{cursor:pointer}",
|
|
2020
2075
|
".mneme-gnode circle{stroke:var(--dsw-alias-bg-layer-2);stroke-width:2;transition:stroke-width .12s}",
|
|
@@ -2058,7 +2113,9 @@ window.__ModuleLoader__.load({
|
|
|
2058
2113
|
".mneme-layer{margin-top:24px}",
|
|
2059
2114
|
".mneme-layerhead{display:flex;align-items:baseline;gap:8px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin-bottom:10px}",
|
|
2060
2115
|
".mneme-layercount{font-size:12px;font-weight:400;color:var(--mneme-fg-dim)}",
|
|
2061
|
-
|
|
2116
|
+
// 属性网格同理:min(200px,100%) 的 100% 兜底压在基础规则上,实体页 detail 在
|
|
2117
|
+
// 481–519px 只剩 257–276px(rail 仍占 224px 横排),裸 200px 下限会溢出。
|
|
2118
|
+
".mneme-attrgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(200px,100%),1fr));gap:8px}",
|
|
2062
2119
|
".mneme-attrcard{min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:8px 12px}",
|
|
2063
2120
|
".mneme-attrkey{font-size:11px;color:var(--mneme-fg-dim);margin-bottom:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
2064
2121
|
".mneme-attrval{font-size:13px;line-height:19px;color:var(--dsw-alias-label-primary);word-break:break-word}",
|
|
@@ -2066,6 +2123,13 @@ window.__ModuleLoader__.load({
|
|
|
2066
2123
|
".mneme-relrow:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
2067
2124
|
".mneme-reltype{flex:none;font-size:11px;line-height:16px;color:var(--mneme-accent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-business-primary) 35%,transparent);border-radius:6px;padding:0 6px}",
|
|
2068
2125
|
".mneme-relname{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
2126
|
+
// issue #24 块4 复核面:抬过的边。用虚线边色(--mneme-graphic-dim 同层)
|
|
2127
|
+
// 而不是新增颜色——它表达的是「同一条边,只是被用过」,不是新类别。
|
|
2128
|
+
".mneme-boosted{margin-top:6px;padding-top:6px;border-top:1px dashed var(--dsw-alias-border-l2)}",
|
|
2129
|
+
".mneme-boostedbody{margin-top:6px}",
|
|
2130
|
+
".mneme-boostedrow{cursor:default;font-size:12px;line-height:18px;padding:5px 10px}",
|
|
2131
|
+
".mneme-boostedrow:hover{background:none}",
|
|
2132
|
+
".mneme-boostedgain{font-size:11px;color:var(--mneme-accent)}",
|
|
2069
2133
|
// --- settings: quiet stacked sections, one concern per section ---
|
|
2070
2134
|
".mneme-set{flex:1;min-height:0;overflow-y:auto;padding:8px 24px 48px;box-sizing:border-box}",
|
|
2071
2135
|
".mneme-set-inner{max-width:720px;margin:0 auto}",
|
|
@@ -2173,8 +2237,12 @@ window.__ModuleLoader__.load({
|
|
|
2173
2237
|
".mneme-seg button{border:none;background:none;color:var(--mneme-fg-dim);cursor:pointer;font-family:inherit;font-size:12px;line-height:16px;padding:5px 10px;display:inline-flex;align-items:center;gap:5px}",
|
|
2174
2238
|
".mneme-seg button:hover{color:var(--dsw-alias-label-primary)}",
|
|
2175
2239
|
".mneme-seg button.mneme-active{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:500}",
|
|
2176
|
-
// 卡片网格:响应式 auto-fill
|
|
2177
|
-
|
|
2240
|
+
// 卡片网格:响应式 auto-fill,卡片悬停轻浮起。
|
|
2241
|
+
// 轨道下限用 min(252px,100%) 而不是裸 252px:容器(.mneme-xcontent)只有
|
|
2242
|
+
// 265–294px 时裸下限仍要求 300px 轨道,网格横向溢出 2–11px 被外层 overflow:hidden
|
|
2243
|
+
// 切掉。这个洞出现在 481–519px——480 主断点之外、两栏又放不下的中间带,所以
|
|
2244
|
+
// 兜底必须落在基础规则上,只在断点里写等于给中间带留缺口。
|
|
2245
|
+
".mneme-cards{flex:1;min-height:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(min(252px,100%),1fr));gap:14px;padding:20px 24px 32px;align-content:start}",
|
|
2178
2246
|
".mneme-card{display:flex;flex-direction:column;gap:7px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:12px 13px;background:var(--dsw-alias-bg-layer-1);cursor:pointer;font-family:inherit;text-align:left;transition:border-color .12s,box-shadow .12s,transform .12s}",
|
|
2179
2247
|
".mneme-card:hover{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 45%,var(--dsw-alias-border-l2));box-shadow:0 6px 20px color-mix(in srgb,var(--dsw-alias-label-primary) 10%,transparent);transform:translateY(-1px)}",
|
|
2180
2248
|
".mneme-card.mneme-active{border-color:var(--dsw-alias-state-business-primary)}",
|
|
@@ -2627,6 +2695,48 @@ window.__ModuleLoader__.load({
|
|
|
2627
2695
|
// 而「为什么禁用」本来就由 title 说明(读不到会话工作目录)。
|
|
2628
2696
|
".mneme-pipe-distillBtn:disabled{cursor:not-allowed;border-style:dashed;color:var(--mneme-fg-dim)}",
|
|
2629
2697
|
".mneme-pipe-distillNote{color:var(--mneme-fg-dim);line-height:17px;min-width:0}",
|
|
2698
|
+
// ===== 移动端适配(mobile-adapt 车道)=====
|
|
2699
|
+
// 为什么主通道是 @container 而不是 @media:面板有两种形态(overlay 弹层与
|
|
2700
|
+
// better-sidebar 工作台 tab),容器宽度与视口宽度并不相等;`.mneme-x` 是
|
|
2701
|
+
// container-type:inline-size 的容器根(1950 行),两种形态下容器查询都成立。
|
|
2702
|
+
// @media 只负责 `.mneme-overlay` 自己的外框——它不在 `.mneme-x` 之内,
|
|
2703
|
+
// 容器查询够不到它,这一条只能走视口。
|
|
2704
|
+
// 纪律:以下几块内一律不写 color/fill/stroke/background。contrast.test.js 的
|
|
2705
|
+
// 规则提取器按「单层花括号」匹配(/^([^{]+)\{([^}]*)\}$/),双花括号的
|
|
2706
|
+
// @container / @media 整串不匹配而被跳过——在这样的块里写颜色等于绕过 #178
|
|
2707
|
+
// 的 AA 主锁,属于静默失效,不是优化。布局属性不受影响。
|
|
2708
|
+
"@media (max-width: 640px){.mneme-overlay{width:calc(100vw - 16px);height:calc(100dvh - 16px);border-radius:12px}}",
|
|
2709
|
+
// 触屏没有 hover:设置页的删除钮 base 是 opacity:0、只靠 :hover/:focus-within
|
|
2710
|
+
// 恢复(2088-2089),手机上等于永久不可见。#457 与 #459 两次回滚都是因为把
|
|
2711
|
+
// hover 配色一起改了,所以这里只加「常显」,不碰颜色。min-height:24px 是
|
|
2712
|
+
// WCAG 2.5.8 AA 的触控目标下限,顺手补到常显按钮上。
|
|
2713
|
+
"@media (hover:none){.mneme-set-del{opacity:1;padding:0 8px;min-height:24px}.mneme-footbtn{min-height:24px}}",
|
|
2714
|
+
// 主断点 480:骨架换向,不是继续收窄数值。临界宽度实算——卡片要 252+48=300、
|
|
2715
|
+
// 左栏 216+1,面板得 ≥517px;实体属性网格要 248、rail 224+1,得 ≥473px。
|
|
2716
|
+
// 目标区间上沿只有 430px,靠收窄永远追不上,只能改骨架。
|
|
2717
|
+
// 左右分栏在窄容器里改上下:左栏/rail 限高可滚,不允许自然撑开把主区挤掉;
|
|
2718
|
+
// 不做折叠按钮(要 JS 状态 + 新 i18n 键,成本高于收益)。
|
|
2719
|
+
// 抽屉改满宽不丢关闭入口:抽屉自带 ✕(.mneme-drawerbar);锚点是
|
|
2720
|
+
// .mneme-xmain 自己的 position:relative(2252),换向后仍然成立。
|
|
2721
|
+
// 实体页换向的前提:rail 侧本就 .mneme-entrailbody{overflow-y:auto} +
|
|
2722
|
+
// .mneme-enttype{sticky}(2046/2047),叠放后天然可滚、零 JS、不加文案键。
|
|
2723
|
+
// 图谱只在 CSS 层止血(高度 + touch-action + 标签字号):它能拖能缩依赖
|
|
2724
|
+
// onMouseDown/onMouseMove/wheel,全仓 onPointerDown/onTouchStart 命中 0,
|
|
2725
|
+
// 那是能力缺失不是样式问题,归后续车道。
|
|
2726
|
+
// 页签的横向滚动也归这一档:4 个带图标的 tab 约 436px,而本档给 .mneme-xbar 留的
|
|
2727
|
+
// 可用宽度是容器宽减 62px(左 10 + 右 52 让位给绝对定位的 ⋯),480px 时只剩 418px
|
|
2728
|
+
// ——整段 361–480px 都会把页签顶到 ⋯ 底下,只写 360 档等于把症状留在主断点全程。
|
|
2729
|
+
// menu / impdialog / sharedialog 三处窄屏宽度必须带 box-sizing:border-box:全文件没有
|
|
2730
|
+
// *{box-sizing} 兜底(实数 0 处),声明的那个数是内容宽,外宽还要再加 padding+border
|
|
2731
|
+
// ——impdialog +30px、menu +12px,320px 视口下比屏幕还宽,会被 overlay 裁掉。
|
|
2732
|
+
// sharedialog 自己不带 padding:它和 impdialog 同挂一条 div(见 JSX 里的
|
|
2733
|
+
// `className:"mneme-impdialog mneme-sharedialog"`),盒模型由 impdialog 带过来,
|
|
2734
|
+
// 所以它同样 +30px;宽度取哪一条由源码顺序决定,故两条都得是 border-box。
|
|
2735
|
+
"@container (max-width: 480px){.mneme-xbar{justify-content:flex-start;padding:0 52px 0 10px}.mneme-vtab{padding:0 10px}.mneme-vtabs{min-width:0;overflow-x:auto;overflow-y:hidden;scrollbar-width:none}.mneme-vtabs::-webkit-scrollbar{height:0}.mneme-xmain{flex-direction:column}.mneme-xside{width:100%;flex:0 1 auto;min-height:0;max-height:min(42vh,300px);border-right:0;border-bottom:1px solid var(--dsw-alias-border-l2);overscroll-behavior:contain}.mneme-xbrowsebar{padding:8px 12px}.mneme-filterbar{padding:8px 12px}.mneme-drawer{width:100%}.mneme-drawerbody{padding:16px 14px 24px}.mneme-cards{padding:14px 12px 24px;overscroll-behavior:contain}.mneme-ent{flex-direction:column}.mneme-entrail{width:100%;flex:0 1 auto;min-height:0;max-height:min(40vh,280px);border-right:0;border-bottom:1px solid var(--dsw-alias-border-l2)}.mneme-entrailbody{overscroll-behavior:contain}.mneme-entdetail{padding:16px 14px 24px}.mneme-set{padding:8px 14px 40px}.mneme-status{padding:16px 14px 32px}.mneme-menu{box-sizing:border-box;min-width:0;width:min(200px,calc(100cqw - 32px))}.mneme-impdialog{box-sizing:border-box;width:min(300px,calc(100cqw - 32px))}.mneme-sharedialog{box-sizing:border-box;width:min(340px,calc(100cqw - 32px))}.mneme-graph{padding:10px 12px 12px}.mneme-graphsvg{max-height:min(320px,52vh);touch-action:pan-y}.mneme-glabel{font-size:14px}.mneme-graphside{position:static;width:auto;max-width:100%;max-height:min(200px,30vh);margin-top:8px}}",
|
|
2736
|
+
// 360 档只做两件事:隐掉 14px 的 tab 图标(.mneme-vtab 带图标时约 109px/个、
|
|
2737
|
+
// 4 个约 436px,320px 容器原地就横向溢出),再把正文 padding 收到 12–14px。
|
|
2738
|
+
// 页签横向滚动不在这一档、属于 480 主断点,理由见 480 档那条算式。
|
|
2739
|
+
"@container (max-width: 360px){.mneme-vtabico{display:none}.mneme-vtab{padding:0 8px;font-size:13px}.mneme-xbar{padding:0 46px 0 8px}.mneme-cards{grid-template-columns:1fr;padding:12px 10px 20px}.mneme-xside{max-height:min(46vh,260px)}.mneme-entrail{max-height:min(44vh,240px)}.mneme-entdetail{padding:14px 12px 22px}.mneme-set{padding:8px 12px 36px}.mneme-status{padding:14px 12px 30px}}",
|
|
2630
2740
|
].join("\n");
|
|
2631
2741
|
if (typeof document !== "undefined" && document.querySelector(`style[data-plugin-css="${CSS_TAG}"]`) === null) {
|
|
2632
2742
|
const tag = document.createElement("style");
|
|
@@ -2720,7 +2830,7 @@ window.__ModuleLoader__.load({
|
|
|
2720
2830
|
// ③ 关联记忆 memories recalled through the entity: prefix search
|
|
2721
2831
|
// ④ 关系图谱 the same ego graph the old graph-only tab drew
|
|
2722
2832
|
function EntityPanel({ t, focusEntity, onJumpMemory }) {
|
|
2723
|
-
const [entities, setEntities] = useState(null);
|
|
2833
|
+
const [entities, setEntities] = useState(null);
|
|
2724
2834
|
const [railQuery, setRailQuery] = useState("");
|
|
2725
2835
|
const [entityName, setEntityName] = useState(focusEntity || "");
|
|
2726
2836
|
const [depth, setDepth] = useState(1);
|
|
@@ -2729,6 +2839,10 @@ window.__ModuleLoader__.load({
|
|
|
2729
2839
|
const [selected, setSelected] = useState(null); // { kind: "node"|"edge", node?|edge? }
|
|
2730
2840
|
const [attrs, setAttrs] = useState([]);
|
|
2731
2841
|
const [related, setRelated] = useState([]);
|
|
2842
|
+
// issue #24 块4:被动确认的复核面。默认收起——它是一个「想查的时候
|
|
2843
|
+
// 才查」的面,不该占实体页第一屏;展开时才去取,省一次请求。
|
|
2844
|
+
const [boostedOpen, setBoostedOpen] = useState(false);
|
|
2845
|
+
const [boosted, setBoosted] = useState(null); // { count, passiveConfirmEnabled, items }
|
|
2732
2846
|
const svgRef = useRef(null);
|
|
2733
2847
|
const posRef = useRef([]); // live simulation positions, not React state
|
|
2734
2848
|
const dragRef = useRef(null); // { id, moved }
|
|
@@ -2794,6 +2908,21 @@ window.__ModuleLoader__.load({
|
|
|
2794
2908
|
return () => { cancelled = true; };
|
|
2795
2909
|
}, [entityName]);
|
|
2796
2910
|
|
|
2911
|
+
// issue #24 块4 复核面:只在展开时取,收起时不发请求。
|
|
2912
|
+
const loadBoosted = useCallback(async () => {
|
|
2913
|
+
try {
|
|
2914
|
+
const res = await apiFetch("/api/dsh-mneme/semantic/graph/boosted-relations?limit=100");
|
|
2915
|
+
setBoosted(res.ok
|
|
2916
|
+
? await res.json()
|
|
2917
|
+
: { count: 0, items: [], passiveConfirmEnabled: false, error: true });
|
|
2918
|
+
} catch {
|
|
2919
|
+
setBoosted({ count: 0, items: [], passiveConfirmEnabled: false, error: true });
|
|
2920
|
+
}
|
|
2921
|
+
}, []);
|
|
2922
|
+
useEffect(() => {
|
|
2923
|
+
if (boostedOpen) loadBoosted();
|
|
2924
|
+
}, [boostedOpen, loadBoosted]);
|
|
2925
|
+
|
|
2797
2926
|
// Simulation: run in rAF against posRef, tick React only every few frames.
|
|
2798
2927
|
// Re-seeds when data changes; dragging writes straight into posRef.
|
|
2799
2928
|
useEffect(() => {
|
|
@@ -3024,6 +3153,17 @@ window.__ModuleLoader__.load({
|
|
|
3024
3153
|
),
|
|
3025
3154
|
h("div", { className: "mneme-gs-meta" },
|
|
3026
3155
|
`${t("memory.graph.relation")} · ${formatRelativeTime(selected.edge.created_at, t)}`),
|
|
3156
|
+
// issue #24 块4:复核要看得见「这条边被抬过」。weight 是当前分,
|
|
3157
|
+
// source 决定它的起点(manual 1.0 / confirmed 1.0 / tag 0.3 / llm 0.4)——
|
|
3158
|
+
// 只看 weight 会把「tag 边被抬到 0.5」误读成「比 manual 边弱」。
|
|
3159
|
+
(selected.edge.weight !== undefined || selected.edge.source !== undefined) && h("div", { className: "mneme-gs-meta" },
|
|
3160
|
+
selected.edge.weight !== undefined
|
|
3161
|
+
? t("memory.graph.weight").replace("{w}", Number(selected.edge.weight).toFixed(2))
|
|
3162
|
+
: null,
|
|
3163
|
+
selected.edge.weight !== undefined && selected.edge.source ? " · " : null,
|
|
3164
|
+
selected.edge.source
|
|
3165
|
+
? t("memory.graph.source").replace("{s}", t(`memory.graph.src.${selected.edge.source}`) || selected.edge.source)
|
|
3166
|
+
: null),
|
|
3027
3167
|
selected.edge.memory_id && h("button", {
|
|
3028
3168
|
className: "mneme-footbtn",
|
|
3029
3169
|
onClick: () => onJumpMemory && onJumpMemory({ id: selected.edge.memory_id })
|
|
@@ -3128,7 +3268,53 @@ window.__ModuleLoader__.load({
|
|
|
3128
3268
|
h("span", { className: "mneme-reltype" }, e.relation_type),
|
|
3129
3269
|
h("span", { className: "mneme-relname", style: { flex: 1 } }, otherName)
|
|
3130
3270
|
);
|
|
3131
|
-
})
|
|
3271
|
+
}),
|
|
3272
|
+
// issue #24 块4「例外管理」的可见面。被动确认是唯一会悄悄
|
|
3273
|
+
// 改变检索形状的动作(正常触达就抬权),所以它必须有个地方
|
|
3274
|
+
// 能查——否则「自动确认」和「没人看过的自动确认」没有区别。
|
|
3275
|
+
// 默认收起:这是审计视角,不是日常视角。
|
|
3276
|
+
h("div", { className: "mneme-boosted" },
|
|
3277
|
+
h("button", {
|
|
3278
|
+
className: boostedOpen ? "mneme-chip mneme-active" : "mneme-chip",
|
|
3279
|
+
"aria-expanded": boostedOpen ? "true" : "false",
|
|
3280
|
+
onClick: () => setBoostedOpen(!boostedOpen),
|
|
3281
|
+
title: t("memory.graph.boosted.hint")
|
|
3282
|
+
}, boostedOpen ? t("memory.graph.boosted.hide") : t("memory.graph.boosted.show")),
|
|
3283
|
+
boostedOpen && h("div", { className: "mneme-boostedbody" },
|
|
3284
|
+
(() => {
|
|
3285
|
+
if (boosted === null) return h("div", { className: "mneme-hint" }, t("memory.graph.loading"));
|
|
3286
|
+
// 加载失败 ≠ 闸关:接口挂了却说「被动确认是关的」,会把人
|
|
3287
|
+
// 引去翻设置,而真正的问题是这一屏没取到数据。
|
|
3288
|
+
if (boosted.error === true) return h("div", { className: "mneme-hint" }, t("memory.graph.boosted.loadFailed"));
|
|
3289
|
+
const items = boosted.items || [];
|
|
3290
|
+
const off = boosted.passiveConfirmEnabled !== true;
|
|
3291
|
+
// 闸关 ≠ 没有数据:已经抬过的边仍然在表里,关掉闸只表示
|
|
3292
|
+
// 「今后不再自动抬」。所以先出闸关提示,再照常列历史——
|
|
3293
|
+
// 否则人永远没法复核既有的提升。
|
|
3294
|
+
// 空表提示与闸门状态无关:闸关且一条都没有时,光说「下面列的是
|
|
3295
|
+
// 此前已经抬过的边」却不给任何行,人会以为这一屏坏了。
|
|
3296
|
+
return h(react.Fragment, null,
|
|
3297
|
+
off && h("div", { className: "mneme-hint" }, t("memory.graph.boosted.gateOff")),
|
|
3298
|
+
items.length === 0
|
|
3299
|
+
? h("div", { className: "mneme-hint" }, t("memory.graph.boosted.empty"))
|
|
3300
|
+
: h(react.Fragment, null,
|
|
3301
|
+
h("div", { className: "mneme-hint" }, t("memory.graph.boosted.hint")),
|
|
3302
|
+
items.map((b) => h("div", {
|
|
3303
|
+
key: b.id,
|
|
3304
|
+
className: "mneme-relrow mneme-boostedrow",
|
|
3305
|
+
title: `${t("memory.graph.source").replace("{s}", t(`memory.graph.src.${b.source}`) || b.source)} · ${Number(b.weight).toFixed(2)}`
|
|
3306
|
+
},
|
|
3307
|
+
h("span", { className: "mneme-relname", style: { textAlign: "right", flex: 1 } }, b.from_name ?? b.from),
|
|
3308
|
+
h("span", { className: "mneme-reltype" }, b.relation_type),
|
|
3309
|
+
h("span", { className: "mneme-relname", style: { flex: 1 } }, `${b.to_name ?? b.to} `,
|
|
3310
|
+
h("span", { className: "mneme-boostedgain" },
|
|
3311
|
+
t("memory.graph.boosted.gain").replace("{g}", Number(b.gain).toFixed(2))))
|
|
3312
|
+
))
|
|
3313
|
+
)
|
|
3314
|
+
);
|
|
3315
|
+
})()
|
|
3316
|
+
)
|
|
3317
|
+
)
|
|
3132
3318
|
)
|
|
3133
3319
|
),
|
|
3134
3320
|
// ② 属性 — current snapshot (valid_until IS NULL);空层整层隐藏
|
|
@@ -5501,11 +5687,16 @@ window.__ModuleLoader__.load({
|
|
|
5501
5687
|
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
5502
5688
|
.then((d) => {
|
|
5503
5689
|
if (cancelled) return;
|
|
5690
|
+
// #495:这块 KPI 说的是「上一轮巩固跳过了什么」,所以要的是**巩固轮**。
|
|
5691
|
+
// organize 回执同样带 skipped,但它的跳过不是巩固决策(例如「与既有
|
|
5692
|
+
// 记忆重复」),混进来会让人以为巩固那一整类动作被拦。后端 lastConsolidation
|
|
5693
|
+
// 只含 auto/sleep;旧服务端没有该键时退回 lastRun,保持原行为。
|
|
5694
|
+
const last = (d && (d.lastConsolidation || d.lastRun)) || null;
|
|
5504
5695
|
setConf({
|
|
5505
5696
|
loading: false,
|
|
5506
5697
|
error: false,
|
|
5507
5698
|
pending: Number((d && d.pendingConflicts) ?? 0),
|
|
5508
|
-
skipped: Array.isArray(
|
|
5699
|
+
skipped: Array.isArray(last && last.skippedReasons) ? last.skippedReasons : []
|
|
5509
5700
|
});
|
|
5510
5701
|
})
|
|
5511
5702
|
.catch(() => { if (!cancelled) setConf({ loading: false, error: true, pending: 0, skipped: [] }); });
|
|
@@ -5756,7 +5947,7 @@ window.__ModuleLoader__.load({
|
|
|
5756
5947
|
// showConflicts=false 用于 2026-10 改造后的降级折叠区:待确认冲突已升入
|
|
5757
5948
|
// KPI 带,这里不再重复渲染冲突卡,只留最近运行卡。
|
|
5758
5949
|
function DreamStatusCards({ t, onGotoQueue, showConflicts = true }) {
|
|
5759
|
-
const [state, setState] = useState({ loading: true, error: false, lastRun: null, pending: 0 });
|
|
5950
|
+
const [state, setState] = useState({ loading: true, error: false, lastRun: null, lastConsolidation: null, pending: 0 });
|
|
5760
5951
|
useEffect(() => {
|
|
5761
5952
|
let cancelled = false;
|
|
5762
5953
|
const loadOnce = () => {
|
|
@@ -5768,10 +5959,11 @@ window.__ModuleLoader__.load({
|
|
|
5768
5959
|
loading: false,
|
|
5769
5960
|
error: false,
|
|
5770
5961
|
lastRun: (d && d.lastRun) || null,
|
|
5962
|
+
lastConsolidation: (d && d.lastConsolidation) || null,
|
|
5771
5963
|
pending: Number((d && d.pendingConflicts) ?? 0)
|
|
5772
5964
|
});
|
|
5773
5965
|
})
|
|
5774
|
-
.catch(() => { if (!cancelled) setState({ loading: false, error: true, lastRun: null, pending: 0 }); });
|
|
5966
|
+
.catch(() => { if (!cancelled) setState({ loading: false, error: true, lastRun: null, lastConsolidation: null, pending: 0 }); });
|
|
5775
5967
|
};
|
|
5776
5968
|
loadOnce();
|
|
5777
5969
|
// #295 评审:裁决完成后队列会广播 mneme:conflicts-changed,这里重取
|
|
@@ -5779,11 +5971,26 @@ window.__ModuleLoader__.load({
|
|
|
5779
5971
|
window.addEventListener("mneme:conflicts-changed", loadOnce);
|
|
5780
5972
|
return () => { cancelled = true; window.removeEventListener("mneme:conflicts-changed", loadOnce); };
|
|
5781
5973
|
}, []);
|
|
5782
|
-
|
|
5974
|
+
// #495:这张卡的标题是「最近巩固」,取数就该是巩固轮,而不是 runs[0]。
|
|
5975
|
+
// organize 与巩固共用 dream_runs,一次 agent 主动整理会写两行(dryRun 报告 +
|
|
5976
|
+
// apply 回执),把它顶到首位后这张卡就变成「整理回执」了。
|
|
5977
|
+
// 卡面数字只认巩固轮:lastConsolidation 由后端排除 organize 后取一条。旧服务端
|
|
5978
|
+
// 没有这个键时退回 lastRun,但退回的那条若是 organize 就仍然不算「最近巩固」。
|
|
5979
|
+
const organizedOnly = state.lastRun && state.lastRun.run_type === "organize" ? state.lastRun : null;
|
|
5980
|
+
const run = state.lastConsolidation
|
|
5981
|
+
|| (state.lastRun && !organizedOnly ? state.lastRun : null);
|
|
5783
5982
|
const num = run ? formatRelativeTime(run.created_at, t) : t("memory.status.dreamNever");
|
|
5983
|
+
// issue 给的两条路里选「标注」:最近一次后台运行是 organize 时,用脚注点名它,
|
|
5984
|
+
// 而不是把它从审计面抹掉。它没有挤掉巩固数(卡面仍是巩固轮),只是补一句说明
|
|
5985
|
+
// ——否则读者会以为整理就是那次巩固。
|
|
5986
|
+
const organizeFoot = organizedOnly && !run
|
|
5987
|
+
? t("memory.status.dreamOrganizeOnly").replace("{time}", formatRelativeTime(organizedOnly.created_at, t))
|
|
5988
|
+
: null;
|
|
5784
5989
|
const cap = run
|
|
5785
5990
|
? `${run.status || "—"}${run.model ? ` · ${run.model}` : ""}`
|
|
5786
|
-
|
|
5991
|
+
// 只有 organize 跑过时不能沿用 offList 那句「已关闭:巩固…」——autoDream 可能
|
|
5992
|
+
// 开着,只是还没轮到。这时只留脚注说明最近那一条是什么。
|
|
5993
|
+
: organizeFoot ? null : t("memory.settings.mode.offList");
|
|
5787
5994
|
// #178:pending>0 时冲突卡可激活(role=button + 可聚焦 + 回车/空格),
|
|
5788
5995
|
// 激活直达队列——StatusPanel 把 onGotoQueue 传进来(一步 prop,无线程)。
|
|
5789
5996
|
const pending = state.pending > 0;
|
|
@@ -5791,7 +5998,7 @@ window.__ModuleLoader__.load({
|
|
|
5791
5998
|
// #521 的跳过明细不在这里渲染:它已提到 TokenFlowSection 的 KPI 带下方,
|
|
5792
5999
|
// 首屏可见。这张卡在折叠区里,展开后再重复一遍只会让人以为有两批跳过。
|
|
5793
6000
|
return h(react.Fragment, null,
|
|
5794
|
-
h(StatusCard, { t, title: t("memory.status.dream"), loading: state.loading, error: state.error, num, cap }),
|
|
6001
|
+
h(StatusCard, { t, title: t("memory.status.dream"), loading: state.loading, error: state.error, num, cap, foot: organizeFoot }),
|
|
5795
6002
|
showConflicts
|
|
5796
6003
|
? h("div", {
|
|
5797
6004
|
role: pending ? "button" : undefined,
|
|
@@ -6405,6 +6612,13 @@ window.__ModuleLoader__.load({
|
|
|
6405
6612
|
const [saveError, setSaveError] = useState(false);
|
|
6406
6613
|
const [copied, setCopied] = useState(false);
|
|
6407
6614
|
const [entities, setEntities] = useState(null);
|
|
6615
|
+
// #512:这条记忆的归因——窗口级(哪一段原文蒸出来的)与行级(哪一轮铸造的)。
|
|
6616
|
+
// null = 还没读到(或读失败),两者要分得开,不能被当成「没有归因」。
|
|
6617
|
+
// 这个 state 必须住在**读它的那个组件**里:加归因区块时它曾被误放进
|
|
6618
|
+
// EntityPanel(同文件的上一个组件),于是 MemoryDrawer 的 effect 与渲染块
|
|
6619
|
+
// 引用到一个不存在的绑定,一开抽屉就 ReferenceError 把整个 renderer 带走
|
|
6620
|
+
// (issue #553)。同名 state 落在隔壁组件里,静态扫描与 review 都不容易看出来。
|
|
6621
|
+
const [provenance, setProvenance] = useState(null); // null = still loading
|
|
6408
6622
|
// 整包退出的两段:先干跑清点(点按钮那一发),再确认物删。撤是**物理删除**不是归档
|
|
6409
6623
|
// (归档行仍占 share_external_id 的 UNIQUE 位,撤了会导不回来,理由在
|
|
6410
6624
|
// src/share-import.js 的 deleteSharePack),所以这里比删除那条路多一站清点。
|
|
@@ -6423,6 +6637,21 @@ window.__ModuleLoader__.load({
|
|
|
6423
6637
|
return () => { cancelled = true; };
|
|
6424
6638
|
}, [memory.id]);
|
|
6425
6639
|
|
|
6640
|
+
// #512:归因单独取一次。读失败/未开启时保持 null——「没读到」不等于
|
|
6641
|
+
// 「没有归因」,界面上不能把这两件事显示成同一句话。
|
|
6642
|
+
useEffect(() => {
|
|
6643
|
+
let cancelled = false;
|
|
6644
|
+
setProvenance(null);
|
|
6645
|
+
apiFetch("/api/dsh-mneme/memory/provenance?id=" + encodeURIComponent(memory.id))
|
|
6646
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
6647
|
+
.then((d) => {
|
|
6648
|
+
if (cancelled) return;
|
|
6649
|
+
if (d && Array.isArray(d.windows) && Array.isArray(d.mints)) setProvenance(d);
|
|
6650
|
+
})
|
|
6651
|
+
.catch(() => { if (!cancelled) setProvenance(null); });
|
|
6652
|
+
return () => { cancelled = true; };
|
|
6653
|
+
}, [memory.id]);
|
|
6654
|
+
|
|
6426
6655
|
const postUpdate = (patch, opts) => {
|
|
6427
6656
|
setBusy(true);
|
|
6428
6657
|
setSaveError(false);
|
|
@@ -6603,6 +6832,25 @@ window.__ModuleLoader__.load({
|
|
|
6603
6832
|
h("span", { className: "mneme-xdot", style: { color: typeColor(e.type) }, "aria-hidden": "true" }),
|
|
6604
6833
|
e.name
|
|
6605
6834
|
)))),
|
|
6835
|
+
provenance !== null && h(react.Fragment, null,
|
|
6836
|
+
h("div", { className: "mneme-xcolhead", style: { padding: "16px 0 0" } }, t("memory.explorer.detail.provenance")),
|
|
6837
|
+
(provenance.windows.length === 0 && provenance.mints.length === 0)
|
|
6838
|
+
? h("div", { className: "mneme-featsubhint", style: { marginTop: 4 } }, t("memory.explorer.detail.provenanceEmpty"))
|
|
6839
|
+
: h("div", { className: "mneme-dmeta" },
|
|
6840
|
+
provenance.mints.map((m, i) => h(react.Fragment, { key: "mint-" + m.run_id + "-" + i },
|
|
6841
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.provenanceMint")),
|
|
6842
|
+
h("span", { className: "mneme-dmetaval", title: m.run_id },
|
|
6843
|
+
[m.run_type, m.trigger_source, formatDateShort(m.created_at)].filter(Boolean).join(" · ")))),
|
|
6844
|
+
provenance.windows.map((w, i) => h(react.Fragment, { key: "win-" + w.session_id + "-" + w.seq_start + "-" + i },
|
|
6845
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.provenanceWindow")),
|
|
6846
|
+
h("span", { className: "mneme-dmetaval", title: w.session_id },
|
|
6847
|
+
[w.session_id + " · seq " + w.seq_start + "–" + w.seq_end,
|
|
6848
|
+
w.distill_run_id
|
|
6849
|
+
? [w.run_type, w.run_status].filter(Boolean).join(" / ")
|
|
6850
|
+
: t("memory.explorer.detail.provenanceNoRun"),
|
|
6851
|
+
t("memory.explorer.detail.provenanceRecords").replace("{n}", String(w.records ? w.records.length : 0))
|
|
6852
|
+
].join(" · ")))))
|
|
6853
|
+
),
|
|
6606
6854
|
editing && h("div", { className: "mneme-xcolhead", style: { padding: "16px 0 4px" } }, t("memory.explorer.detail.editContent")),
|
|
6607
6855
|
editing
|
|
6608
6856
|
? h("textarea", { className: "mneme-dcontent-input", value: content, onChange: (e) => setContent(e.target.value) })
|
package/lib/config.js
CHANGED
|
@@ -586,6 +586,16 @@ export const Config = z.object({
|
|
|
586
586
|
// Minimum gap between two sleep runs (hours) — a second idle window within
|
|
587
587
|
// this interval does not retrigger.
|
|
588
588
|
sleepMinIntervalHours: z.natural().min(1).max(168).default(8),
|
|
589
|
+
// Issue #529(C 项的替代实现):sleep 连续失败退避(默认关 = 行为逐字节不变)。
|
|
590
|
+
// 基数是 sleepMinIntervalHours,有效间隔 = 基数 × 2^连续失败数(成功清零),
|
|
591
|
+
// 封顶 max(基数, 24 小时)——不是硬 24 小时:这个键允许配到 168,用户配 48 就是
|
|
592
|
+
// 明说「两天跑一次」,退避只能放宽间隔、不该把用户自己的基数压小。
|
|
593
|
+
// 为什么不按 issue 原案用 snapshot_hash 判据:sleep 的
|
|
594
|
+
// snapshot_hash 是**跑完之后**对 phases 结果算的哈希(dream/sleep.js 的
|
|
595
|
+
// createHash(JSON.stringify(phases))),不是输入快照;同一批输入跑两次结果不同
|
|
596
|
+
// 就会得到不同哈希,用它当「同一输入又在失败」的判据不成立。退避与 #292 的
|
|
597
|
+
// autoDreamFailureBackoff 同构:只放大既有的最小间隔闸,不新增状态面。
|
|
598
|
+
sleepFailureBackoff: z.boolean().default(false),
|
|
589
599
|
// Conflict adjudication strictness:
|
|
590
600
|
// gentle only high-confidence conflicts (threshold 0.92) are resolved
|
|
591
601
|
// normal standard dream-level (threshold 0.85)
|
|
@@ -653,6 +663,22 @@ export const Config = z.object({
|
|
|
653
663
|
// 增长);流式计费按实际用量,不按上限。
|
|
654
664
|
sleepMaxTokens: z.natural().min(256).max(131072).default(8192).description("sleep 冲突消解与模式发现阶段的 LLM 输出预算上限(token)。原为硬编码 2048,sleepActionSet=full 实测需约 7000 导致裁决被截断而整轮失败;流式计费按实际用量,调大不增加成本。"),
|
|
655
665
|
|
|
666
|
+
// Issue #529:冲突阶段此前没有任何输入侧预算——同 scope 的全部相似对被一次性
|
|
667
|
+
// 拼进一个 prompt,实测单请求 163,054 输入 token(服务端 --ctx-size 163,840,
|
|
668
|
+
// 占 99.5%),449 对的池子必然撞墙;而失败的轮次不落 pairs、判 keep 又是
|
|
669
|
+
// no-op,下一轮冷却结束再送**完全相同的输入**,于是 23 轮连续 llm failed。
|
|
670
|
+
// 输出侧早有 sleepMaxTokens(#257),输入侧这一半一直是空的。
|
|
671
|
+
//
|
|
672
|
+
// 每批上限:0 = 不设上限(严格回退到旧的一次性全送,用于排查"是不是分批
|
|
673
|
+
// 引入的差异")。取 12 是因为实测单对约 112~168 输入 token、12 对落在
|
|
674
|
+
// 1.5K~2K 区间,远低于任何服务商的上下文窗口,也不至于让 LLM 在长清单里
|
|
675
|
+
// 走神。
|
|
676
|
+
sleepConflictMaxPairs: z.natural().min(0).max(500).default(12).description("sleep 冲突阶段每批送给 LLM 的候选对上限(0 = 不设上限,回退旧的一次性全送)。实测单对约 112~168 输入 token,默认 12 对约 1.5K~2K。"),
|
|
677
|
+
// 每轮总上限:分批之后仍要有个总闸,否则一个 449 对的池子会拆成 38 批、
|
|
678
|
+
// 每批一次 LLM 往返,单轮跑上几小时(本身也没比撞墙好)。0 = 不设上限。
|
|
679
|
+
// 未入选的对不是"被丢弃"——它们留在 conflict_pending 与下一轮的候选池里。
|
|
680
|
+
sleepConflictMaxPairsPerRun: z.natural().min(0).max(5000).default(60).description("sleep 冲突阶段单轮累计裁定的候选对上限(0 = 不设上限)。默认 60 对 ≈ 5 批,约 10K 输入 token。"),
|
|
681
|
+
|
|
656
682
|
// --- epistemic trust: memory source credibility (v0.4.5) -----------------
|
|
657
683
|
// Distinguish memories by source: observation (measured / witnessed),
|
|
658
684
|
// subjective (opinion / guess) and inferred (derived from other evidence).
|
|
@@ -966,7 +992,13 @@ export const Config = z.object({
|
|
|
966
992
|
// 显式保存,画像正文必须是用户手写的原文,不给模型改写权。
|
|
967
993
|
// 进 LIGHT_MODE_OFF:一次调用要读上百行记忆 + 一轮模型生成,与低资源档互斥(同
|
|
968
994
|
// preInjectGate.enabled 的定性);是预设不是强制,用户显式勾选仍然赢。
|
|
969
|
-
profileExtractEnabled: z.boolean().default(false)
|
|
995
|
+
profileExtractEnabled: z.boolean().default(false),
|
|
996
|
+
|
|
997
|
+
// #539 逐会话无痕:总开关默认关,保持旧用户行为;子代理选择器默认开,但总开关
|
|
998
|
+
// 未开时不生效。手动名单是精确的 session id 匹配。
|
|
999
|
+
sessionGateEnabled: z.boolean().default(false),
|
|
1000
|
+
sessionGateSubagents: z.boolean().default(true),
|
|
1001
|
+
sessionGateSessions: z.array(z.string()).default([])
|
|
970
1002
|
});
|
|
971
1003
|
|
|
972
1004
|
// Fields forced to false by the light-mode preset. Everything not listed here
|
package/lib/continuity.js
CHANGED
|
@@ -173,10 +173,10 @@ function noticeAlreadyInSurface(session, text) {
|
|
|
173
173
|
* 挂两条监听(#249 §7 的 `agent/pre-step` 触发位 + 压缩事件面),返回 { dispose }。
|
|
174
174
|
*
|
|
175
175
|
* `store` 直接注入:本模块只写提案表、不碰检索/嵌入,没有用到 service 的任何能力。
|
|
176
|
-
*
|
|
177
|
-
*
|
|
176
|
+
* 调用方负责父/子开关(`autoInject` + `continuityRescueEnabled`),本函数另检查逐会话
|
|
177
|
+
* 无痕闸门;保持"挂上就是开、命中会话就不处理"的单一口径。
|
|
178
178
|
*/
|
|
179
|
-
export function createContinuityRescue(ctx, store) {
|
|
179
|
+
export function createContinuityRescue(ctx, store, sessionGate = null) {
|
|
180
180
|
// 待处理的边缘:sessionId → 压缩事件序号。**真落地**的压缩事件到达时置位,下一次 pre-step 消费后
|
|
181
181
|
// 删除(同一个边缘绝不注入第二次)。只留序号、不留 Session:插件在宿主进程里只 apply
|
|
182
182
|
// 一次、服务所有会话,握着 Session 就是握着它的全量日志不放——压缩完就收工、不再有
|
|
@@ -184,6 +184,10 @@ export function createContinuityRescue(ctx, store) {
|
|
|
184
184
|
const edge = new Map();
|
|
185
185
|
|
|
186
186
|
const unsubscribe = ctx.on("session/event", (session, event) => {
|
|
187
|
+
if (sessionGate?.isOff(session)) {
|
|
188
|
+
if (session?.id) edge.delete(session.id);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
187
191
|
if (!isCompactionEdge(event)) return;
|
|
188
192
|
if (!session?.id) return;
|
|
189
193
|
if (!edge.has(session.id) && edge.size >= MAX_TRACKED_EDGES) {
|
|
@@ -200,6 +204,10 @@ export function createContinuityRescue(ctx, store) {
|
|
|
200
204
|
// reject 是"这一步不该发生",往里面塞消息没有意义;未知形状原样放行。
|
|
201
205
|
if (decision?.kind !== "enter") return decision;
|
|
202
206
|
const session = payload?.agent?.session;
|
|
207
|
+
if (sessionGate?.isOff(session)) {
|
|
208
|
+
if (session?.id) edge.delete(session.id);
|
|
209
|
+
return decision;
|
|
210
|
+
}
|
|
203
211
|
const sessionId = session?.id;
|
|
204
212
|
if (!sessionId) return decision;
|
|
205
213
|
if (!edge.has(sessionId)) return decision;
|