@modusensus/dsh-mneme 0.12.0 → 0.14.0-beta.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.
Files changed (182) hide show
  1. package/README.md +51 -20
  2. package/bin/dsh-mneme-choro.mjs +9 -1
  3. package/docs/CONFIGURATION.md +47 -6
  4. package/docs/DIRECTORIES.md +114 -0
  5. package/docs/DISTILL.md +64 -0
  6. package/docs/DSH-GO.md +97 -0
  7. package/docs/ENTITIES.md +5 -3
  8. package/docs/FORGET.md +77 -0
  9. package/docs/KB.md +110 -0
  10. package/docs/SLEEP.md +28 -3
  11. package/docs/STORAGE.md +23 -0
  12. package/lib/api-standalone.js +4 -1
  13. package/lib/api.js +788 -30
  14. package/lib/attachment-text.js +122 -0
  15. package/lib/choro-command.js +9 -2
  16. package/lib/client.js +4627 -1409
  17. package/lib/config.js +166 -1
  18. package/lib/continuity.js +7 -6
  19. package/lib/diary/auto.js +190 -0
  20. package/lib/diary/compose.js +440 -0
  21. package/lib/diary/dir.js +59 -0
  22. package/lib/diary/export.js +97 -0
  23. package/lib/diary/index.js +26 -0
  24. package/lib/diary/library.js +189 -0
  25. package/lib/diary/qq-inbox.js +142 -0
  26. package/lib/diary/rollover.js +61 -0
  27. package/lib/diary/session-provider.js +122 -0
  28. package/lib/dream/decisions.js +95 -6
  29. package/lib/dream/sleep.js +760 -19
  30. package/lib/dream/triage.js +139 -0
  31. package/lib/dream.js +168 -21
  32. package/lib/dsh-go/host.js +162 -0
  33. package/lib/dsh-go/panel.js +2091 -0
  34. package/lib/dsh-go/text.js +342 -0
  35. package/lib/dsh-go/views/common.js +68 -0
  36. package/lib/dsh-go/views/entity.js +48 -0
  37. package/lib/dsh-go/views/memory.js +198 -0
  38. package/lib/dsh-go/views/root.js +30 -0
  39. package/lib/dsh-go/views/settings.js +398 -0
  40. package/lib/dsh-go/views/status.js +51 -0
  41. package/lib/embedding.js +42 -8
  42. package/lib/entities/extractor.js +36 -7
  43. package/lib/hot-memory.js +22 -11
  44. package/lib/index.js +123 -3
  45. package/lib/inject.js +203 -20
  46. package/lib/kb/index.js +309 -0
  47. package/lib/kb/routes.js +220 -0
  48. package/lib/kb/tools.js +148 -0
  49. package/lib/lang.js +98 -4
  50. package/lib/peak-hours.js +57 -2
  51. package/lib/semantic.js +347 -60
  52. package/lib/service.js +268 -69
  53. package/lib/settings.js +128 -15
  54. package/lib/store.js +585 -26
  55. package/lib/summarize.js +38 -11
  56. package/lib/tools.js +35 -4
  57. package/lib/user-settings.js +128 -32
  58. package/lib/vector-index.js +76 -6
  59. package/lib/wiki/claims.js +267 -0
  60. package/lib/wiki/cli-hint.js +90 -0
  61. package/lib/wiki/queries.js +282 -2
  62. package/lib/wiki/render.js +74 -0
  63. package/lib/wiki/t0.js +43 -6
  64. package/lib/wiki/t1-jobs.js +232 -0
  65. package/lib/wiki/t1.js +145 -32
  66. package/package.json +39 -1
  67. package/scripts/release-docs.mjs +7 -1
  68. package/scripts/release-notes.mjs +4 -1
  69. package/src/api-standalone.js +4 -1
  70. package/src/api.js +788 -30
  71. package/src/attachment-text.js +122 -0
  72. package/src/choro-command.js +9 -2
  73. package/src/config.js +166 -1
  74. package/src/continuity.js +7 -6
  75. package/src/diary/auto.js +190 -0
  76. package/src/diary/compose.js +440 -0
  77. package/src/diary/dir.js +59 -0
  78. package/src/diary/export.js +97 -0
  79. package/src/diary/index.js +26 -0
  80. package/src/diary/library.js +189 -0
  81. package/src/diary/qq-inbox.js +142 -0
  82. package/src/diary/rollover.js +61 -0
  83. package/src/diary/session-provider.js +122 -0
  84. package/src/dream/decisions.js +95 -6
  85. package/src/dream/sleep.js +760 -19
  86. package/src/dream/triage.js +139 -0
  87. package/src/dream.js +168 -21
  88. package/src/dsh-go/host.js +162 -0
  89. package/src/dsh-go/panel.js +2091 -0
  90. package/src/dsh-go/text.js +342 -0
  91. package/src/dsh-go/views/common.js +68 -0
  92. package/src/dsh-go/views/entity.js +48 -0
  93. package/src/dsh-go/views/memory.js +198 -0
  94. package/src/dsh-go/views/root.js +30 -0
  95. package/src/dsh-go/views/settings.js +398 -0
  96. package/src/dsh-go/views/status.js +51 -0
  97. package/src/embedding.js +42 -8
  98. package/src/entities/extractor.js +36 -7
  99. package/src/hot-memory.js +22 -11
  100. package/src/index.js +123 -3
  101. package/src/inject.js +203 -20
  102. package/src/kb/index.js +309 -0
  103. package/src/kb/routes.js +220 -0
  104. package/src/kb/tools.js +148 -0
  105. package/src/lang.js +98 -4
  106. package/src/peak-hours.js +57 -2
  107. package/src/semantic.js +347 -60
  108. package/src/service.js +268 -69
  109. package/src/settings.js +128 -15
  110. package/src/store.js +585 -26
  111. package/src/summarize.js +38 -11
  112. package/src/tools.js +35 -4
  113. package/src/user-settings.js +128 -32
  114. package/src/vector-index.js +76 -6
  115. package/src/wiki/claims.js +267 -0
  116. package/src/wiki/cli-hint.js +90 -0
  117. package/src/wiki/queries.js +282 -2
  118. package/src/wiki/render.js +74 -0
  119. package/src/wiki/t0.js +43 -6
  120. package/src/wiki/t1-jobs.js +232 -0
  121. package/src/wiki/t1.js +145 -32
  122. package/test/api-routes-gaps.test.js +63 -0
  123. package/test/api.test.js +385 -4
  124. package/test/attachment-text.test.js +140 -0
  125. package/test/bm25-corpus-cache.test.js +145 -0
  126. package/test/capacity-warning.test.js +262 -0
  127. package/test/client.test.js +1051 -185
  128. package/test/conflict-freeze.test.js +45 -1
  129. package/test/conflict-ledger-enforce.test.js +452 -0
  130. package/test/conflict-ledger.test.js +242 -0
  131. package/test/continuity.test.js +8 -1
  132. package/test/contrast.test.js +124 -12
  133. package/test/diary-library.test.js +187 -0
  134. package/test/diary.test.js +576 -0
  135. package/test/dream-baseline-clamp.test.js +174 -0
  136. package/test/dream-maxtokens-fallback.test.js +3 -3
  137. package/test/dream.test.js +62 -1
  138. package/test/dsh-go-host.test.js +134 -0
  139. package/test/dsh-go-panel-actions.test.js +854 -0
  140. package/test/dsh-go-panel.test.js +251 -0
  141. package/test/embedding-ready.test.js +33 -3
  142. package/test/entities.test.js +73 -0
  143. package/test/forget-corrections.test.js +70 -0
  144. package/test/graph-api.test.js +124 -0
  145. package/test/hot-memory.test.js +48 -0
  146. package/test/image-memory.test.js +171 -0
  147. package/test/inject-parent-gate.test.js +6 -3
  148. package/test/inject.test.js +59 -0
  149. package/test/issue-617-step1-query-and-fusion.test.js +361 -0
  150. package/test/kb-api-wiring.test.js +128 -0
  151. package/test/kb-collections.test.js +272 -0
  152. package/test/kb-routes.test.js +379 -0
  153. package/test/mint-source.test.js +26 -0
  154. package/test/panel-chart-scale.test.js +90 -0
  155. package/test/panel-tokens.test.js +245 -0
  156. package/test/peak-hours-observability.test.js +70 -0
  157. package/test/persona-api.test.js +3 -3
  158. package/test/persona-by-session.test.js +568 -0
  159. package/test/persona-settings.test.js +7 -7
  160. package/test/reflection-update-gate.test.js +129 -0
  161. package/test/reindex-backfill.test.js +334 -0
  162. package/test/reindex-scheduler.test.js +369 -0
  163. package/test/release-docs.test.js +20 -0
  164. package/test/release-notes.test.js +25 -0
  165. package/test/scope-candidates.test.js +249 -0
  166. package/test/security-doc.test.js +10 -0
  167. package/test/sleep-action-set.test.js +14 -0
  168. package/test/sleep-heat.test.js +49 -0
  169. package/test/sleep.test.js +635 -0
  170. package/test/standalone-api.test.js +24 -0
  171. package/test/store.test.js +20 -0
  172. package/test/summarize.test.js +26 -0
  173. package/test/user-settings-segments.test.js +528 -0
  174. package/test/vector-index.test.js +120 -6
  175. package/test/wiki-api.test.js +463 -1
  176. package/test/wiki-claims.test.js +194 -0
  177. package/test/wiki-cli-hint.test.js +118 -0
  178. package/test/wiki-provenance.test.js +202 -0
  179. package/test/wiki-queries.test.js +128 -0
  180. package/test/wiki-t0.test.js +77 -1
  181. package/test/wiki-t1-jobs.test.js +125 -0
  182. package/test/wiki-t1.test.js +226 -1
