@modusensus/dsh-mneme 0.14.0-beta.1 → 0.14.0-beta.2

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 (543) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +830 -830
  3. package/assets/icon.svg +3 -3
  4. package/bin/cli.mjs +964 -964
  5. package/bin/dsh-mneme-choro.mjs +262 -262
  6. package/bin/dsh-mneme-mcp.mjs +510 -510
  7. package/bin/dsh-mneme-serve.mjs +151 -151
  8. package/cordis.patch.yml +21 -21
  9. package/docs/CONFIGURATION.md +407 -404
  10. package/docs/CONTINUITY.md +139 -139
  11. package/docs/DAEMON.md +57 -57
  12. package/docs/DIRECTORIES.md +114 -114
  13. package/docs/DISTILL.md +397 -371
  14. package/docs/DSH-GO.md +97 -97
  15. package/docs/ENTITIES.md +247 -247
  16. package/docs/FORGET.md +77 -77
  17. package/docs/KB.md +110 -110
  18. package/docs/LOCAL_MODEL.md +222 -222
  19. package/docs/SEMANTIC.md +256 -256
  20. package/docs/SHARE.md +552 -552
  21. package/docs/SLEEP.md +202 -202
  22. package/docs/STORAGE.md +122 -122
  23. package/docs/WORKFLOWS.md +366 -366
  24. package/lib/api-standalone.js +1007 -1007
  25. package/lib/api.js +3600 -3600
  26. package/lib/attachment-text.js +122 -122
  27. package/lib/bootstrap-http.js +105 -105
  28. package/lib/bootstrap.js +215 -215
  29. package/lib/candidates.js +454 -454
  30. package/lib/capabilities.js +819 -819
  31. package/lib/choro-command.js +111 -111
  32. package/lib/client.js +14420 -14322
  33. package/lib/commands.js +95 -95
  34. package/lib/config-keys.js +144 -144
  35. package/lib/config.js +1361 -1344
  36. package/lib/content-hash.js +54 -54
  37. package/lib/continuity.js +258 -258
  38. package/lib/date-hint.js +32 -0
  39. package/lib/diary/auto.js +190 -190
  40. package/lib/diary/compose.js +440 -440
  41. package/lib/diary/dir.js +59 -59
  42. package/lib/diary/export.js +97 -97
  43. package/lib/diary/index.js +26 -26
  44. package/lib/diary/library.js +189 -189
  45. package/lib/diary/qq-inbox.js +142 -142
  46. package/lib/diary/rollover.js +61 -61
  47. package/lib/diary/session-provider.js +122 -122
  48. package/lib/document-index.js +94 -94
  49. package/lib/document.js +322 -322
  50. package/lib/dream/clustering.js +123 -123
  51. package/lib/dream/decisions.js +945 -945
  52. package/lib/dream/narratives.js +44 -44
  53. package/lib/dream/sleep.js +2027 -2027
  54. package/lib/dream/triage.js +139 -139
  55. package/lib/dream.js +1879 -1879
  56. package/lib/dsh-go/host.js +162 -162
  57. package/lib/dsh-go/panel.js +2091 -2091
  58. package/lib/dsh-go/text.js +342 -342
  59. package/lib/dsh-go/views/common.js +68 -68
  60. package/lib/dsh-go/views/entity.js +48 -48
  61. package/lib/dsh-go/views/memory.js +198 -198
  62. package/lib/dsh-go/views/root.js +30 -30
  63. package/lib/dsh-go/views/settings.js +398 -398
  64. package/lib/dsh-go/views/status.js +51 -51
  65. package/lib/embedding.js +342 -342
  66. package/lib/entities/extractor.js +285 -285
  67. package/lib/fence.js +24 -24
  68. package/lib/graph/anchoring.js +184 -184
  69. package/lib/guide.js +74 -74
  70. package/lib/heat.js +144 -144
  71. package/lib/hot-memory.js +119 -119
  72. package/lib/image-describe.js +158 -0
  73. package/lib/index.js +852 -852
  74. package/lib/inject.js +656 -651
  75. package/lib/kb/index.js +309 -309
  76. package/lib/kb/routes.js +219 -219
  77. package/lib/kb/tools.js +148 -148
  78. package/lib/lang.js +607 -595
  79. package/lib/local-embedder.js +358 -358
  80. package/lib/maintenance.js +162 -162
  81. package/lib/memory-source.js +9 -9
  82. package/lib/mirror.js +330 -330
  83. package/lib/organize.js +456 -456
  84. package/lib/peak-hours.js +172 -172
  85. package/lib/pipeline-activities.js +178 -178
  86. package/lib/pipeline-attribution.js +165 -165
  87. package/lib/pipeline-command.js +279 -279
  88. package/lib/pipeline-confirm.js +142 -142
  89. package/lib/pipeline-exec.js +174 -174
  90. package/lib/pipeline-nudge-auto.js +129 -129
  91. package/lib/pipeline-nudge.js +1623 -1623
  92. package/lib/pipeline-observations.js +181 -181
  93. package/lib/pipeline-observe.js +133 -133
  94. package/lib/pipeline-runner.js +361 -361
  95. package/lib/pipeline-runs.js +442 -442
  96. package/lib/pipeline.js +716 -716
  97. package/lib/placement.js +13 -13
  98. package/lib/pre-inject-gate.js +354 -354
  99. package/lib/profile-extract.js +164 -164
  100. package/lib/profile-facts.js +356 -356
  101. package/lib/provenance-http.js +251 -251
  102. package/lib/quality-filter.js +162 -162
  103. package/lib/recall-stats.js +190 -190
  104. package/lib/reclaim-http.js +150 -150
  105. package/lib/reranker.js +269 -269
  106. package/lib/runtime/adopt.js +176 -176
  107. package/lib/runtime/closure.js +172 -172
  108. package/lib/runtime/download.js +247 -247
  109. package/lib/runtime/layout.js +265 -265
  110. package/lib/runtime/loader.js +252 -252
  111. package/lib/runtime/model-fetch.js +361 -361
  112. package/lib/runtime/provision.js +237 -237
  113. package/lib/runtime/tarball.js +130 -130
  114. package/lib/runtime/verify.js +223 -223
  115. package/lib/safe-slice.js +18 -18
  116. package/lib/scope-state.js +277 -277
  117. package/lib/scope.js +187 -187
  118. package/lib/search/adaptive.js +60 -60
  119. package/lib/search/bm25.js +96 -96
  120. package/lib/semantic.js +572 -572
  121. package/lib/sensitive-scan.js +209 -209
  122. package/lib/serve.js +228 -228
  123. package/lib/service.js +3268 -3268
  124. package/lib/session-gate.js +91 -91
  125. package/lib/session-usage.js +350 -350
  126. package/lib/settings.js +827 -817
  127. package/lib/share-http.js +281 -281
  128. package/lib/share-import.js +234 -234
  129. package/lib/share.js +335 -335
  130. package/lib/store.js +5895 -5895
  131. package/lib/summarize.js +1388 -1343
  132. package/lib/tools.js +1370 -1356
  133. package/lib/user-settings.js +230 -230
  134. package/lib/vector-index.js +180 -180
  135. package/lib/version-check.js +122 -122
  136. package/lib/wiki/claims.js +267 -267
  137. package/lib/wiki/cli-hint.js +90 -90
  138. package/lib/wiki/inject.js +164 -164
  139. package/lib/wiki/partition.js +200 -200
  140. package/lib/wiki/plan.js +214 -214
  141. package/lib/wiki/queries.js +640 -640
  142. package/lib/wiki/render.js +261 -261
  143. package/lib/wiki/scan.js +371 -371
  144. package/lib/wiki/t0.js +500 -500
  145. package/lib/wiki/t1-jobs.js +232 -232
  146. package/lib/wiki/t1.js +609 -609
  147. package/lib/write-admission.js +404 -404
  148. package/locale/en.json +6 -6
  149. package/locale/zh.json +6 -6
  150. package/package.json +1 -1
  151. package/runtime-manifest.json +526 -526
  152. package/scripts/benchmark-embed.js +201 -201
  153. package/scripts/benchmark-recall.js +249 -249
  154. package/scripts/benchmark-rerank.js +166 -166
  155. package/scripts/build-runtime-manifest.mjs +224 -224
  156. package/scripts/changelog-prep.mjs +91 -91
  157. package/scripts/check-sync.js +44 -44
  158. package/scripts/e2e-dsh.js +220 -220
  159. package/scripts/mneme-runtime.mjs +277 -277
  160. package/scripts/publish-gate.mjs +197 -197
  161. package/scripts/release-docs.mjs +210 -210
  162. package/scripts/release-notes.mjs +164 -164
  163. package/scripts/stress-dsh.js +255 -255
  164. package/scripts/sync-lib.js +47 -47
  165. package/scripts/sync-test-badge.mjs +69 -69
  166. package/scripts/test-count-sync.mjs +48 -48
  167. package/src/api-standalone.js +1007 -1007
  168. package/src/api.js +3600 -3600
  169. package/src/attachment-text.js +122 -122
  170. package/src/bootstrap-http.js +105 -105
  171. package/src/bootstrap.js +215 -215
  172. package/src/candidates.js +454 -454
  173. package/src/capabilities.js +819 -819
  174. package/src/choro-command.js +111 -111
  175. package/src/commands.js +95 -95
  176. package/src/config-keys.js +144 -144
  177. package/src/config.js +1361 -1344
  178. package/src/content-hash.js +54 -54
  179. package/src/continuity.js +258 -258
  180. package/src/date-hint.js +32 -0
  181. package/src/diary/auto.js +190 -190
  182. package/src/diary/compose.js +440 -440
  183. package/src/diary/dir.js +59 -59
  184. package/src/diary/export.js +97 -97
  185. package/src/diary/index.js +26 -26
  186. package/src/diary/library.js +189 -189
  187. package/src/diary/qq-inbox.js +142 -142
  188. package/src/diary/rollover.js +61 -61
  189. package/src/diary/session-provider.js +122 -122
  190. package/src/document-index.js +94 -94
  191. package/src/document.js +322 -322
  192. package/src/dream/clustering.js +123 -123
  193. package/src/dream/decisions.js +945 -945
  194. package/src/dream/narratives.js +44 -44
  195. package/src/dream/sleep.js +2027 -2027
  196. package/src/dream/triage.js +139 -139
  197. package/src/dream.js +1879 -1879
  198. package/src/dsh-go/host.js +162 -162
  199. package/src/dsh-go/panel.js +2091 -2091
  200. package/src/dsh-go/text.js +342 -342
  201. package/src/dsh-go/views/common.js +68 -68
  202. package/src/dsh-go/views/entity.js +48 -48
  203. package/src/dsh-go/views/memory.js +198 -198
  204. package/src/dsh-go/views/root.js +30 -30
  205. package/src/dsh-go/views/settings.js +398 -398
  206. package/src/dsh-go/views/status.js +51 -51
  207. package/src/embedding.js +342 -342
  208. package/src/entities/extractor.js +285 -285
  209. package/src/fence.js +24 -24
  210. package/src/graph/anchoring.js +184 -184
  211. package/src/guide.js +74 -74
  212. package/src/heat.js +144 -144
  213. package/src/hot-memory.js +119 -119
  214. package/src/image-describe.js +158 -0
  215. package/src/index.js +852 -852
  216. package/src/inject.js +656 -651
  217. package/src/kb/index.js +309 -309
  218. package/src/kb/routes.js +219 -219
  219. package/src/kb/tools.js +148 -148
  220. package/src/lang.js +607 -595
  221. package/src/local-embedder.js +358 -358
  222. package/src/maintenance.js +162 -162
  223. package/src/memory-source.js +9 -9
  224. package/src/mirror.js +330 -330
  225. package/src/organize.js +456 -456
  226. package/src/peak-hours.js +172 -172
  227. package/src/pipeline-activities.js +178 -178
  228. package/src/pipeline-attribution.js +165 -165
  229. package/src/pipeline-command.js +279 -279
  230. package/src/pipeline-confirm.js +142 -142
  231. package/src/pipeline-exec.js +174 -174
  232. package/src/pipeline-nudge-auto.js +129 -129
  233. package/src/pipeline-nudge.js +1623 -1623
  234. package/src/pipeline-observations.js +181 -181
  235. package/src/pipeline-observe.js +133 -133
  236. package/src/pipeline-runner.js +361 -361
  237. package/src/pipeline-runs.js +442 -442
  238. package/src/pipeline.js +716 -716
  239. package/src/placement.js +13 -13
  240. package/src/pre-inject-gate.js +354 -354
  241. package/src/profile-extract.js +164 -164
  242. package/src/profile-facts.js +356 -356
  243. package/src/provenance-http.js +251 -251
  244. package/src/quality-filter.js +162 -162
  245. package/src/recall-stats.js +190 -190
  246. package/src/reclaim-http.js +150 -150
  247. package/src/reranker.js +269 -269
  248. package/src/runtime/adopt.js +176 -176
  249. package/src/runtime/closure.js +172 -172
  250. package/src/runtime/download.js +247 -247
  251. package/src/runtime/layout.js +265 -265
  252. package/src/runtime/loader.js +252 -252
  253. package/src/runtime/model-fetch.js +361 -361
  254. package/src/runtime/provision.js +237 -237
  255. package/src/runtime/tarball.js +130 -130
  256. package/src/runtime/verify.js +223 -223
  257. package/src/safe-slice.js +18 -18
  258. package/src/scope-state.js +277 -277
  259. package/src/scope.js +187 -187
  260. package/src/search/adaptive.js +60 -60
  261. package/src/search/bm25.js +96 -96
  262. package/src/semantic.js +572 -572
  263. package/src/sensitive-scan.js +209 -209
  264. package/src/serve.js +228 -228
  265. package/src/service.js +3268 -3268
  266. package/src/session-gate.js +91 -91
  267. package/src/session-usage.js +350 -350
  268. package/src/settings.js +827 -817
  269. package/src/share-http.js +281 -281
  270. package/src/share-import.js +234 -234
  271. package/src/share.js +335 -335
  272. package/src/store.js +5895 -5895
  273. package/src/summarize.js +1388 -1343
  274. package/src/tools.js +1370 -1356
  275. package/src/user-settings.js +230 -230
  276. package/src/vector-index.js +180 -180
  277. package/src/version-check.js +122 -122
  278. package/src/wiki/claims.js +267 -267
  279. package/src/wiki/cli-hint.js +90 -90
  280. package/src/wiki/inject.js +164 -164
  281. package/src/wiki/partition.js +200 -200
  282. package/src/wiki/plan.js +214 -214
  283. package/src/wiki/queries.js +640 -640
  284. package/src/wiki/render.js +261 -261
  285. package/src/wiki/scan.js +371 -371
  286. package/src/wiki/t0.js +500 -500
  287. package/src/wiki/t1-jobs.js +232 -232
  288. package/src/wiki/t1.js +609 -609
  289. package/src/write-admission.js +404 -404
  290. package/test/adaptive-inject-budget.test.js +85 -85
  291. package/test/api-hardening.test.js +173 -173
  292. package/test/api-nudge-route.test.js +259 -259
  293. package/test/api-routes-gaps.test.js +1092 -1092
  294. package/test/api.test.js +3007 -2998
  295. package/test/attachment-text.test.js +139 -139
  296. package/test/audit.test.js +448 -448
  297. package/test/baseline-persist.test.js +248 -248
  298. package/test/benchmark.test.js +94 -94
  299. package/test/bm25-corpus-cache.test.js +145 -145
  300. package/test/bootstrap.test.js +228 -228
  301. package/test/candidates.test.js +345 -345
  302. package/test/capabilities.test.js +577 -577
  303. package/test/capacity-warning.test.js +262 -262
  304. package/test/changelog-prep.test.js +125 -125
  305. package/test/choro-command.test.js +218 -218
  306. package/test/cli-enums.test.js +37 -37
  307. package/test/cli-flag-value.test.js +86 -86
  308. package/test/cli-receive.test.js +390 -390
  309. package/test/cli-share.test.js +437 -437
  310. package/test/client-share.test.js +225 -225
  311. package/test/client.test.js +4595 -4425
  312. package/test/clustering.test.js +100 -100
  313. package/test/commands.test.js +106 -106
  314. package/test/config-keys-routes.test.js +233 -233
  315. package/test/config.test.js +153 -153
  316. package/test/conflict-freeze.test.js +344 -344
  317. package/test/conflict-ledger-enforce.test.js +452 -452
  318. package/test/conflict-ledger.test.js +242 -242
  319. package/test/conflict-reviewed.test.js +79 -79
  320. package/test/content-hash.test.js +204 -204
  321. package/test/continuity.test.js +452 -452
  322. package/test/contrast.test.js +432 -432
  323. package/test/date-hint.test.js +156 -0
  324. package/test/dedupe-candidate-window.test.js +180 -180
  325. package/test/dependency-overrides.test.js +78 -78
  326. package/test/diary-library.test.js +187 -187
  327. package/test/diary.test.js +576 -576
  328. package/test/differentiate-feedback.test.js +248 -248
  329. package/test/distill-tx-embed-gap.test.js +110 -110
  330. package/test/docs-config-keys.test.js +65 -65
  331. package/test/document-dir.test.js +233 -233
  332. package/test/document.test.js +703 -703
  333. package/test/dream-archive-guardrail.test.js +116 -116
  334. package/test/dream-baseline-clamp.test.js +174 -174
  335. package/test/dream-candidate-hybrid.test.js +281 -281
  336. package/test/dream-coverage-degrade.test.js +86 -86
  337. package/test/dream-decision-evidence.test.js +314 -314
  338. package/test/dream-failure-backoff.test.js +173 -173
  339. package/test/dream-fence.test.js +207 -207
  340. package/test/dream-maxtokens-fallback.test.js +132 -132
  341. package/test/dream-merge-guardrail.test.js +219 -219
  342. package/test/dream-peak-hours.test.js +194 -194
  343. package/test/dream.test.js +1528 -1528
  344. package/test/dsh-go-host.test.js +134 -134
  345. package/test/dsh-go-panel-actions.test.js +854 -854
  346. package/test/dsh-go-panel.test.js +251 -251
  347. package/test/embed-ready-poll.test.js +94 -94
  348. package/test/embed-text-reasons.test.js +112 -112
  349. package/test/embedding-cache-f32.test.js +276 -276
  350. package/test/embedding-ready.test.js +226 -226
  351. package/test/entities.test.js +711 -711
  352. package/test/entity-recall.test.js +179 -179
  353. package/test/epistemic.test.js +325 -325
  354. package/test/failure-query.test.js +147 -147
  355. package/test/failure-retention.test.js +105 -105
  356. package/test/fnew-0112.test.js +311 -311
  357. package/test/fnew-03.test.js +422 -422
  358. package/test/forget-corrections.test.js +70 -70
  359. package/test/graph-anchoring.test.js +184 -184
  360. package/test/graph-api.test.js +333 -333
  361. package/test/graph-boosted-relations.test.js +189 -189
  362. package/test/graph-inject-budget.test.js +103 -103
  363. package/test/graph-multipath.test.js +269 -269
  364. package/test/graph-passive-confirm.test.js +91 -91
  365. package/test/graph-weight-evolution.test.js +104 -104
  366. package/test/heat.test.js +175 -175
  367. package/test/helpers/dream-mock.js +86 -86
  368. package/test/helpers/peer-worker.mjs +25 -25
  369. package/test/helpers/runtime-source.js +60 -60
  370. package/test/helpers/tar-builder.js +80 -80
  371. package/test/helpers/write-admission-samples.js +312 -312
  372. package/test/hot-memory.test.js +259 -259
  373. package/test/image-description.test.js +271 -0
  374. package/test/image-memory.test.js +170 -170
  375. package/test/inject-content-cap.test.js +91 -91
  376. package/test/inject-first-round-bm25.test.js +89 -89
  377. package/test/inject-heat.test.js +77 -77
  378. package/test/inject-parent-gate.test.js +134 -109
  379. package/test/inject-pin-explicit.test.js +300 -300
  380. package/test/inject-pin-vector-crowd.test.js +225 -225
  381. package/test/inject-pools.test.js +195 -195
  382. package/test/inject-recall.test.js +172 -172
  383. package/test/inject-rotation.test.js +161 -161
  384. package/test/inject.test.js +553 -553
  385. package/test/issue-617-step1-query-and-fusion.test.js +361 -361
  386. package/test/kb-api-wiring.test.js +128 -128
  387. package/test/kb-collections.test.js +272 -272
  388. package/test/kb-routes.test.js +379 -379
  389. package/test/lang.test.js +98 -98
  390. package/test/lastrunat-persist.test.js +101 -101
  391. package/test/lib-smoke.test.js +116 -116
  392. package/test/llm-audit.test.js +815 -815
  393. package/test/llm-message-source.test.js +207 -207
  394. package/test/local-embedder.test.js +292 -292
  395. package/test/maintenance.test.js +501 -501
  396. package/test/mcp-stdio.test.js +385 -385
  397. package/test/mint-source.test.js +311 -311
  398. package/test/mirror-dirty.test.js +424 -424
  399. package/test/mirror-edit-digest.test.js +187 -187
  400. package/test/mirror-generation.test.js +499 -499
  401. package/test/mirror-types.test.js +208 -208
  402. package/test/mirror.test.js +249 -249
  403. package/test/narrative-bars.test.js +162 -162
  404. package/test/organize-tool.test.js +232 -232
  405. package/test/organize.test.js +418 -418
  406. package/test/panel-chart-scale.test.js +90 -90
  407. package/test/panel-tokens.test.js +245 -245
  408. package/test/peak-hours-observability.test.js +70 -70
  409. package/test/peer-blockers.test.js +193 -193
  410. package/test/persona-addressing.test.js +83 -83
  411. package/test/persona-api.test.js +172 -172
  412. package/test/persona-by-session.test.js +568 -568
  413. package/test/persona-flag.test.js +36 -36
  414. package/test/persona-inject.test.js +145 -145
  415. package/test/persona-settings.test.js +198 -198
  416. package/test/pipeline-activities.test.js +155 -155
  417. package/test/pipeline-attribution.test.js +175 -175
  418. package/test/pipeline-command.test.js +243 -243
  419. package/test/pipeline-confirm.test.js +148 -148
  420. package/test/pipeline-exec.test.js +172 -172
  421. package/test/pipeline-nudge-auto.test.js +248 -248
  422. package/test/pipeline-nudge.test.js +1278 -1278
  423. package/test/pipeline-observations.test.js +157 -157
  424. package/test/pipeline-observe.test.js +127 -127
  425. package/test/pipeline-r1r2.test.js +278 -278
  426. package/test/pipeline-runner.test.js +468 -468
  427. package/test/pipeline-runs.test.js +767 -767
  428. package/test/pipeline.test.js +636 -636
  429. package/test/placement.test.js +36 -36
  430. package/test/policy-epoch.test.js +259 -259
  431. package/test/pre-inject-gate.test.js +444 -444
  432. package/test/profile-extract.test.js +269 -269
  433. package/test/profile-facts.test.js +492 -492
  434. package/test/prompt-stance.test.js +128 -128
  435. package/test/provenance-http.test.js +367 -367
  436. package/test/publish-gate.test.js +272 -272
  437. package/test/quality-backfill.test.js +290 -290
  438. package/test/quality-filter.test.js +295 -295
  439. package/test/readme-claims.test.js +113 -113
  440. package/test/reasoning-effort.test.js +752 -752
  441. package/test/recall-anti-update.test.js +82 -82
  442. package/test/recall-evals.test.js +253 -253
  443. package/test/recall-layer.test.js +326 -326
  444. package/test/recall-retention.test.js +128 -128
  445. package/test/recall-stats.test.js +274 -274
  446. package/test/receipt-chain.test.js +451 -451
  447. package/test/reclaim-http.test.js +209 -209
  448. package/test/reflection-update-gate.test.js +129 -129
  449. package/test/reflection.test.js +226 -226
  450. package/test/reindex-backfill.test.js +475 -475
  451. package/test/reindex-scheduler.test.js +369 -369
  452. package/test/release-docs.test.js +156 -156
  453. package/test/release-notes.test.js +265 -265
  454. package/test/release-workflow.test.js +93 -93
  455. package/test/reranker-single-logit.test.js +73 -73
  456. package/test/reranker.test.js +197 -197
  457. package/test/resident-status.test.js +83 -83
  458. package/test/route-policy.test.js +113 -113
  459. package/test/runtime-adopt.test.js +206 -206
  460. package/test/runtime-cli.test.js +89 -89
  461. package/test/runtime-closure.test.js +193 -193
  462. package/test/runtime-download.test.js +302 -302
  463. package/test/runtime-layout.test.js +163 -163
  464. package/test/runtime-loader.test.js +255 -255
  465. package/test/runtime-manifest.test.js +159 -159
  466. package/test/runtime-model-fetch.test.js +348 -348
  467. package/test/runtime-provision.test.js +101 -101
  468. package/test/runtime-tarball.test.js +81 -81
  469. package/test/runtime-verify.test.js +319 -319
  470. package/test/scope-candidates.test.js +491 -491
  471. package/test/scope-distill-stamp.test.js +134 -134
  472. package/test/scope-explicit.test.js +452 -452
  473. package/test/scope-inject-soft.test.js +106 -106
  474. package/test/scope-optional-service.test.js +110 -110
  475. package/test/scope-retrieval.test.js +197 -197
  476. package/test/scope-state.test.js +610 -610
  477. package/test/scope-storage.test.js +255 -255
  478. package/test/scope-strict.test.js +367 -367
  479. package/test/search-fusion.test.js +90 -90
  480. package/test/security-doc.test.js +188 -188
  481. package/test/semantic.test.js +124 -124
  482. package/test/sensitive-scan.test.js +251 -251
  483. package/test/serve-bin.test.js +121 -121
  484. package/test/serve.test.js +258 -258
  485. package/test/service-search.test.js +174 -174
  486. package/test/service.test.js +410 -410
  487. package/test/session-gate.test.js +264 -264
  488. package/test/session-usage.test.js +317 -317
  489. package/test/settings.test.js +273 -273
  490. package/test/share-foreign-rows.test.js +144 -144
  491. package/test/share-http.test.js +144 -144
  492. package/test/share-import.test.js +577 -577
  493. package/test/share-imported-guard.test.js +311 -311
  494. package/test/share-imported-identity.test.js +111 -111
  495. package/test/share-pack.test.js +511 -511
  496. package/test/share-receive-http.test.js +756 -756
  497. package/test/share-routes.test.js +337 -337
  498. package/test/share-store.test.js +133 -133
  499. package/test/share-validate.test.js +101 -101
  500. package/test/skip-invalid-restore.test.js +262 -262
  501. package/test/sleep-action-set.test.js +441 -441
  502. package/test/sleep-catchup.test.js +315 -315
  503. package/test/sleep-failure-backoff.test.js +156 -156
  504. package/test/sleep-heat.test.js +174 -174
  505. package/test/sleep-scheduler-rearm.test.js +89 -89
  506. package/test/sleep.test.js +1455 -1455
  507. package/test/standalone-api.test.js +1344 -1344
  508. package/test/store-vector-cache.test.js +98 -98
  509. package/test/store-write-path-columns.test.js +191 -191
  510. package/test/store.test.js +744 -744
  511. package/test/stress.test.js +209 -209
  512. package/test/summarize-occurred-at.test.js +127 -127
  513. package/test/summarize-parse-diagnostics.test.js +75 -75
  514. package/test/summarize-reasoning-effort.test.js +189 -189
  515. package/test/summarize.test.js +2251 -2251
  516. package/test/supersede-state-tracking.test.js +394 -394
  517. package/test/test-count-sync.test.js +71 -71
  518. package/test/tool-state-ignore.test.js +65 -65
  519. package/test/tools-dto-schema-matrix.test.js +398 -398
  520. package/test/tools-memory-get-schema.test.js +65 -65
  521. package/test/tools.test.js +585 -585
  522. package/test/updated-at-semantics.test.js +113 -113
  523. package/test/user-settings-preview.test.js +127 -127
  524. package/test/user-settings-segments.test.js +527 -527
  525. package/test/vector-index.test.js +334 -334
  526. package/test/version-check.test.js +152 -152
  527. package/test/webServer-optional.test.js +82 -82
  528. package/test/wiki-api.test.js +734 -734
  529. package/test/wiki-claims.test.js +194 -194
  530. package/test/wiki-cli-hint.test.js +118 -118
  531. package/test/wiki-inject.test.js +265 -265
  532. package/test/wiki-partition.test.js +112 -112
  533. package/test/wiki-plan.test.js +80 -80
  534. package/test/wiki-provenance.test.js +202 -202
  535. package/test/wiki-queries.test.js +398 -398
  536. package/test/wiki-scan.test.js +208 -208
  537. package/test/wiki-store.test.js +190 -190
  538. package/test/wiki-t0.test.js +456 -456
  539. package/test/wiki-t1-jobs.test.js +125 -125
  540. package/test/wiki-t1.test.js +460 -460
  541. package/test/wiki-tools.test.js +176 -176
  542. package/test/workflow-guards.test.js +155 -155
  543. package/test/write-admission.test.js +668 -668