package/lib/settings.js CHANGED
@@ -34,7 +34,9 @@ function parseList(raw) {
34
34
  // persona 对象的字段白名单:写入时只挑这些键,未知字段丢弃(设计稿①:字段从简,
35
35
  // beta 收到真实反馈再加;provenance 是 v1.5 协作式人格的预留位,v1 不写管道)。
36
36
  // 读侧损坏即回空对象——同一容错口径照 parseList(坏值不抛错、面板显示为空可重写)。
37
- const PERSONA_FIELDS = ["name", "personality", "replyStyle", "enabled", "provenance"];
37
+ // scenario / mesExample(#606):对标 Character Card V2 的同名字段,是「人格卡能装
38
+ // 下完整角色」所需的最小补充。两者都可选、都不写时渲染零输出(老用户逐字节不变)。
39
+ const PERSONA_FIELDS = ["name", "personality", "replyStyle", "scenario", "mesExample", "enabled", "provenance"];
38
40
 
39
41
  function parsePersona(raw) {
40
42
  try {
@@ -54,14 +56,20 @@ function parsePersona(raw) {
54
56
  // 兼容层:v1 存的是单对象(有 PERSONA_FIELDS 键、无 default/byAgent 键),读侧
55
57
  // 判形状——单对象原样落 default 位,旧数据零迁移。byAgent 的键是 agent 身份
56
58
  // id(注入时按会话身份寻址,取不到落 default)。default 卡不可删(兜底位)。
59
+ //
60
+ // 绑会话(v1.3,#606):加第三维 bySession: { <sessionId>: <卡> }。为什么需要它——
61
+ // byAgent 的键来自 session.header.agentPreset,而 DS Bot 这类宿主把它写成常量
62
+ // (所有 bot 会话都是 "standard",实测 43/50),按 bot 名字建卡永远不命中。会话 id
63
+ // 才是那个「一个 bot 一张卡」真正能分开的键。bySession 同样是纯加法:没有这个键
64
+ // 的老数据读成 {},注入文本逐字节不变。
57
65
  function parsePersonaStore(raw) {
58
66
  try {
59
67
  const value = JSON.parse(raw);
60
68
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
61
- return { default: {}, byAgent: {} };
69
+ return { default: {}, byAgent: {}, bySession: {} };
62
70
  }
63
- // 新形:含 default 或 byAgent 键 → 按多卡解析
64
- if ("default" in value || "byAgent" in value) {
71
+ // 新形:含 default / byAgent / bySession 任一键 → 按多卡解析
72
+ if ("default" in value || "byAgent" in value || "bySession" in value) {
65
73
  const byAgent = {};
66
74
  if (value.byAgent && typeof value.byAgent === "object" && !Array.isArray(value.byAgent)) {
67
75
  for (const [agentId, card] of Object.entries(value.byAgent)) {
@@ -69,19 +77,27 @@ function parsePersonaStore(raw) {
69
77
  if (Object.keys(parsed).length) byAgent[String(agentId)] = parsed;
70
78
  }
71
79
  }
72
- return { default: parsePersona(JSON.stringify(value.default ?? {})), byAgent };
80
+ const bySession = {};
81
+ if (value.bySession && typeof value.bySession === "object" && !Array.isArray(value.bySession)) {
82
+ for (const [sessionId, card] of Object.entries(value.bySession)) {
83
+ const parsed = parsePersona(JSON.stringify(card ?? {}));
84
+ if (Object.keys(parsed).length) bySession[String(sessionId)] = parsed;
85
+ }
86
+ }
87
+ return { default: parsePersona(JSON.stringify(value.default ?? {})), byAgent, bySession };
73
88
  }
74
89
  // 旧形:v1 单对象 → 整体作 default(字段白名单在 parsePersona 内裁)
75
- return { default: parsePersona(raw), byAgent: {} };
90
+ return { default: parsePersona(raw), byAgent: {}, bySession: {} };
76
91
  } catch {
77
- return { default: {}, byAgent: {} };
92
+ return { default: {}, byAgent: {}, bySession: {} };
78
93
  }
79
94
  }
80
95
 
81
96
  function serializePersonaStore(store) {
82
97
  const out = {
83
98
  default: parsePersona(JSON.stringify(store?.default ?? {})),
84
- byAgent: {}
99
+ byAgent: {},
100
+ bySession: {}
85
101
  };
86
102
  if (store?.byAgent && typeof store.byAgent === "object") {
87
103
  for (const [agentId, card] of Object.entries(store.byAgent)) {
@@ -89,6 +105,12 @@ function serializePersonaStore(store) {
89
105
  if (Object.keys(parsed).length) out.byAgent[String(agentId)] = parsed;
90
106
  }
91
107
  }
108
+ if (store?.bySession && typeof store.bySession === "object") {
109
+ for (const [sessionId, card] of Object.entries(store.bySession)) {
110
+ const parsed = parsePersona(JSON.stringify(card ?? {}));
111
+ if (Object.keys(parsed).length) out.bySession[String(sessionId)] = parsed;
112
+ }
113
+ }
92
114
  return JSON.stringify(out);
93
115
  }
94
116
 
@@ -137,15 +159,27 @@ const FEATURE_FLAG_BOOLEANS = [
137
159
  // Issue #230:document 型记忆——agent 产长文档的指针行(注册校验/摘要+
138
160
  // doc_path 落库/C2 去重/supersede 记账;全文归 agent;默认关,lightMode 强制关)。
139
161
  "documentMemoryEnabled",
162
+ // 日记本:跨天惰性判定 + 启动补写,正文落 <memoryDir>/diaries/<项目键>/。
163
+ // 默认关,进 LIGHT_MODE_OFF(要调模型,与低资源档互斥)。与 documentMemoryEnabled
164
+ // 成对但不相依:关掉文档记忆只影响检索,正文照样写。
165
+ "diaryEnabled",
140
166
  "codingRetrospect",
141
167
  "autoDream",
142
168
  // Issue #292:autoDream 连续失败退避(默认关)。基数是 dreamMinIntervalMinutes,
143
169
  // 有效间隔 = 基数 × 2^连续失败数(成功清零),封顶 30 分钟;面板可启停。
144
170
  "autoDreamFailureBackoff",
171
+ // Issue #557:dream 审计行输入快照瘦身(默认关)。开启后 auto 轮只落骨架
172
+ // (不含 content),organize 轮的重放载荷不受影响;面板可启停。
173
+ "dreamInputSummaryEnabled",
145
174
  "sleepModeEnabled",
146
175
  // Issue #529:sleep 连续失败退避(默认关)。基数是 sleepMinIntervalHours,
147
176
  // 有效间隔 = 基数 × 2^连续失败数(成功清零),封顶 max(基数, 24h);面板可启停。
148
177
  "sleepFailureBackoff",
178
+ // Issue #619:情感归因相位(默认关)。把「某人某场合的情绪」写成 occasion-bound
179
+ // 的 affective_context 关系,只有跨够 sleepAffectiveTraitMinSessions 个独立会话
180
+ // 才允许升成稳定特质。要调模型且依赖实体抽取,所以必须显式打开、可随时关掉;
181
+ // 它是 sleepModeEnabled 的子相:父开关关着时整个 sleep 不跑。
182
+ "sleepAffectiveEnabled",
149
183
  "heatEnabled",
150
184
  // issue #218 / E5:注入侧 heat 独立开关(默认关,老用户 injectHeatEnabled=true 回滚)。
151
185
  "injectHeatEnabled",
@@ -159,6 +193,15 @@ const FEATURE_FLAG_BOOLEANS = [
159
193
  "reflectionFailureTracking",
160
194
  "bm25SearchEnabled",
161
195
  "conflictFreezeEnabled",
196
+ // Issue #603 第 4 层:自动裁决档(默认关)。相似度极高的同 scope 重复对直接
197
+ // 保留新条、归档旧条,不进人工队列;模糊地带照旧留给人。会归档记忆,所以必须
198
+ // 显式打开,面板可启停。阈值见 FEATURE_FLAG_NUMBER_RANGES 的
199
+ // conflictAutoResolveMinSimilarity。
200
+ "conflictAutoResolveEnabled",
201
+ // Issue #641(beta 第一阶段):conflictLedgerMode / conflictLedgerDupMinSimilarity
202
+ // 不在本表——前者是枚举键(FEATURE_FLAG_ENUMS),后者是数值键
203
+ // (FEATURE_FLAG_NUMBER_RANGES),flagKeysFromSpec 会把三张表合成一份白名单,
204
+ // 在这里重复登记会让同键被收两次(config-keys 的覆盖完整性锁抓过)。
162
205
  "trustEpistemicWeighting",
163
206
  // Plan #2: attach per-source {keyword, vector, bm25, final} signals to each
164
207
  // search result for transparency/debugging. Default off, purely decorative.
@@ -255,7 +298,14 @@ const FEATURE_FLAG_BOOLEANS = [
255
298
  // #539:逐会话无痕默认关闭;子代理开关受总开关约束。手动 session id 名单留在配置,
256
299
  // 不进入只支持标量的 feature_flags 白名单。
257
300
  "sessionGateEnabled",
258
- "sessionGateSubagents"
301
+ "sessionGateSubagents",
302
+ // 2026-11-08 kb-qoder parity(契约 §3.1):chorography 面板闭环的两个授权闸 +
303
+ // kb 子系统总闸。三条都与 config.js schema 成对出现,计数锁 test/api.test.js 钉住。
304
+ // 不进 LIGHT_MODE_OFF —— 它们是「谁按下了按钮」而不是「每轮付多少」,理由写在
305
+ // config.js 那三个键的注释里。kbEnabled 本车道只登记、不接线(B 线做子系统)。
306
+ "chorographyAutoRefresh",
307
+ "chorographyT1FromPanel",
308
+ "kbEnabled"
259
309
  ];
260
310
  // 整数开关的闭区间,与 config.js 里 z.natural().min().max() 对齐。
261
311
  const FEATURE_FLAG_INT_RANGES = {
@@ -265,6 +315,10 @@ const FEATURE_FLAG_INT_RANGES = {
265
315
  distillMaxChars: [1000, 200000],
266
316
  codingBoostFactor: [1, 5],
267
317
  dreamMinIntervalMinutes: [0, 10080],
318
+ // Issue #637:注入跨轮轮换的窗口(0 = 关,#205 既有语义;config.js 的
319
+ // z.natural().min(0).max(20) 与此成对)。此前不在白名单——报告者想开只能写
320
+ // 宿主 patch 文件(cordis.patch.yml),面板上根本看不见这个键的存在。
321
+ injectRotationTurns: [0, 20],
268
322
  dreamMaxTokens: [256, 131072],
269
323
  // Issue #127:autoSummarize 节流三键(0 = 零行为变化,等同现状)。
270
324
  summarizeMinIntervalMinutes: [0, 10080],
@@ -280,6 +334,9 @@ const FEATURE_FLAG_INT_RANGES = {
280
334
  summarizePeakMaxDeferMinutes: [0, 1440],
281
335
  // Issue #239 第 4 项镜像到巩固:同一口径(分钟,0 = 不设上限)。
282
336
  dreamPeakMaxDeferMinutes: [0, 1440],
337
+ // v0.13 日记定时写:每隔 N 分钟唤醒一次跨天判定(0 = 关,只在 turn/end 与
338
+ // 启动时判)。上限一天——判定单位是天,更长的间隔没有意义。
339
+ diaryWriteEveryMinutes: [0, 1440],
283
340
  // Issue #125:hybrid 候选量上限(0 = 复用 dreamMaxSnapshotSize)。
284
341
  dreamCandidateMax: [0, 5000],
285
342
  // #135 成本审计派生:单轮 merge 决策上限(merge 是唯一回吐正文的动作,默认 40
@@ -295,6 +352,11 @@ const FEATURE_FLAG_INT_RANGES = {
295
352
  pinnedInjectBudget: [0, 5],
296
353
  // Issue #257:sleep 冲突/模式阶段的 LLM 输出预算(原硬编码 2048,实测不足)。
297
354
  sleepMaxTokens: [256, 131072],
355
+ // Issue #619:情感归因的两个闸——升特质所需的最小独立会话数(下界 2:N=1 就是
356
+ // 单次场合,正是必须禁止升特质的那个情形),以及单轮可写的 affective_context
357
+ // 关系条数上限(0 = 关闭关系通道)。闭区间与 config.js 的 z.natural() 对齐。
358
+ sleepAffectiveTraitMinSessions: [2, 10],
359
+ sleepAffectiveMaxRelationsPerRun: [0, 50],
298
360
  // Issue #529:sleep 冲突阶段的输入侧预算(此前完全没有——全部候选对一次进一个
299
361
  // prompt,实测 163,054 token 撞服务端 163,840 上限)。两个键都是 0 = 不设上限,
300
362
  // 与 config.js 的 z.natural().min(0).max() 成对。
@@ -309,7 +371,12 @@ const FEATURE_FLAG_INT_RANGES = {
309
371
  graphInjectBudget: [0, 5],
310
372
  // Issue #399:蒸馏原文记录的保留天数(与 config.js 的 z.natural().min(1).max(3650)
311
373
  // 对齐;同 llmAudit.retentionDays,是 int 而非开关)。
312
- "distillRecords.retentionDays": [1, 3650]
374
+ "distillRecords.retentionDays": [1, 3650],
375
+ // #275 容量预警阈值(嵌套点号键,必须同时进 api.js 的 NESTED_FLAG_PATHS)。
376
+ // 0 = 显式关掉那一条检查(阈值判级的启用条件写在 service.getCapacityHealth)。
377
+ // 上限与 config.js 的 z.natural().min(0).max() 成对。
378
+ "capacityWarning.dbSizeWarnMb": [0, 1048576],
379
+ "capacityWarning.archivedRowsWarn": [0, 10000000]
313
380
  };
314
381
  // 浮点开关的闭区间(与 config.js 的 z.number().min().max() 对齐)。与整数开关
315
382
  // 分开:面板的整数控件要求 Number.isInteger,而余弦相似度阈值必须允许小数。
@@ -319,7 +386,17 @@ const FEATURE_FLAG_NUMBER_RANGES = {
319
386
  // Issue #125:hybrid 判"高相似"的阈值。
320
387
  dreamCandidateMinSim: [0.5, 0.99],
321
388
  // Issue #24 块2:单次有效触达的边权抬升幅度(与 config.js 的闭区间对齐)。
322
- graphWeightDelta: [0, 1]
389
+ graphWeightDelta: [0, 1],
390
+ // Issue #603 第 4 层:自动裁决的相似度门槛。下界 0.85 与冲突检测阈值同源——
391
+ // 低于它就不是「重复」而是「相似」,不该自动归档任何一条。
392
+ conflictAutoResolveMinSimilarity: [0.85, 1],
393
+ // Issue #641:T1(dup)档的相似度下限。检测阈值(normal 0.85)与自动档 autoMin
394
+ // (0.95,#616 重测在现库覆盖为 0)之间的空带由它接管,下界与检测阈值同源。
395
+ conflictLedgerDupMinSimilarity: [0.85, 1],
396
+ // #275 容量预警的归档占比阈值(0 = 关):健康形态本身就落在 0.8~0.9,默认
397
+ // 0.95 起才算「归档区吞掉活跃区」——0.9 会对着健康库天天报警(依据见 config.js
398
+ // capacityWarning 的注释)。浮点键,同 conflictAutoResolveMinSimilarity 的缘由。
399
+ "capacityWarning.archivedRatioWarn": [0, 1]
323
400
  };
324
401
  // 自由字符串开关(与 config.js 的 z.string() 同名同型):trim 后 ≤200 字符,
325
402
  // 空串合法(= 跟随主对话模型/默认路径,面板显示 placeholder)。
@@ -368,7 +445,11 @@ const FEATURE_FLAG_ENUMS = {
368
445
  localEmbedPooling: ["auto", "cls", "mean"],
369
446
  // M3 开局注入档位(spec §7):轻量档在消费侧压成 off,见 wiki/inject.js 的
370
447
  // resolveInjectTier——LIGHT_MODE_OFF 对平铺键一律赋 false,枚举值进去会静默失效。
371
- chorographyInject: ["off", "light", "full"]
448
+ chorographyInject: ["off", "light", "full"],
449
+ // Issue #641:冲突分诊账本档位。off(默认,零行为)/ shadow(只记账)/ enforce
450
+ // (确定性处置:T1 信息量优先 supersede,T2-T4 记账不停车不送 LLM)。三个档位
451
+ // 都有真实行为——「设了但没行为」的档位不进枚举(#616「假成功」教训)。
452
+ conflictLedgerMode: ["off", "shadow", "enforce"]
372
453
  };
373
454
  const FEATURE_FLAG_STRING_MAX = 200;
374
455
 
@@ -534,10 +615,10 @@ export function createSettings(db) {
534
615
  return parsePersonaStore(getSetting("persona") ?? "{}");
535
616
  },
536
617
  setPersona(input) {
537
- // 双形兼容:单对象形 = 只更新 default、byAgent 保留(旧面板/旧调用方合并语义);
538
- // 多卡形 = 全量替换。坏体(非对象)在此不落库不抛错,保值由调用方守卫。
618
+ // 双形兼容:单对象形 = 只更新 default、byAgent/bySession 保留(旧面板/旧调用方
619
+ // 合并语义);多卡形 = 全量替换。坏体(非对象)在此不落库不抛错,保值由调用方守卫。
539
620
  if (typeof input !== "object" || input === null || Array.isArray(input)) return;
540
- if ("default" in input || "byAgent" in input) {
621
+ if ("default" in input || "byAgent" in input || "bySession" in input) {
541
622
  setSetting("persona", serializePersonaStore(input));
542
623
  return;
543
624
  }
@@ -573,6 +654,38 @@ export function createSettings(db) {
573
654
  return true;
574
655
  },
575
656
 
657
+ /**
658
+ * 绑会话卡(#606):按 sessionId 存/删/列。形状与 setPersonaFor 一族逐一对应,
659
+ * 只是换了一个维度——为什么需要第二维见 parsePersonaStore 的注释。
660
+ * sessionId 为空或 "default" 拒绝(与 byAgent 同一条纪律:兜底位不可被顶替)。
661
+ */
662
+ setPersonaForSession(sessionId, card) {
663
+ const id = typeof sessionId === "string" && sessionId.trim() ? sessionId.trim() : "";
664
+ if (!id || id === "default") return false;
665
+ const store = parsePersonaStore(getSetting("persona") ?? "{}");
666
+ const clean = parsePersona(JSON.stringify(
667
+ typeof card === "object" && card !== null && !Array.isArray(card) ? card : {}
668
+ ));
669
+ if (Object.keys(clean).length) store.bySession[id] = clean;
670
+ else delete store.bySession[id];
671
+ setSetting("persona", serializePersonaStore(store));
672
+ return true;
673
+ },
674
+ /** bySession 键列表(面板「按会话建的卡」用,default 不在其中)。 */
675
+ listPersonaSessions() {
676
+ return Object.keys(parsePersonaStore(getSetting("persona") ?? "{}").bySession);
677
+ },
678
+ /** 删一张 bySession 卡;不存在返回 false。 */
679
+ removePersonaForSession(sessionId) {
680
+ const id = typeof sessionId === "string" && sessionId.trim() ? sessionId.trim() : "";
681
+ if (!id || id === "default") return false;
682
+ const store = parsePersonaStore(getSetting("persona") ?? "{}");
683
+ if (!(id in store.bySession)) return false;
684
+ delete store.bySession[id];
685
+ setSetting("persona", serializePersonaStore(store));
686
+ return true;
687
+ },
688
+
576
689
  /** All custom commands, sorted by name. */
577
690
  listCommands() {
578
691
  const rows = db.prepare("SELECT * FROM custom_commands ORDER BY name ASC").all();