package/src/config.js CHANGED
@@ -1,1344 +1,1361 @@
1
- import z from "@deepseek-ai/schemastery";
2
- import { TYPE_DECAY_DEFAULTS } from "./heat.js";
3
-
4
- // #275 容量预警的阈值默认值,单独导出:service.getCapacityHealth 在 config 缺席
5
- // (直连调用方 / 测试传裸对象)时也要用同一套数字判级,不能另立一份——schema 的
6
- // .default({}) 只在「Config() 解析过」的路径上补值,裸 config 走不到。
7
- // 数字锚定 #275 议题里写的形态区间(活跃数百条、归档数千条),依据见 schema 处注释。
8
- export const CAPACITY_WARNING_DEFAULTS = Object.freeze({
9
- dbSizeWarnMb: 256,
10
- archivedRowsWarn: 5000,
11
- archivedRatioWarn: 0.95
12
- });
13
-
14
- export const Config = z.object({
15
- // 记忆语言:生成记忆、注入标题与后台 LLM 提示词所用语言;'zh'(默认,
16
- // 行为不变)/ 'en'。启动时读入,切换后重启生效。
17
- language: z.union([z.const("zh"), z.const("en")]).default("zh")
18
- .description("记忆语言:生成的记忆条目、注入标题与后台 LLM 提示词所用语言(zh=默认中文,en=英文)。"),
19
- memoryDir: z.string().default("~/.dsh/memory"),
20
- autoInject: z.boolean().default(true),
21
- autoSummarize: z.boolean().default(true),
22
- // Optional model override for summarization. When both are non-empty, they
23
- // take priority over the session's current model. Empty = use the session's
24
- // active provider/model (same as before).
25
- summarizeProvider: z.string().default(""),
26
- summarizeModel: z.string().default(""),
27
- // 蒸馏转录上限(字符)。借鉴 Codex「保留原始、替代压缩摘要」的思路:
28
- // 蒸馏把完整对话上下文交给 LLM 提炼,不硬裁到 8000 字就截断语义;默认
29
- // 24000 字符(约覆盖一整轮中等对话),需要更完整可调大。
30
- distillMaxChars: z.natural().min(1000).max(200000).default(24000),
31
- // autoSummarize 节流、产出上限与写入端同会话去重(Issue #127,默认全 0/off =
32
- // 零行为变化)。此前每个 turn/end 必跑、产出条数由模型输出决定、写入端只按
33
- // 「同类型 + 标题 trim 全等」去重——本机实测单日 84 次、单会话一天 198 条、
34
- // 同一事实 6 小时铸出 28+ 条。阀门交给用户按需拧,升级本身不改行为。
35
- summarizeMinIntervalMinutes: z.natural().min(0).max(10080).default(0),
36
- summarizeMaxEntriesPerRun: z.natural().min(0).max(50).default(0),
37
- // Issue #239(成本感知级联第一级):蒸馏前的零 LLM 预判——窗口内可蒸馏文本
38
- // 不足此字符数时直接跳过 LLM 调用(0 = 关闭,行为与现状逐字节一致)。判定
39
- // 纯规则、无模型参与;被挡下的窗口照常消费游标(否则每个 turn/end 都会重新
40
- // 评估同一段短文本),并留一行 status='skipped' 审计,error_message 写明原因
41
- // ——「这一窗为什么没蒸馏」必须可观测,否则阀门等于黑盒。
42
- summarizeMinWindowChars: z.natural().min(0).max(100000).default(0),
43
- // Issue #239(有界检查点):同一会话最多发起多少次蒸馏(0 = 不限,等同现状)。
44
- // 只统计真正发起过 LLM 调用的 run——被预判挡下的窗口不占预算,否则阀门会把
45
- // 额度浪费在零成本窗口上。计数是进程内 per-session(与最小间隔闸门同生命
46
- // 周期),宿主重启即清零,与 #229 的游标持久化是两件事。
47
- summarizeMaxRunsPerSession: z.natural().min(0).max(1000).default(0),
48
- // Issue #493:连续解析失败几次后放弃当前窗口(0 = 关闭,行为与历史版本逐字节
49
- // 一致)。失败默认不推进游标——语义是「这段还没提炼完,下次接着来」,这对
50
- // 偶发失败(429/5xx/超时)是对的。但确定性失败(模型把转录当成该自己回话,
51
- // finish_reason=stop,同文同温)重试收益为 0,而窗口只会随新事件继续变大,
52
- // 于是一条会话能被同一窗口卡死几十轮、每轮都白烧一次完整调用。设成 2-3 即
53
- // 可止血:达到阈值后强制消费游标跳过该窗口,并留一行带 streak/raw_head 的
54
- // error 审计。默认关是因为放弃内容本身有代价——短窗口偶尔失败仍值得重试。
55
- summarizeParseFailureMaxRetries: z.natural().min(0).max(10).default(0),
56
- // Issue #239(第 4 项,错峰队列):高峰期蒸馏顺延。空串 = 关闭,行为与现状逐
57
- // 字节一致。取值是本地时间的时段列表(逗号分隔,支持跨零点),可带**星期前缀**
58
- // (可省;ISO 1=周一…7=周日,也认 mon..sun;省略 = 每天):
59
- // "09:00-18:00" 每天 09:00-18:00
60
- // "mon-fri 08:00-12:00,14:00-18:00" 工作日两段(按高峰计费的供应商即此形态)
61
- // "sat,sun 23:00-06:00" 周末跨零点段
62
- // 命中高峰时:不调 LLM、不消费游标(窗口继续累积,留到非高峰一次性蒸馏——批量
63
- // 比逐轮碎蒸更省),登记一行 status='skipped' / error_message='peak-hours' 审计,
64
- // 并按下面的上限择时补跑。任一写法非法则整串按「未配置」处理:排程是省钱
65
- // 手段,绝不该因为写错格式把蒸馏停掉。
66
- summarizePeakHours: z.string().default(""),
67
- // 高峰顺延上限(分钟,0 = 不设上限):到点仍处高峰就照常跑,避免整天高峰把蒸馏
68
- // 饿死。默认 120 分钟。仅在上面的时段串非空时生效。
69
- summarizePeakMaxDeferMinutes: z.natural().min(0).max(1440).default(120),
70
- // 落库前去重档位:off(默认,等同现状)/ title(零成本,仅拦完全同名)/
71
- // vector(复用已有 embedding 列做同会话语义近邻,无 LLM 调用)。
72
- summarizeDedupeMode: z.union([z.const("off"), z.const("title"), z.const("vector")]).default("off"),
73
- summarizeDedupeMinSim: z.number().min(0.5).max(0.99).default(0.92),
74
- summarizeDedupeWindowHours: z.natural().min(0).max(168).default(24),
75
- // 智能调速器(429 保护,默认开):蒸馏 LLM 调用全局串行排队,相邻请求
76
- // 间隔 distillRateLimitIntervalMs(默认 1s 一次);命中 429 限流时按
77
- // distillRateLimitBaseDelayMs 指数退避(1s→2s→4s…)自动重试
78
- // distillRateLimitRetries 次,全程对用户透明,不把 429 错误码抛给用户。
79
- distillRateLimitIntervalMs: z.natural().min(0).max(60000).default(1000),
80
- distillRateLimitRetries: z.natural().min(0).max(10).default(3),
81
- distillRateLimitBaseDelayMs: z.natural().min(100).max(60000).default(1000),
82
- maxInjectedItems: z.natural().min(1).max(20).default(5),
83
- // Issue #205:注入位跨轮轮换。同一会话里,最近 N 个不同用户查询轮次注入过
84
- // 的记忆本轮不再优先(新鲜优先、原序回填,槽位数不变);长会话中避免相邻
85
- // 轮次反复注入同一条(实测 15.5h 会话 86 次注入 93% 重复)。0 = 关闭(默认,
86
- // 保持既有行为)。会话边界自动重置:新会话从零开始。
87
- injectRotationTurns: z.natural().min(0).max(20).default(0),
88
- // Issue #164①:注入单条正文截断上限可配(原硬编码 300)。截断时尾部带
89
- // 提示:上限/原长/全文 memory_get 指引(BUDGET_EXCEEDED 原则——绝不静默)。
90
- // 默认 300 = 与既有行为一致。
91
- injectContentMaxChars: z.natural().min(60).max(4000).default(300),
92
- // Issue #34(v0.7.2 引入;v0.7.11 误删后随 #333 恢复):对话开始注入一次当前
93
- // 时间。默认关 = 与既有行为一致;格式与键名不变,存量 feature_flags 恢复即生效。
94
- injectTimePrefix: z.boolean().default(false),
95
- importanceThreshold: z.natural().min(1).max(5).default(3),
96
- // Issue #239(第 5 项):注入条数的查询自适应(默认关)。确定性强的话题收缩注入
97
- // 条数(减半、下限 1),模糊话题(回指/时间线索,或极短查询)维持
98
- // maxInjectedItems 上限——只做**单向收缩**,绝不越过用户配置的上限;判据只看
99
- // 查询本身,不做额外检索(先探针检索等于白付一次 fuseRecall)。
100
- injectUncertaintyAdaptive: z.boolean().default(false),
101
- // #249(第一批;第二批归位为注入子开关):能力说明——「怎么用记忆」的判断
102
- // 指引。落两个零注入成本的位:①`memory_search` / `memory_save` 的工具描述补
103
- // 一句判断指引(工具描述是常驻文本,不进每轮上下文);②一段 order 150 的
104
- // 系统提示段(一次性、同会话内不随轮次变化,因此不作废前缀缓存),只讲总则
105
- // (优先序、何时查、何时写、何时 no-op)。默认开=基础档:#249 §10 的判据是
106
- // 「只修正既有位、不引入新注入时机/新表面/额外 LLM 调用」的子项随父开关
107
- // 生效——「agent 不知道何时该查、何时该写」是已实测的缺口,把它默认关着是反的。
108
- // 父开关 `autoInject` 关闭时它不生效(闸门见 injectChildEnabled);用户显式写进
109
- // feature_flags 的值永远优先于这里的默认值。
110
- injectGuidanceEnabled: z.boolean().default(true),
111
- // #249 N3(压缩边缘双落点):上下文即将大幅精简前抢救「正在做什么」。默认关。
112
- // 触发只认「历史真的被折叠了」的压缩事件(summary / prune / 不带 error 的 end;
113
- // 判定见 continuity.js 的 isCompactionEdge —— start 只是「试过」,带 error 的 end 是失败收尾),
114
- // 不自定一套阈值参数——「压缩边缘」由宿主定义,我们再校准一份只会与之漂移。
115
- // 为什么是「新时机 + 新表面」因而默认关:它往对话里**追加消息**(新的注入表面,
116
- // 参照实现里最容易累积成一堆历史的那类),并多写一张提案表。按 §10 判据,引入
117
- // 新时机/新表面/新成本的子项独立成键、默认关;只修正既有块的(基础内容分池、
118
- // 库可见性行)才随父开关默认开。`pinnedInjectBudget` 与它同批,但属前者之外:
119
- // 那是既有块内的预算,不是新表面。
120
- // 双落点是硬要求,不能只留一半:宿主的压缩摘要器**只看对话里的内容**,只落库不
121
- // 注入,等于在摘要重建里什么都没留下;只注入不落库,则压缩一过就随旧消息一起
122
- // 消失。两者都做,且落库先于注入(注入失败不该丢提案)。
123
- // 祖先:`autoInject`(父关则本项不生效,闸门见 injectChildEnabled)。轻量档默认
124
- // 置关(见 LIGHT_MODE_OFF):轻量档多一份注入物是反的,与 injectGuidanceEnabled
125
- // 同一取舍。注意预设只是默认值而非强制——装配时用户显式开关在它之后展开,勾了就赢
126
- // (合并顺序见 index.js 装配处)。
127
- continuityRescueEnabled: z.boolean().default(false),
128
- // #249(第一批):B1 pin 池预算——约束/偏好类注入条目的独立小上限。约束与
129
- // 偏好被静默降级是本议题的立项核心(同类知识与情景日志同池同速率摘要,实测
130
- // 一轮压缩后仅保 53%、五轮 10%),故这两类不进相关性竞争、不参与跨轮轮换、
131
- // 逐字保真(仅受超大条目的硬顶保护,截断仍带提示)并排在块内排序之前。
132
- // 独立预算的意义:pin 不占 maxInjectedItems 名额,不会把当前任务需要的情景
133
- // 候选挤出预算(另一种「批量塞历史」)。0(默认)= 关闭,注入块构成与既有
134
- // 行为逐字节一致;超出预算的条数在块内如实标注未展示条数,绝不静默。
135
- pinnedInjectBudget: z.natural().min(0).max(5).default(0),
136
- // 编码记忆蒸馏(codingRetrospect,opt-in,默认关)。开启时,turn/end 蒸馏
137
- // 额外提取三类编码专属记忆:rejected_solution(被否决方案)/ pitfall(踩坑)/
138
- // constraint(工程约束)。蒸馏上下文为整轮完整对话(用户输入 → 助手思考/回答
139
- // → 工具调用与结果 → 代码执行),不再只看用户消息,便于提炼踩坑根因。
140
- // 关闭时行为与之前完全一致。
141
- codingRetrospect: z.boolean().default(false),
142
- // 编码任务识别词表(读取侧门控用):命中即视为编码类任务,编码记忆才注入。
143
- // 后一组是「工程协作动作」词:合并/开 PR/看 CI/发版/验收/冲突/分支/
144
- // issue 这类话在原词表里一个都不命中,于是被判成非编码任务,编码记忆
145
- // 整类被 codingGate 滤掉——生产日志里这类 query 占非编码调用的 17.1~17.8%。
146
- // 只加中文:isCodingTask 是纯子串匹配,英文短词(pr/ci/commit)会命中
147
- // prompt / GitHub CLI 路径这类文本,无词边界时误命中率达 57.9%。
148
- // 「评审」与「审评」并列保留,不是笔误:她真实说「等审评」「看CI和审评」
149
- // (非编码池内审评 9 条 / 717 次,评审 5 条 / 421 次,审评反而更多),
150
- // 同义不同形,子串匹配必须两套都放,删掉任一个都会漏掉一类真实说法。
151
- // 刻意不加的:事务(唯一命中是政治学「公共事务」简答)、依赖包(依赖已在表内)。
152
- codingKeywords: z.array(z.string()).default([
153
- "代码", "编码", "写一个", "写个", "实现", "函数", "方法", "类",
154
- "接口", "bug", "调试", "报错", "错误", "异常", "堆栈", "脚本",
155
- "python", "javascript", "typescript", "node", "js", "ts",
156
- "sql", "sqlite", "数据库", "算法", "重构", "优化", "性能",
157
- "测试", "单测", "修复", "补丁", "依赖", "npm", "pip",
158
- "命令行", "shell", "配置", "配置文件", "yaml", "json",
159
- "插件", "开发", "编译", "构建", "部署", "git", "commit",
160
- "review", "前端", "后端", "页面", "组件", "dsh", "memos",
161
- "提交", "合并", "分支", "冲突", "验收", "审查", "评审", "审评", "发版",
162
- "仓库", "回滚", "推送", "拉取", "变基", "上线", "打包", "迁移",
163
- "字段", "鉴权", "超时", "并发", "闪退", "白屏", "端口", "进程",
164
- "守护", "定时任务", "索引", "缓存", "死锁", "内存泄漏", "日志",
165
- "版本号", "环境变量"
166
- ]),
167
- // 编码记忆注入加权系数:编码任务时对 rejected_solution/pitfall/constraint
168
- // 记忆的 importance 乘以该系数排序,让编码记忆在编码场景更靠前。
169
- codingBoostFactor: z.number().min(1).max(5).default(2),
170
- // 注入边界花括号转义(issue #162,v0.7.4 曾修后被 v0.7.11 重构误删):默认开启,
171
- // 在 memory / 短期上下文 / 用户设置三处注入出口对连续 2+ 个花括号插 `\`
172
- // (`{{a}}`→`{\{a\}\}`),让 DSH 的 interpolate() 不再扫到非法变量名而 throw。
173
- // 关闭时按原样透传。
174
- escapePromptVariables: z.boolean().default(true),
175
- autoDream: z.boolean().default(true),
176
- dreamThresholdCount: z.natural().min(1).max(1000).default(10),
177
- dreamThresholdChars: z.natural().min(100).max(100000).default(5000),
178
- dreamDelayMs: z.natural().min(0).max(60000).default(2000),
179
- // autoDream 触发最小间隔(分钟,0 = 不限制,Issue #89 请求 2):高频写入
180
- // 场景下防止巩固调用(含失败重试)连发刷爆配额。间隔从每次实际开跑时刻
181
- // 起算,失败/degraded 的 run 也占用间隔;间隔内的触发请求静默跳过,下一次
182
- // 写入事件会重新评估。
183
- dreamMinIntervalMinutes: z.natural().min(0).max(10080).default(0),
184
- // Issue #292(#135 派生):autoDream 连续失败退避(opt-in,默认关 = 行为与
185
- // 现状逐字节一致)。开启后调度器对连续失败做指数退避:有效最小间隔 =
186
- // dreamMinIntervalMinutes × 2^连续失败数(成功一次清零恢复),封顶 30 分钟。
187
- // #89 的最小间隔闸失败 run 也占用,但间隔恒定——恒定失败的模型(#135 空体
188
- // 面)会按固定节奏连发刷爆配额;退避把下次重试按失败次数指数推远。基数取
189
- // dreamMinIntervalMinutes:基数为 0 时无闸可翻倍,本键不自己产生间隔(先配
190
- // dreamMinIntervalMinutes 再开本键)。与 dreamPeakHours / dreamMinIntervalMinutes
191
- // 同族(节流阀,不新增任何 LLM 调用),故不进 LIGHT_MODE_OFF。
192
- autoDreamFailureBackoff: z.boolean().default(false),
193
- // Issue #557(建议 2,opt-in 默认关 = 审计行逐字节一致):dream 审计行的输入
194
- // 快照瘦身。dream_runs.input 单行体积随记忆量涨(#557 实测平均 40-79 KB/轮,
195
- // 绝大部分是 content),轮数又随天数涨,两者相乘超线性(报告库占 43% 的页)。
196
- // 开启后 auto 轮的快照只留骨架(id/type/title/importance/updated_at,不含
197
- // content)。巩固判定不回读审计行:LLM 输入由记忆库现算(prompt 不变),
198
- // snapshot_hash 在内存里对含 content 的快照计算(receipt 完整性不变);变的
199
- // 只是离线回放——原文不在库里就重算不出 hash,#512「每一行都能回放一次巩固」
200
- // 对瘦身轮降级为骨架级(#275 reclaim 清过 input 的库本就丢过这层,本键把同一
201
- // 权衡前移到写入期)。organize 轮不经过这里(重放载荷走 organize.js 自己的
202
- // saveDreamRun),写入期与 reclaim(run_type != 'organize')同一条边界。
203
- // storage-only、不新增任何 LLM 调用,不进 LIGHT_MODE_OFF(同上定性)。
204
- dreamInputSummaryEnabled: z.boolean().default(false),
205
- // Issue #239(第 4 项,错峰队列)镜像到巩固:高峰期不做梦。与
206
- // summarizePeakHours 同一份时段语法(复用 src/summarize.js 的 parsePeakSpec /
207
- // isInPeakWindow / nextOffPeakAt,不另写解析器):逗号分隔、可带星期前缀、支持
208
- // 跨零点。空串 = 关闭,行为与现状逐字节一致。
209
- // "09:00-18:00" 每天 09:00-18:00
210
- // "mon-fri 08:00-12:00,14:00-18:00" 工作日两段(按高峰计费的供应商即此形态)
211
- // 为什么巩固比蒸馏更该有这道闸:单次巩固的输入是整窗快照(dreamMaxSnapshotSize
212
- // 条),实测一次 run 的 LLM 时长可达数分钟量级,撞上高峰时既贵又慢;而它由写入
213
- // 事件触发、没有天然的「等到空闲再跑」路径。命中高峰时:不调 LLM、不刷新
214
- // baseline(阈值继续累积,留到非高峰一次性巩固),登记一行 status='skipped' /
215
- // error_message='peak-hours' 审计,并按下面的上限择时补跑。任一写法非法则整串
216
- // 按「未配置」处理——排程是省钱手段,绝不该因为写错格式把巩固停掉。
217
- dreamPeakHours: z.string().default(""),
218
- // 高峰顺延上限(分钟,0 = 不设上限):到点仍处高峰就照常跑,避免整天高峰把巩固
219
- // 饿死。默认 120,与 summarizePeakMaxDeferMinutes 对齐。仅在时段串非空时生效。
220
- dreamPeakMaxDeferMinutes: z.natural().min(0).max(1440).default(120),
221
- // 巩固模型路由(settings panel「巩固模型」/ dreamProvider+dreamModel):
222
- // dream 的记忆沉淀专用 LLM 路由,显式配置优先于 agent 默认模型(config-first,
223
- // Issue #25)。模型分类声明:
224
- // - 非思考模型(推荐,如 glm-5-2 类):无 reasoning 声明,effort 请求被 harness
225
- // 拒绝后 withEffortFallback 去掉字段重试即成功;空体/no json array 风险最低。
226
- // - 思考模型(如 deepseek-v4-flash-ga 等 v4-flash-ga 系):默认开推理,可能烧光
227
- // token 预算返回空体;且部分(如 v4-flash-ga)在 harness 侧被声明为不接受任何
228
- // reasoning effort —— 即使去掉 effort 重试,harness 的 defaultEffort 也会顶上来
229
- // 再次拒绝(UNSUPPORTED_REASONING_EFFORT),插件 fallback 无法绕开。
230
- // 选用时建议配 dreamReasoningEffort 并实测;不行就换非思考模型。
231
- dreamProvider: z.string().description("记忆巩固专用模型的服务商(settings「巩固模型」)。巩固反复失败时,优先改用官方非思考模型的服务商(如 deepseek / glm)。"),
232
- dreamModel: z.string().description("记忆巩固专用模型。建议选非思考模型(如 deepseek-chat、glm-5-2 类):思考模型可能烧光 token 预算返回空体,导致巩固失败(UNSUPPORTED_REASONING_EFFORT)。"),
233
- // Issue #135 建议 3:#9 只抬了 max(8192→131072),default 停在 32768。思考型
234
- // 模型的推理与正文共享该预算,effort 未压低时 32768 恰好被推理烧光 → 空体
235
- // ("no json array in llm output",报告实测 146 轮因此失败)。默认抬到上限,
236
- // 让默认配置也留足正文预算;流式计费按实际用量,不按上限。
237
- //
238
- // 成本审计(2026-10-12,本机库只读探针)复验了「默认值该不该降回去」这个问题,
239
- // 结论是**不该降**,故默认维持 131072。三条实测证据:
240
- // 1. 这个键是**上限**,不是计费口径——流式按实际用量计费(与 sleepMaxTokens
241
- // 的 description 同口径)。压上限只在**真撞顶**时生效:近 10 天 88 个成功
242
- // 轮 output 中位 24022、73.9% 超过 16384,因此把默认压到 16384 会让近四分之三
243
- // 的正常轮撞顶。
244
- // 2. 撞顶的后果不是「少烧一点」,而是**整轮失败**:max_tokens 截断 → 流上没有
245
- // 完整 JSON → extractJsonArray 返回 null → finish({ok:false,
246
- // error:"no json array in llm output"})(src/dream.js:1167-1171);而调度器
247
- // **只有 result.ok 才刷新 baseline**(src/dream.js:905-915,注释明写这是刻意
248
- // 设计「失败必须能立刻重试」)→ 下一次写入用**同一批输入**重跑一遍。按 133 个
249
- // 有真实用量的成功轮逐轮折算:cap=16384 撞顶 78.9%、净总量 +54.1%;
250
- // cap=24576 +43.3%;cap=32768 +15.9%;cap=49152 +6.5%;98304 起为 0。
251
- // 即**调低这个值会把 token 花得更多**,而不是更少。
252
- // 3. 真正的大头它治不到:把「时间就近」配上的 133 次成功调用(同一次配对里取
253
- // 分子与分母)的决策正文(merge/update 的 content+title,剥离代码自动补的隐式
254
- // keep)按中英分估(中文 ≈1.5 字符/token、英文 ≈4)折 419,487 token,而同一批
255
- // 调用输出合计 3,543,111 token —— 正文约占 11.8%(换算方法敏感性区间
256
- // 8.2%~22.0%,逐轮中位 12.1%),其余约 88% 是推理与 JSON 骨架。
257
- // 注意别用「全部有 decisions 的 run」当分子配「配对子集」的分母(会得 13.9%
258
- // 这种偏高的数)——跨口径比较先校量纲、分子分母必须同集合。
259
- // 病根是推理与正文共享 max_tokens 单一池子。宿主(DSH)侧确有分离机制:
260
- // DEFAULT_THINKING_BUDGETS = { minimal:1024, low:2048, medium:8192, high:16384 }、
261
- // adjustMaxTokensForThinking()(正文预算 + 推理预算相加后卡模型上限)、
262
- // clampThinkingBudgetToAnswerRoom()(MIN_ANSWER_TOKENS=1024,保证正文至少留
263
- // 1024 token)——但它只对 Anthropic Claude 路径默认生效;chat-completions 网关
264
- // (volcano-engine / deepseek / glm 均在此列)须由路由 profile 开
265
- // compat.supportsThinkingTokenBudget 并指定 compat.thinkingTokenBudgetField
266
- // (vLLM/Qwen/llama.cpp 风格顶层字段,上游端点还必须认它),asar 注释明写
267
- // "Off by default"。两者都是 **profile 级**配置,插件 stream 调用无法
268
- // per-request 传递(ctx.llm.stream 唯一的推理旋钮是 reasoningEffort)——故本插件
269
- // 在调用面无法单独约束推理预算:抬高这个上限是当前路由下唯一可用的止损手段,
270
- // 治标不治本;治本是换非思考模型,或走能分离预算的路由 profile。
271
- // 显式调小仍是允许的(min 256):适合「宁可整轮失败也要压单轮上限」的场景,但请
272
- // 知悉上面第 2 条的净成本方向。需要旧行为(无上限感)的库显式设回 131072。
273
- dreamMaxTokens: z.natural().min(256).max(131072).default(131072),
274
- // Pass-through reasoning effort for dream's LLM calls (Issue #135 建议 4/5).
275
- // Unset (no value) resolves to the LOWEST effort the model declares before
276
- // streaming: background consolidation must produce JSON, and omitting the
277
- // field lets the harness substitute the model's defaultEffort (thinking-type
278
- // models often default to high/max) — the reasoning then drains the whole
279
- // token budget and the run dies with an empty body. Explicit 'none' still
280
- // omits the field (provider default applies); off/low/medium/high are
281
- // forwarded verbatim, unsupported values are remapped (v0.7.26+).
282
- // Caveat: on some thinking models (e.g. v4-flash-ga) the harness declares NO
283
- // supported effort, so even the fallback retry (field stripped) is rejected
284
- // again via its defaultEffort — prefer a non-reasoning dreamProvider/dreamModel.
285
- dreamReasoningEffort: z.union([
286
- z.const("off"),
287
- z.const("low"),
288
- z.const("medium"),
289
- z.const("high"),
290
- z.const("none")
291
- ]).description("巩固模型的推理档位:未配置 = 自动取模型支持的最低档(避免思考模型用自带默认档烧光预算返回空体,#135);显式 'none' = 不发送字段、用服务商自带默认;off/low/medium/high 原样传递。模型不支持的值会自动换用其支持的档位(v0.7.26+)。"),
292
- // 滑动窗口上限(v0.4.4):autoDream 每次只对最近 dreamMaxSnapshotSize 条
293
- // 记忆做 consolidation。大记忆量下全量快照会把 LLM 输入撑爆(636 记忆 →
294
- // 677 "missing" errors、applied=0),窗口外的旧记忆不进 snapshot。
295
- dreamMaxSnapshotSize: z.natural().min(1).max(1000).default(200),
296
- // Issue #104 方向 2:单轮 archive 决策上限。一次性大扫除(12 条互不相关主题
297
- // 被批量归档,其中 8 条疑似误伤长保留类型)是失控信号,与 update 上限同类的
298
- // 全局闸门:skipInvalid 也不豁免,超限整单拒绝。正常清理可调高。
299
- dreamMaxArchivePerRun: z.natural().min(1).max(200).default(8),
300
- // #135 成本审计派生:单轮 merge 决策上限。merge 是唯一**回吐正文**的动作(每条
301
- // 要求模型重新写出合并后的 content,实测平均 550 字符/条),其余动作都不带正文,
302
- // 所以这是「输出里正文占比」唯一可控的杠杆。与 update/archive 上限同类:全局
303
- // 闸门,skipInvalid 也不豁免,超限整单拒绝(errors 由 decisions.js 的数量闸段
304
- // 统一汇总,run 记 failed)。默认 40 的校准依据(只读探针 246 轮 / 近 10 天 88 轮):
305
- // 每轮 merge 数全库 p50 7 / p90 15 / p99 28 / max 39,近 10 天 p99 39 / max 39
306
- // ——40 即观测分布上沿(历史命中 0/246),与 archive 闸默认 8 恰为其 p90=max 是
307
- // 同一校准法。刻意保守(不取 p90=15 之类):超限代价不对称,整单拒绝会让调度器
308
- // 不刷新 baseline、下轮同输入重跑,一次误伤约等于该轮成本 ×2,而探针里 merge≥15
309
- // 的大轮正文占比正常(10~22%),没有失控特征。想拿覆盖换成本的用户可调低。
310
- dreamMaxMergePerRun: z.natural().min(1).max(200).default(40),
311
- // Issue #125:候选集构造方式。"window"(默认,等同现状)只取最近
312
- // dreamMaxSnapshotSize 条;"hybrid" 在此基础上并入**向量翻出的高相似组**——
313
- // 本机实测 45 对「双方活跃且 sim≥0.85」里 0 对能同时进窗口(窗口覆盖率 11.7%),
314
- // 纯时间窗口让"该合并的一对"几乎永远碰不到面。候选总量仍由下面的上限封顶、
315
- // 与库总量解耦,这是相对"调大窗口"的核心收益:输入成本不随库增长。
316
- dreamCandidateMode: z.union([
317
- z.const("window"),
318
- z.const("hybrid")
319
- ]).default("window"),
320
- // hybrid 的候选总量上限;0 = 复用 dreamMaxSnapshotSize(不迁移,需要时显式覆盖)。
321
- dreamCandidateMax: z.natural().min(0).max(5000).default(0),
322
- // Issue #258:总览(dream_summarize)独立路由。consolidate 有 dreamMaxSnapshotSize
323
- // 窗口、总览原为全库无界——两者对 ctx 的需求差 4 倍以上却强制共用 dream 路由,
324
- // dreamProvider 指向小 ctx 模型时总览当场 CONTEXT_WINDOW_EXCEEDED(实测 120,969
325
- // tokens > 32,768),指向大 ctx 模型则 consolidate 的卸载收益归零。未配置 = 回落
326
- // dream 路由,行为逐字节不变。
327
- dreamSummaryProvider: z.string().default("").description("记忆总览(dream_summarize)专用模型服务商,留空用巩固模型。总览输入为全库(或 dreamSummaryMaxInputs 上限),ctx 需求远大于 consolidation,建议大 ctx 模型(issue #258)。"),
328
- dreamSummaryModel: z.string().default("").description("记忆总览(dream_summarize)专用模型,留空用巩固模型;总览输入随库增长,建议大 ctx 非思考模型(issue #258)。"),
329
- // Issue #258:总览输入条数硬上限。0 = 不设上限(历史行为,库增长可能撑爆小
330
- // ctx 模型);>0 时按 updated_at 倒序保留最新的 N 条(与 consolidate 窗口同一
331
- // 排序口径)。总览是常驻叙述而非逐条巩固,限输入只影响口径脚注里的条数。
332
- dreamSummaryMaxInputs: z.natural().min(0).max(100000).default(0),
333
- // hybrid 判"高相似"的阈值;0.85 与 sleep normal 档对齐——两个模块对"高相似"
334
- // 保持同一个定义。
335
- dreamCandidateMinSim: z.number().min(0.5).max(0.99).default(0.85),
336
- // 叙述条(#164 对齐,opt-in):dream 期间按共享 tag 主题聚类(dream/
337
- // narratives.js),每簇 LLM 合成一条叙述落库(source=narrative、evidence
338
- // 回链簇内原子记忆、求交防捏造)。按需检索、不常驻注入。默认关=行为与
339
- // 此前一致;lightMode 强制关闭。
340
- dreamNarrativeEnabled: z.boolean().default(false),
341
- // 成簇门槛:共享同一 tag 的记忆 ≥ 此值才合成叙述条。
342
- dreamNarrativeMinCluster: z.natural().min(2).max(20).default(3),
343
- // document 型记忆(#164/#230,opt-in):agent 产长文档的指针行——注册校验
344
- // (文件存在 + 路径合法 + evidence 求交)、摘要 + doc_path 落库、C2 比对
345
- // 去重、supersede 记账。全文归 agent,管线零触碰;默认关=行为与此前一致;
346
- // lightMode 强制关闭。
347
- documentMemoryEnabled: z.boolean().default(false),
348
- // document 摘要行的注入预算(#230 拍板):次优先档内最多注入的 document
349
- // 行数,超预算跳过由后续候选补位。只约束注入,不约束检索。
350
- documentInjectBudget: z.natural().min(1).max(5).default(2),
351
- // document 的 managed 落盘目录(#296 第二批):空串 = 跟随 memoryDir 的
352
- // `<memoryDir>/documents/`。`~` / `~/` / `~\` 展开到 home,绝对路径原样用,相对
353
- // 路径落在 memoryDir 下(这是本键自己的规则:memoryDir 的相对路径是原样留着的
354
- // cwd 语义,两者刻意不同;`~user` 形式两边都不展开)。目录由 mneme 建,里面的
355
- // `index.md`
356
- // 整文件机器所有。这个目录**之外**的文件只登记指针行,正文一个字节都不碰
357
- // (= #230 的「管线对正文零读零写」);与 memoryDir 一样是路径配置,不是
358
- // 行为开关,故不进 settings.js 的 feature flags 白名单、也不上面板。
359
- documentDir: z.string().default(""),
360
- // 日记本(QQ 机器人写日记):跨天惰性判定 + 启动补写,正文落
361
- // `<memoryDir>/diaries/<项目键>/YYYY-MM-DD.md`,记忆库里留一条 document 指针行。
362
- // 默认关=行为与此前一致;lightMode 强制关闭(它要调模型,与低资源档互斥)。
363
- // 与 documentMemoryEnabled **成对但不相依**:关掉文档记忆只影响「能不能被检索到」,
364
- // 正文照样写——那是用户自己的日记,不该被一个检索开关收走。
365
- diaryEnabled: z.boolean().default(false),
366
- // 日记根目录:空串 = 跟随 `<memoryDir>/diaries/`。与 documentDir 同一套路径规则
367
- // (`~` 展开、绝对路径原样、相对路径落 memoryDir 下),同样是**路径配置不是行为
368
- // 开关**,故不进 settings.js 白名单、也不上面板。
369
- diaryDir: z.string().default(""),
370
- // QQ 收件箱目录:空串 = 从 memoryDir 推(`<memoryDir>/../media/onebot`,本机即
371
- // `D:\DSH\.dsh\media\onebot`)。推出不来就报 no-inbox-dir,绝不猜别的盘。
372
- diaryInboxDir: z.string().default(""),
373
- // 定时写(v0.13,桉桉 2026-11-09「日记本按理来说应该需要定时写,这个要存设置中」):
374
- // 每隔 N 分钟主动检查一次「今天是否还没写」,0 = 关(只在 turn/end 与启动时判,
375
- // 即此前行为)。为什么要这一项:auto.js 原来只在回合结束时判跨天,用户整天不说话
376
- // 就整天不写——日记不该取决于谁先开口。它仍然**不是 cron**:定时器只负责唤醒判定,
377
- // 写不写在 compose 里按落盘日期去重,所以一天最多一篇,失败也只是 warn。
378
- // 上限 1440(一天):比一天更长的间隔没有意义,因为判定单位就是「天」。
379
- diaryWriteEveryMinutes: z.natural().min(0).max(1440).default(0),
380
- // 隐式 keep(v0.4.4):LLM 未提及的 snapshot 记忆自动补 {action:"keep"},
381
- // 避免"未覆盖即全拒"白白浪费整轮 run。设为 false 时保留旧的严格校验
382
- // (未覆盖即拒绝整单)。
383
- dreamImplicitKeep: z.boolean().default(true),
384
- // 显式决策覆盖率下限(v0.4.4 fix):dreamImplicitKeep 开启时,LLM 输出被
385
- // 截断只显式 claim 少量 snapshot 记忆(claimed.size / snapshot.size < 该阈值)
386
- // → 整单拒绝,防止残缺输出被隐式 keep 洗白成 ok 后再被真实 apply。0-1,
387
- // 默认 0.5(至少显式覆盖一半 snapshot)。
388
- dreamMinExplicitCoverage: z.number().min(0).max(1).default(0.5),
389
- // Issue #89:v0.6.9(Issue #26)的宽容路径回归。默认跳过单条非法决策
390
- // (未知 id / 跨类型合并等)、应用合法子集、run 记 degraded;设 false 恢复
391
- // 整单拒绝的严格模式。全局上限与覆盖率下限不受此开关影响、始终整单拒绝。
392
- dreamSkipInvalid: z.boolean().default(true),
393
- // 显式开启后放宽跨类型合并检查(类型边界由用户自行承担);配合
394
- // dreamSkipInvalid 理解:关闭 skipInvalid 时跨类型 merge 直接整单拒绝。
395
- allowCrossTypeMerge: z.boolean().default(false),
396
- // issue #339 / E8 考卷:merge 护栏(默认开)。开启后合并对象命中长保留类型
397
- // (preference/pattern/rejected_solution/constraint/pitfall,与 archive 护栏
398
- // 同一张表)的 merge 决策整条跳过——E8 实测巩固损耗里 10/26 条被丢约束已归位
399
- // guarded 类型仍被 merge 吃掉,而 archive 护栏只挡 archive 不挡 merge,
400
- // 「更精炼的摘要」恰是约束失真的主通道(同类压缩管线实测:安全规则被同速压缩
401
- // 一轮只剩 53%、五轮只剩 10%,且全程无任何报错信号)。skipInvalid(默认开)
402
- // 时被跳条目进 dream_runs.skipped、run 记 degraded;关闭 skipInvalid 时整单
403
- // 拒绝。设为 false 才恢复旧行为:guarded 类型的 merge 不再被跳过,约束可被
404
- // 「更精炼的摘要」吃掉。与 allowCrossTypeMerge 同用时长 guard 先判。
405
- dreamMergeGuard: z.boolean().default(true),
406
- // Rule version for dream adjudication: when this bumps, older dream_runs
407
- // degrade to historical evidence (their receipts no longer drive live
408
- // decisions). Default 0 = no versioning in use yet.
409
- policyEpoch: z.natural().min(0).max(1000000).default(0),
410
-
411
- // --- API protection ------------------------------------------------------
412
- // Optional shared token for the plugin's HTTP API. Empty (default) keeps
413
- // the API open (DSH binds to 127.0.0.1 and has no built-in auth); when set,
414
- // sensitive endpoints (vector-config, vector-reindex, and all write ops on
415
- // profile/rules/commands) require `Authorization: Bearer <apiToken>` (or
416
- // `X-DSH-Mneme-Token`). Read-only list/search/semantic stay open so the
417
- // Web panel keeps working without the token.
418
- apiToken: z.string(),
419
-
420
- // --- semantic: local embedding provider (v0.2) --------------------------
421
- // "openai" keeps the legacy external-API path (settings vector config);
422
- // "local" runs an ONNX model in-process; "ollama" calls a local Ollama.
423
- embedProvider: z.union([z.const("openai"), z.const("local"), z.const("ollama")]).default("openai"),
424
-
425
- // Local ONNX embedder (transformers.js / onnxruntime).
426
- localEmbedModel: z.string().default("Xenova/bge-small-zh-v1.5"),
427
- localEmbedDimension: z.natural().default(512),
428
- localEmbedDevice: z.union([z.const("cpu"), z.const("gpu")]).default("cpu"),
429
- localEmbedBatchSize: z.natural().min(1).max(64).default(8),
430
- // 本地嵌入的池化方式(**bug 修复,不是新能力**):BGE 系是按 CLS 池化训练的——
431
- // 模型自带的 `1_Pooling/config.json` 明确写着 `pooling_mode_cls_token: true` /
432
- // `pooling_mode_mean_tokens: false`,官方 README 也是「select the last hidden state of
433
- // the first token」+ L2 normalize;而 transformers.js 的 `feature-extraction` 默认 mean。
434
- // ⇒ 此前本地嵌入对 BGE 系一直用错池化:**不报错、只是向量系统性偏差**,检索质量静默受损。
435
- // 'auto' = 按模型族判定(BGE → cls,其余 → mean,未受影响的模型行为不变);也可显式钉住。
436
- // ⚠️ 池化决定向量空间,改它会改变 modelHash(见 local-embedder.js),既有索引会被判失配并重建。
437
- localEmbedPooling: z.union([
438
- z.const("auto"),
439
- z.const("cls"),
440
- z.const("mean")
441
- ]).default("auto"),
442
-
443
- // Ollama embedder.
444
- ollamaBaseUrl: z.string().default("http://localhost:11434"),
445
- ollamaModel: z.string().default("nomic-embed-text"),
446
-
447
- // Model download/cache. When empty (default), models are cached under the
448
- // user-level path ~/.dsh/mneme/models (resolved in local-embedder/reranker);
449
- // a non-empty value is used verbatim.
450
- embedModelCacheDir: z.string().default(""),
451
- embedModelMirror: z.string().default("https://hf-mirror.com"),
452
-
453
- // issue #194:模型文件下载的断点续传与重试。开启时 transformers.js 的 env.fetch 被
454
- // 替换为带断点续传的实现(已收字节落盘、Range/If-Range 续传、空闲看门狗),大文件
455
- // 中断后不必整份重来;关闭 = env.fetch 保持原样(线上回滚开关)。
456
- resilientModelDownload: z.boolean().default(true),
457
-
458
- // 自管运行时目录(issue #131)。空 = ~/.dsh/mneme/runtime:里面放收编或下载来的
459
- // transformers + onnxruntime 闭包,插件经 src/runtime/loader.js 的三层解析加载它。
460
- // 目的就是让这条重依赖不必留在宿主 profile 的依赖图里——profile 是所有插件共用的
461
- // 依赖图,留在里面会让「装任何插件都要替它重走一遍这条链」。
462
- runtimeDir: z.string().default(""),
463
-
464
- // 运行时取件的两个来源(issue #131 / PR-C)。都是「克制」的默认:留空 = 不额外改变行为。
465
- // - runtimeTarballDir:本地 .tgz 目录。某个包网络下不到时(例如 onnxruntime-node),
466
- // 把 `npm pack <pkg>` 出来的 tgz 丢进这个目录即可离线取件;有它就优先于联网。
467
- // - runtimeMirror:registry 镜像前缀,例如 https://npmmirror.com/mirrors/npm/ 。
468
- // 留空则用 runtime-manifest.json 里写死的官方地址(那是随包发布的固定清单)。
469
- runtimeTarballDir: z.string().default(""),
470
- runtimeMirror: z.string().default(""),
471
-
472
- // Vector search tuning.
473
- vectorSearchTopK: z.natural().min(1).max(100).default(20),
474
- vectorSearchThreshold: z.number().min(0).max(1).default(0.65),
475
- hybridSearchVectorWeight: z.number().min(0).max(1).default(0.6),
476
- hybridSearchKeywordWeight: z.number().min(0).max(1).default(0.4),
477
- // Lazy auto-backfill of missing embeddings on boot (Bug2): when the vector
478
- // API is configured and rows still lack an embedding, the index is rebuilt
479
- // in the background after a short delay, rate-limited in batches. On by
480
- // default; set false to keep the backfill manual only.
481
- autoReindexOnBoot: z.boolean().default(true),
482
- // Backfill pacing (previously hardcoded 500 / 10 / 200 in semantic.js). A
483
- // fixed 500-per-boot cap meant a 4k-row backlog needed nine restarts to
484
- // clear, and nothing reported that the run had been capped.
485
- // reindexMaxPerBoot = 0 means "no cap, drain the whole backlog this run".
486
- reindexMaxPerBoot: z.natural().default(500),
487
- reindexBatchSize: z.natural().min(1).max(64).default(10),
488
- reindexRateLimitMs: z.natural().default(200),
489
- // Semantic-first injection (Bug4): when enabled, injectCandidates with a
490
- // non-empty query recalls via the vector index first and falls back to the
491
- // rule-based pick to fill/dedupe. Empty query / no vector → legacy behavior.
492
- hybridInject: z.boolean().default(true),
493
-
494
- // --- recall optimization (v0.5.0) ----------------------------------------
495
- // BM25 third recall path beside vector + LIKE keyword (1.1): per-token IDF
496
- // scoring recalls rows whose query terms are scattered — identifiers, code
497
- // fragments, mixed CJK/ASCII — where substring LIKE cannot match.
498
- bm25SearchEnabled: z.boolean().default(true),
499
- // Query-aware vector cutoff (1.2) replacing the fixed 0.65: entity:/attr:
500
- // prefixes loosen to 0.5, short queries tighten to 0.7, long queries loosen
501
- // to 0.6, and a decisive top-1/top-5 score gap loosens to 0.5 so the tail
502
- // still reaches the reranker. Off = legacy fixed threshold behavior.
503
- adaptiveThresholdEnabled: z.boolean().default(true),
504
- // Session-scoped hot memory (1.3): the latest N dialogue rounds rendered
505
- // ahead of the long-term recall block — short-term context that never
506
- // enters the memory store.
507
- hotMemoryEnabled: z.boolean().default(true),
508
- hotMemoryRounds: z.natural().min(1).max(50).default(5),
509
- hotMemoryMaxTokens: z.natural().min(200).max(32000).default(2000),
510
- // Topic-ranked injection (2.2): when a query vector is available the whole
511
- // injection candidate list is re-ordered by similarity to the current
512
- // query instead of keeping the rule-based order.
513
- selectiveInjectEnabled: z.boolean().default(true),
514
- // Search-time semantic dedup (2.3): greedy pass over the merged candidate
515
- // list dropping rows whose embedding cosine-similarity to an already-kept
516
- // row exceeds the threshold — duplicates are filtered at recall time
517
- // instead of waiting for a dream consolidation. Opt-in aggressive mode:
518
- // small embedding models can collapse legitimately distinct rows, so the
519
- // default keeps every recalled row.
520
- searchSemanticDedup: z.boolean().default(false),
521
- searchSemanticDedupThreshold: z.number().min(0.5).max(1).default(0.95),
522
-
523
- // Recall fusion recipe (plan #1): how the keyword/vector/BM25 ranked lists
524
- // are combined into the final ranking. `blend` (default) is the legacy
525
- // behavior — weighted sum for vector/hybrid, union backfill for auto —
526
- // unchanged. `rrf` (Reciprocal Rank Fusion) and `minmax` (min-max normalized
527
- // weighted sum) are rank/scale-aware recipes that fix the unit mismatch the
528
- // issue describes (raw cosine vs keyword score vs normalized IDF are added
529
- // directly). Off by default so existing behavior holds exactly.
530
- recallFusion: z.union([z.const("blend"), z.const("rrf"), z.const("minmax")]).default("blend"),
531
- // Attach a `signals` object { keyword, vector, bm25, final } to each search
532
- // result for transparency/debugging (plan #2). Default off; when on it only
533
- // decorates the returned rows, never changes the ranking.
534
- signalTransparency: z.boolean().default(false),
535
-
536
- // --- semantic: rerank layer (v0.2) --------------------------------------
537
- // Opt-in by default (item ⑥): the local cross-encoder pulls in onnxruntime
538
- // (transformers.js) at init, so a bare install must not load it. Only an
539
- // explicit rerankEnabled=true + rerankProvider="local" constructs LocalReranker.
540
- rerankEnabled: z.boolean().default(false),
541
- rerankProvider: z.union([z.const("local"), z.const("none")]).default("none"),
542
- rerankModel: z.string().default("Xenova/bge-reranker-base"),
543
- rerankBatchSize: z.natural().min(1).max(64).default(8),
544
- rerankMaxCandidates: z.natural().min(5).max(100).default(30),
545
- rerankScoreThreshold: z.number().min(0).max(1).default(0.1),
546
- // #188:量化档,与嵌入层的 useDtype 同语义(q8 = model_quantized.onnx,约为
547
- // fp32 体积的 1/4)。此前重排层不传 dtype,即使缓存里已有量化文件也会去下载
548
- // 1GB 级的 model.onnx。非法值由 transformers 在 init 时抛出 → 重排降级并告警。
549
- rerankDtype: z.string().default("q8"),
550
-
551
- // --- reflection: update decision + failure tracking (v0.2.1) ------------
552
- reflectionUpdateEnabled: z.boolean().default(true),
553
- reflectionFailureTracking: z.boolean().default(true),
554
- reflectionUpdateMaxPerRun: z.natural().min(0).max(5).default(2),
555
- reflectionUpdateMinAgeHours: z.natural().min(0).max(168).default(24),
556
- // failure_memories 的启动期保留期(天)。此前在 index.js 里写死 90,是这一族
557
- // 四张启动期清理表里唯一没有键可陈述的一条(#275 伞下的欠账)。形态对齐
558
- // recallRetentionDays:顶层扁平键、只从配置文件改;暂不进 settings.js 白名单,
559
- // 面板可调是后续的事(那要同时补 api.js 的 NESTED 表与 client.js 的面板行)。
560
- // 下限取 1 而非 0:这张表的用途是「挖掘重复出现的纠正模式」,窗口太短就只剩
561
- // 最近一次;要彻底停掉清理应当显式关掉 reflectionFailureTracking。
562
- reflectionFailureRetentionDays: z.natural().min(1).max(3650).default(90),
563
-
564
- // --- conflict freeze: manual review for conflicting memories (v0.2.1) ---
565
- // Opt-in by default: when true, conflicting memories are not auto-merged
566
- // and are marked as pending manual review instead.
567
- conflictFreezeEnabled: z.boolean().default(false),
568
- // Maximum number of frozen conflicts to keep pending for manual review.
569
- conflictFreezeMaxPending: z.natural().min(1).max(1000).default(100),
570
- // Issue #603 第 4 层(自动裁决档):freeze 模式里相似度极高的同 scope 对直接
571
- // 裁决(保留信息量更大的那条、归档另一条),不进人工队列——人只看模糊地带。
572
- // 默认关:这是会归档记忆的自动动作,必须显式打开。
573
- //
574
- // ⚠️ Issue #616:这一档有三个已知边界,打开前先读,别当成「开了就清净」。
575
- //
576
- // 1) 阈值论证用的是早期快照,当前库上 0.95 等于不生效。
577
- // 注释此前写「345 条有相似度、>=0.95 有 20 条(5.8%)」——2026-11-09 在
578
- // live 库重跑同口径:conflict_pending 全表 748 行,similarity 非 NULL 只有
579
- // 11 行,其中 >=0.95 是 0 行,最高一对 0.914。这不是说阈值错了,是说
580
- // 按 0.95 打开今晚一对都不会被判,用户会以为功能坏了。真要开,先按当前
581
- // 分布重定阈值(配置下限 0.85),别沿用这两个数。
582
- //
583
- // 2) 它不是独立开关,是 freeze 分支的子节点。整段自动裁决都在
584
- // sleep.js 的 `if (config.conflictFreezeEnabled === true)` 里;关掉 freeze
585
- // 会连它一起静默消失。面板上两个键看着平行,实际是父子。
586
- //
587
- // 3) 未标注 scope 会被判成同 scope。isCrossScopePair 走 scopeKeyOf,NULL 归一
588
- // 成 null,null !== null 为 false,于是「归属未知」被当成「归属相同」。
589
- // live 库活跃 8259 行里 2710 行(32.8%)两个 scope 全 NULL,这批因此绕过了
590
- // 「归属是用户的决定、永不自动」(v0.8.1 #170)这条守卫。改判据前不要打开。
591
- //
592
- // 另外 autoResolved/autoFailed 目前没有任何消费者(dream.js 只取 frozen),
593
- // conflict_reviewed 台账也不记赢家、无 API 暴露——自动裁决了什么是看不见的。
594
- // 可见性补齐之前,这一档只适合在能盯日志的前提下短时打开。
595
- conflictAutoResolveEnabled: z.boolean().default(false),
596
- conflictAutoResolveMinSimilarity: z.number().min(0.85).max(1).default(0.95),
597
-
598
- // --- conflict ledger(issue #641,beta)-----------------------------------
599
- // 冲突分诊账本的档位。真实库 788 对人审队列的全量分类:46% 近重复 + 20% 跨作用域
600
- // 重复 + 5% 时序演进,真矛盾只有 ~27%——现行窄 prompt 把前 71% 全逼成了「输赢」,
601
- // 再由人逐对裁决(≈30 条/天,且 92% 的对从未两侧同场)。分诊把「谁该机械处理、
602
- // 谁才是真矛盾」从 LLM 手里拿回来(E13b:winner-take-all 裁决 P(毒胜出)=86.8%)。
603
- //
604
- // "off" 默认。不分类、不记账,conflict 相位与本键引入前逐字节一致。
605
- // "shadow" 只分类 + 落 conflict_ledger 账本(bookkeeping,不触发 write hooks),
606
- // freeze/LLM 路径零变化——beta 实验的测量期:shadow 一周拿真实分诊
607
- // 分布,验证是否复现 46/20/5/27。
608
- // "enforce" 确定性处置:T1(dup)走信息量优先 supersede(supersedeInfoFirstPair,
609
- // 败者归档、胜者正文不动、可追溯注记在被归档侧);T2(跨作用域重复)
610
- // 与 T4(真矛盾)只记账——不停车、不送 LLM、不动正文;T3(时序演进)
611
- // 落取代链:旧行 superseded_by/superseded_at 指向新行,两行都留着、
612
- // 正文都不动,检索侧按降权处理。人审队列与 LLM 裁决都不再消费冲突
613
- // 候选;与 conflictFreezeEnabled 同开时 freeze 让位(降级 shadow
614
- // + warn,#616 父子绑定不继承)。
615
- //
616
- // 判据唯一实现在 src/dream/triage.js;处置共享实现在 decisions.js 的
617
- // supersedeInfoFirstPair。每个档位的行为都真实存在——#616 的「假成功」教训
618
- // (autoMin 0.95 在现库覆盖为 0,开了等于没开)。
619
- conflictLedgerMode: z.union([z.const("off"), z.const("shadow"), z.const("enforce")]).default("off"),
620
- // T1(dup)档的相似度下限。检测阈值(normal 0.85)与自动档 autoMin(0.95)之间
621
- // 的空带由它接管:真实库 reason 相似度回声集中在 0.85-0.93,正好落在两个阈值
622
- // 之间、自动档开了也接不住的那一段。下界与检测阈值同源。
623
- conflictLedgerDupMinSimilarity: z.number().min(0.85).max(1).default(0.9),
624
-
625
- // --- entity gene (v0.3.0) -----------------------------------------------
626
- // Opt-in: when false (default) nothing in the pipeline extracts entities.
627
- // The storage layer (entities/entity_attrs/entity_relations tables + CRUD)
628
- // is always available regardless of this flag.
629
- entityExtractionEnabled: z.boolean().default(false),
630
- // Optional provider override for entity extraction; empty = use the caller's
631
- // default provider/model. Combined with entityExtractionModel — provider
632
- // without model (or vice versa) falls through to the caller default.
633
- entityExtractionProvider: z.string().default(""),
634
- // Optional model override for entity extraction; empty = use the caller's
635
- // default provider/model.
636
- entityExtractionModel: z.string().default(""),
637
- // Reasoning effort for entity extraction (issue #109), mirrors
638
- // dreamReasoningEffort pass-through: 'none' omits the field / provider
639
- // default; off/low/medium/high passed through. A provider that rejects the
640
- // effort retries once without it, so opting in is safe to experiment with.
641
- entityExtractionReasoning: z.union([
642
- z.const("low"),
643
- z.const("medium"),
644
- z.const("high"),
645
- z.const("none")
646
- ]).default("none"),
647
- // Cap on entities per extraction pass and attributes per entity.
648
- entityExtractionMaxEntities: z.natural().min(1).max(20).default(10),
649
- entityExtractionMaxAttrs: z.natural().min(1).max(50).default(20),
650
- // Prefix/semantic search over entity names (used by recall).
651
- entitySearchEnabled: z.boolean().default(true),
652
- // 图召回轴(issue #219):查询文本命中实体名时,把该实体挂联的记忆并入
653
- // 检索融合池(与 BM25 同级的确认/回填信号)。默认关=检索行为与 #219 前
654
- // 逐字节一致;走 feature_flags 面板可启停,lightMode 强制关闭。
655
- entityRecallEnabled: z.boolean().default(false),
656
- // 边权重演化(issue #24 块2):touch 门控是否把被寻回路径的边权重抬升。
657
- // 默认关=边初值纯由建边来源决定(store.RELATION_SOURCE_DEFAULTS),演化
658
- // 不生效;开=被激活的边经 bumpRelationWeight 上涨(只加不减、封顶 1.0)。
659
- graphWeightEnabled: z.boolean().default(false),
660
- // 单次有效触达的抬升幅度。
661
- graphWeightDelta: z.number().min(0).max(1).default(0.1),
662
- // 被动确认(issue #24 块4):heat 触达(记忆被召回/注入=被用)即对该记忆
663
- // 挂联的关系边做被动确认——bumpRelationWeight 抬一格。默认关=零行为变化;
664
- // 开=正常触达演化边权,「关系是用出来的」闭环落地(异常路径复核留后续块)。
665
- graphPassiveConfirm: z.boolean().default(false),
666
-
667
- // --- 图谱锚定层(issue #24 · 块1 Activation Anchoring)-------------------
668
- // entityRecallEnabled 之上再开一层:以查询命中的实体为种子,沿实体关系表
669
- // 级联扩散(默认深度 2),把「邻居实体挂联的记忆」也带进融合池。种子集
670
- // 上限 graphSeedCap、级联深度 graphCascadeDepth 一并下发到 anchorSeeds /
671
- // cascadeDepths(见 src/graph/anchoring.js)。默认关=行为与 #219 单跳轴
672
- // 逐字节一致;走 feature_flags 面板可启停,lightMode 强制关闭。
673
- graphAnchoringEnabled: z.boolean().default(false),
674
- graphSeedCap: z.natural().min(1).max(30).default(12),
675
- graphCascadeDepth: z.natural().min(1).max(3).default(2),
676
-
677
- // 关联提示防幻觉(issue #24 块3):图召回的候选(查询命中实体的挂联记忆)
678
- // 进注入时默认只参与排序、不改变注入块构成(graphInjectHint=false 保守档);
679
- // 开启后注入文本里以固定句式「[检索线索] …」标出,引导语说明这是链路信息
680
- // 非事实断言,防 LLM 单次消费把桥接召回当有依据的事实回头扩散。
681
- graphInjectHint: z.boolean().default(false),
682
- // 图召回候选在注入块里的独立预算(条数上限)。与 pin/document 预算同构:
683
- // 不占 maxInjectedItems 名额、只约束「桥接线索」这类低信任信息的常驻占比。
684
- graphInjectBudget: z.natural().min(0).max(5).default(1),
685
-
686
- // --- sleep mode: idle-triggered deep maintenance (v0.4.0) ---------------
687
- // Opt-in, off by default. Unlike autoDream (threshold-triggered, lightweight)
688
- // sleep fires when the store has been quiet for sleepIdleMinutes and deep-
689
- // maintains the whole library: conflict resolution, archival demotion,
690
- // pattern discovery and entity relation completion. Abortable on user
691
- // activity, audited into dream_runs (run_type='sleep'), and serialized with
692
- // autoDream so the two never overlap.
693
- sleepModeEnabled: z.boolean().default(false),
694
- // Quiet window before a cycle fires (minutes).
695
- sleepIdleMinutes: z.natural().min(1).max(60).default(5),
696
- // Minimum gap between two sleep runs (hours) — a second idle window within
697
- // this interval does not retrigger.
698
- sleepMinIntervalHours: z.natural().min(1).max(168).default(8),
699
- // Issue #529(C 项的替代实现):sleep 连续失败退避(默认关 = 行为逐字节不变)。
700
- // 基数是 sleepMinIntervalHours,有效间隔 = 基数 × 2^连续失败数(成功清零),
701
- // 封顶 max(基数, 24 小时)——不是硬 24 小时:这个键允许配到 168,用户配 48 就是
702
- // 明说「两天跑一次」,退避只能放宽间隔、不该把用户自己的基数压小。
703
- // 为什么不按 issue 原案用 snapshot_hash 判据:sleep 的
704
- // snapshot_hash 是**跑完之后**对 phases 结果算的哈希(dream/sleep.js 的
705
- // createHash(JSON.stringify(phases))),不是输入快照;同一批输入跑两次结果不同
706
- // 就会得到不同哈希,用它当「同一输入又在失败」的判据不成立。退避与 #292 的
707
- // autoDreamFailureBackoff 同构:只放大既有的最小间隔闸,不新增状态面。
708
- sleepFailureBackoff: z.boolean().default(false),
709
- // Conflict adjudication strictness:
710
- // gentle only high-confidence conflicts (threshold 0.92) are resolved
711
- // normal standard dream-level (threshold 0.85)
712
- // aggressive low-confidence pairs are also adjudicated (threshold 0.75)
713
- sleepConflictStrictness: z.union([
714
- z.const("gentle"),
715
- z.const("normal"),
716
- z.const("aggressive")
717
- ]).default("normal"),
718
- // Issue #126:冲突阶段的动作集。"conflict"(默认 = 现状)只用 conflict/keep——
719
- // 后台自动流程不静默扩张行为;"full" 开放六分支(merge / update / supersede /
720
- // differentiate / conflict / keep),互补型与演进型重复因此有了正确出口,不再
721
- // 被迫"输赢化"(实测抽样 10 对里 2 对属演进型/互补型,用 conflict 处理会丢信息)。
722
- sleepActionSet: z.union([
723
- z.const("conflict"),
724
- z.const("full")
725
- ]).default("conflict"),
726
- // Archival demotion tiering (days since last access):
727
- // >= sleepArchiveDays → shrink to summary, full body kept in _full_content
728
- // >= sleepCompressDays → archived outright (entity relations preserved)
729
- sleepArchiveDays: z.natural().min(7).max(365).default(30),
730
- sleepCompressDays: z.natural().min(7).max(365).default(90),
731
- // Pattern discovery scan window (most recent memories to scan).
732
- sleepPatternMinMemories: z.natural().min(10).max(1000).default(100),
733
- // How far back pattern discovery considers entity attr changes (days).
734
- sleepPatternLookbackDays: z.natural().min(1).max(90).default(30),
735
- // Max pattern memories minted per run (0 = disabled).
736
- sleepMaxPatternPerRun: z.natural().min(0).max(10).default(3),
737
- // --- affective attribution (v0.13.0, issue #619) --------------------------
738
- // Opt-in: when false (default) the affective phase does not run at all — no
739
- // LLM call, no relations written. It exists so that a situational emotion
740
- // ("she was anxious on defence day") can be stored as an occasion-bound
741
- // relation instead of being generalised into "she is an anxious person".
742
- sleepAffectiveEnabled: z.boolean().default(false),
743
- // How many **independent sessions** a candidate must span before it may be
744
- // promoted to a type=pattern trait. Below this it stays an occasion-bound
745
- // affective_context relation. The floor is 2 on purpose: N=1 means a single
746
- // occasion, and a single occasion is exactly what must never become a trait
747
- // (that is the failure mode this phase exists to prevent).
748
- sleepAffectiveTraitMinSessions: z.natural().min(2).max(10).default(3),
749
- // Max affective_context relations written per run. Same role as
750
- // sleepMaxPatternPerRun for the relation channel: a runaway model that
751
- // "finds" an emotion in every memory must be capped, not trusted.
752
- sleepAffectiveMaxRelationsPerRun: z.natural().min(0).max(50).default(10),
753
- // Optional LLM route override for sleep's bulk passes (empty = use dream
754
- // route / agent default model).
755
- sleepProvider: z.string().default("").description("sleep 深维护专用模型服务商,留空用巩固模型或当前模型。"),
756
- sleepModel: z.string().default("").description("sleep 深维护专用模型,留空用巩固模型或当前模型;建议同巩固模型选非思考模型。"),
757
- // Pass-through reasoning effort for sleep's LLM passes, same semantics as
758
- // dreamReasoningEffort (Issue #135 建议 4/5): unset resolves to the lowest
759
- // effort the model declares; explicit 'none' omits the field; off/low/
760
- // medium/high are forwarded verbatim.
761
- sleepReasoningEffort: z.union([
762
- z.const("off"),
763
- z.const("low"),
764
- z.const("medium"),
765
- z.const("high"),
766
- z.const("none")
767
- ]).description("同 dreamReasoningEffort:sleep 各阶段 LLM 的推理档位,未配置 = 自动取模型支持的最低档;显式 'none' = 不发送字段、用服务商自带默认。"),
768
- // Pass-through reasoning effort for the distill (summarize) LLM call
769
- // (issue #315). Mirrors entityExtractionReasoning rather than dream: the
770
- // default 'none' omits the field entirely (provider default), so current
771
- // behavior is unchanged until the user opts in — no auto-lowest resolution
772
- // here, distill failures are retried at the window level anyway. off/low/
773
- // medium/high are forwarded verbatim; a provider that rejects the effort
774
- // retries once without it (withEffortFallback), so opting in is safe to
775
- // experiment with. The failure being addressed is the #9 shape on the
776
- // distill path: a thinking model drains the output budget on reasoning and
777
- // the summary comes back empty/truncated.
778
- summarizeReasoningEffort: z.union([
779
- z.const("off"),
780
- z.const("low"),
781
- z.const("medium"),
782
- z.const("high"),
783
- z.const("none")
784
- ]).default("none").description("蒸馏(会话总结提炼)LLM 的推理档位:默认 'none' = 不发送字段、用服务商自带默认;off/low/medium/high 原样传递,被模型拒收时自动去掉字段重试一次(与巩固/睡眠同款降级,#315)。思考型模型建议 off/low,避免推理烧光输出预算。"),
785
- // Issue #257:sleep 冲突/模式两阶段的输出预算(原硬编码 2048)。实测默认档
786
- // 每对裁决约 90 token、24 对 2097——2048 恰好压在边界(53 次运行 48 败);
787
- // full 档六分支实测约 290 token/对、24 对 6967,2048 必然截断。默认 8192
788
- // 覆盖实测峰值(候选对按「每记忆至多一对」去重,饱和在 ~25 对、不随库无限
789
- // 增长);流式计费按实际用量,不按上限。
790
- sleepMaxTokens: z.natural().min(256).max(131072).default(8192).description("sleep 冲突消解与模式发现阶段的 LLM 输出预算上限(token)。原为硬编码 2048,sleepActionSet=full 实测需约 7000 导致裁决被截断而整轮失败;流式计费按实际用量,调大不增加成本。"),
791
-
792
- // Issue #529:冲突阶段此前没有任何输入侧预算——同 scope 的全部相似对被一次性
793
- // 拼进一个 prompt,实测单请求 163,054 输入 token(服务端 --ctx-size 163,840,
794
- // 占 99.5%),449 对的池子必然撞墙;而失败的轮次不落 pairs、判 keep 又是
795
- // no-op,下一轮冷却结束再送**完全相同的输入**,于是 23 轮连续 llm failed。
796
- // 输出侧早有 sleepMaxTokens(#257),输入侧这一半一直是空的。
797
- //
798
- // 每批上限:0 = 不设上限(严格回退到旧的一次性全送,用于排查"是不是分批
799
- // 引入的差异")。取 12 是因为实测单对约 112~168 输入 token、12 对落在
800
- // 1.5K~2K 区间,远低于任何服务商的上下文窗口,也不至于让 LLM 在长清单里
801
- // 走神。
802
- sleepConflictMaxPairs: z.natural().min(0).max(500).default(12).description("sleep 冲突阶段每批送给 LLM 的候选对上限(0 = 不设上限,回退旧的一次性全送)。实测单对约 112~168 输入 token,默认 12 对约 1.5K~2K。"),
803
- // 每轮总上限:分批之后仍要有个总闸,否则一个 449 对的池子会拆成 38 批、
804
- // 每批一次 LLM 往返,单轮跑上几小时(本身也没比撞墙好)。0 = 不设上限。
805
- // 未入选的对不是"被丢弃"——它们留在 conflict_pending 与下一轮的候选池里。
806
- sleepConflictMaxPairsPerRun: z.natural().min(0).max(5000).default(60).description("sleep 冲突阶段单轮累计裁定的候选对上限(0 = 不设上限)。默认 60 对 ≈ 5 批,约 10K 输入 token。"),
807
-
808
- // --- epistemic trust: memory source credibility (v0.4.5) -----------------
809
- // Distinguish memories by source: observation (measured / witnessed),
810
- // subjective (opinion / guess) and inferred (derived from other evidence).
811
- // Opt-in by default: when false (default) retrieval ranking, injection
812
- // marking and dream merge/conflict keepSource are untouched and
813
- // epistemic_status stays inert data (still written + inferred on save, just
814
- // never used to influence behavior).
815
- trustEpistemicWeighting: z.boolean().default(false),
816
-
817
- // --- memory quality filter (Bug7) ------------------------------------------
818
- // Heuristic gate on what deserves the injection/recall surface. When enabled,
819
- // saveWithDedupe scores each new memory after dedupe and before write:
820
- // score >= degradeThreshold (60) → stored normally
821
- // archiveThreshold (30) <= score < 60 → quality_score persisted and the
822
- // injection sort re-ranks by importance * quality_score/100 (degraded)
823
- // score < 30 → archived + tagged low_quality (still explicitly searchable)
824
- // Meta-memory markers, near-duplicates and repetitive filler lose points.
825
- // "Repetitive" is trigram repetition > 0.7 (this change). The shipped v0.7.12
826
- // measure was unique-chars/total, which decays with length: it flagged 94% of
827
- // 800-2000 char memories and 100% of longer ones, i.e. long Chinese prose was
828
- // punished for being long. Threshold rationale lives in quality-filter.js.
829
- memoryQualityFilter: z.object({
830
- enabled: z.boolean().default(true),
831
- archiveThreshold: z.natural().min(1).max(100).default(30),
832
- degradeThreshold: z.natural().min(1).max(100).default(60),
833
- minContentLength: z.natural().min(1).max(1000).default(10),
834
- // Issue #135 附属发现 1:importance ≥ 该值的记忆不参与静默自动归档——评分
835
- // 与信号标签照常写入(可观测)、注入排序照常降权,但 setArchived 跳过。
836
- // 报告实测 150 条低分归档里 128 条 importance ≥ 4(51 条 = 5)。设 1 = 全部
837
- // 豁免(等效关闭自动归档),设 5 = 仅最重要的豁免。
838
- exemptImportance: z.natural().min(1).max(5).default(4)
839
- }).default({}),
840
-
841
- // --- LLM audit trail (Bug8) ------------------------------------------------
842
- // Records every background LLM call into llm_audit_logs: tokens, duration,
843
- // status and which trigger produced it. Failures are recorded as
844
- // status='error' and never block the feature. retentionDays bounds the table:
845
- // older rows are purged on boot.
846
- //
847
- // Covered trigger_source values: autoDream (consolidation + summary),
848
- // autoSummarize (compression), sleep (conflict + pattern) and entityExtract
849
- // (issue #250 — the last two were structurally missing the hook, not a
850
- // deliberate narrowing). entityExtract runs on every memory write, so row
851
- // growth scales with write volume; retentionDays caps the ceiling but not the
852
- // rate. If that turns out too fast, sample by operation_type or give it a
853
- // separate retention — do not add a second gate: this flag is the single one.
854
- llmAudit: z.object({
855
- enabled: z.boolean().default(true),
856
- retentionDays: z.natural().min(1).max(3650).default(90)
857
- }).default({}),
858
-
859
- // --- distill records + provenance (#399) ------------------------------------
860
- // 蒸馏把窗口压成记忆后,原文默认不留——于是「这条记忆出自哪段对话」事后无法
861
- // 复核(arXiv 2609.36059 §3 Eq.1:旁路索引不丢信息,替换才丢)。开启后蒸馏时
862
- // 顺带把**公开**正文原样落盘(distill_records),并给写出的记忆挂上窗口链接
863
- // (memory_provenance)。默认关:这是一份对话原文副本,价值与暴露面都取决于
864
- // 用户是否愿意留档,不该由升级默认打开。
865
- //
866
- // retentionDays 只裁 distill_records(启动即清,挂 llmAudit purge 旁);链接
867
- // 保留指向空区间,面板/CLI 显示「来源已过期」——删原文不等于篡改历史。落盘
868
- // 文本不受 distillMaxChars / trim(args,300) / trim(out,500) 约束(记录是凭证,
869
- // 不是喂给 LLM 的预算);工具调用入参与结果、code 输出不落盘(推理亦然)。
870
- distillRecords: z.object({
871
- enabled: z.boolean().default(false),
872
- retentionDays: z.natural().min(1).max(3650).default(90)
873
- }).default({}),
874
-
875
- // --- capacity warning (#275: 空间维度) ---------------------------------------
876
- // 容量预警:库体积 / 归档区规模过线时在 /health 与面板上报(维护者 2026-10-21
877
- // 接手口径:有 retentionDays 的时间维度,缺空间维度)。三个阈值都是**观察口**,
878
- // 不是动作触发:没有任何周期性消费者(/health 被读到才算一次),预警也永不
879
- // 自动归档 / 删除(#275 拍板:真删由人显式确认)——所以不设 enabled 总开关,
880
- // 数字恒在 /health,阈值 0 = 显式关掉那一条检查。
881
- //
882
- // 默认值锚定议题里的形态区间,不是拍脑袋:
883
- // dbSizeWarnMb 256 —— 议题实测 55 MB 库 VACUUM 0.8s;按同议题的归档增速
884
- // (+143 行/天 ≈ 2.8 MB/天)留约 70 天余量,正落在「现在便宜、以后贵」
885
- // 该开始看数字的量级;
886
- // archivedRowsWarn 5000 —— 「归档是数千条这个量级」的上沿(议题实测 2,400
887
- // 时还没坏);
888
- // archivedRatioWarn 0.95 —— 健康形态本身就落在 0.8~0.9(活跃数百 / 归档
889
- // 数千,议题实测 0.90),0.9 会对着健康库天天报警;0.95 起才是「归档区
890
- // 把活跃区吞掉」的形态。
891
- // 判级实现在 service.getCapacityHealth,答复挂 /health 的 capacity 块;出口是
892
- // 维护卡的无损回收(dsh-mneme reclaim,见 docs/STORAGE.md)与后续批次的归档区
893
- // 周期处理。增速指标(归档净增速率)在 recall-stats 的 archive 块,不在这里重复。
894
- capacityWarning: z.object({
895
- dbSizeWarnMb: z.natural().min(0).max(1048576).default(CAPACITY_WARNING_DEFAULTS.dbSizeWarnMb),
896
- archivedRowsWarn: z.natural().min(0).max(10000000).default(CAPACITY_WARNING_DEFAULTS.archivedRowsWarn),
897
- // 浮点:与 conflictAutoResolveMinSimilarity 同款(面板整数控件吃不下小数,
898
- // 走 FEATURE_FLAG_NUMBER_RANGES)。
899
- archivedRatioWarn: z.number().min(0).max(1).default(CAPACITY_WARNING_DEFAULTS.archivedRatioWarn)
900
- }).default({}),
901
-
902
- // --- write admission (#254) ------------------------------------------------
903
- // 写入准入:把「这条该不该进库」前移到 LLM 之前。与 memoryQualityFilter 的分工
904
- // 是时点不是判据——那个是**写入后**打分归档(扣分可以错,只影响排序与归档),
905
- // 这个是**写入前**判定(硬拒不能错)。
906
- //
907
- // 两个开关都默认关(验收第 1 条「默认关零行为变化」):
908
- // enabled — 跑第 1 级确定性判据。关时 evaluate 的返回与只计量那一阶段逐字段
909
- // 一致,写入路径完全不碰。
910
- // enforce — 命中时真的拒绝。关时判据照跑、审计照留,决策回落 allow(仅告警)。
911
- // 读审计时「metadata.deny 非空且 metadata.decision=allow」就是这一档的指纹。
912
- // 分层是为了能把「先观察」和「开始拦」分成两次上线:enforce 打开前先在真实流量
913
- // 里看误杀面,而不是靠回滚开关当保险。
914
- //
915
- // 第 1 级的判据面刻意收窄到两类:密钥/PII(#164 A2 是判据来源,经
916
- // createWriteAdmission 的 sensitiveScan 注入;本批只定义它怎么被消费)与空白/
917
- // 纯噪声文本。去重键命中**不**进第 1 级——按维护者 09-22 的判定归 write-update
918
- // 放行(2606.24535 的失效模式:同步近重复门跑在异步矛盾检测之前,把本该被裁决
919
- // 的矛盾直接拒掉);G1/G2 的阈值继续只计量,等遥测有分布再单独提。
920
- writeAdmission: z.object({
921
- enabled: z.boolean().default(false),
922
- enforce: z.boolean().default(false)
923
- }).default({}),
924
-
925
- // --- issue #380: pre-inject gate (opinion/stance judgment before injection) -
926
- // 注入前判定:每帧注入候选出池后、进入 system prompt 前,一次池级 LLM 调用判出
927
- // 「会向本次请求注入意见/立场」的记忆(E12 D1 口径,判定协议同源实验
928
- // protocol-preinject.txt)。两级语义复用 writeAdmission:
929
- // enabled — 跑判定。判定 async prefetch + cache、同步消费(宿主 systemPrompt
930
- // 渲染是同步回调,冷缓存首帧降级为不判定原样注入,下一轮同 key 生效);
931
- // 判定完成落 llm_audit_logs(trigger_source=preInjectGate,真实 token 用量,
932
- // related_memory_ids=被标记记忆)——先在真实负载看意见占比分布。
933
- // enforce — 缓存命中帧真的滤除被标记候选。E12 实测只有过滤过判据(D1 −24.4pp,
934
- // 判定+标记 D2 比它差 +17.6pp——给模型看标记的线已被 E2/E3 关闭,本闸判定
935
- // 结果绝不进模型上下文)。enforce 无 enabled 时无效果。
936
- // 降级路径:判定失败/超时/不可解析 → 本帧原样注入 + 审计行 degraded=true 且不
937
- // 缓存——防线故障永不阻塞注入。判定 LLM 走 agentDefaultModel 当前选择(entity
938
- // adapter 同款装配),不设独立 provider/model 键;llmAudit.enabled=false 时本闸
939
- // 不运行(无账目的判定不可见也不该花钱)。lightMode 强制关(LLM-per-turn 附加
940
- // 路径与低资源档互斥)。
941
- preInjectGate: z.object({
942
- enabled: z.boolean().default(false),
943
- enforce: z.boolean().default(false)
944
- }).default({}),
945
-
946
- // --- #164 A2: secret / PII scan at the write boundary ----------------------
947
- // 写入边界的密钥 / PII 判据(src/sensitive-scan.js)自身的闸。与 writeAdmission
948
- // 的 enabled 分开是有意的:那一个管 #254 第 1 级的空白 / 噪声判据,本键管 A2 这
949
- // 一类判据,两批的误杀面差一个量级(空白 / 噪声没有解释空间,邮箱 / 手机号有),
950
- // 绑在同一个开关上就没法单独观察 A2 的命中分布。
951
- //
952
- // 分层必须写清:判据的唯一调用点在 #254 第 1 级的闸门里(write-admission.js 的
953
- // firstLevelHit),闸门不走第 1 级就没人来调这个扫描器。所以本键是「闸门内这一批
954
- // 判据参不参与」,不是一条能独立跑的链路——单开本键就是零行为变化:
955
- // writeAdmission.enabled 关 → 第 1 级整个不跑,本键开也没用
956
- // enabled 开 + 本键关 → 只跑空白 / 噪声那一批
957
- // enabled 开 + 本键开 + enforce 关 → 命中留审计,写入照常(观察档)
958
- // enabled 开 + 本键开 + enforce 开 → 命中即拒绝
959
- // 默认关 = 只计量那一阶段的行为逐字节保留(#332 合并时 sensitiveScan 就是 null)。
960
- // #164 的「默认仅告警、拦截 opt-in」落在 enforce 上:命中落一条审计
961
- // (metadata.deny.kind)但照常写入,真要拦得 enabled 与 enforce 同时开。
962
- // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
963
- sensitiveScanEnabled: z.boolean().default(false),
964
-
965
- // --- recall evaluation: test-result storage (v0.4.6, 方案 B) --------------
966
- // Separate retrieval evaluation snapshots from the production recall audit.
967
- // When false (default) evaluateRetrieval still computes precision/recall/mrr
968
- // and returns them to the caller, but writes nothing to recall_evals — the
969
- // eval table only grows when the operator opts in. Production searchMemories
970
- // audits to recall_runs and NEVER touches recall_evals, regardless of this
971
- // flag (production isolation is unconditional).
972
- evalPersistTestResults: z.boolean().default(false),
973
-
974
- // --- standalone external API (v0.7.12) ------------------------------------
975
- // A plain node:http server for ecosystem integrations that cannot reach the
976
- // DSH-internal webServer. Disabled by default; when enabled the Bearer token
977
- // is persisted in the settings kv ("external_api"), auto-generated on first
978
- // boot. Bind host: keep the loopback default — moving it to a non-loopback
979
- // address exposes the whole memory store to the network and is the
980
- // operator's responsibility.
981
- externalApiEnabled: z.boolean().default(false),
982
- externalApiPort: z.natural().default(8790),
983
- externalApiHost: z.string().default("127.0.0.1"),
984
-
985
- // --- light mode preset (v0.7.12) -------------------------------------------
986
- // One switch for low-resource setups: turns off every background/semantic
987
- // heavy path (dream consolidation, entity extraction, vector pipeline,
988
- // reranker, BM25, semantic dedup / selective inject, sleep mode) while
989
- // keeping the core loop (autoInject, autoSummarize, hot memory, quality
990
- // filter, keyword search). Applied by applyLightModePreset before the config
991
- // reaches any service; a persisted panel_mode="light" (settings kv) counts
992
- // as lightMode=true too and wins over the bundle config.
993
- lightMode: z.boolean().default(false),
994
-
995
- // --- heat: v0.7.0 self-evolution (heat + interest drift) ----------------
996
- // 总开关,默认关(v0.7.12+ 用户已习惯无 heat 行为,默认开=全员行为变更)。
997
- // 开启后:提供热度字段 / sleep 降级联合判定保护 / 前端热度投影,并只在
998
- // 注入排序的优先级层内乘 heat(#218 v1;store 的 order=chrono 分页序与
999
- // 召回融合序不动)。关闭则跳过所有 heat 计算与热度触达,注入排序乘数
1000
- // 恒 1,sleep 降级退回纯时间分层。
1001
- // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
1002
- heatEnabled: z.boolean().default(false),
1003
- // issue #218 / E5 效用考卷:heat 乘进注入排序在真实年龄混合下饿死老约束
1004
- // (现行量级 ≡ 拟合参数,注入集逐条相同;importance-only 遵从 +12.7pp),
1005
- // 故注入侧 heat 改为独立 opt-in:默认关 = heatEnabled 开启后热度字段/sleep
1006
- // 保护/前端投影照常,但注入排序乘数恒 1(与 heat 关闭逐字节一致);
1007
- // 显式开启才恢复「层内乘 heat」的旧序。召回侧时钟(touchLastAccess)与
1008
- // sleep 降级联判不受本键影响——它们不参与注入排序。
1009
- injectHeatEnabled: z.boolean().default(false),
1010
- // 广义指数形状参数 β(heat = exp(-λ·Δt^β),issue #218 拍板):β=1 纯指数。
1011
- // 快慢以 Δt>1 小时为准——β<1 衰减更慢(亚线性长尾)、β>1 更快(超线性);
1012
- // 0<Δt<1 的首小时内方向相反(Δt^β 随 β 增大而变小)。专家调优项,不进面板白名单。
1013
- heatGlobalBeta: z.number().min(0.5).max(2).default(1.0),
1014
- // per-type 衰减因子 λ;λ=0 的类型免疫(热度恒 1.0,sleep 永不降级)。
1015
- // 未知类型走默认 0.002。dict 的键为 type 字符串、值为数字 λ。
1016
- heatTypeDecay: z.dict(z.number(), z.string()).default({ ...TYPE_DECAY_DEFAULTS }),
1017
- // sleep 降级联合判定的热度下限:heat < 该值 且 importance<5 才允许降级。
1018
- sleepHeatThreshold: z.number().min(0).max(1).default(0.05),
1019
- // recordRecall 默认值(recall_runs 记录默认开;显式传 false 的调用方不受影响)。
1020
- recallRecordDefault: z.boolean().default(true),
1021
- // recall_runs 滚动清理保留天数。
1022
- recallRetentionDays: z.natural().min(1).max(3650).default(90),
1023
-
1024
- // --- tool exposure: 慢模型/轻量模型的工具往返节流(v0.8.5)----------------
1025
- // 跨会话记忆已由 inject.js 每轮自动注入系统提示词,memory_search 只用于
1026
- // 「注入里没有、需要深挖」的补充检索;memory_archive 是隐藏/恢复条目的整理
1027
- // 操作,正常会话里很少需要。轻量模型对「何时该用工具」判断弱,容易每轮
1028
- // 顺手调一遍——每次工具调用都是一次串行往返(生成参数→执行→回填→再生成),
1029
- // 在慢模型上会被放大成明显卡顿。这两个开关允许直接隐藏对应工具(默认全
1030
- // 开=行为不变),隐藏后模型根本看不到它,也就不会调。也走 feature_flags
1031
- // 白名单,面板可启停=线上回滚开关。
1032
- disableMemorySearch: z.boolean().default(false),
1033
- disableMemoryArchive: z.boolean().default(false),
1034
-
1035
- // Issue #231 第二批:agent 主动整理接口(src/organize.js)的**暴露**闸。引擎在
1036
- // PR #267 就造好了,当时刻意不进工具列表、不加开关(模块头注释:「与 #249 同批
1037
- // 暴露时只差『注册工具 + 注入指引』一步」),配置面留到这一批再定——就是这里。
1038
- // 与上面两个 disable* 是**反向**语义:那两个把默认在场的工具藏起来,本键把默认
1039
- // 不在场的能力开出来。因为 dryRun→apply 是一套两步协议(apply 必须引用真实报告
1040
- // 的 run_id),让 agent 未经用户同意就能整理记忆,不是「少一次往返」的成本问题,
1041
- // 而是「谁在改库」的授权问题。
1042
- // 进 LIGHT_MODE_OFF:两步协议在弱模型上最容易走歪(漏 run_id、或跳过报告直接
1043
- // apply——那一拒绝就是一次失败的工具调用),而一次 dryRun 的代价是一回嵌入调用
1044
- // 加按 type 有界扫库。这是预设给的默认值、不是强制:用户显式勾选仍然赢(合并顺序
1045
- // 「用户开关 > 轻量预设 > bundle」,同 continuityRescueEnabled / distillRecords.enabled)。
1046
- organizeEnabled: z.boolean().default(false),
1047
-
1048
- // chorography(仓库地志,spec: docs/specs/2026-11-04-chorography-design.md)的
1049
- // 暴露闸(opt-in,默认关):管模型侧三个只读工具(choro_search / read_page /
1050
- // module_map)、面板浏览页,以及 M3 的开局注入。工具描述常驻模型上下文 = 新增
1051
- // 成本,按仓库纪律 opt-in;默认关=工具不存在、面板页空态、注入块不渲染,行为与
1052
- // 0.10.1 逐字节一致。
1053
- //
1054
- // 本键仍**不进 LIGHT_MODE_OFF**,但理由收窄成一句:它是「是否启用这个子系统」,
1055
- // 不是「每轮付多少」。轻量档要付的是下面那条 chorographyInject(把档位压成
1056
- // off)——两层分开,用户才能在轻量档里保留工具、只关掉每轮那块文本。
1057
- chorographyEnabled: z.boolean().default(false),
1058
-
1059
- // 开局注入档位(M3,spec §7):off / light / full,默认 light。
1060
- // light ≤8 行(模块名 + 文件数 + 页状态 + 页路径,按「还有 N 个」明说截断);
1061
- // full 列全部模块;off 不注入但工具照旧可用。
1062
- // 为什么是枚举而不是布尔:spec 原文就是三档,「开了但只给 6 行」与「全给」是两种
1063
- // 用法,布尔键表达不了;跟随 chorographyEnabled 隐式开又会丢掉「留工具、去文本」
1064
- // 这个组合。
1065
- // 为什么写作 z.union([z.const(...)]) 而不是 z.enum:与 summarizeDedupeMode 同款,
1066
- // 全仓枚举键统一这一种写法(z.enum 的报错文案与既有键不一致)。
1067
- // 轻量档**不进 LIGHT_MODE_OFF**:那张表对平铺键一律赋 false,枚举键进去会得到与
1068
- // schema 不符的 false,而 false !== "off" 时注入照样渲染——静默失效比不关更糟。
1069
- // 轻量档的压制改在消费侧(src/wiki/inject.js 的 resolveInjectTier 判 lightMode)。
1070
- chorographyInject: z.union([z.const("off"), z.const("light"), z.const("full")]).default("light"),
1071
-
1072
- // --- scope: v0.8.0 A1 存储层(issue #17)--------------------------------
1073
- // 总开关默认关:关闭时写入不标注 scope、去重维持 (type, title) 现状,行为
1074
- // 逐字节不变。开启后**两条写入路径都自动盖章**(*_scope_source='auto'):
1075
- // ① 工具调用(tools.js:memory_save / memory_update,显式参数改标 'explicit')
1076
- // ② 机器提炼(summarize.js:会话蒸馏;合并进既有行时不盖章,作用域属于那一行
1077
- // 原来被提炼出来的语境——v0.9.0 补,此前只有工具路径盖章,于是「项目记忆」
1078
- // 在库里几乎不存在)。
1079
- // 两路的值同源:agent 维取 session header 的 agentPreset,workspace 维取
1080
- // registry 反查的 canonical path(header.cwd 兜底,取不到 NULL——解析绝不阻塞
1081
- // 写入),去重键扩展为 (type, title, agent_scope, workspace_scope, sensitivity)。
1082
- // 检索侧加权/过滤在 A2/A3 落地。
1083
- // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
1084
- scopeEnabled: z.boolean().default(false),
1085
- // strictScope(A3):硬过滤模式。关闭=A2 软隔离(他 scope 降权保留可见,
1086
- // 检索与注入排序同权重,issue #339 补齐注入通道);
1087
- // 开启后检索/注入/list/get 按 issue #17 四象限可见性公式硬过滤——带他 scope
1088
- // 的记忆完全不可见,未标注(NULL)恒可见;当前会话某维度解析不到时该维度
1089
- // 带标注的记忆一律不可见(fail-closed:身份不明只见全局)。依赖 scopeEnabled
1090
- // 打开才有意义(没有写入标注就无可过滤维度),但独立成键:可先开标注积累
1091
- // 数据、观察 A2 加权质量后再切硬过滤。
1092
- strictScope: z.boolean().default(false),
1093
-
1094
- // scopeState(issue #471):政策轴——作用域级可逆闸门。与上面两个键是**两条不同
1095
- // 的轴**:scopeEnabled/strictScope 管「这条记忆属于谁」(事实轴:持久、跟着记忆
1096
- // 行走、改的是归属数据),本键管「这个作用域现在通不通」(政策轴:暂时、由人单
1097
- // 方面按下、松开即恢复、不改任何记忆行)。
1098
- // 两级语义同 writeAdmission/preInjectGate——enabled 打开时被按下的作用域其候选
1099
- // 照常注入,但留一条审计行说清「本应被拦下几条」(观察档,先在真实流量看会少掉
1100
- // 多少再决定真按);enforce 打开才真的不注入(拦截档)。两个都默认关,打开前
1101
- // 行为与今天逐字段一致。
1102
- // 不进 LIGHT_MODE_OFF:它只是查一张小表的判定,不引入重型后台路径、不加常驻
1103
- // 文本(同 actionPipelineEnabled 的定性)——轻量档预设该关的是重型能力,不该替
1104
- // 用户关掉一个纯判定入口,用户显式勾选仍然赢。
1105
- // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
1106
- scopeState: z.object({
1107
- enabled: z.boolean().default(false),
1108
- enforce: z.boolean().default(false)
1109
- }).default({}),
1110
-
1111
- // --- v0.9.0 M.O.(Modus Operandi,beta)--------------------------
1112
- // 命名:M.O. = Modus Operandi,拉丁语「一个人一贯的做事方式」,缩写 M.O.;
1113
- // 也正是 org 词根 modus(modusensus)。界面主显示 M.O.,设置项 hint 第一句
1114
- // 给全名——缩写不靠注释解释,靠用户第一眼能看见全名。
1115
- // 这是客户端只读视图(做法图谱 + 对话行结算卡)的门控。API 的 effective 取值只认
1116
- // config 顶层/嵌套路径,schema 里没有这个键 → effective 静默少键 → 客户端读
1117
- // 不到就一个座位都不注册(#254 漏登记 NESTED_FLAG_PATHS 是同一个坑)。
1118
- // 默认关:beta 功能必须 opt-in,老用户行为逐字节不变。不进 LIGHT_MODE_OFF——
1119
- // 它只是只读视图、不引入任何重型后台路径,轻量档用户显式勾选仍然赢(预设
1120
- // 该关的是重型能力,不该替用户关掉一个纯 UI 入口)。
1121
- // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
1122
- actionPipelineEnabled: z.boolean().default(false),
1123
-
1124
- // M.O. 执行记账(v0.9.0 第二批):`/mo-run` 显式开一次执行窗口,窗口内的命令执行
1125
- // 才可能被记成一条 run。与上面那个键**分开**:上面管「看图的座位」,这个管
1126
- // 「往账本里写」——看图和写账是两件事,混成一个键会让「只想看图」的人不知不觉
1127
- // 获得写入能力。
1128
- //
1129
- // 默认关(beta 必须 opt-in)。它挂在热路径上(每个会话事件都过一次监听),
1130
- // 所以轻量档要关掉:`LIGHT_MODE_OFF` 里有它。
1131
- actionRunEnabled: z.boolean().default(false),
1132
-
1133
- // M.O. 自动沉淀(v0.9.0 第三批):会话跑完一轮后,把这个项目自己的会话日志挖一遍,
1134
- // 把「反复出现的同一段能力序列」落成提议(memoryDir 下),可选再落一份 draft 草稿。
1135
- //
1136
- // 与上面两个键三件事分开:actionPipelineEnabled 管「看图的座位」,actionRunEnabled
1137
- // 管「往账本里写」,这个管「从日志里长出候选」。三个都是独立能力,混成一个键会让
1138
- // 「只想看图」的人顺手获得写盘能力。
1139
- //
1140
- // 默认关(beta 必须 opt-in),并且进 LIGHT_MODE_OFF:它挂在 session/event 热路径上,
1141
- // 且解压会话日志是**同步**的(本机 118 份实测 16.2s),轻量档最不该背这条路径。
1142
- // 触发侧另有 10 分钟/项目的冷却与 budgetMs 上限(见 src/pipeline-nudge.js 的
1143
- // NUDGE_AUTO_MIN_INTERVAL_MS 与 src/index.js 的装配注释)——旗标只是总闸。
1144
- // (冷却值以常量为准:早前这里写「30 分钟」,与实际 10 分钟不符。)
1145
- actionNudgeEnabled: z.boolean().default(false),
1146
-
1147
- // --- 人格定义页(beta)---------------------------------------------------
1148
- // 注入器渲染人格段的顶层总闸(双闸门的 config 半边;user_settings "persona"
1149
- // 键内的 persona.enabled 是 per-agent 半边,两闸同真才渲染)。API 的 effective
1150
- // 必须含这个键——schema 里没有它 effective 就静默少键,面板开关形同虚设
1151
- // (#254 漏登记 NESTED_FLAG_PATHS 是同一个坑,计数锁 test/api.test.js 钉住)。
1152
- // 默认关:beta 功能必须 opt-in,老用户行为逐字节不变。不进 LIGHT_MODE_OFF——
1153
- // 人格段是用户自写的一小段轻量常驻文本,不引入任何重型后台路径,轻量档用户
1154
- // 显式勾选仍然赢(同 actionPipelineEnabled 的定性)。
1155
- // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
1156
- personaEnabled: z.boolean().default(false),
1157
-
1158
- // 人格寻址的兜底口径(只在上一个键打开时才有意义)。关(默认,= 历史行为):
1159
- // 会话身份没有卡、或有卡但卡被关掉时,一律落 default 卡——于是 default 成了
1160
- // 「所有没建卡的 agent 都能拿到某个人格」,宿主里跑的外部 bot(自带身份与设定)
1161
- // 会在同一帧里收到两句「你是谁」。开:这两种情况都不注入人格,default 退回它的
1162
- // 本意——只兜「这一帧解析不到会话身份」的无名会话(无身份时行为不变)。
1163
- // 默认关是因为这是一次口径变更:老用户升级后注入文本不许静默变化。
1164
- // 不进 LIGHT_MODE_OFF:它只是取卡时的一个分支,不引入常驻文本、也不引入后台路径,
1165
- // 且它本身只在 personaEnabled 打开时才起作用(同 scopeState 的定性)。
1166
- personaStrictAddressing: z.boolean().default(false),
1167
-
1168
- // Issue #497:从活跃记忆反向提取「助手眼中的用户」画像**草稿**(POST
1169
- // /api/dsh-mneme/profile/extract)。默认关有三层理由:① 它每次点一次就是一次 LLM
1170
- // 调用并读上百行记忆,成本与 dream/summarize 同级,不该在没人要求时挂在面上;
1171
- // ② 产物会被用户当成「关于我的第一版画像」读,编造的代价不对称——所以空库直接报错、
1172
- // 解析失败也报错,绝不回空草稿;③ 它**不写** user_settings,采用草稿是面板里另一次
1173
- // 显式保存,画像正文必须是用户手写的原文,不给模型改写权。
1174
- // 进 LIGHT_MODE_OFF:一次调用要读上百行记忆 + 一轮模型生成,与低资源档互斥(同
1175
- // preInjectGate.enabled 的定性);是预设不是强制,用户显式勾选仍然赢。
1176
- profileExtractEnabled: z.boolean().default(false),
1177
-
1178
- // 「Agent 眼中的你」自动事实(2026-11-06,对标 ChatGPT 记忆 / DS Bot 每 bot 的
1179
- // MEMORY.md):dream 巩固轮成功后按会话身份分卡增量维护一份逐条事实清单,
1180
- // 注入在手填画像之后(src/user-settings.js 的渲染顺序),面板可手动触发与逐条
1181
- // 修正(POST /api/dsh-mneme/profile-facts/*)。与 profileExtractEnabled 的三层
1182
- // 理由同源:① 自动轮是 dream 级别的 LLM 调用 + 读上百行记忆,不该在没人要求时
1183
- // 挂在面上;② 产物每帧注入,编造的代价不对称(维护 prompt 的三段硬约束与
1184
- // profileExtract 同源);③ 它**不写** user_settings——手填画像神圣(#497 口径
1185
- // 不变),事实住独立的 profile_facts 表,可被人在面板上逐条修正。
1186
- // 进 LIGHT_MODE_OFF:自动维护轮与低资源档互斥(同 organizeEnabled 的定性);
1187
- // 是预设不是强制,用户显式勾选仍然赢。注入段本身极轻(几十行文本),不额外
1188
- // 占用轻量档预算的考量见键注释末句。
1189
- profileFactsEnabled: z.boolean().default(false),
1190
-
1191
- // chorography 面板闭环(2026-11-08 kb-qoder parity,契约见
1192
- // docs/plans/2026-11-08-kb-qoder-parity.md §3.1)。三个键都是「面板能不能替人
1193
- // 动手」的授权闸,与上面的 chorographyEnabled(子系统总闸)分层:总闸关着时
1194
- // 它们一律不生效,所以不必再进 LIGHT_MODE_OFF —— 轻量档要压的是「每轮付多少」,
1195
- // 而这三个键管的是「谁按下了重跑按钮」,关了也省不下一次调用,反倒让面板在
1196
- // 轻量档里静默少一块可操作区(同 chorographyEnabled 的定性与理由)。
1197
- //
1198
- // 过期的仓库在面板上直接刷新(POST /choro/run 的 refresh)。默认关:一次 T0 重跑
1199
- // 会扫整个仓库并重写 .chorography/ 下的页面,属于「改动工作区」的动作,不该在
1200
- // 没人要求时被面板顺手触发。
1201
- chorographyAutoRefresh: z.boolean().default(false),
1202
- // 面板发起 T1(POST /choro/t1)。默认**开**:这是面板闭环的主路径,关掉等于面板
1203
- // 上那个按钮永远是死的;真花钱的那一步仍由用户点按钮触发,且有
1204
- // chorographyEnabled 总闸在前面。需要「只读面板」的部署可以单独关它。
1205
- chorographyT1FromPanel: z.boolean().default(true),
1206
- // kb 子系统(知识库问答,B 线实现)的总闸。本车道只登记键与白名单,接线由后续
1207
- // 车道做——先有键才能让 /features 稳定暴露 false,客户端不至于静默少键。
1208
- kbEnabled: z.boolean().default(false),
1209
-
1210
- // #539 逐会话无痕:总开关默认关,保持旧用户行为;子代理选择器默认开,但总开关
1211
- // 未开时不生效。手动名单是精确的 session id 匹配。
1212
- sessionGateEnabled: z.boolean().default(false),
1213
- sessionGateSubagents: z.boolean().default(true),
1214
- sessionGateSessions: z.array(z.string()).default([])
1215
- });
1216
-
1217
- // Fields forced to false by the light-mode preset. Everything not listed here
1218
- // (autoInject, autoSummarize, hotMemory*, memoryQualityFilter, dream
1219
- // thresholds/delays, ...) is left untouched — those are the core loop.
1220
- const LIGHT_MODE_OFF = [
1221
- "entityExtractionEnabled",
1222
- "autoDream",
1223
- "sleepModeEnabled",
1224
- // #619:情感归因相位要调模型、且依赖实体抽取产出(抽取关掉就没有实体可挂边),
1225
- // 与低资源档双重互斥。默认本就是关,这里只是轻量预设再确认一次。
1226
- "sleepAffectiveEnabled",
1227
- "rerankEnabled",
1228
- "autoReindexOnBoot",
1229
- "hybridInject",
1230
- // #249 第二批:能力说明在轻量档保持关闭——它是工具描述与提示段上的额外常驻
1231
- // 文本,轻量档(小模型 / 小上下文)不该因默认值翻转而多付这份提示成本。
1232
- "injectGuidanceEnabled",
1233
- "searchSemanticDedup",
1234
- "selectiveInjectEnabled",
1235
- "bm25SearchEnabled",
1236
- // 轻量模式不开图召回轴(#219,依赖实体抽取产出;抽取本身已被关掉)。
1237
- "entityRecallEnabled",
1238
- // 轻量模式不开图谱锚定层级联(#24 块1,依赖实体抽取 + 关系表,抽取已关则无边可扩)。
1239
- "graphAnchoringEnabled",
1240
- // 轻量模式不开边权重演化(#24 块2,依赖实体抽取产出;抽取关则无 v08 边可抬升)。
1241
- "graphWeightEnabled",
1242
- // 轻量模式不开关联提示标注(#24 块3,常驻注入文本多一段提示成本)。
1243
- "graphInjectHint",
1244
- // 轻量模式不开被动确认(#24 块4,依赖实体抽取产出;抽取关则无 v08 边可抬升)。
1245
- "graphPassiveConfirm",
1246
- // 轻量模式不开叙述条(额外 LLM 调用;#164 对齐,opt-in)。
1247
- "dreamNarrativeEnabled",
1248
- // 轻量模式不开 document 指针行(#230,opt-in:注册/注入/检索增强全随闸)。
1249
- "documentMemoryEnabled",
1250
- // #231 第二批:轻量档不开 agent 主动整理。理由不是成本而是协议——dryRun→apply
1251
- // 要求 agent 拿住上一轮的 run_id 再回来,弱模型漏这一环就是一次被拒的调用;
1252
- // 预设关、不剥夺显式开启的权利(合并顺序同 continuityRescueEnabled)。
1253
- "organizeEnabled",
1254
- // 轻量档不开画像事实自动维护轮(2026-11-06):dream 后的逐卡 LLM 调用 + 读上百行
1255
- // 记忆,与低资源档互斥(同 organizeEnabled 的定性)。注入段本身是几十行常驻文本,
1256
- // 关掉自动轮不省注入成本、省的是维护调用——这正是该进这张表的路径。
1257
- "profileFactsEnabled",
1258
- // 轻量模式不开热计算(heat 属于重型增强;关掉后 sleep 降级也退回纯时间分层)。
1259
- "heatEnabled",
1260
- // 轻量模式不开注入前判定(#380:LLM-per-turn 附加路径,每帧注入多一次池级
1261
- // 判定调用——与低资源档互斥;预设给的默认值,用户显式勾选仍然赢)。点分键
1262
- // 由 applyLightModePreset 的嵌套赋值分支处理。
1263
- "preInjectGate.enabled",
1264
- // #249 N3:轻量档默认不开压缩边缘双落点——它往对话里追加消息(新的注入表面),
1265
- // 轻量档(小模型 / 小上下文)最不该再多一份注入物。这是预设给的默认值、不是强制:
1266
- // 用户显式勾选仍然赢(合并顺序「用户开关 > 轻量预设 > bundle 配置」,同
1267
- // injectGuidanceEnabled)。
1268
- "continuityRescueEnabled",
1269
- // #399:轻量档不开蒸馏原文留存——它是一份对话原文副本(磁盘 + 暴露面),
1270
- // 与轻量档「小模型 / 小上下文 / 少留痕」的取向相反。默认关,这里只是
1271
- // 轻量预设再确认一次;用户显式勾选仍然赢(合并顺序同 continuityRescueEnabled)。
1272
- "distillRecords.enabled",
1273
- // M.O. 执行记账:它挂在**热路径**上(每个会话事件都过一次监听)。轻量档的取向
1274
- // 是「小模型 / 小上下文 / 少常驻路径」,这与一个每事件都过一遍的监听器相冲突。
1275
- // 默认关,这里只是轻量预设再确认一次;用户显式勾选仍然赢(合并顺序同上面两条)。
1276
- "actionRunEnabled",
1277
- // #497:轻量档不开画像提取——一次调用要读上百行记忆再生成一段画像,与低资源档
1278
- // 互斥(同 preInjectGate.enabled 的定性)。默认关,这里只是预设再确认一次;
1279
- // 用户显式勾选仍然赢(合并顺序同 continuityRescueEnabled)。
1280
- "profileExtractEnabled",
1281
- // M.O. 自动沉淀:同样挂在 session/event 热路径上,而且解压会话日志是同步 I/O。
1282
- // 轻量档的取向(小模型 / 小上下文 / 少常驻路径)与它直接冲突,预设里关掉;
1283
- // 用户显式勾选仍然赢(合并顺序同上面几条)。
1284
- "actionNudgeEnabled",
1285
- // 日记本:一次要调模型写一整段散文,且挂 session/event 热路径做跨天判定。
1286
- // 与低资源档(小模型 / 小上下文 / 少常驻路径)直接冲突,预设里关掉;
1287
- // 用户显式勾选仍然赢(合并顺序同上面几条)。
1288
- "diaryEnabled"
1289
- ];
1290
-
1291
- /**
1292
- * Apply the light-mode preset to a resolved config object (pure function,
1293
- * exported for tests). When cfg.lightMode is not exactly true the config is
1294
- * returned unchanged; otherwise a shallow copy carries false for every heavy
1295
- * feature. Idempotent and side-effect free.
1296
- */
1297
- export function applyLightModePreset(cfg) {
1298
- if (cfg?.lightMode !== true) return cfg;
1299
- const preset = { ...cfg, lightMode: true };
1300
- for (const key of LIGHT_MODE_OFF) {
1301
- // 点分键(#380 preInjectGate.enabled):对象子键的预设翻转——浅拷贝父对象后
1302
- // 改子键,避免直接改到用户 config 里的同一引用。平铺键照旧直赋。
1303
- const dot = key.indexOf(".");
1304
- if (dot < 0) {
1305
- preset[key] = false;
1306
- continue;
1307
- }
1308
- const parent = key.slice(0, dot);
1309
- preset[parent] = { ...(preset[parent] ?? {}), [key.slice(dot + 1)]: false };
1310
- }
1311
- return preset;
1312
- }
1313
-
1314
- /**
1315
- * #249 第二批:注入形态的父/子开关。父 = `autoInject`(既有的挂载总闸,默认
1316
- * 开)。子项按 #249 §10 的判据分档——看它是否引入**新的注入时机/新的注入
1317
- * 表面/额外成本**:
1318
- *
1319
- * - 只修正既有每轮块内的位(能力说明)→ 随父开关生效,键的默认值给开;
1320
- * - 需要新时机或新表面(N2 回合结束提醒、N3 压缩边缘)→ 独立开关、默认关,
1321
- * 实现时挂到这张表下(今天还没有这两个键,故表里只有能力说明一项)。
1322
- *
1323
- * 语义:**父关 = 子项一律不生效,但子项的持久值原样保留**(父关是「不生效」,
1324
- * 不是「重置用户配置」)。用户显式写进 kv 的值也不因父关被改写。
1325
- *
1326
- * 闸门放在消费点而不是合并后的 cfg 上:压 cfg 会让 `/features` 的 effective
1327
- * 失去「子项自己勾着、父关时当前不生效」这个状态,面板就显示不出来。跨文件
1328
- * 契约见 test/inject-parent-gate.test.js(含面板侧关系的漂移检查)。
1329
- *
1330
- * 表里只放**顶层扁平键**:点号键(如 `memoryQualityFilter.enabled`)在合并后的
1331
- * cfg 里是嵌套对象,`cfg?.["a.b"]` 取不到,挂进来会恒判不生效且不报错。N2/N3
1332
- * 是扁平键,不受这条限制。
1333
- */
1334
- export const INJECT_CHILD_FLAGS = Object.freeze({
1335
- autoInject: Object.freeze(["injectGuidanceEnabled", "continuityRescueEnabled"])
1336
- });
1337
-
1338
- /** 子开关的运行时生效值:父开关显式关(false)时恒不生效。 */
1339
- export function injectChildEnabled(cfg, key) {
1340
- for (const [parent, children] of Object.entries(INJECT_CHILD_FLAGS)) {
1341
- if (children.includes(key) && cfg?.[parent] === false) return false;
1342
- }
1343
- return cfg?.[key] === true;
1344
- }
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { TYPE_DECAY_DEFAULTS } from "./heat.js";
3
+
4
+ // #275 容量预警的阈值默认值,单独导出:service.getCapacityHealth 在 config 缺席
5
+ // (直连调用方 / 测试传裸对象)时也要用同一套数字判级,不能另立一份——schema 的
6
+ // .default({}) 只在「Config() 解析过」的路径上补值,裸 config 走不到。
7
+ // 数字锚定 #275 议题里写的形态区间(活跃数百条、归档数千条),依据见 schema 处注释。
8
+ export const CAPACITY_WARNING_DEFAULTS = Object.freeze({
9
+ dbSizeWarnMb: 256,
10
+ archivedRowsWarn: 5000,
11
+ archivedRatioWarn: 0.95
12
+ });
13
+
14
+ export const Config = z.object({
15
+ // 记忆语言:生成记忆、注入标题与后台 LLM 提示词所用语言;'zh'(默认,
16
+ // 行为不变)/ 'en'。启动时读入,切换后重启生效。
17
+ language: z.union([z.const("zh"), z.const("en")]).default("zh")
18
+ .description("记忆语言:生成的记忆条目、注入标题与后台 LLM 提示词所用语言(zh=默认中文,en=英文)。"),
19
+ memoryDir: z.string().default("~/.dsh/memory"),
20
+ autoInject: z.boolean().default(true),
21
+ autoSummarize: z.boolean().default(true),
22
+ // Optional model override for summarization. When both are non-empty, they
23
+ // take priority over the session's current model. Empty = use the session's
24
+ // active provider/model (same as before).
25
+ summarizeProvider: z.string().default(""),
26
+ summarizeModel: z.string().default(""),
27
+ // 蒸馏转录上限(字符)。借鉴 Codex「保留原始、替代压缩摘要」的思路:
28
+ // 蒸馏把完整对话上下文交给 LLM 提炼,不硬裁到 8000 字就截断语义;默认
29
+ // 24000 字符(约覆盖一整轮中等对话),需要更完整可调大。
30
+ distillMaxChars: z.natural().min(1000).max(200000).default(24000),
31
+ // 图像描述档(docs/DISTILL.md:346):蒸馏前先用一个**多模态模型**给窗口里的
32
+ // 每张图写一段描述,并把描述并进转录——图本身仍然只是 `[图: …]` 占位,原图
33
+ // 不落库、不读字节(#611 的口径不变,这里只补「图里是什么」这段文字)。
34
+ //
35
+ // 默认关,三个理由:① 它要一次额外的视觉调用,且只有带图的窗口才付;② 描述
36
+ // 质量取决于所选模型,配错模型(纯文本模型)时宿主会把图换成占位,等于白花钱
37
+ // 换一行占位——所以必须显式选一个能看图的 provider;③ 转录是蒸馏的唯一输入,
38
+ // 往里加自动生成的内容属于改产物口径,不该在用户没要求时发生。
39
+ //
40
+ // 路由留空 = 不描述(不是「跟随主模型」):蒸馏路由和会话主模型都可能是纯文本
41
+ // 模型,跟着它们跑等于默认把这一次调用浪费掉。三个键在同一条路径上,读一处。
42
+ imageDescribeEnabled: z.boolean().default(false),
43
+ imageDescribeProvider: z.string().default(""),
44
+ imageDescribeModel: z.string().default(""),
45
+ // autoSummarize 节流、产出上限与写入端同会话去重(Issue #127,默认全 0/off =
46
+ // 零行为变化)。此前每个 turn/end 必跑、产出条数由模型输出决定、写入端只按
47
+ // 「同类型 + 标题 trim 全等」去重——本机实测单日 84 次、单会话一天 198 条、
48
+ // 同一事实 6 小时铸出 28+ 条。阀门交给用户按需拧,升级本身不改行为。
49
+ summarizeMinIntervalMinutes: z.natural().min(0).max(10080).default(0),
50
+ summarizeMaxEntriesPerRun: z.natural().min(0).max(50).default(0),
51
+ // Issue #239(成本感知级联第一级):蒸馏前的零 LLM 预判——窗口内可蒸馏文本
52
+ // 不足此字符数时直接跳过 LLM 调用(0 = 关闭,行为与现状逐字节一致)。判定
53
+ // 纯规则、无模型参与;被挡下的窗口照常消费游标(否则每个 turn/end 都会重新
54
+ // 评估同一段短文本),并留一行 status='skipped' 审计,error_message 写明原因
55
+ // ——「这一窗为什么没蒸馏」必须可观测,否则阀门等于黑盒。
56
+ summarizeMinWindowChars: z.natural().min(0).max(100000).default(0),
57
+ // Issue #239(有界检查点):同一会话最多发起多少次蒸馏(0 = 不限,等同现状)。
58
+ // 只统计真正发起过 LLM 调用的 run——被预判挡下的窗口不占预算,否则阀门会把
59
+ // 额度浪费在零成本窗口上。计数是进程内 per-session(与最小间隔闸门同生命
60
+ // 周期),宿主重启即清零,与 #229 的游标持久化是两件事。
61
+ summarizeMaxRunsPerSession: z.natural().min(0).max(1000).default(0),
62
+ // Issue #493:连续解析失败几次后放弃当前窗口(0 = 关闭,行为与历史版本逐字节
63
+ // 一致)。失败默认不推进游标——语义是「这段还没提炼完,下次接着来」,这对
64
+ // 偶发失败(429/5xx/超时)是对的。但确定性失败(模型把转录当成该自己回话,
65
+ // finish_reason=stop,同文同温)重试收益为 0,而窗口只会随新事件继续变大,
66
+ // 于是一条会话能被同一窗口卡死几十轮、每轮都白烧一次完整调用。设成 2-3 即
67
+ // 可止血:达到阈值后强制消费游标跳过该窗口,并留一行带 streak/raw_head 的
68
+ // error 审计。默认关是因为放弃内容本身有代价——短窗口偶尔失败仍值得重试。
69
+ summarizeParseFailureMaxRetries: z.natural().min(0).max(10).default(0),
70
+ // Issue #239(第 4 项,错峰队列):高峰期蒸馏顺延。空串 = 关闭,行为与现状逐
71
+ // 字节一致。取值是本地时间的时段列表(逗号分隔,支持跨零点),可带**星期前缀**
72
+ // (可省;ISO 1=周一…7=周日,也认 mon..sun;省略 = 每天):
73
+ // "09:00-18:00" 每天 09:00-18:00
74
+ // "mon-fri 08:00-12:00,14:00-18:00" 工作日两段(按高峰计费的供应商即此形态)
75
+ // "sat,sun 23:00-06:00" 周末跨零点段
76
+ // 命中高峰时:不调 LLM、不消费游标(窗口继续累积,留到非高峰一次性蒸馏——批量
77
+ // 比逐轮碎蒸更省),登记一行 status='skipped' / error_message='peak-hours' 审计,
78
+ // 并按下面的上限择时补跑。任一写法非法则整串按「未配置」处理:排程是省钱
79
+ // 手段,绝不该因为写错格式把蒸馏停掉。
80
+ summarizePeakHours: z.string().default(""),
81
+ // 高峰顺延上限(分钟,0 = 不设上限):到点仍处高峰就照常跑,避免整天高峰把蒸馏
82
+ // 饿死。默认 120 分钟。仅在上面的时段串非空时生效。
83
+ summarizePeakMaxDeferMinutes: z.natural().min(0).max(1440).default(120),
84
+ // 落库前去重档位:off(默认,等同现状)/ title(零成本,仅拦完全同名)/
85
+ // vector(复用已有 embedding 列做同会话语义近邻,无 LLM 调用)。
86
+ summarizeDedupeMode: z.union([z.const("off"), z.const("title"), z.const("vector")]).default("off"),
87
+ summarizeDedupeMinSim: z.number().min(0.5).max(0.99).default(0.92),
88
+ summarizeDedupeWindowHours: z.natural().min(0).max(168).default(24),
89
+ // 智能调速器(429 保护,默认开):蒸馏 LLM 调用全局串行排队,相邻请求
90
+ // 间隔 distillRateLimitIntervalMs(默认 1s 一次);命中 429 限流时按
91
+ // distillRateLimitBaseDelayMs 指数退避(1s→2s→4s…)自动重试
92
+ // distillRateLimitRetries 次,全程对用户透明,不把 429 错误码抛给用户。
93
+ distillRateLimitIntervalMs: z.natural().min(0).max(60000).default(1000),
94
+ distillRateLimitRetries: z.natural().min(0).max(10).default(3),
95
+ distillRateLimitBaseDelayMs: z.natural().min(100).max(60000).default(1000),
96
+ maxInjectedItems: z.natural().min(1).max(20).default(5),
97
+ // Issue #205:注入位跨轮轮换。同一会话里,最近 N 个不同用户查询轮次注入过
98
+ // 的记忆本轮不再优先(新鲜优先、原序回填,槽位数不变);长会话中避免相邻
99
+ // 轮次反复注入同一条(实测 15.5h 会话 86 次注入 93% 重复)。0 = 关闭(默认,
100
+ // 保持既有行为)。会话边界自动重置:新会话从零开始。
101
+ injectRotationTurns: z.natural().min(0).max(20).default(0),
102
+ // Issue #164①:注入单条正文截断上限可配(原硬编码 300)。截断时尾部带
103
+ // 提示:上限/原长/全文 memory_get 指引(BUDGET_EXCEEDED 原则——绝不静默)。
104
+ // 默认 300 = 与既有行为一致。
105
+ injectContentMaxChars: z.natural().min(60).max(4000).default(300),
106
+ // Issue #34(v0.7.2 引入;v0.7.11 误删后随 #333 恢复):对话开始注入一次当前
107
+ // 时间。默认关 = 与既有行为一致;格式与键名不变,存量 feature_flags 恢复即生效。
108
+ injectTimePrefix: z.boolean().default(false),
109
+ importanceThreshold: z.natural().min(1).max(5).default(3),
110
+ // Issue #239(第 5 项):注入条数的查询自适应(默认关)。确定性强的话题收缩注入
111
+ // 条数(减半、下限 1),模糊话题(回指/时间线索,或极短查询)维持
112
+ // maxInjectedItems 上限——只做**单向收缩**,绝不越过用户配置的上限;判据只看
113
+ // 查询本身,不做额外检索(先探针检索等于白付一次 fuseRecall)。
114
+ injectUncertaintyAdaptive: z.boolean().default(false),
115
+ // #249(第一批;第二批归位为注入子开关):能力说明——「怎么用记忆」的判断
116
+ // 指引。落两个零注入成本的位:①`memory_search` / `memory_save` 的工具描述补
117
+ // 一句判断指引(工具描述是常驻文本,不进每轮上下文);②一段 order 150 的
118
+ // 系统提示段(一次性、同会话内不随轮次变化,因此不作废前缀缓存),只讲总则
119
+ // (优先序、何时查、何时写、何时 no-op)。默认开=基础档:#249 §10 的判据是
120
+ // 「只修正既有位、不引入新注入时机/新表面/额外 LLM 调用」的子项随父开关
121
+ // 生效——「agent 不知道何时该查、何时该写」是已实测的缺口,把它默认关着是反的。
122
+ // 父开关 `autoInject` 关闭时它不生效(闸门见 injectChildEnabled);用户显式写进
123
+ // feature_flags 的值永远优先于这里的默认值。
124
+ injectGuidanceEnabled: z.boolean().default(true),
125
+ // #249 N3(压缩边缘双落点):上下文即将大幅精简前抢救「正在做什么」。默认关。
126
+ // 触发只认「历史真的被折叠了」的压缩事件(summary / prune / 不带 error 的 end;
127
+ // 判定见 continuity.js 的 isCompactionEdge —— start 只是「试过」,带 error 的 end 是失败收尾),
128
+ // 不自定一套阈值参数——「压缩边缘」由宿主定义,我们再校准一份只会与之漂移。
129
+ // 为什么是「新时机 + 新表面」因而默认关:它往对话里**追加消息**(新的注入表面,
130
+ // 参照实现里最容易累积成一堆历史的那类),并多写一张提案表。按 §10 判据,引入
131
+ // 新时机/新表面/新成本的子项独立成键、默认关;只修正既有块的(基础内容分池、
132
+ // 库可见性行)才随父开关默认开。`pinnedInjectBudget` 与它同批,但属前者之外:
133
+ // 那是既有块内的预算,不是新表面。
134
+ // 双落点是硬要求,不能只留一半:宿主的压缩摘要器**只看对话里的内容**,只落库不
135
+ // 注入,等于在摘要重建里什么都没留下;只注入不落库,则压缩一过就随旧消息一起
136
+ // 消失。两者都做,且落库先于注入(注入失败不该丢提案)。
137
+ // 祖先:`autoInject`(父关则本项不生效,闸门见 injectChildEnabled)。轻量档默认
138
+ // 置关(见 LIGHT_MODE_OFF):轻量档多一份注入物是反的,与 injectGuidanceEnabled
139
+ // 同一取舍。注意预设只是默认值而非强制——装配时用户显式开关在它之后展开,勾了就赢
140
+ // (合并顺序见 index.js 装配处)。
141
+ continuityRescueEnabled: z.boolean().default(false),
142
+ // #249(第一批):B1 pin 池预算——约束/偏好类注入条目的独立小上限。约束与
143
+ // 偏好被静默降级是本议题的立项核心(同类知识与情景日志同池同速率摘要,实测
144
+ // 一轮压缩后仅保 53%、五轮 10%),故这两类不进相关性竞争、不参与跨轮轮换、
145
+ // 逐字保真(仅受超大条目的硬顶保护,截断仍带提示)并排在块内排序之前。
146
+ // 独立预算的意义:pin 不占 maxInjectedItems 名额,不会把当前任务需要的情景
147
+ // 候选挤出预算(另一种「批量塞历史」)。0(默认)= 关闭,注入块构成与既有
148
+ // 行为逐字节一致;超出预算的条数在块内如实标注未展示条数,绝不静默。
149
+ pinnedInjectBudget: z.natural().min(0).max(5).default(0),
150
+ // 编码记忆蒸馏(codingRetrospect,opt-in,默认关)。开启时,turn/end 蒸馏
151
+ // 额外提取三类编码专属记忆:rejected_solution(被否决方案)/ pitfall(踩坑)/
152
+ // constraint(工程约束)。蒸馏上下文为整轮完整对话(用户输入 → 助手思考/回答
153
+ // → 工具调用与结果 → 代码执行),不再只看用户消息,便于提炼踩坑根因。
154
+ // 关闭时行为与之前完全一致。
155
+ codingRetrospect: z.boolean().default(false),
156
+ // 编码任务识别词表(读取侧门控用):命中即视为编码类任务,编码记忆才注入。
157
+ // 后一组是「工程协作动作」词:合并/开 PR/看 CI/发版/验收/冲突/分支/
158
+ // issue 这类话在原词表里一个都不命中,于是被判成非编码任务,编码记忆
159
+ // 整类被 codingGate 滤掉——生产日志里这类 query 占非编码调用的 17.1~17.8%。
160
+ // 只加中文:isCodingTask 是纯子串匹配,英文短词(pr/ci/commit)会命中
161
+ // prompt / GitHub CLI 路径这类文本,无词边界时误命中率达 57.9%。
162
+ // 「评审」与「审评」并列保留,不是笔误:她真实说「等审评」「看CI和审评」
163
+ // (非编码池内审评 9 条 / 717 次,评审 5 条 / 421 次,审评反而更多),
164
+ // 同义不同形,子串匹配必须两套都放,删掉任一个都会漏掉一类真实说法。
165
+ // 刻意不加的:事务(唯一命中是政治学「公共事务」简答)、依赖包(依赖已在表内)。
166
+ codingKeywords: z.array(z.string()).default([
167
+ "代码", "编码", "写一个", "写个", "实现", "函数", "方法", "类",
168
+ "接口", "bug", "调试", "报错", "错误", "异常", "堆栈", "脚本",
169
+ "python", "javascript", "typescript", "node", "js", "ts",
170
+ "sql", "sqlite", "数据库", "算法", "重构", "优化", "性能",
171
+ "测试", "单测", "修复", "补丁", "依赖", "npm", "pip",
172
+ "命令行", "shell", "配置", "配置文件", "yaml", "json",
173
+ "插件", "开发", "编译", "构建", "部署", "git", "commit",
174
+ "review", "前端", "后端", "页面", "组件", "dsh", "memos",
175
+ "提交", "合并", "分支", "冲突", "验收", "审查", "评审", "审评", "发版",
176
+ "仓库", "回滚", "推送", "拉取", "变基", "上线", "打包", "迁移",
177
+ "字段", "鉴权", "超时", "并发", "闪退", "白屏", "端口", "进程",
178
+ "守护", "定时任务", "索引", "缓存", "死锁", "内存泄漏", "日志",
179
+ "版本号", "环境变量"
180
+ ]),
181
+ // 编码记忆注入加权系数:编码任务时对 rejected_solution/pitfall/constraint
182
+ // 记忆的 importance 乘以该系数排序,让编码记忆在编码场景更靠前。
183
+ codingBoostFactor: z.number().min(1).max(5).default(2),
184
+ // 注入边界花括号转义(issue #162,v0.7.4 曾修后被 v0.7.11 重构误删):默认开启,
185
+ // 在 memory / 短期上下文 / 用户设置三处注入出口对连续 2+ 个花括号插 `\`
186
+ // (`{{a}}`→`{\{a\}\}`),让 DSH 的 interpolate() 不再扫到非法变量名而 throw。
187
+ // 关闭时按原样透传。
188
+ escapePromptVariables: z.boolean().default(true),
189
+ autoDream: z.boolean().default(true),
190
+ dreamThresholdCount: z.natural().min(1).max(1000).default(10),
191
+ dreamThresholdChars: z.natural().min(100).max(100000).default(5000),
192
+ dreamDelayMs: z.natural().min(0).max(60000).default(2000),
193
+ // autoDream 触发最小间隔(分钟,0 = 不限制,Issue #89 请求 2):高频写入
194
+ // 场景下防止巩固调用(含失败重试)连发刷爆配额。间隔从每次实际开跑时刻
195
+ // 起算,失败/degraded 的 run 也占用间隔;间隔内的触发请求静默跳过,下一次
196
+ // 写入事件会重新评估。
197
+ dreamMinIntervalMinutes: z.natural().min(0).max(10080).default(0),
198
+ // Issue #292(#135 派生):autoDream 连续失败退避(opt-in,默认关 = 行为与
199
+ // 现状逐字节一致)。开启后调度器对连续失败做指数退避:有效最小间隔 =
200
+ // dreamMinIntervalMinutes × 2^连续失败数(成功一次清零恢复),封顶 30 分钟。
201
+ // #89 的最小间隔闸失败 run 也占用,但间隔恒定——恒定失败的模型(#135 空体
202
+ // 面)会按固定节奏连发刷爆配额;退避把下次重试按失败次数指数推远。基数取
203
+ // dreamMinIntervalMinutes:基数为 0 时无闸可翻倍,本键不自己产生间隔(先配
204
+ // dreamMinIntervalMinutes 再开本键)。与 dreamPeakHours / dreamMinIntervalMinutes
205
+ // 同族(节流阀,不新增任何 LLM 调用),故不进 LIGHT_MODE_OFF。
206
+ autoDreamFailureBackoff: z.boolean().default(false),
207
+ // Issue #557(建议 2,opt-in 默认关 = 审计行逐字节一致):dream 审计行的输入
208
+ // 快照瘦身。dream_runs.input 单行体积随记忆量涨(#557 实测平均 40-79 KB/轮,
209
+ // 绝大部分是 content),轮数又随天数涨,两者相乘超线性(报告库占 43% 的页)。
210
+ // 开启后 auto 轮的快照只留骨架(id/type/title/importance/updated_at,不含
211
+ // content)。巩固判定不回读审计行:LLM 输入由记忆库现算(prompt 不变),
212
+ // snapshot_hash 在内存里对含 content 的快照计算(receipt 完整性不变);变的
213
+ // 只是离线回放——原文不在库里就重算不出 hash,#512「每一行都能回放一次巩固」
214
+ // 对瘦身轮降级为骨架级(#275 reclaim 清过 input 的库本就丢过这层,本键把同一
215
+ // 权衡前移到写入期)。organize 轮不经过这里(重放载荷走 organize.js 自己的
216
+ // saveDreamRun),写入期与 reclaim(run_type != 'organize')同一条边界。
217
+ // storage-only、不新增任何 LLM 调用,不进 LIGHT_MODE_OFF(同上定性)。
218
+ dreamInputSummaryEnabled: z.boolean().default(false),
219
+ // Issue #239(第 4 项,错峰队列)镜像到巩固:高峰期不做梦。与
220
+ // summarizePeakHours 同一份时段语法(复用 src/summarize.js 的 parsePeakSpec /
221
+ // isInPeakWindow / nextOffPeakAt,不另写解析器):逗号分隔、可带星期前缀、支持
222
+ // 跨零点。空串 = 关闭,行为与现状逐字节一致。
223
+ // "09:00-18:00" 每天 09:00-18:00
224
+ // "mon-fri 08:00-12:00,14:00-18:00" 工作日两段(按高峰计费的供应商即此形态)
225
+ // 为什么巩固比蒸馏更该有这道闸:单次巩固的输入是整窗快照(dreamMaxSnapshotSize
226
+ // 条),实测一次 run 的 LLM 时长可达数分钟量级,撞上高峰时既贵又慢;而它由写入
227
+ // 事件触发、没有天然的「等到空闲再跑」路径。命中高峰时:不调 LLM、不刷新
228
+ // baseline(阈值继续累积,留到非高峰一次性巩固),登记一行 status='skipped' /
229
+ // error_message='peak-hours' 审计,并按下面的上限择时补跑。任一写法非法则整串
230
+ // 按「未配置」处理——排程是省钱手段,绝不该因为写错格式把巩固停掉。
231
+ dreamPeakHours: z.string().default(""),
232
+ // 高峰顺延上限(分钟,0 = 不设上限):到点仍处高峰就照常跑,避免整天高峰把巩固
233
+ // 饿死。默认 120,与 summarizePeakMaxDeferMinutes 对齐。仅在时段串非空时生效。
234
+ dreamPeakMaxDeferMinutes: z.natural().min(0).max(1440).default(120),
235
+ // 巩固模型路由(settings panel「巩固模型」/ dreamProvider+dreamModel):
236
+ // dream 的记忆沉淀专用 LLM 路由,显式配置优先于 agent 默认模型(config-first,
237
+ // Issue #25)。模型分类声明:
238
+ // - 非思考模型(推荐,如 glm-5-2 类):无 reasoning 声明,effort 请求被 harness
239
+ // 拒绝后 withEffortFallback 去掉字段重试即成功;空体/no json array 风险最低。
240
+ // - 思考模型(如 deepseek-v4-flash-ga 等 v4-flash-ga 系):默认开推理,可能烧光
241
+ // token 预算返回空体;且部分(如 v4-flash-ga)在 harness 侧被声明为不接受任何
242
+ // reasoning effort —— 即使去掉 effort 重试,harness 的 defaultEffort 也会顶上来
243
+ // 再次拒绝(UNSUPPORTED_REASONING_EFFORT),插件 fallback 无法绕开。
244
+ // 选用时建议配 dreamReasoningEffort 并实测;不行就换非思考模型。
245
+ dreamProvider: z.string().description("记忆巩固专用模型的服务商(settings「巩固模型」)。巩固反复失败时,优先改用官方非思考模型的服务商(如 deepseek / glm)。"),
246
+ dreamModel: z.string().description("记忆巩固专用模型。建议选非思考模型(如 deepseek-chat、glm-5-2 类):思考模型可能烧光 token 预算返回空体,导致巩固失败(UNSUPPORTED_REASONING_EFFORT)。"),
247
+ // Issue #135 建议 3:#9 只抬了 max(8192→131072),default 停在 32768。思考型
248
+ // 模型的推理与正文共享该预算,effort 未压低时 32768 恰好被推理烧光 → 空体
249
+ // ("no json array in llm output",报告实测 146 轮因此失败)。默认抬到上限,
250
+ // 让默认配置也留足正文预算;流式计费按实际用量,不按上限。
251
+ //
252
+ // 成本审计(2026-10-12,本机库只读探针)复验了「默认值该不该降回去」这个问题,
253
+ // 结论是**不该降**,故默认维持 131072。三条实测证据:
254
+ // 1. 这个键是**上限**,不是计费口径——流式按实际用量计费(与 sleepMaxTokens
255
+ // 的 description 同口径)。压上限只在**真撞顶**时生效:近 10 天 88 个成功
256
+ // 轮 output 中位 24022、73.9% 超过 16384,因此把默认压到 16384 会让近四分之三
257
+ // 的正常轮撞顶。
258
+ // 2. 撞顶的后果不是「少烧一点」,而是**整轮失败**:max_tokens 截断 → 流上没有
259
+ // 完整 JSON → extractJsonArray 返回 null → finish({ok:false,
260
+ // error:"no json array in llm output"})(src/dream.js:1167-1171);而调度器
261
+ // **只有 result.ok 才刷新 baseline**(src/dream.js:905-915,注释明写这是刻意
262
+ // 设计「失败必须能立刻重试」)→ 下一次写入用**同一批输入**重跑一遍。按 133 个
263
+ // 有真实用量的成功轮逐轮折算:cap=16384 撞顶 78.9%、净总量 +54.1%;
264
+ // cap=24576 +43.3%;cap=32768 +15.9%;cap=49152 +6.5%;98304 起为 0。
265
+ // 即**调低这个值会把 token 花得更多**,而不是更少。
266
+ // 3. 真正的大头它治不到:把「时间就近」配上的 133 次成功调用(同一次配对里取
267
+ // 分子与分母)的决策正文(merge/update 的 content+title,剥离代码自动补的隐式
268
+ // keep)按中英分估(中文 ≈1.5 字符/token、英文 ≈4)折 419,487 token,而同一批
269
+ // 调用输出合计 3,543,111 token —— 正文约占 11.8%(换算方法敏感性区间
270
+ // 8.2%~22.0%,逐轮中位 12.1%),其余约 88% 是推理与 JSON 骨架。
271
+ // 注意别用「全部有 decisions 的 run」当分子配「配对子集」的分母(会得 13.9%
272
+ // 这种偏高的数)——跨口径比较先校量纲、分子分母必须同集合。
273
+ // 病根是推理与正文共享 max_tokens 单一池子。宿主(DSH)侧确有分离机制:
274
+ // DEFAULT_THINKING_BUDGETS = { minimal:1024, low:2048, medium:8192, high:16384 }、
275
+ // adjustMaxTokensForThinking()(正文预算 + 推理预算相加后卡模型上限)、
276
+ // clampThinkingBudgetToAnswerRoom()(MIN_ANSWER_TOKENS=1024,保证正文至少留
277
+ // 1024 token)——但它只对 Anthropic Claude 路径默认生效;chat-completions 网关
278
+ // (volcano-engine / deepseek / glm 均在此列)须由路由 profile 开
279
+ // compat.supportsThinkingTokenBudget 并指定 compat.thinkingTokenBudgetField
280
+ // (vLLM/Qwen/llama.cpp 风格顶层字段,上游端点还必须认它),asar 注释明写
281
+ // "Off by default"。两者都是 **profile 级**配置,插件 stream 调用无法
282
+ // per-request 传递(ctx.llm.stream 唯一的推理旋钮是 reasoningEffort)——故本插件
283
+ // 在调用面无法单独约束推理预算:抬高这个上限是当前路由下唯一可用的止损手段,
284
+ // 治标不治本;治本是换非思考模型,或走能分离预算的路由 profile。
285
+ // 显式调小仍是允许的(min 256):适合「宁可整轮失败也要压单轮上限」的场景,但请
286
+ // 知悉上面第 2 条的净成本方向。需要旧行为(无上限感)的库显式设回 131072。
287
+ dreamMaxTokens: z.natural().min(256).max(131072).default(131072),
288
+ // Pass-through reasoning effort for dream's LLM calls (Issue #135 建议 4/5).
289
+ // Unset (no value) resolves to the LOWEST effort the model declares before
290
+ // streaming: background consolidation must produce JSON, and omitting the
291
+ // field lets the harness substitute the model's defaultEffort (thinking-type
292
+ // models often default to high/max) — the reasoning then drains the whole
293
+ // token budget and the run dies with an empty body. Explicit 'none' still
294
+ // omits the field (provider default applies); off/low/medium/high are
295
+ // forwarded verbatim, unsupported values are remapped (v0.7.26+).
296
+ // Caveat: on some thinking models (e.g. v4-flash-ga) the harness declares NO
297
+ // supported effort, so even the fallback retry (field stripped) is rejected
298
+ // again via its defaultEffort — prefer a non-reasoning dreamProvider/dreamModel.
299
+ dreamReasoningEffort: z.union([
300
+ z.const("off"),
301
+ z.const("low"),
302
+ z.const("medium"),
303
+ z.const("high"),
304
+ z.const("none")
305
+ ]).description("巩固模型的推理档位:未配置 = 自动取模型支持的最低档(避免思考模型用自带默认档烧光预算返回空体,#135);显式 'none' = 不发送字段、用服务商自带默认;off/low/medium/high 原样传递。模型不支持的值会自动换用其支持的档位(v0.7.26+)。"),
306
+ // 滑动窗口上限(v0.4.4):autoDream 每次只对最近 dreamMaxSnapshotSize 条
307
+ // 记忆做 consolidation。大记忆量下全量快照会把 LLM 输入撑爆(636 记忆 →
308
+ // 677 "missing" errors、applied=0),窗口外的旧记忆不进 snapshot。
309
+ dreamMaxSnapshotSize: z.natural().min(1).max(1000).default(200),
310
+ // Issue #104 方向 2:单轮 archive 决策上限。一次性大扫除(12 条互不相关主题
311
+ // 被批量归档,其中 8 条疑似误伤长保留类型)是失控信号,与 update 上限同类的
312
+ // 全局闸门:skipInvalid 也不豁免,超限整单拒绝。正常清理可调高。
313
+ dreamMaxArchivePerRun: z.natural().min(1).max(200).default(8),
314
+ // #135 成本审计派生:单轮 merge 决策上限。merge 是唯一**回吐正文**的动作(每条
315
+ // 要求模型重新写出合并后的 content,实测平均 550 字符/条),其余动作都不带正文,
316
+ // 所以这是「输出里正文占比」唯一可控的杠杆。与 update/archive 上限同类:全局
317
+ // 闸门,skipInvalid 也不豁免,超限整单拒绝(errors 由 decisions.js 的数量闸段
318
+ // 统一汇总,run 记 failed)。默认 40 的校准依据(只读探针 246 轮 / 近 10 天 88 轮):
319
+ // 每轮 merge 数全库 p50 7 / p90 15 / p99 28 / max 39,近 10 天 p99 39 / max 39
320
+ // ——40 即观测分布上沿(历史命中 0/246),与 archive 闸默认 8 恰为其 p90=max 是
321
+ // 同一校准法。刻意保守(不取 p90=15 之类):超限代价不对称,整单拒绝会让调度器
322
+ // 不刷新 baseline、下轮同输入重跑,一次误伤约等于该轮成本 ×2,而探针里 merge≥15
323
+ // 的大轮正文占比正常(10~22%),没有失控特征。想拿覆盖换成本的用户可调低。
324
+ dreamMaxMergePerRun: z.natural().min(1).max(200).default(40),
325
+ // Issue #125:候选集构造方式。"window"(默认,等同现状)只取最近
326
+ // dreamMaxSnapshotSize 条;"hybrid" 在此基础上并入**向量翻出的高相似组**——
327
+ // 本机实测 45 对「双方活跃且 sim≥0.85」里 0 对能同时进窗口(窗口覆盖率 11.7%),
328
+ // 纯时间窗口让"该合并的一对"几乎永远碰不到面。候选总量仍由下面的上限封顶、
329
+ // 与库总量解耦,这是相对"调大窗口"的核心收益:输入成本不随库增长。
330
+ dreamCandidateMode: z.union([
331
+ z.const("window"),
332
+ z.const("hybrid")
333
+ ]).default("window"),
334
+ // hybrid 的候选总量上限;0 = 复用 dreamMaxSnapshotSize(不迁移,需要时显式覆盖)。
335
+ dreamCandidateMax: z.natural().min(0).max(5000).default(0),
336
+ // Issue #258:总览(dream_summarize)独立路由。consolidate 有 dreamMaxSnapshotSize
337
+ // 窗口、总览原为全库无界——两者对 ctx 的需求差 4 倍以上却强制共用 dream 路由,
338
+ // dreamProvider 指向小 ctx 模型时总览当场 CONTEXT_WINDOW_EXCEEDED(实测 120,969
339
+ // tokens > 32,768),指向大 ctx 模型则 consolidate 的卸载收益归零。未配置 = 回落
340
+ // dream 路由,行为逐字节不变。
341
+ dreamSummaryProvider: z.string().default("").description("记忆总览(dream_summarize)专用模型服务商,留空用巩固模型。总览输入为全库(或 dreamSummaryMaxInputs 上限),ctx 需求远大于 consolidation,建议大 ctx 模型(issue #258)。"),
342
+ dreamSummaryModel: z.string().default("").description("记忆总览(dream_summarize)专用模型,留空用巩固模型;总览输入随库增长,建议大 ctx 非思考模型(issue #258)。"),
343
+ // Issue #258:总览输入条数硬上限。0 = 不设上限(历史行为,库增长可能撑爆小
344
+ // ctx 模型);>0 时按 updated_at 倒序保留最新的 N 条(与 consolidate 窗口同一
345
+ // 排序口径)。总览是常驻叙述而非逐条巩固,限输入只影响口径脚注里的条数。
346
+ dreamSummaryMaxInputs: z.natural().min(0).max(100000).default(0),
347
+ // hybrid 判"高相似"的阈值;0.85 与 sleep normal 档对齐——两个模块对"高相似"
348
+ // 保持同一个定义。
349
+ dreamCandidateMinSim: z.number().min(0.5).max(0.99).default(0.85),
350
+ // 叙述条(#164 对齐,opt-in):dream 期间按共享 tag 主题聚类(dream/
351
+ // narratives.js),每簇 LLM 合成一条叙述落库(source=narrative、evidence
352
+ // 回链簇内原子记忆、求交防捏造)。按需检索、不常驻注入。默认关=行为与
353
+ // 此前一致;lightMode 强制关闭。
354
+ dreamNarrativeEnabled: z.boolean().default(false),
355
+ // 成簇门槛:共享同一 tag 的记忆 ≥ 此值才合成叙述条。
356
+ dreamNarrativeMinCluster: z.natural().min(2).max(20).default(3),
357
+ // document 型记忆(#164/#230,opt-in):agent 产长文档的指针行——注册校验
358
+ // (文件存在 + 路径合法 + evidence 求交)、摘要 + doc_path 落库、C2 比对
359
+ // 去重、supersede 记账。全文归 agent,管线零触碰;默认关=行为与此前一致;
360
+ // lightMode 强制关闭。
361
+ documentMemoryEnabled: z.boolean().default(false),
362
+ // document 摘要行的注入预算(#230 拍板):次优先档内最多注入的 document
363
+ // 行数,超预算跳过由后续候选补位。只约束注入,不约束检索。
364
+ documentInjectBudget: z.natural().min(1).max(5).default(2),
365
+ // document 的 managed 落盘目录(#296 第二批):空串 = 跟随 memoryDir 的
366
+ // `<memoryDir>/documents/`。`~` / `~/` / `~\` 展开到 home,绝对路径原样用,相对
367
+ // 路径落在 memoryDir 下(这是本键自己的规则:memoryDir 的相对路径是原样留着的
368
+ // cwd 语义,两者刻意不同;`~user` 形式两边都不展开)。目录由 mneme 建,里面的
369
+ // `index.md`
370
+ // 整文件机器所有。这个目录**之外**的文件只登记指针行,正文一个字节都不碰
371
+ // (= #230 的「管线对正文零读零写」);与 memoryDir 一样是路径配置,不是
372
+ // 行为开关,故不进 settings.js 的 feature flags 白名单、也不上面板。
373
+ documentDir: z.string().default(""),
374
+ // 日记本(QQ 机器人写日记):跨天惰性判定 + 启动补写,正文落
375
+ // `<memoryDir>/diaries/<项目键>/YYYY-MM-DD.md`,记忆库里留一条 document 指针行。
376
+ // 默认关=行为与此前一致;lightMode 强制关闭(它要调模型,与低资源档互斥)。
377
+ // 与 documentMemoryEnabled **成对但不相依**:关掉文档记忆只影响「能不能被检索到」,
378
+ // 正文照样写——那是用户自己的日记,不该被一个检索开关收走。
379
+ diaryEnabled: z.boolean().default(false),
380
+ // 日记根目录:空串 = 跟随 `<memoryDir>/diaries/`。与 documentDir 同一套路径规则
381
+ // (`~` 展开、绝对路径原样、相对路径落 memoryDir 下),同样是**路径配置不是行为
382
+ // 开关**,故不进 settings.js 白名单、也不上面板。
383
+ diaryDir: z.string().default(""),
384
+ // QQ 收件箱目录:空串 = 从 memoryDir 推(`<memoryDir>/../media/onebot`,本机即
385
+ // `D:\DSH\.dsh\media\onebot`)。推出不来就报 no-inbox-dir,绝不猜别的盘。
386
+ diaryInboxDir: z.string().default(""),
387
+ // 定时写(v0.13,桉桉 2026-11-09「日记本按理来说应该需要定时写,这个要存设置中」):
388
+ // 每隔 N 分钟主动检查一次「今天是否还没写」,0 = 关(只在 turn/end 与启动时判,
389
+ // 即此前行为)。为什么要这一项:auto.js 原来只在回合结束时判跨天,用户整天不说话
390
+ // 就整天不写——日记不该取决于谁先开口。它仍然**不是 cron**:定时器只负责唤醒判定,
391
+ // 写不写在 compose 里按落盘日期去重,所以一天最多一篇,失败也只是 warn。
392
+ // 上限 1440(一天):比一天更长的间隔没有意义,因为判定单位就是「天」。
393
+ diaryWriteEveryMinutes: z.natural().min(0).max(1440).default(0),
394
+ // 隐式 keep(v0.4.4):LLM 未提及的 snapshot 记忆自动补 {action:"keep"},
395
+ // 避免"未覆盖即全拒"白白浪费整轮 run。设为 false 时保留旧的严格校验
396
+ // (未覆盖即拒绝整单)。
397
+ dreamImplicitKeep: z.boolean().default(true),
398
+ // 显式决策覆盖率下限(v0.4.4 fix):dreamImplicitKeep 开启时,LLM 输出被
399
+ // 截断只显式 claim 少量 snapshot 记忆(claimed.size / snapshot.size < 该阈值)
400
+ // → 整单拒绝,防止残缺输出被隐式 keep 洗白成 ok 后再被真实 apply。0-1,
401
+ // 默认 0.5(至少显式覆盖一半 snapshot)。
402
+ dreamMinExplicitCoverage: z.number().min(0).max(1).default(0.5),
403
+ // Issue #89:v0.6.9(Issue #26)的宽容路径回归。默认跳过单条非法决策
404
+ // (未知 id / 跨类型合并等)、应用合法子集、run 记 degraded;设 false 恢复
405
+ // 整单拒绝的严格模式。全局上限与覆盖率下限不受此开关影响、始终整单拒绝。
406
+ dreamSkipInvalid: z.boolean().default(true),
407
+ // 显式开启后放宽跨类型合并检查(类型边界由用户自行承担);配合
408
+ // dreamSkipInvalid 理解:关闭 skipInvalid 时跨类型 merge 直接整单拒绝。
409
+ allowCrossTypeMerge: z.boolean().default(false),
410
+ // issue #339 / E8 考卷:merge 护栏(默认开)。开启后合并对象命中长保留类型
411
+ // (preference/pattern/rejected_solution/constraint/pitfall,与 archive 护栏
412
+ // 同一张表)的 merge 决策整条跳过——E8 实测巩固损耗里 10/26 条被丢约束已归位
413
+ // guarded 类型仍被 merge 吃掉,而 archive 护栏只挡 archive 不挡 merge,
414
+ // 「更精炼的摘要」恰是约束失真的主通道(同类压缩管线实测:安全规则被同速压缩
415
+ // 一轮只剩 53%、五轮只剩 10%,且全程无任何报错信号)。skipInvalid(默认开)
416
+ // 时被跳条目进 dream_runs.skipped、run 记 degraded;关闭 skipInvalid 时整单
417
+ // 拒绝。设为 false 才恢复旧行为:guarded 类型的 merge 不再被跳过,约束可被
418
+ // 「更精炼的摘要」吃掉。与 allowCrossTypeMerge 同用时长 guard 先判。
419
+ dreamMergeGuard: z.boolean().default(true),
420
+ // Rule version for dream adjudication: when this bumps, older dream_runs
421
+ // degrade to historical evidence (their receipts no longer drive live
422
+ // decisions). Default 0 = no versioning in use yet.
423
+ policyEpoch: z.natural().min(0).max(1000000).default(0),
424
+
425
+ // --- API protection ------------------------------------------------------
426
+ // Optional shared token for the plugin's HTTP API. Empty (default) keeps
427
+ // the API open (DSH binds to 127.0.0.1 and has no built-in auth); when set,
428
+ // sensitive endpoints (vector-config, vector-reindex, and all write ops on
429
+ // profile/rules/commands) require `Authorization: Bearer <apiToken>` (or
430
+ // `X-DSH-Mneme-Token`). Read-only list/search/semantic stay open so the
431
+ // Web panel keeps working without the token.
432
+ apiToken: z.string(),
433
+
434
+ // --- semantic: local embedding provider (v0.2) --------------------------
435
+ // "openai" keeps the legacy external-API path (settings vector config);
436
+ // "local" runs an ONNX model in-process; "ollama" calls a local Ollama.
437
+ embedProvider: z.union([z.const("openai"), z.const("local"), z.const("ollama")]).default("openai"),
438
+
439
+ // Local ONNX embedder (transformers.js / onnxruntime).
440
+ localEmbedModel: z.string().default("Xenova/bge-small-zh-v1.5"),
441
+ localEmbedDimension: z.natural().default(512),
442
+ localEmbedDevice: z.union([z.const("cpu"), z.const("gpu")]).default("cpu"),
443
+ localEmbedBatchSize: z.natural().min(1).max(64).default(8),
444
+ // 本地嵌入的池化方式(**bug 修复,不是新能力**):BGE 系是按 CLS 池化训练的——
445
+ // 模型自带的 `1_Pooling/config.json` 明确写着 `pooling_mode_cls_token: true` /
446
+ // `pooling_mode_mean_tokens: false`,官方 README 也是「select the last hidden state of
447
+ // the first token」+ L2 normalize;而 transformers.js 的 `feature-extraction` 默认 mean。
448
+ // ⇒ 此前本地嵌入对 BGE 系一直用错池化:**不报错、只是向量系统性偏差**,检索质量静默受损。
449
+ // 'auto' = 按模型族判定(BGE → cls,其余 → mean,未受影响的模型行为不变);也可显式钉住。
450
+ // ⚠️ 池化决定向量空间,改它会改变 modelHash(见 local-embedder.js),既有索引会被判失配并重建。
451
+ localEmbedPooling: z.union([
452
+ z.const("auto"),
453
+ z.const("cls"),
454
+ z.const("mean")
455
+ ]).default("auto"),
456
+
457
+ // Ollama embedder.
458
+ ollamaBaseUrl: z.string().default("http://localhost:11434"),
459
+ ollamaModel: z.string().default("nomic-embed-text"),
460
+
461
+ // Model download/cache. When empty (default), models are cached under the
462
+ // user-level path ~/.dsh/mneme/models (resolved in local-embedder/reranker);
463
+ // a non-empty value is used verbatim.
464
+ embedModelCacheDir: z.string().default(""),
465
+ embedModelMirror: z.string().default("https://hf-mirror.com"),
466
+
467
+ // issue #194:模型文件下载的断点续传与重试。开启时 transformers.js 的 env.fetch 被
468
+ // 替换为带断点续传的实现(已收字节落盘、Range/If-Range 续传、空闲看门狗),大文件
469
+ // 中断后不必整份重来;关闭 = env.fetch 保持原样(线上回滚开关)。
470
+ resilientModelDownload: z.boolean().default(true),
471
+
472
+ // 自管运行时目录(issue #131)。空 = ~/.dsh/mneme/runtime:里面放收编或下载来的
473
+ // transformers + onnxruntime 闭包,插件经 src/runtime/loader.js 的三层解析加载它。
474
+ // 目的就是让这条重依赖不必留在宿主 profile 的依赖图里——profile 是所有插件共用的
475
+ // 依赖图,留在里面会让「装任何插件都要替它重走一遍这条链」。
476
+ runtimeDir: z.string().default(""),
477
+
478
+ // 运行时取件的两个来源(issue #131 / PR-C)。都是「克制」的默认:留空 = 不额外改变行为。
479
+ // - runtimeTarballDir:本地 .tgz 目录。某个包网络下不到时(例如 onnxruntime-node),
480
+ // 把 `npm pack <pkg>` 出来的 tgz 丢进这个目录即可离线取件;有它就优先于联网。
481
+ // - runtimeMirror:registry 镜像前缀,例如 https://npmmirror.com/mirrors/npm/ 。
482
+ // 留空则用 runtime-manifest.json 里写死的官方地址(那是随包发布的固定清单)。
483
+ runtimeTarballDir: z.string().default(""),
484
+ runtimeMirror: z.string().default(""),
485
+
486
+ // Vector search tuning.
487
+ vectorSearchTopK: z.natural().min(1).max(100).default(20),
488
+ vectorSearchThreshold: z.number().min(0).max(1).default(0.65),
489
+ hybridSearchVectorWeight: z.number().min(0).max(1).default(0.6),
490
+ hybridSearchKeywordWeight: z.number().min(0).max(1).default(0.4),
491
+ // Lazy auto-backfill of missing embeddings on boot (Bug2): when the vector
492
+ // API is configured and rows still lack an embedding, the index is rebuilt
493
+ // in the background after a short delay, rate-limited in batches. On by
494
+ // default; set false to keep the backfill manual only.
495
+ autoReindexOnBoot: z.boolean().default(true),
496
+ // Backfill pacing (previously hardcoded 500 / 10 / 200 in semantic.js). A
497
+ // fixed 500-per-boot cap meant a 4k-row backlog needed nine restarts to
498
+ // clear, and nothing reported that the run had been capped.
499
+ // reindexMaxPerBoot = 0 means "no cap, drain the whole backlog this run".
500
+ reindexMaxPerBoot: z.natural().default(500),
501
+ reindexBatchSize: z.natural().min(1).max(64).default(10),
502
+ reindexRateLimitMs: z.natural().default(200),
503
+ // Semantic-first injection (Bug4): when enabled, injectCandidates with a
504
+ // non-empty query recalls via the vector index first and falls back to the
505
+ // rule-based pick to fill/dedupe. Empty query / no vector → legacy behavior.
506
+ hybridInject: z.boolean().default(true),
507
+
508
+ // --- recall optimization (v0.5.0) ----------------------------------------
509
+ // BM25 third recall path beside vector + LIKE keyword (1.1): per-token IDF
510
+ // scoring recalls rows whose query terms are scattered — identifiers, code
511
+ // fragments, mixed CJK/ASCII — where substring LIKE cannot match.
512
+ bm25SearchEnabled: z.boolean().default(true),
513
+ // Query-aware vector cutoff (1.2) replacing the fixed 0.65: entity:/attr:
514
+ // prefixes loosen to 0.5, short queries tighten to 0.7, long queries loosen
515
+ // to 0.6, and a decisive top-1/top-5 score gap loosens to 0.5 so the tail
516
+ // still reaches the reranker. Off = legacy fixed threshold behavior.
517
+ adaptiveThresholdEnabled: z.boolean().default(true),
518
+ // Session-scoped hot memory (1.3): the latest N dialogue rounds rendered
519
+ // ahead of the long-term recall block — short-term context that never
520
+ // enters the memory store.
521
+ hotMemoryEnabled: z.boolean().default(true),
522
+ hotMemoryRounds: z.natural().min(1).max(50).default(5),
523
+ hotMemoryMaxTokens: z.natural().min(200).max(32000).default(2000),
524
+ // Topic-ranked injection (2.2): when a query vector is available the whole
525
+ // injection candidate list is re-ordered by similarity to the current
526
+ // query instead of keeping the rule-based order.
527
+ selectiveInjectEnabled: z.boolean().default(true),
528
+ // Search-time semantic dedup (2.3): greedy pass over the merged candidate
529
+ // list dropping rows whose embedding cosine-similarity to an already-kept
530
+ // row exceeds the threshold — duplicates are filtered at recall time
531
+ // instead of waiting for a dream consolidation. Opt-in aggressive mode:
532
+ // small embedding models can collapse legitimately distinct rows, so the
533
+ // default keeps every recalled row.
534
+ searchSemanticDedup: z.boolean().default(false),
535
+ searchSemanticDedupThreshold: z.number().min(0.5).max(1).default(0.95),
536
+
537
+ // Recall fusion recipe (plan #1): how the keyword/vector/BM25 ranked lists
538
+ // are combined into the final ranking. `blend` (default) is the legacy
539
+ // behavior — weighted sum for vector/hybrid, union backfill for auto —
540
+ // unchanged. `rrf` (Reciprocal Rank Fusion) and `minmax` (min-max normalized
541
+ // weighted sum) are rank/scale-aware recipes that fix the unit mismatch the
542
+ // issue describes (raw cosine vs keyword score vs normalized IDF are added
543
+ // directly). Off by default so existing behavior holds exactly.
544
+ recallFusion: z.union([z.const("blend"), z.const("rrf"), z.const("minmax")]).default("blend"),
545
+ // Attach a `signals` object { keyword, vector, bm25, final } to each search
546
+ // result for transparency/debugging (plan #2). Default off; when on it only
547
+ // decorates the returned rows, never changes the ranking.
548
+ signalTransparency: z.boolean().default(false),
549
+
550
+ // --- semantic: rerank layer (v0.2) --------------------------------------
551
+ // Opt-in by default (item ⑥): the local cross-encoder pulls in onnxruntime
552
+ // (transformers.js) at init, so a bare install must not load it. Only an
553
+ // explicit rerankEnabled=true + rerankProvider="local" constructs LocalReranker.
554
+ rerankEnabled: z.boolean().default(false),
555
+ rerankProvider: z.union([z.const("local"), z.const("none")]).default("none"),
556
+ rerankModel: z.string().default("Xenova/bge-reranker-base"),
557
+ rerankBatchSize: z.natural().min(1).max(64).default(8),
558
+ rerankMaxCandidates: z.natural().min(5).max(100).default(30),
559
+ rerankScoreThreshold: z.number().min(0).max(1).default(0.1),
560
+ // #188:量化档,与嵌入层的 useDtype 同语义(q8 = model_quantized.onnx,约为
561
+ // fp32 体积的 1/4)。此前重排层不传 dtype,即使缓存里已有量化文件也会去下载
562
+ // 1GB 级的 model.onnx。非法值由 transformers 在 init 时抛出 → 重排降级并告警。
563
+ rerankDtype: z.string().default("q8"),
564
+
565
+ // --- reflection: update decision + failure tracking (v0.2.1) ------------
566
+ reflectionUpdateEnabled: z.boolean().default(true),
567
+ reflectionFailureTracking: z.boolean().default(true),
568
+ reflectionUpdateMaxPerRun: z.natural().min(0).max(5).default(2),
569
+ reflectionUpdateMinAgeHours: z.natural().min(0).max(168).default(24),
570
+ // failure_memories 的启动期保留期(天)。此前在 index.js 里写死 90,是这一族
571
+ // 四张启动期清理表里唯一没有键可陈述的一条(#275 伞下的欠账)。形态对齐
572
+ // recallRetentionDays:顶层扁平键、只从配置文件改;暂不进 settings.js 白名单,
573
+ // 面板可调是后续的事(那要同时补 api.js 的 NESTED 表与 client.js 的面板行)。
574
+ // 下限取 1 而非 0:这张表的用途是「挖掘重复出现的纠正模式」,窗口太短就只剩
575
+ // 最近一次;要彻底停掉清理应当显式关掉 reflectionFailureTracking。
576
+ reflectionFailureRetentionDays: z.natural().min(1).max(3650).default(90),
577
+
578
+ // --- conflict freeze: manual review for conflicting memories (v0.2.1) ---
579
+ // Opt-in by default: when true, conflicting memories are not auto-merged
580
+ // and are marked as pending manual review instead.
581
+ conflictFreezeEnabled: z.boolean().default(false),
582
+ // Maximum number of frozen conflicts to keep pending for manual review.
583
+ conflictFreezeMaxPending: z.natural().min(1).max(1000).default(100),
584
+ // Issue #603 第 4 层(自动裁决档):freeze 模式里相似度极高的同 scope 对直接
585
+ // 裁决(保留信息量更大的那条、归档另一条),不进人工队列——人只看模糊地带。
586
+ // 默认关:这是会归档记忆的自动动作,必须显式打开。
587
+ //
588
+ // ⚠️ Issue #616:这一档有三个已知边界,打开前先读,别当成「开了就清净」。
589
+ //
590
+ // 1) 阈值论证用的是早期快照,当前库上 0.95 等于不生效。
591
+ // 注释此前写「345 条有相似度、>=0.95 有 20 条(5.8%)」——2026-11-09 在
592
+ // live 库重跑同口径:conflict_pending 全表 748 行,similarity 非 NULL 只有
593
+ // 11 行,其中 >=0.95 是 0 行,最高一对 0.914。这不是说阈值错了,是说
594
+ // 按 0.95 打开今晚一对都不会被判,用户会以为功能坏了。真要开,先按当前
595
+ // 分布重定阈值(配置下限 0.85),别沿用这两个数。
596
+ //
597
+ // 2) 它不是独立开关,是 freeze 分支的子节点。整段自动裁决都在
598
+ // sleep.js 的 `if (config.conflictFreezeEnabled === true)` 里;关掉 freeze
599
+ // 会连它一起静默消失。面板上两个键看着平行,实际是父子。
600
+ //
601
+ // 3) 未标注 scope 会被判成同 scope。isCrossScopePair 走 scopeKeyOf,NULL 归一
602
+ // 成 null,null !== null 为 false,于是「归属未知」被当成「归属相同」。
603
+ // live 库活跃 8259 行里 2710 行(32.8%)两个 scope 全 NULL,这批因此绕过了
604
+ // 「归属是用户的决定、永不自动」(v0.8.1 #170)这条守卫。改判据前不要打开。
605
+ //
606
+ // 另外 autoResolved/autoFailed 目前没有任何消费者(dream.js 只取 frozen),
607
+ // conflict_reviewed 台账也不记赢家、无 API 暴露——自动裁决了什么是看不见的。
608
+ // 可见性补齐之前,这一档只适合在能盯日志的前提下短时打开。
609
+ conflictAutoResolveEnabled: z.boolean().default(false),
610
+ conflictAutoResolveMinSimilarity: z.number().min(0.85).max(1).default(0.95),
611
+
612
+ // --- conflict ledger(issue #641,beta)-----------------------------------
613
+ // 冲突分诊账本的档位。真实库 788 对人审队列的全量分类:46% 近重复 + 20% 跨作用域
614
+ // 重复 + 5% 时序演进,真矛盾只有 ~27%——现行窄 prompt 把前 71% 全逼成了「输赢」,
615
+ // 再由人逐对裁决(≈30 条/天,且 92% 的对从未两侧同场)。分诊把「谁该机械处理、
616
+ // 谁才是真矛盾」从 LLM 手里拿回来(E13b:winner-take-all 裁决 P(毒胜出)=86.8%)。
617
+ //
618
+ // "off" 默认。不分类、不记账,conflict 相位与本键引入前逐字节一致。
619
+ // "shadow" 只分类 + 落 conflict_ledger 账本(bookkeeping,不触发 write hooks),
620
+ // freeze/LLM 路径零变化——beta 实验的测量期:shadow 一周拿真实分诊
621
+ // 分布,验证是否复现 46/20/5/27。
622
+ // "enforce" 确定性处置:T1(dup)走信息量优先 supersede(supersedeInfoFirstPair,
623
+ // 败者归档、胜者正文不动、可追溯注记在被归档侧);T2(跨作用域重复)
624
+ // 与 T4(真矛盾)只记账——不停车、不送 LLM、不动正文;T3(时序演进)
625
+ // 落取代链:旧行 superseded_by/superseded_at 指向新行,两行都留着、
626
+ // 正文都不动,检索侧按降权处理。人审队列与 LLM 裁决都不再消费冲突
627
+ // 候选;与 conflictFreezeEnabled 同开时 freeze 让位(降级 shadow
628
+ // + warn,#616 父子绑定不继承)。
629
+ //
630
+ // 判据唯一实现在 src/dream/triage.js;处置共享实现在 decisions.js 的
631
+ // supersedeInfoFirstPair。每个档位的行为都真实存在——#616 的「假成功」教训
632
+ // (autoMin 0.95 在现库覆盖为 0,开了等于没开)。
633
+ conflictLedgerMode: z.union([z.const("off"), z.const("shadow"), z.const("enforce")]).default("off"),
634
+ // T1(dup)档的相似度下限。检测阈值(normal 0.85)与自动档 autoMin(0.95)之间
635
+ // 的空带由它接管:真实库 reason 相似度回声集中在 0.85-0.93,正好落在两个阈值
636
+ // 之间、自动档开了也接不住的那一段。下界与检测阈值同源。
637
+ conflictLedgerDupMinSimilarity: z.number().min(0.85).max(1).default(0.9),
638
+
639
+ // --- entity gene (v0.3.0) -----------------------------------------------
640
+ // Opt-in: when false (default) nothing in the pipeline extracts entities.
641
+ // The storage layer (entities/entity_attrs/entity_relations tables + CRUD)
642
+ // is always available regardless of this flag.
643
+ entityExtractionEnabled: z.boolean().default(false),
644
+ // Optional provider override for entity extraction; empty = use the caller's
645
+ // default provider/model. Combined with entityExtractionModel — provider
646
+ // without model (or vice versa) falls through to the caller default.
647
+ entityExtractionProvider: z.string().default(""),
648
+ // Optional model override for entity extraction; empty = use the caller's
649
+ // default provider/model.
650
+ entityExtractionModel: z.string().default(""),
651
+ // Reasoning effort for entity extraction (issue #109), mirrors
652
+ // dreamReasoningEffort pass-through: 'none' omits the field / provider
653
+ // default; off/low/medium/high passed through. A provider that rejects the
654
+ // effort retries once without it, so opting in is safe to experiment with.
655
+ entityExtractionReasoning: z.union([
656
+ z.const("low"),
657
+ z.const("medium"),
658
+ z.const("high"),
659
+ z.const("none")
660
+ ]).default("none"),
661
+ // Cap on entities per extraction pass and attributes per entity.
662
+ entityExtractionMaxEntities: z.natural().min(1).max(20).default(10),
663
+ entityExtractionMaxAttrs: z.natural().min(1).max(50).default(20),
664
+ // Prefix/semantic search over entity names (used by recall).
665
+ entitySearchEnabled: z.boolean().default(true),
666
+ // 图召回轴(issue #219):查询文本命中实体名时,把该实体挂联的记忆并入
667
+ // 检索融合池(与 BM25 同级的确认/回填信号)。默认关=检索行为与 #219 前
668
+ // 逐字节一致;走 feature_flags 面板可启停,lightMode 强制关闭。
669
+ entityRecallEnabled: z.boolean().default(false),
670
+ // 边权重演化(issue #24 块2):touch 门控是否把被寻回路径的边权重抬升。
671
+ // 默认关=边初值纯由建边来源决定(store.RELATION_SOURCE_DEFAULTS),演化
672
+ // 不生效;开=被激活的边经 bumpRelationWeight 上涨(只加不减、封顶 1.0)。
673
+ graphWeightEnabled: z.boolean().default(false),
674
+ // 单次有效触达的抬升幅度。
675
+ graphWeightDelta: z.number().min(0).max(1).default(0.1),
676
+ // 被动确认(issue #24 块4):heat 触达(记忆被召回/注入=被用)即对该记忆
677
+ // 挂联的关系边做被动确认——bumpRelationWeight 抬一格。默认关=零行为变化;
678
+ // 开=正常触达演化边权,「关系是用出来的」闭环落地(异常路径复核留后续块)。
679
+ graphPassiveConfirm: z.boolean().default(false),
680
+
681
+ // --- 图谱锚定层(issue #24 · 块1 Activation Anchoring)-------------------
682
+ // entityRecallEnabled 之上再开一层:以查询命中的实体为种子,沿实体关系表
683
+ // 级联扩散(默认深度 2),把「邻居实体挂联的记忆」也带进融合池。种子集
684
+ // 上限 graphSeedCap、级联深度 graphCascadeDepth 一并下发到 anchorSeeds /
685
+ // cascadeDepths(见 src/graph/anchoring.js)。默认关=行为与 #219 单跳轴
686
+ // 逐字节一致;走 feature_flags 面板可启停,lightMode 强制关闭。
687
+ graphAnchoringEnabled: z.boolean().default(false),
688
+ graphSeedCap: z.natural().min(1).max(30).default(12),
689
+ graphCascadeDepth: z.natural().min(1).max(3).default(2),
690
+
691
+ // 关联提示防幻觉(issue #24 块3):图召回的候选(查询命中实体的挂联记忆)
692
+ // 进注入时默认只参与排序、不改变注入块构成(graphInjectHint=false 保守档);
693
+ // 开启后注入文本里以固定句式「[检索线索] …」标出,引导语说明这是链路信息
694
+ // 非事实断言,防 LLM 单次消费把桥接召回当有依据的事实回头扩散。
695
+ graphInjectHint: z.boolean().default(false),
696
+ // 图召回候选在注入块里的独立预算(条数上限)。与 pin/document 预算同构:
697
+ // 不占 maxInjectedItems 名额、只约束「桥接线索」这类低信任信息的常驻占比。
698
+ graphInjectBudget: z.natural().min(0).max(5).default(1),
699
+
700
+ // --- sleep mode: idle-triggered deep maintenance (v0.4.0) ---------------
701
+ // Opt-in, off by default. Unlike autoDream (threshold-triggered, lightweight)
702
+ // sleep fires when the store has been quiet for sleepIdleMinutes and deep-
703
+ // maintains the whole library: conflict resolution, archival demotion,
704
+ // pattern discovery and entity relation completion. Abortable on user
705
+ // activity, audited into dream_runs (run_type='sleep'), and serialized with
706
+ // autoDream so the two never overlap.
707
+ sleepModeEnabled: z.boolean().default(false),
708
+ // Quiet window before a cycle fires (minutes).
709
+ sleepIdleMinutes: z.natural().min(1).max(60).default(5),
710
+ // Minimum gap between two sleep runs (hours) — a second idle window within
711
+ // this interval does not retrigger.
712
+ sleepMinIntervalHours: z.natural().min(1).max(168).default(8),
713
+ // Issue #529(C 项的替代实现):sleep 连续失败退避(默认关 = 行为逐字节不变)。
714
+ // 基数是 sleepMinIntervalHours,有效间隔 = 基数 × 2^连续失败数(成功清零),
715
+ // 封顶 max(基数, 24 小时)——不是硬 24 小时:这个键允许配到 168,用户配 48 就是
716
+ // 明说「两天跑一次」,退避只能放宽间隔、不该把用户自己的基数压小。
717
+ // 为什么不按 issue 原案用 snapshot_hash 判据:sleep 的
718
+ // snapshot_hash 是**跑完之后**对 phases 结果算的哈希(dream/sleep.js 的
719
+ // createHash(JSON.stringify(phases))),不是输入快照;同一批输入跑两次结果不同
720
+ // 就会得到不同哈希,用它当「同一输入又在失败」的判据不成立。退避与 #292 的
721
+ // autoDreamFailureBackoff 同构:只放大既有的最小间隔闸,不新增状态面。
722
+ sleepFailureBackoff: z.boolean().default(false),
723
+ // Conflict adjudication strictness:
724
+ // gentle only high-confidence conflicts (threshold 0.92) are resolved
725
+ // normal standard dream-level (threshold 0.85)
726
+ // aggressive low-confidence pairs are also adjudicated (threshold 0.75)
727
+ sleepConflictStrictness: z.union([
728
+ z.const("gentle"),
729
+ z.const("normal"),
730
+ z.const("aggressive")
731
+ ]).default("normal"),
732
+ // Issue #126:冲突阶段的动作集。"conflict"(默认 = 现状)只用 conflict/keep——
733
+ // 后台自动流程不静默扩张行为;"full" 开放六分支(merge / update / supersede /
734
+ // differentiate / conflict / keep),互补型与演进型重复因此有了正确出口,不再
735
+ // 被迫"输赢化"(实测抽样 10 对里 2 对属演进型/互补型,用 conflict 处理会丢信息)。
736
+ sleepActionSet: z.union([
737
+ z.const("conflict"),
738
+ z.const("full")
739
+ ]).default("conflict"),
740
+ // Archival demotion tiering (days since last access):
741
+ // >= sleepArchiveDays → shrink to summary, full body kept in _full_content
742
+ // >= sleepCompressDays → archived outright (entity relations preserved)
743
+ sleepArchiveDays: z.natural().min(7).max(365).default(30),
744
+ sleepCompressDays: z.natural().min(7).max(365).default(90),
745
+ // Pattern discovery scan window (most recent memories to scan).
746
+ sleepPatternMinMemories: z.natural().min(10).max(1000).default(100),
747
+ // How far back pattern discovery considers entity attr changes (days).
748
+ sleepPatternLookbackDays: z.natural().min(1).max(90).default(30),
749
+ // Max pattern memories minted per run (0 = disabled).
750
+ sleepMaxPatternPerRun: z.natural().min(0).max(10).default(3),
751
+ // --- affective attribution (v0.13.0, issue #619) --------------------------
752
+ // Opt-in: when false (default) the affective phase does not run at all — no
753
+ // LLM call, no relations written. It exists so that a situational emotion
754
+ // ("she was anxious on defence day") can be stored as an occasion-bound
755
+ // relation instead of being generalised into "she is an anxious person".
756
+ sleepAffectiveEnabled: z.boolean().default(false),
757
+ // How many **independent sessions** a candidate must span before it may be
758
+ // promoted to a type=pattern trait. Below this it stays an occasion-bound
759
+ // affective_context relation. The floor is 2 on purpose: N=1 means a single
760
+ // occasion, and a single occasion is exactly what must never become a trait
761
+ // (that is the failure mode this phase exists to prevent).
762
+ sleepAffectiveTraitMinSessions: z.natural().min(2).max(10).default(3),
763
+ // Max affective_context relations written per run. Same role as
764
+ // sleepMaxPatternPerRun for the relation channel: a runaway model that
765
+ // "finds" an emotion in every memory must be capped, not trusted.
766
+ sleepAffectiveMaxRelationsPerRun: z.natural().min(0).max(50).default(10),
767
+ // Optional LLM route override for sleep's bulk passes (empty = use dream
768
+ // route / agent default model).
769
+ sleepProvider: z.string().default("").description("sleep 深维护专用模型服务商,留空用巩固模型或当前模型。"),
770
+ sleepModel: z.string().default("").description("sleep 深维护专用模型,留空用巩固模型或当前模型;建议同巩固模型选非思考模型。"),
771
+ // Pass-through reasoning effort for sleep's LLM passes, same semantics as
772
+ // dreamReasoningEffort (Issue #135 建议 4/5): unset resolves to the lowest
773
+ // effort the model declares; explicit 'none' omits the field; off/low/
774
+ // medium/high are forwarded verbatim.
775
+ sleepReasoningEffort: z.union([
776
+ z.const("off"),
777
+ z.const("low"),
778
+ z.const("medium"),
779
+ z.const("high"),
780
+ z.const("none")
781
+ ]).description("同 dreamReasoningEffort:sleep 各阶段 LLM 的推理档位,未配置 = 自动取模型支持的最低档;显式 'none' = 不发送字段、用服务商自带默认。"),
782
+ // Pass-through reasoning effort for the distill (summarize) LLM call
783
+ // (issue #315). Mirrors entityExtractionReasoning rather than dream: the
784
+ // default 'none' omits the field entirely (provider default), so current
785
+ // behavior is unchanged until the user opts in — no auto-lowest resolution
786
+ // here, distill failures are retried at the window level anyway. off/low/
787
+ // medium/high are forwarded verbatim; a provider that rejects the effort
788
+ // retries once without it (withEffortFallback), so opting in is safe to
789
+ // experiment with. The failure being addressed is the #9 shape on the
790
+ // distill path: a thinking model drains the output budget on reasoning and
791
+ // the summary comes back empty/truncated.
792
+ summarizeReasoningEffort: z.union([
793
+ z.const("off"),
794
+ z.const("low"),
795
+ z.const("medium"),
796
+ z.const("high"),
797
+ z.const("none")
798
+ ]).default("none").description("蒸馏(会话总结提炼)LLM 的推理档位:默认 'none' = 不发送字段、用服务商自带默认;off/low/medium/high 原样传递,被模型拒收时自动去掉字段重试一次(与巩固/睡眠同款降级,#315)。思考型模型建议 off/low,避免推理烧光输出预算。"),
799
+ // Issue #257:sleep 冲突/模式两阶段的输出预算(原硬编码 2048)。实测默认档
800
+ // 每对裁决约 90 token、24 对 2097——2048 恰好压在边界(53 次运行 48 败);
801
+ // full 档六分支实测约 290 token/对、24 对 6967,2048 必然截断。默认 8192
802
+ // 覆盖实测峰值(候选对按「每记忆至多一对」去重,饱和在 ~25 对、不随库无限
803
+ // 增长);流式计费按实际用量,不按上限。
804
+ sleepMaxTokens: z.natural().min(256).max(131072).default(8192).description("sleep 冲突消解与模式发现阶段的 LLM 输出预算上限(token)。原为硬编码 2048,sleepActionSet=full 实测需约 7000 导致裁决被截断而整轮失败;流式计费按实际用量,调大不增加成本。"),
805
+
806
+ // Issue #529:冲突阶段此前没有任何输入侧预算——同 scope 的全部相似对被一次性
807
+ // 拼进一个 prompt,实测单请求 163,054 输入 token(服务端 --ctx-size 163,840,
808
+ // 占 99.5%),449 对的池子必然撞墙;而失败的轮次不落 pairs、判 keep 又是
809
+ // no-op,下一轮冷却结束再送**完全相同的输入**,于是 23 轮连续 llm failed。
810
+ // 输出侧早有 sleepMaxTokens(#257),输入侧这一半一直是空的。
811
+ //
812
+ // 每批上限:0 = 不设上限(严格回退到旧的一次性全送,用于排查"是不是分批
813
+ // 引入的差异")。取 12 是因为实测单对约 112~168 输入 token、12 对落在
814
+ // 1.5K~2K 区间,远低于任何服务商的上下文窗口,也不至于让 LLM 在长清单里
815
+ // 走神。
816
+ sleepConflictMaxPairs: z.natural().min(0).max(500).default(12).description("sleep 冲突阶段每批送给 LLM 的候选对上限(0 = 不设上限,回退旧的一次性全送)。实测单对约 112~168 输入 token,默认 12 对约 1.5K~2K。"),
817
+ // 每轮总上限:分批之后仍要有个总闸,否则一个 449 对的池子会拆成 38 批、
818
+ // 每批一次 LLM 往返,单轮跑上几小时(本身也没比撞墙好)。0 = 不设上限。
819
+ // 未入选的对不是"被丢弃"——它们留在 conflict_pending 与下一轮的候选池里。
820
+ sleepConflictMaxPairsPerRun: z.natural().min(0).max(5000).default(60).description("sleep 冲突阶段单轮累计裁定的候选对上限(0 = 不设上限)。默认 60 对 ≈ 5 批,约 10K 输入 token。"),
821
+
822
+ // --- epistemic trust: memory source credibility (v0.4.5) -----------------
823
+ // Distinguish memories by source: observation (measured / witnessed),
824
+ // subjective (opinion / guess) and inferred (derived from other evidence).
825
+ // Opt-in by default: when false (default) retrieval ranking, injection
826
+ // marking and dream merge/conflict keepSource are untouched and
827
+ // epistemic_status stays inert data (still written + inferred on save, just
828
+ // never used to influence behavior).
829
+ trustEpistemicWeighting: z.boolean().default(false),
830
+
831
+ // --- memory quality filter (Bug7) ------------------------------------------
832
+ // Heuristic gate on what deserves the injection/recall surface. When enabled,
833
+ // saveWithDedupe scores each new memory after dedupe and before write:
834
+ // score >= degradeThreshold (60) → stored normally
835
+ // archiveThreshold (30) <= score < 60 → quality_score persisted and the
836
+ // injection sort re-ranks by importance * quality_score/100 (degraded)
837
+ // score < 30 → archived + tagged low_quality (still explicitly searchable)
838
+ // Meta-memory markers, near-duplicates and repetitive filler lose points.
839
+ // "Repetitive" is trigram repetition > 0.7 (this change). The shipped v0.7.12
840
+ // measure was unique-chars/total, which decays with length: it flagged 94% of
841
+ // 800-2000 char memories and 100% of longer ones, i.e. long Chinese prose was
842
+ // punished for being long. Threshold rationale lives in quality-filter.js.
843
+ memoryQualityFilter: z.object({
844
+ enabled: z.boolean().default(true),
845
+ archiveThreshold: z.natural().min(1).max(100).default(30),
846
+ degradeThreshold: z.natural().min(1).max(100).default(60),
847
+ minContentLength: z.natural().min(1).max(1000).default(10),
848
+ // Issue #135 附属发现 1:importance ≥ 该值的记忆不参与静默自动归档——评分
849
+ // 与信号标签照常写入(可观测)、注入排序照常降权,但 setArchived 跳过。
850
+ // 报告实测 150 条低分归档里 128 条 importance ≥ 4(51 条 = 5)。设 1 = 全部
851
+ // 豁免(等效关闭自动归档),设 5 = 仅最重要的豁免。
852
+ exemptImportance: z.natural().min(1).max(5).default(4)
853
+ }).default({}),
854
+
855
+ // --- LLM audit trail (Bug8) ------------------------------------------------
856
+ // Records every background LLM call into llm_audit_logs: tokens, duration,
857
+ // status and which trigger produced it. Failures are recorded as
858
+ // status='error' and never block the feature. retentionDays bounds the table:
859
+ // older rows are purged on boot.
860
+ //
861
+ // Covered trigger_source values: autoDream (consolidation + summary),
862
+ // autoSummarize (compression), sleep (conflict + pattern) and entityExtract
863
+ // (issue #250 — the last two were structurally missing the hook, not a
864
+ // deliberate narrowing). entityExtract runs on every memory write, so row
865
+ // growth scales with write volume; retentionDays caps the ceiling but not the
866
+ // rate. If that turns out too fast, sample by operation_type or give it a
867
+ // separate retention — do not add a second gate: this flag is the single one.
868
+ llmAudit: z.object({
869
+ enabled: z.boolean().default(true),
870
+ retentionDays: z.natural().min(1).max(3650).default(90)
871
+ }).default({}),
872
+
873
+ // --- distill records + provenance (#399) ------------------------------------
874
+ // 蒸馏把窗口压成记忆后,原文默认不留——于是「这条记忆出自哪段对话」事后无法
875
+ // 复核(arXiv 2609.36059 §3 Eq.1:旁路索引不丢信息,替换才丢)。开启后蒸馏时
876
+ // 顺带把**公开**正文原样落盘(distill_records),并给写出的记忆挂上窗口链接
877
+ // (memory_provenance)。默认关:这是一份对话原文副本,价值与暴露面都取决于
878
+ // 用户是否愿意留档,不该由升级默认打开。
879
+ //
880
+ // retentionDays 只裁 distill_records(启动即清,挂 llmAudit purge 旁);链接
881
+ // 保留指向空区间,面板/CLI 显示「来源已过期」——删原文不等于篡改历史。落盘
882
+ // 文本不受 distillMaxChars / trim(args,300) / trim(out,500) 约束(记录是凭证,
883
+ // 不是喂给 LLM 的预算);工具调用入参与结果、code 输出不落盘(推理亦然)。
884
+ distillRecords: z.object({
885
+ enabled: z.boolean().default(false),
886
+ retentionDays: z.natural().min(1).max(3650).default(90)
887
+ }).default({}),
888
+
889
+ // --- capacity warning (#275: 空间维度) ---------------------------------------
890
+ // 容量预警:库体积 / 归档区规模过线时在 /health 与面板上报(维护者 2026-10-21
891
+ // 接手口径:有 retentionDays 的时间维度,缺空间维度)。三个阈值都是**观察口**,
892
+ // 不是动作触发:没有任何周期性消费者(/health 被读到才算一次),预警也永不
893
+ // 自动归档 / 删除(#275 拍板:真删由人显式确认)——所以不设 enabled 总开关,
894
+ // 数字恒在 /health,阈值 0 = 显式关掉那一条检查。
895
+ //
896
+ // 默认值锚定议题里的形态区间,不是拍脑袋:
897
+ // dbSizeWarnMb 256 —— 议题实测 55 MB 库 VACUUM 0.8s;按同议题的归档增速
898
+ // (+143 行/天 ≈ 2.8 MB/天)留约 70 天余量,正落在「现在便宜、以后贵」
899
+ // 该开始看数字的量级;
900
+ // archivedRowsWarn 5000 —— 「归档是数千条这个量级」的上沿(议题实测 2,400
901
+ // 时还没坏);
902
+ // archivedRatioWarn 0.95 —— 健康形态本身就落在 0.8~0.9(活跃数百 / 归档
903
+ // 数千,议题实测 0.90),0.9 会对着健康库天天报警;0.95 起才是「归档区
904
+ // 把活跃区吞掉」的形态。
905
+ // 判级实现在 service.getCapacityHealth,答复挂 /health 的 capacity 块;出口是
906
+ // 维护卡的无损回收(dsh-mneme reclaim,见 docs/STORAGE.md)与后续批次的归档区
907
+ // 周期处理。增速指标(归档净增速率)在 recall-stats 的 archive 块,不在这里重复。
908
+ capacityWarning: z.object({
909
+ dbSizeWarnMb: z.natural().min(0).max(1048576).default(CAPACITY_WARNING_DEFAULTS.dbSizeWarnMb),
910
+ archivedRowsWarn: z.natural().min(0).max(10000000).default(CAPACITY_WARNING_DEFAULTS.archivedRowsWarn),
911
+ // 浮点:与 conflictAutoResolveMinSimilarity 同款(面板整数控件吃不下小数,
912
+ // 走 FEATURE_FLAG_NUMBER_RANGES)。
913
+ archivedRatioWarn: z.number().min(0).max(1).default(CAPACITY_WARNING_DEFAULTS.archivedRatioWarn)
914
+ }).default({}),
915
+
916
+ // --- write admission (#254) ------------------------------------------------
917
+ // 写入准入:把「这条该不该进库」前移到 LLM 之前。与 memoryQualityFilter 的分工
918
+ // 是时点不是判据——那个是**写入后**打分归档(扣分可以错,只影响排序与归档),
919
+ // 这个是**写入前**判定(硬拒不能错)。
920
+ //
921
+ // 两个开关都默认关(验收第 1 条「默认关零行为变化」):
922
+ // enabled — 跑第 1 级确定性判据。关时 evaluate 的返回与只计量那一阶段逐字段
923
+ // 一致,写入路径完全不碰。
924
+ // enforce — 命中时真的拒绝。关时判据照跑、审计照留,决策回落 allow(仅告警)。
925
+ // 读审计时「metadata.deny 非空且 metadata.decision=allow」就是这一档的指纹。
926
+ // 分层是为了能把「先观察」和「开始拦」分成两次上线:enforce 打开前先在真实流量
927
+ // 里看误杀面,而不是靠回滚开关当保险。
928
+ //
929
+ // 第 1 级的判据面刻意收窄到两类:密钥/PII(#164 A2 是判据来源,经
930
+ // createWriteAdmission 的 sensitiveScan 注入;本批只定义它怎么被消费)与空白/
931
+ // 纯噪声文本。去重键命中**不**进第 1 级——按维护者 09-22 的判定归 write-update
932
+ // 放行(2606.24535 的失效模式:同步近重复门跑在异步矛盾检测之前,把本该被裁决
933
+ // 的矛盾直接拒掉);G1/G2 的阈值继续只计量,等遥测有分布再单独提。
934
+ writeAdmission: z.object({
935
+ enabled: z.boolean().default(false),
936
+ enforce: z.boolean().default(false)
937
+ }).default({}),
938
+
939
+ // --- issue #380: pre-inject gate (opinion/stance judgment before injection) -
940
+ // 注入前判定:每帧注入候选出池后、进入 system prompt 前,一次池级 LLM 调用判出
941
+ // 「会向本次请求注入意见/立场」的记忆(E12 D1 口径,判定协议同源实验
942
+ // protocol-preinject.txt)。两级语义复用 writeAdmission:
943
+ // enabled — 跑判定。判定 async prefetch + cache、同步消费(宿主 systemPrompt
944
+ // 渲染是同步回调,冷缓存首帧降级为不判定原样注入,下一轮同 key 生效);
945
+ // 判定完成落 llm_audit_logs(trigger_source=preInjectGate,真实 token 用量,
946
+ // related_memory_ids=被标记记忆)——先在真实负载看意见占比分布。
947
+ // enforce — 缓存命中帧真的滤除被标记候选。E12 实测只有过滤过判据(D1 −24.4pp,
948
+ // 判定+标记 D2 比它差 +17.6pp——给模型看标记的线已被 E2/E3 关闭,本闸判定
949
+ // 结果绝不进模型上下文)。enforce 无 enabled 时无效果。
950
+ // 降级路径:判定失败/超时/不可解析 → 本帧原样注入 + 审计行 degraded=true 且不
951
+ // 缓存——防线故障永不阻塞注入。判定 LLM 走 agentDefaultModel 当前选择(entity
952
+ // adapter 同款装配),不设独立 provider/model 键;llmAudit.enabled=false 时本闸
953
+ // 不运行(无账目的判定不可见也不该花钱)。lightMode 强制关(LLM-per-turn 附加
954
+ // 路径与低资源档互斥)。
955
+ preInjectGate: z.object({
956
+ enabled: z.boolean().default(false),
957
+ enforce: z.boolean().default(false)
958
+ }).default({}),
959
+
960
+ // --- #164 A2: secret / PII scan at the write boundary ----------------------
961
+ // 写入边界的密钥 / PII 判据(src/sensitive-scan.js)自身的闸。与 writeAdmission
962
+ // 的 enabled 分开是有意的:那一个管 #254 第 1 级的空白 / 噪声判据,本键管 A2 这
963
+ // 一类判据,两批的误杀面差一个量级(空白 / 噪声没有解释空间,邮箱 / 手机号有),
964
+ // 绑在同一个开关上就没法单独观察 A2 的命中分布。
965
+ //
966
+ // 分层必须写清:判据的唯一调用点在 #254 第 1 级的闸门里(write-admission.js 的
967
+ // firstLevelHit),闸门不走第 1 级就没人来调这个扫描器。所以本键是「闸门内这一批
968
+ // 判据参不参与」,不是一条能独立跑的链路——单开本键就是零行为变化:
969
+ // writeAdmission.enabled 关 → 第 1 级整个不跑,本键开也没用
970
+ // enabled 开 + 本键关 → 只跑空白 / 噪声那一批
971
+ // enabled 开 + 本键开 + enforce 关 → 命中留审计,写入照常(观察档)
972
+ // enabled 开 + 本键开 + enforce 开 → 命中即拒绝
973
+ // 默认关 = 只计量那一阶段的行为逐字节保留(#332 合并时 sensitiveScan 就是 null)。
974
+ // #164 的「默认仅告警、拦截 opt-in」落在 enforce 上:命中落一条审计
975
+ // (metadata.deny.kind)但照常写入,真要拦得 enabled 与 enforce 同时开。
976
+ // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
977
+ sensitiveScanEnabled: z.boolean().default(false),
978
+
979
+ // --- recall evaluation: test-result storage (v0.4.6, 方案 B) --------------
980
+ // Separate retrieval evaluation snapshots from the production recall audit.
981
+ // When false (default) evaluateRetrieval still computes precision/recall/mrr
982
+ // and returns them to the caller, but writes nothing to recall_evals — the
983
+ // eval table only grows when the operator opts in. Production searchMemories
984
+ // audits to recall_runs and NEVER touches recall_evals, regardless of this
985
+ // flag (production isolation is unconditional).
986
+ evalPersistTestResults: z.boolean().default(false),
987
+
988
+ // --- standalone external API (v0.7.12) ------------------------------------
989
+ // A plain node:http server for ecosystem integrations that cannot reach the
990
+ // DSH-internal webServer. Disabled by default; when enabled the Bearer token
991
+ // is persisted in the settings kv ("external_api"), auto-generated on first
992
+ // boot. Bind host: keep the loopback default — moving it to a non-loopback
993
+ // address exposes the whole memory store to the network and is the
994
+ // operator's responsibility.
995
+ externalApiEnabled: z.boolean().default(false),
996
+ externalApiPort: z.natural().default(8790),
997
+ externalApiHost: z.string().default("127.0.0.1"),
998
+
999
+ // --- light mode preset (v0.7.12) -------------------------------------------
1000
+ // One switch for low-resource setups: turns off every background/semantic
1001
+ // heavy path (dream consolidation, entity extraction, vector pipeline,
1002
+ // reranker, BM25, semantic dedup / selective inject, sleep mode) while
1003
+ // keeping the core loop (autoInject, autoSummarize, hot memory, quality
1004
+ // filter, keyword search). Applied by applyLightModePreset before the config
1005
+ // reaches any service; a persisted panel_mode="light" (settings kv) counts
1006
+ // as lightMode=true too and wins over the bundle config.
1007
+ lightMode: z.boolean().default(false),
1008
+
1009
+ // --- heat: v0.7.0 self-evolution (heat + interest drift) ----------------
1010
+ // 总开关,默认关(v0.7.12+ 用户已习惯无 heat 行为,默认开=全员行为变更)。
1011
+ // 开启后:提供热度字段 / sleep 降级联合判定保护 / 前端热度投影,并只在
1012
+ // 注入排序的优先级层内乘 heat(#218 v1;store 的 order=chrono 分页序与
1013
+ // 召回融合序不动)。关闭则跳过所有 heat 计算与热度触达,注入排序乘数
1014
+ // 恒 1,sleep 降级退回纯时间分层。
1015
+ // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
1016
+ heatEnabled: z.boolean().default(false),
1017
+ // issue #218 / E5 效用考卷:heat 乘进注入排序在真实年龄混合下饿死老约束
1018
+ // (现行量级 ≡ 拟合参数,注入集逐条相同;importance-only 遵从 +12.7pp),
1019
+ // 故注入侧 heat 改为独立 opt-in:默认关 = heatEnabled 开启后热度字段/sleep
1020
+ // 保护/前端投影照常,但注入排序乘数恒 1(与 heat 关闭逐字节一致);
1021
+ // 显式开启才恢复「层内乘 heat」的旧序。召回侧时钟(touchLastAccess)与
1022
+ // sleep 降级联判不受本键影响——它们不参与注入排序。
1023
+ injectHeatEnabled: z.boolean().default(false),
1024
+ // 广义指数形状参数 β(heat = exp(-λ·Δt^β),issue #218 拍板):β=1 纯指数。
1025
+ // 快慢以 Δt>1 小时为准——β<1 衰减更慢(亚线性长尾)、β>1 更快(超线性);
1026
+ // 0<Δt<1 的首小时内方向相反(Δt^β 随 β 增大而变小)。专家调优项,不进面板白名单。
1027
+ heatGlobalBeta: z.number().min(0.5).max(2).default(1.0),
1028
+ // per-type 衰减因子 λ;λ=0 的类型免疫(热度恒 1.0,sleep 永不降级)。
1029
+ // 未知类型走默认 0.002。dict 的键为 type 字符串、值为数字 λ。
1030
+ heatTypeDecay: z.dict(z.number(), z.string()).default({ ...TYPE_DECAY_DEFAULTS }),
1031
+ // sleep 降级联合判定的热度下限:heat < 该值 且 importance<5 才允许降级。
1032
+ sleepHeatThreshold: z.number().min(0).max(1).default(0.05),
1033
+ // recordRecall 默认值(recall_runs 记录默认开;显式传 false 的调用方不受影响)。
1034
+ recallRecordDefault: z.boolean().default(true),
1035
+ // recall_runs 滚动清理保留天数。
1036
+ recallRetentionDays: z.natural().min(1).max(3650).default(90),
1037
+
1038
+ // --- tool exposure: 慢模型/轻量模型的工具往返节流(v0.8.5)----------------
1039
+ // 跨会话记忆已由 inject.js 每轮自动注入系统提示词,memory_search 只用于
1040
+ // 「注入里没有、需要深挖」的补充检索;memory_archive 是隐藏/恢复条目的整理
1041
+ // 操作,正常会话里很少需要。轻量模型对「何时该用工具」判断弱,容易每轮
1042
+ // 顺手调一遍——每次工具调用都是一次串行往返(生成参数→执行→回填→再生成),
1043
+ // 在慢模型上会被放大成明显卡顿。这两个开关允许直接隐藏对应工具(默认全
1044
+ // 开=行为不变),隐藏后模型根本看不到它,也就不会调。也走 feature_flags
1045
+ // 白名单,面板可启停=线上回滚开关。
1046
+ disableMemorySearch: z.boolean().default(false),
1047
+ disableMemoryArchive: z.boolean().default(false),
1048
+
1049
+ // Issue #231 第二批:agent 主动整理接口(src/organize.js)的**暴露**闸。引擎在
1050
+ // PR #267 就造好了,当时刻意不进工具列表、不加开关(模块头注释:「与 #249 同批
1051
+ // 暴露时只差『注册工具 + 注入指引』一步」),配置面留到这一批再定——就是这里。
1052
+ // 与上面两个 disable* 是**反向**语义:那两个把默认在场的工具藏起来,本键把默认
1053
+ // 不在场的能力开出来。因为 dryRun→apply 是一套两步协议(apply 必须引用真实报告
1054
+ // 的 run_id),让 agent 未经用户同意就能整理记忆,不是「少一次往返」的成本问题,
1055
+ // 而是「谁在改库」的授权问题。
1056
+ // 进 LIGHT_MODE_OFF:两步协议在弱模型上最容易走歪(漏 run_id、或跳过报告直接
1057
+ // apply——那一拒绝就是一次失败的工具调用),而一次 dryRun 的代价是一回嵌入调用
1058
+ // 加按 type 有界扫库。这是预设给的默认值、不是强制:用户显式勾选仍然赢(合并顺序
1059
+ // 「用户开关 > 轻量预设 > bundle」,同 continuityRescueEnabled / distillRecords.enabled)。
1060
+ organizeEnabled: z.boolean().default(false),
1061
+
1062
+ // chorography(仓库地志,spec: docs/specs/2026-11-04-chorography-design.md)的
1063
+ // 暴露闸(opt-in,默认关):管模型侧三个只读工具(choro_search / read_page /
1064
+ // module_map)、面板浏览页,以及 M3 的开局注入。工具描述常驻模型上下文 = 新增
1065
+ // 成本,按仓库纪律 opt-in;默认关=工具不存在、面板页空态、注入块不渲染,行为与
1066
+ // 0.10.1 逐字节一致。
1067
+ //
1068
+ // 本键仍**不进 LIGHT_MODE_OFF**,但理由收窄成一句:它是「是否启用这个子系统」,
1069
+ // 不是「每轮付多少」。轻量档要付的是下面那条 chorographyInject(把档位压成
1070
+ // off)——两层分开,用户才能在轻量档里保留工具、只关掉每轮那块文本。
1071
+ chorographyEnabled: z.boolean().default(false),
1072
+
1073
+ // 开局注入档位(M3,spec §7):off / light / full,默认 light。
1074
+ // light ≤8 行(模块名 + 文件数 + 页状态 + 页路径,按「还有 N 个」明说截断);
1075
+ // full 列全部模块;off 不注入但工具照旧可用。
1076
+ // 为什么是枚举而不是布尔:spec 原文就是三档,「开了但只给 6 行」与「全给」是两种
1077
+ // 用法,布尔键表达不了;跟随 chorographyEnabled 隐式开又会丢掉「留工具、去文本」
1078
+ // 这个组合。
1079
+ // 为什么写作 z.union([z.const(...)]) 而不是 z.enum:与 summarizeDedupeMode 同款,
1080
+ // 全仓枚举键统一这一种写法(z.enum 的报错文案与既有键不一致)。
1081
+ // 轻量档**不进 LIGHT_MODE_OFF**:那张表对平铺键一律赋 false,枚举键进去会得到与
1082
+ // schema 不符的 false,而 false !== "off" 时注入照样渲染——静默失效比不关更糟。
1083
+ // 轻量档的压制改在消费侧(src/wiki/inject.js 的 resolveInjectTier 判 lightMode)。
1084
+ chorographyInject: z.union([z.const("off"), z.const("light"), z.const("full")]).default("light"),
1085
+
1086
+ // --- scope: v0.8.0 A1 存储层(issue #17)--------------------------------
1087
+ // 总开关默认关:关闭时写入不标注 scope、去重维持 (type, title) 现状,行为
1088
+ // 逐字节不变。开启后**两条写入路径都自动盖章**(*_scope_source='auto'):
1089
+ // ① 工具调用(tools.js:memory_save / memory_update,显式参数改标 'explicit')
1090
+ // ② 机器提炼(summarize.js:会话蒸馏;合并进既有行时不盖章,作用域属于那一行
1091
+ // 原来被提炼出来的语境——v0.9.0 补,此前只有工具路径盖章,于是「项目记忆」
1092
+ // 在库里几乎不存在)。
1093
+ // 两路的值同源:agent 维取 session header 的 agentPreset,workspace 维取
1094
+ // registry 反查的 canonical path(header.cwd 兜底,取不到 NULL——解析绝不阻塞
1095
+ // 写入),去重键扩展为 (type, title, agent_scope, workspace_scope, sensitivity)。
1096
+ // 检索侧加权/过滤在 A2/A3 落地。
1097
+ // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
1098
+ scopeEnabled: z.boolean().default(false),
1099
+ // strictScope(A3):硬过滤模式。关闭=A2 软隔离(他 scope 降权保留可见,
1100
+ // 检索与注入排序同权重,issue #339 补齐注入通道);
1101
+ // 开启后检索/注入/list/get 按 issue #17 四象限可见性公式硬过滤——带他 scope
1102
+ // 的记忆完全不可见,未标注(NULL)恒可见;当前会话某维度解析不到时该维度
1103
+ // 带标注的记忆一律不可见(fail-closed:身份不明只见全局)。依赖 scopeEnabled
1104
+ // 打开才有意义(没有写入标注就无可过滤维度),但独立成键:可先开标注积累
1105
+ // 数据、观察 A2 加权质量后再切硬过滤。
1106
+ strictScope: z.boolean().default(false),
1107
+
1108
+ // scopeState(issue #471):政策轴——作用域级可逆闸门。与上面两个键是**两条不同
1109
+ // 的轴**:scopeEnabled/strictScope 管「这条记忆属于谁」(事实轴:持久、跟着记忆
1110
+ // 行走、改的是归属数据),本键管「这个作用域现在通不通」(政策轴:暂时、由人单
1111
+ // 方面按下、松开即恢复、不改任何记忆行)。
1112
+ // 两级语义同 writeAdmission/preInjectGate——enabled 打开时被按下的作用域其候选
1113
+ // 照常注入,但留一条审计行说清「本应被拦下几条」(观察档,先在真实流量看会少掉
1114
+ // 多少再决定真按);enforce 打开才真的不注入(拦截档)。两个都默认关,打开前
1115
+ // 行为与今天逐字段一致。
1116
+ // 不进 LIGHT_MODE_OFF:它只是查一张小表的判定,不引入重型后台路径、不加常驻
1117
+ // 文本(同 actionPipelineEnabled 的定性)——轻量档预设该关的是重型能力,不该替
1118
+ // 用户关掉一个纯判定入口,用户显式勾选仍然赢。
1119
+ // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
1120
+ scopeState: z.object({
1121
+ enabled: z.boolean().default(false),
1122
+ enforce: z.boolean().default(false)
1123
+ }).default({}),
1124
+
1125
+ // --- v0.9.0 M.O.(Modus Operandi,beta)--------------------------
1126
+ // 命名:M.O. = Modus Operandi,拉丁语「一个人一贯的做事方式」,缩写 M.O.;
1127
+ // 也正是 org 词根 modus(modusensus)。界面主显示 M.O.,设置项 hint 第一句
1128
+ // 给全名——缩写不靠注释解释,靠用户第一眼能看见全名。
1129
+ // 这是客户端只读视图(做法图谱 + 对话行结算卡)的门控。API 的 effective 取值只认
1130
+ // config 顶层/嵌套路径,schema 里没有这个键 → effective 静默少键 → 客户端读
1131
+ // 不到就一个座位都不注册(#254 漏登记 NESTED_FLAG_PATHS 是同一个坑)。
1132
+ // 默认关:beta 功能必须 opt-in,老用户行为逐字节不变。不进 LIGHT_MODE_OFF——
1133
+ // 它只是只读视图、不引入任何重型后台路径,轻量档用户显式勾选仍然赢(预设
1134
+ // 该关的是重型能力,不该替用户关掉一个纯 UI 入口)。
1135
+ // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
1136
+ actionPipelineEnabled: z.boolean().default(false),
1137
+
1138
+ // M.O. 执行记账(v0.9.0 第二批):`/mo-run` 显式开一次执行窗口,窗口内的命令执行
1139
+ // 才可能被记成一条 run。与上面那个键**分开**:上面管「看图的座位」,这个管
1140
+ // 「往账本里写」——看图和写账是两件事,混成一个键会让「只想看图」的人不知不觉
1141
+ // 获得写入能力。
1142
+ //
1143
+ // 默认关(beta 必须 opt-in)。它挂在热路径上(每个会话事件都过一次监听),
1144
+ // 所以轻量档要关掉:`LIGHT_MODE_OFF` 里有它。
1145
+ actionRunEnabled: z.boolean().default(false),
1146
+
1147
+ // M.O. 自动沉淀(v0.9.0 第三批):会话跑完一轮后,把这个项目自己的会话日志挖一遍,
1148
+ // 把「反复出现的同一段能力序列」落成提议(memoryDir 下),可选再落一份 draft 草稿。
1149
+ //
1150
+ // 与上面两个键三件事分开:actionPipelineEnabled 管「看图的座位」,actionRunEnabled
1151
+ // 管「往账本里写」,这个管「从日志里长出候选」。三个都是独立能力,混成一个键会让
1152
+ // 「只想看图」的人顺手获得写盘能力。
1153
+ //
1154
+ // 默认关(beta 必须 opt-in),并且进 LIGHT_MODE_OFF:它挂在 session/event 热路径上,
1155
+ // 且解压会话日志是**同步**的(本机 118 份实测 16.2s),轻量档最不该背这条路径。
1156
+ // 触发侧另有 10 分钟/项目的冷却与 budgetMs 上限(见 src/pipeline-nudge.js 的
1157
+ // NUDGE_AUTO_MIN_INTERVAL_MS 与 src/index.js 的装配注释)——旗标只是总闸。
1158
+ // (冷却值以常量为准:早前这里写「30 分钟」,与实际 10 分钟不符。)
1159
+ actionNudgeEnabled: z.boolean().default(false),
1160
+
1161
+ // --- 人格定义页(beta)---------------------------------------------------
1162
+ // 注入器渲染人格段的顶层总闸(双闸门的 config 半边;user_settings "persona"
1163
+ // 键内的 persona.enabled 是 per-agent 半边,两闸同真才渲染)。API 的 effective
1164
+ // 必须含这个键——schema 里没有它 effective 就静默少键,面板开关形同虚设
1165
+ // (#254 漏登记 NESTED_FLAG_PATHS 是同一个坑,计数锁 test/api.test.js 钉住)。
1166
+ // 默认关:beta 功能必须 opt-in,老用户行为逐字节不变。不进 LIGHT_MODE_OFF——
1167
+ // 人格段是用户自写的一小段轻量常驻文本,不引入任何重型后台路径,轻量档用户
1168
+ // 显式勾选仍然赢(同 actionPipelineEnabled 的定性)。
1169
+ // 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
1170
+ personaEnabled: z.boolean().default(false),
1171
+
1172
+ // 人格寻址的兜底口径(只在上一个键打开时才有意义)。关(默认,= 历史行为):
1173
+ // 会话身份没有卡、或有卡但卡被关掉时,一律落 default 卡——于是 default 成了
1174
+ // 「所有没建卡的 agent 都能拿到某个人格」,宿主里跑的外部 bot(自带身份与设定)
1175
+ // 会在同一帧里收到两句「你是谁」。开:这两种情况都不注入人格,default 退回它的
1176
+ // 本意——只兜「这一帧解析不到会话身份」的无名会话(无身份时行为不变)。
1177
+ // 默认关是因为这是一次口径变更:老用户升级后注入文本不许静默变化。
1178
+ // 不进 LIGHT_MODE_OFF:它只是取卡时的一个分支,不引入常驻文本、也不引入后台路径,
1179
+ // 且它本身只在 personaEnabled 打开时才起作用(同 scopeState 的定性)。
1180
+ personaStrictAddressing: z.boolean().default(false),
1181
+
1182
+ // Issue #497:从活跃记忆反向提取「助手眼中的用户」画像**草稿**(POST
1183
+ // /api/dsh-mneme/profile/extract)。默认关有三层理由:① 它每次点一次就是一次 LLM
1184
+ // 调用并读上百行记忆,成本与 dream/summarize 同级,不该在没人要求时挂在面上;
1185
+ // ② 产物会被用户当成「关于我的第一版画像」读,编造的代价不对称——所以空库直接报错、
1186
+ // 解析失败也报错,绝不回空草稿;③ 它**不写** user_settings,采用草稿是面板里另一次
1187
+ // 显式保存,画像正文必须是用户手写的原文,不给模型改写权。
1188
+ // 进 LIGHT_MODE_OFF:一次调用要读上百行记忆 + 一轮模型生成,与低资源档互斥(同
1189
+ // preInjectGate.enabled 的定性);是预设不是强制,用户显式勾选仍然赢。
1190
+ profileExtractEnabled: z.boolean().default(false),
1191
+
1192
+ // 「Agent 眼中的你」自动事实(2026-11-06,对标 ChatGPT 记忆 / DS Bot 每 bot 的
1193
+ // MEMORY.md):dream 巩固轮成功后按会话身份分卡增量维护一份逐条事实清单,
1194
+ // 注入在手填画像之后(src/user-settings.js 的渲染顺序),面板可手动触发与逐条
1195
+ // 修正(POST /api/dsh-mneme/profile-facts/*)。与 profileExtractEnabled 的三层
1196
+ // 理由同源:① 自动轮是 dream 级别的 LLM 调用 + 读上百行记忆,不该在没人要求时
1197
+ // 挂在面上;② 产物每帧注入,编造的代价不对称(维护 prompt 的三段硬约束与
1198
+ // profileExtract 同源);③ 它**不写** user_settings——手填画像神圣(#497 口径
1199
+ // 不变),事实住独立的 profile_facts 表,可被人在面板上逐条修正。
1200
+ // 进 LIGHT_MODE_OFF:自动维护轮与低资源档互斥(同 organizeEnabled 的定性);
1201
+ // 是预设不是强制,用户显式勾选仍然赢。注入段本身极轻(几十行文本),不额外
1202
+ // 占用轻量档预算的考量见键注释末句。
1203
+ profileFactsEnabled: z.boolean().default(false),
1204
+
1205
+ // chorography 面板闭环(2026-11-08 kb-qoder parity,契约见
1206
+ // docs/plans/2026-11-08-kb-qoder-parity.md §3.1)。三个键都是「面板能不能替人
1207
+ // 动手」的授权闸,与上面的 chorographyEnabled(子系统总闸)分层:总闸关着时
1208
+ // 它们一律不生效,所以不必再进 LIGHT_MODE_OFF —— 轻量档要压的是「每轮付多少」,
1209
+ // 而这三个键管的是「谁按下了重跑按钮」,关了也省不下一次调用,反倒让面板在
1210
+ // 轻量档里静默少一块可操作区(同 chorographyEnabled 的定性与理由)。
1211
+ //
1212
+ // 过期的仓库在面板上直接刷新(POST /choro/run 的 refresh)。默认关:一次 T0 重跑
1213
+ // 会扫整个仓库并重写 .chorography/ 下的页面,属于「改动工作区」的动作,不该在
1214
+ // 没人要求时被面板顺手触发。
1215
+ chorographyAutoRefresh: z.boolean().default(false),
1216
+ // 面板发起 T1(POST /choro/t1)。默认**开**:这是面板闭环的主路径,关掉等于面板
1217
+ // 上那个按钮永远是死的;真花钱的那一步仍由用户点按钮触发,且有
1218
+ // chorographyEnabled 总闸在前面。需要「只读面板」的部署可以单独关它。
1219
+ chorographyT1FromPanel: z.boolean().default(true),
1220
+ // kb 子系统(知识库问答,B 线实现)的总闸。本车道只登记键与白名单,接线由后续
1221
+ // 车道做——先有键才能让 /features 稳定暴露 false,客户端不至于静默少键。
1222
+ kbEnabled: z.boolean().default(false),
1223
+
1224
+ // #539 逐会话无痕:总开关默认关,保持旧用户行为;子代理选择器默认开,但总开关
1225
+ // 未开时不生效。手动名单是精确的 session id 匹配。
1226
+ sessionGateEnabled: z.boolean().default(false),
1227
+ sessionGateSubagents: z.boolean().default(true),
1228
+ sessionGateSessions: z.array(z.string()).default([])
1229
+ });
1230
+
1231
+ // Fields forced to false by the light-mode preset. Everything not listed here
1232
+ // (autoInject, autoSummarize, hotMemory*, memoryQualityFilter, dream
1233
+ // thresholds/delays, ...) is left untouched — those are the core loop.
1234
+ const LIGHT_MODE_OFF = [
1235
+ "entityExtractionEnabled",
1236
+ "autoDream",
1237
+ "sleepModeEnabled",
1238
+ // #619:情感归因相位要调模型、且依赖实体抽取产出(抽取关掉就没有实体可挂边),
1239
+ // 与低资源档双重互斥。默认本就是关,这里只是轻量预设再确认一次。
1240
+ "sleepAffectiveEnabled",
1241
+ "rerankEnabled",
1242
+ "autoReindexOnBoot",
1243
+ "hybridInject",
1244
+ // #249 第二批:能力说明在轻量档保持关闭——它是工具描述与提示段上的额外常驻
1245
+ // 文本,轻量档(小模型 / 小上下文)不该因默认值翻转而多付这份提示成本。
1246
+ "injectGuidanceEnabled",
1247
+ "searchSemanticDedup",
1248
+ "selectiveInjectEnabled",
1249
+ "bm25SearchEnabled",
1250
+ // 轻量模式不开图召回轴(#219,依赖实体抽取产出;抽取本身已被关掉)。
1251
+ "entityRecallEnabled",
1252
+ // 轻量模式不开图谱锚定层级联(#24 块1,依赖实体抽取 + 关系表,抽取已关则无边可扩)。
1253
+ "graphAnchoringEnabled",
1254
+ // 轻量模式不开边权重演化(#24 块2,依赖实体抽取产出;抽取关则无 v08 边可抬升)。
1255
+ "graphWeightEnabled",
1256
+ // 轻量模式不开关联提示标注(#24 块3,常驻注入文本多一段提示成本)。
1257
+ "graphInjectHint",
1258
+ // 轻量模式不开被动确认(#24 块4,依赖实体抽取产出;抽取关则无 v08 边可抬升)。
1259
+ "graphPassiveConfirm",
1260
+ // 轻量模式不开叙述条(额外 LLM 调用;#164 对齐,opt-in)。
1261
+ "dreamNarrativeEnabled",
1262
+ // 轻量模式不开 document 指针行(#230,opt-in:注册/注入/检索增强全随闸)。
1263
+ "documentMemoryEnabled",
1264
+ // #231 第二批:轻量档不开 agent 主动整理。理由不是成本而是协议——dryRun→apply
1265
+ // 要求 agent 拿住上一轮的 run_id 再回来,弱模型漏这一环就是一次被拒的调用;
1266
+ // 预设关、不剥夺显式开启的权利(合并顺序同 continuityRescueEnabled)。
1267
+ "organizeEnabled",
1268
+ // 轻量档不开画像事实自动维护轮(2026-11-06):dream 后的逐卡 LLM 调用 + 读上百行
1269
+ // 记忆,与低资源档互斥(同 organizeEnabled 的定性)。注入段本身是几十行常驻文本,
1270
+ // 关掉自动轮不省注入成本、省的是维护调用——这正是该进这张表的路径。
1271
+ "profileFactsEnabled",
1272
+ // 图像描述档:一次额外的视觉调用,与低资源档互斥(同 profileFactsEnabled 的
1273
+ // 定性)。预设关不是强制,用户显式勾选仍然赢。
1274
+ "imageDescribeEnabled",
1275
+ // 轻量模式不开热计算(heat 属于重型增强;关掉后 sleep 降级也退回纯时间分层)。
1276
+ "heatEnabled",
1277
+ // 轻量模式不开注入前判定(#380:LLM-per-turn 附加路径,每帧注入多一次池级
1278
+ // 判定调用——与低资源档互斥;预设给的默认值,用户显式勾选仍然赢)。点分键
1279
+ // 由 applyLightModePreset 的嵌套赋值分支处理。
1280
+ "preInjectGate.enabled",
1281
+ // #249 N3:轻量档默认不开压缩边缘双落点——它往对话里追加消息(新的注入表面),
1282
+ // 轻量档(小模型 / 小上下文)最不该再多一份注入物。这是预设给的默认值、不是强制:
1283
+ // 用户显式勾选仍然赢(合并顺序「用户开关 > 轻量预设 > bundle 配置」,同
1284
+ // injectGuidanceEnabled)。
1285
+ "continuityRescueEnabled",
1286
+ // #399:轻量档不开蒸馏原文留存——它是一份对话原文副本(磁盘 + 暴露面),
1287
+ // 与轻量档「小模型 / 小上下文 / 少留痕」的取向相反。默认关,这里只是
1288
+ // 轻量预设再确认一次;用户显式勾选仍然赢(合并顺序同 continuityRescueEnabled)。
1289
+ "distillRecords.enabled",
1290
+ // M.O. 执行记账:它挂在**热路径**上(每个会话事件都过一次监听)。轻量档的取向
1291
+ // 是「小模型 / 小上下文 / 少常驻路径」,这与一个每事件都过一遍的监听器相冲突。
1292
+ // 默认关,这里只是轻量预设再确认一次;用户显式勾选仍然赢(合并顺序同上面两条)。
1293
+ "actionRunEnabled",
1294
+ // #497:轻量档不开画像提取——一次调用要读上百行记忆再生成一段画像,与低资源档
1295
+ // 互斥(同 preInjectGate.enabled 的定性)。默认关,这里只是预设再确认一次;
1296
+ // 用户显式勾选仍然赢(合并顺序同 continuityRescueEnabled)。
1297
+ "profileExtractEnabled",
1298
+ // M.O. 自动沉淀:同样挂在 session/event 热路径上,而且解压会话日志是同步 I/O。
1299
+ // 轻量档的取向(小模型 / 小上下文 / 少常驻路径)与它直接冲突,预设里关掉;
1300
+ // 用户显式勾选仍然赢(合并顺序同上面几条)。
1301
+ "actionNudgeEnabled",
1302
+ // 日记本:一次要调模型写一整段散文,且挂 session/event 热路径做跨天判定。
1303
+ // 与低资源档(小模型 / 小上下文 / 少常驻路径)直接冲突,预设里关掉;
1304
+ // 用户显式勾选仍然赢(合并顺序同上面几条)。
1305
+ "diaryEnabled"
1306
+ ];
1307
+
1308
+ /**
1309
+ * Apply the light-mode preset to a resolved config object (pure function,
1310
+ * exported for tests). When cfg.lightMode is not exactly true the config is
1311
+ * returned unchanged; otherwise a shallow copy carries false for every heavy
1312
+ * feature. Idempotent and side-effect free.
1313
+ */
1314
+ export function applyLightModePreset(cfg) {
1315
+ if (cfg?.lightMode !== true) return cfg;
1316
+ const preset = { ...cfg, lightMode: true };
1317
+ for (const key of LIGHT_MODE_OFF) {
1318
+ // 点分键(#380 preInjectGate.enabled):对象子键的预设翻转——浅拷贝父对象后
1319
+ // 改子键,避免直接改到用户 config 里的同一引用。平铺键照旧直赋。
1320
+ const dot = key.indexOf(".");
1321
+ if (dot < 0) {
1322
+ preset[key] = false;
1323
+ continue;
1324
+ }
1325
+ const parent = key.slice(0, dot);
1326
+ preset[parent] = { ...(preset[parent] ?? {}), [key.slice(dot + 1)]: false };
1327
+ }
1328
+ return preset;
1329
+ }
1330
+
1331
+ /**
1332
+ * #249 第二批:注入形态的父/子开关。父 = `autoInject`(既有的挂载总闸,默认
1333
+ * 开)。子项按 #249 §10 的判据分档——看它是否引入**新的注入时机/新的注入
1334
+ * 表面/额外成本**:
1335
+ *
1336
+ * - 只修正既有每轮块内的位(能力说明)→ 随父开关生效,键的默认值给开;
1337
+ * - 需要新时机或新表面(N2 回合结束提醒、N3 压缩边缘)→ 独立开关、默认关,
1338
+ * 实现时挂到这张表下(今天还没有这两个键,故表里只有能力说明一项)。
1339
+ *
1340
+ * 语义:**父关 = 子项一律不生效,但子项的持久值原样保留**(父关是「不生效」,
1341
+ * 不是「重置用户配置」)。用户显式写进 kv 的值也不因父关被改写。
1342
+ *
1343
+ * 闸门放在消费点而不是合并后的 cfg 上:压 cfg 会让 `/features` 的 effective
1344
+ * 失去「子项自己勾着、父关时当前不生效」这个状态,面板就显示不出来。跨文件
1345
+ * 契约见 test/inject-parent-gate.test.js(含面板侧关系的漂移检查)。
1346
+ *
1347
+ * 表里只放**顶层扁平键**:点号键(如 `memoryQualityFilter.enabled`)在合并后的
1348
+ * cfg 里是嵌套对象,`cfg?.["a.b"]` 取不到,挂进来会恒判不生效且不报错。N2/N3
1349
+ * 是扁平键,不受这条限制。
1350
+ */
1351
+ export const INJECT_CHILD_FLAGS = Object.freeze({
1352
+ autoInject: Object.freeze(["injectGuidanceEnabled", "continuityRescueEnabled"])
1353
+ });
1354
+
1355
+ /** 子开关的运行时生效值:父开关显式关(false)时恒不生效。 */
1356
+ export function injectChildEnabled(cfg, key) {
1357
+ for (const [parent, children] of Object.entries(INJECT_CHILD_FLAGS)) {
1358
+ if (children.includes(key) && cfg?.[parent] === false) return false;
1359
+ }
1360
+ return cfg?.[key] === true;
1361
+ }