@modusensus/dsh-mneme 0.13.0 → 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 -828
  3. package/assets/icon.svg +3 -3
  4. package/bin/cli.mjs +964 -964
  5. package/bin/dsh-mneme-choro.mjs +262 -254
  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 -402
  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 -3394
  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 -13436
  33. package/lib/commands.js +95 -95
  34. package/lib/config-keys.js +144 -144
  35. package/lib/config.js +1361 -1317
  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 -907
  52. package/lib/dream/narratives.js +44 -44
  53. package/lib/dream/sleep.js +2027 -1887
  54. package/lib/dream/triage.js +139 -0
  55. package/lib/dream.js +1879 -1764
  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 -107
  72. package/lib/image-describe.js +158 -0
  73. package/lib/index.js +852 -822
  74. package/lib/inject.js +656 -639
  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 -584
  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 -3196
  124. package/lib/session-gate.js +91 -91
  125. package/lib/session-usage.js +350 -350
  126. package/lib/settings.js +827 -802
  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 -5662
  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 -0
  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 -474
  142. package/lib/wiki/render.js +261 -187
  143. package/lib/wiki/scan.js +371 -371
  144. package/lib/wiki/t0.js +500 -463
  145. package/lib/wiki/t1-jobs.js +232 -232
  146. package/lib/wiki/t1.js +609 -502
  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 +15 -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 -204
  162. package/scripts/release-notes.mjs +164 -161
  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 -3394
  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 -1317
  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 -907
  194. package/src/dream/narratives.js +44 -44
  195. package/src/dream/sleep.js +2027 -1887
  196. package/src/dream/triage.js +139 -0
  197. package/src/dream.js +1879 -1764
  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 -107
  214. package/src/image-describe.js +158 -0
  215. package/src/index.js +852 -822
  216. package/src/inject.js +656 -639
  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 -584
  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 -3196
  266. package/src/session-gate.js +91 -91
  267. package/src/session-usage.js +350 -350
  268. package/src/settings.js +827 -802
  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 -5662
  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 -0
  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 -474
  284. package/src/wiki/render.js +261 -187
  285. package/src/wiki/scan.js +371 -371
  286. package/src/wiki/t0.js +500 -463
  287. package/src/wiki/t1-jobs.js +232 -232
  288. package/src/wiki/t1.js +609 -502
  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 -2988
  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 -4184
  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 -0
  318. package/test/conflict-ledger.test.js +242 -0
  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 -320
  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 -0
  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 -209
  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 -211
  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 -106
  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 -494
  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 -0
  407. package/test/panel-tokens.test.js +245 -0
  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 -136
  453. package/test/release-notes.test.js +265 -240
  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 -178
  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 -1418
  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 -627
  529. package/test/wiki-claims.test.js +194 -0
  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 -0
  535. package/test/wiki-queries.test.js +398 -277
  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 -380
  539. package/test/wiki-t1-jobs.test.js +125 -125
  540. package/test/wiki-t1.test.js +460 -235
  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
@@ -1,354 +1,354 @@
1
- // Issue #380 注入前判定(preInjectGate):每帧注入候选出池后、进入 system prompt
2
- // 前,由一次池级 LLM 调用判出「会向本次请求注入意见/立场」的记忆(E12 D1 口径:
3
- // 判定协议与实验 protocol-preinject.txt 同源;「意见/立场」框架依据 E3 读数 2
4
- // 「FR 跟随意见内容」,且已被用户采纳为执行期决策)。
5
- //
6
- // 两条硬约束决定了本模块的形状:
7
- // 1. 宿主 systemPrompt 渲染是同步回调(inject.js 的 text callback 不支持 await,
8
- // service.js Bug4 注释同源)——判定只能 async prefetch + cache、同步消费,与
9
- // inject.js 的 queryVectorCache 同款。冷缓存首帧降级为不判定原样注入,下一轮
10
- // 同 key 渲染生效;防线故障永不阻塞注入。
11
- // 2. 判定只给闸门用,绝不进模型上下文——给模型看任何标记的整条线已被 E2/E3
12
- // 关闭(E12:判定+标记 D2 比判定+过滤 D1 差 +17.6pp)。
13
- //
14
- // 消费点在 service.injectCandidates 内、touchRecalled **之前**(#380 评审:与
15
- // strictScope 同纪律,见 service.js 的「闸门还必须在 touchRecalled 之前」注释):
16
- // 滤除若发生在函数返回之后,被滤掉的记忆照样刷温时钟、照样进 recall_runs 曝光账,
17
- // 反馈环会把闸门想压下的意见记忆重新顶上来。所以判定由 injectCandidates 调用,
18
- // 帧状态经 gateStats 出参回给调用方做面板快照(不动「返回数组」的既有契约)。
19
- //
20
- // 两级语义复用 #254 writeAdmission(enabled 观察档 / enforce 真拦截):
21
- // enabled=true 才跑判定;enforce=true 且缓存命中时同步滤除被标记候选;
22
- // observe 档(enforce=false)候选集不动,判定完成时落审计行看真实负载的意见
23
- // 占比分布(issue:先测分布再决定 enforce)。
24
- //
25
- // 审计走 llm_audit_logs:trigger_source="preInjectGate",真实 model_id 与 token
26
- // 用量(区别于 writeAdmission 的 "skipped"/"-" 占位——本闸有真实 LLM 花费,面板
27
- // 消费视图自动生效),related_memory_ids=被标记记忆的真实 UUID,metadata 带分布
28
- // 与降级标记。llmAudit 关闭时本闸不运行(与 write-admission 的 evaluate 早退同
29
- // 口径:无保留期的表不记账,无账目的判定不可见也不该花钱)。审计行**无条件落**:
30
- // 无路由时 model_id 落 "unknown"(summarize.js 的无路由占位同款),不许出现
31
- // 「判定照花钱、账目不见影」(#380 评审)。session_key 不写——store.js 的列语义是
32
- // 「#254 写入准入的会话键(LLM 调用行恒 NULL)」,写进去会挤占 write-admission
33
- // 话题回填的 TOPIC_LOOKBACK_ROWS 窗口。
34
- //
35
- // 候选映射为 m#<i> 局部序号参与判定、出参映射回真实 UUID——E13b 实测模型抄 36
36
- // 位 UUID 的错误率足以让整单决策作废,局部短 id 是同源解法。
37
- //
38
- // 超时与取消(CodeRabbit on #382):判定挂起用 GenerateOptions.signal(宿主
39
- // llm.stream 契约自带)真取消底层流,取消发起后**有界等待流真正停止**才放行同
40
- // key 重试;宿主不理会 signal 时该 key 进冷却期,冷却内不再对该池发新流——宁可
41
- // 暂缓防线也不累积悬挂流;冷却按 key,跨 key 的悬挂流由熔断兜(不守取消契约与
42
- // key 无关,同样计入连续降级)。冷却期与宽限期可注入(测试用),默认值不变。
43
- //
44
- // 两条资源护栏(#380 评审):
45
- // - 连续降级熔断:判定器系统性坏掉时(模型持续返回不可解析输出、宿主持续报错)
46
- // 逐帧重试等于把成本绑到交互帧数上——连续 FAILURE_BREAKER_N 次降级后全局停判
47
- // 一个冷却期,到期自动恢复。熔断只停花钱,不改防线行为(照旧原样注入)。
48
- // - 整池被标记时放弃本次 enforce(fail-open):记忆正文是判定模型的可影响输入,
49
- // 一条指令型记忆可诱导模型标记全池 → 整个记忆块静默失明。该形态本帧退回观察档
50
- // 原样注入并 warn;判定结果仍在缓存与审计里(n_flagged == n_pool 即此形态)。
51
- import { STR, langOf } from "./lang.js";
52
-
53
- export const GATE_TRIGGER_SOURCE = "preInjectGate";
54
- export const GATE_OPERATION_TYPE = "pre_inject_gate";
55
- // 判定输出上限:E12 实验同款硬编码(700),池级单次判定足够。
56
- const JUDGE_MAX_TOKENS = 700;
57
- // 缓存容量与 inject.js 的 queryVectorCache 同口径(cap 8,命中即刷新序 = LRU:
58
- // FIFO 下活跃查询会被一次性新池挤出去,之后反复重判)。
59
- const VERDICT_CACHE_MAX = 8;
60
- // 流未停止时的 key 冷却期:冷却内不发新流(等旧的死掉/被宿主回收)。
61
- const DEFAULT_COOLDOWN_MS = 60000;
62
- // abort 之后的宽限:等底层流停止最多这么久,超过即视为宿主不响应取消。
63
- const DEFAULT_SETTLE_GRACE_MS = 2000;
64
- // 连续降级熔断阈值:连坏这么多次就全局停判一个冷却期(见文件头)。
65
- const FAILURE_BREAKER_N = 3;
66
-
67
- function extractJson(content) {
68
- const s = String(content ?? "").trim();
69
- try { return JSON.parse(s); } catch {}
70
- const m = s.match(/\{[\s\S]*\}/);
71
- if (m) { try { return JSON.parse(m[0]); } catch {} }
72
- return null;
73
- }
74
-
75
- export function createPreInjectGate({ llm, agentDefaultModel, service, config, logger, judgeTimeoutMs, cooldownMs, settleGraceMs } = {}) {
76
- const language = langOf(config);
77
- const enabled = config?.preInjectGate?.enabled === true;
78
- const enforce = config?.preInjectGate?.enforce === true;
79
- // llmAudit 关闭 → 本闸不运行(见文件头:无账目的判定不可见也不该花钱)。
80
- const auditDisabled = config?.llmAudit?.enabled === false;
81
- const usable = enabled && !auditDisabled && typeof llm?.stream === "function";
82
- if (enabled && !usable) {
83
- logger?.warn?.("[dsh-mneme] preInjectGate enabled but unusable (llm missing or llmAudit disabled) — running as off");
84
- }
85
-
86
- const verdictCache = new Map(); // cacheKey -> { flaggedIds: Set<uuid>, nPool, reason }
87
- const inFlight = new Map(); // cacheKey -> Promise(并行渲染去重,防调用风暴)
88
- const cooldown = new Map(); // cacheKey -> 冷却截止 ms(流未停止的 key,冷却内不发新流)
89
- const JUDGE_TIMEOUT = judgeTimeoutMs ?? 60000;
90
- const COOLDOWN_MS = cooldownMs ?? DEFAULT_COOLDOWN_MS;
91
- const SETTLE_GRACE_MS = settleGraceMs ?? DEFAULT_SETTLE_GRACE_MS;
92
- let consecutiveFailures = 0; // 连续降级次数(成功即归零)
93
- let breakerUntil = 0; // 熔断截止 ms(0 = 未熔断)
94
- let disposed = false;
95
-
96
- // cacheKey = 查询 + 排序后的「id:content」:查询变、候选集变、**候选内容变**
97
- // (updateMemory 就地修正)都换 key——旧判定不沾新内容(CodeRabbit on #382)。
98
- // 排序保证同池同 key(等价池确定性);内容进 key 换来的是 map 占用略涨,
99
- // 缓存 cap 8 有界。
100
- function cacheKey(query, candidates) {
101
- return `${query}\u0000${candidates.map((c) => `${c.id}:${c.content}`).sort().join("\u0001")}`;
102
- }
103
-
104
- function getCached(key) {
105
- return verdictCache.get(key) ?? null;
106
- }
107
-
108
- // 渲染帧的唯一同步入口(text callback 内调用,绝不 await)。返回
109
- // state: "off" 闸门未启用/不可用/已 dispose/空池,调用方原样
110
- // "filtered" 缓存命中 + enforce:调用方按 flaggedIds 滤除
111
- // "observed" 缓存命中 + observe(或整池被标记的 enforce 退让):
112
- // 候选未动,分布已知(flaggedIds 带回)
113
- // "pending" 冷缓存/冷却中/熔断中:本帧原样渲染;非冷却时判定已在后台发起
114
- // flaggedIds 只在缓存命中时非空。豁免(pin 池/graphHint 线索行不进判定集)由
115
- // 调用方在传入 judgedPart 时完成——本模块只认传入的池。
116
- function forFrame(query, candidates) {
117
- if (disposed || !usable || !Array.isArray(candidates) || candidates.length === 0) {
118
- return { state: "off", flaggedIds: null, nPool: 0 };
119
- }
120
- const key = cacheKey(query, candidates);
121
- const cached = verdictCache.get(key);
122
- if (cached) {
123
- // 命中刷序(LRU,见 VERDICT_CACHE_MAX 注释)。
124
- verdictCache.delete(key);
125
- verdictCache.set(key, cached);
126
- // enforce 黑屏保护:整池被标记时放弃本次滤除(fail-open,见文件头)。
127
- // 判定与审计都保留原样,面板上就是 n_flagged == n_pool 这一可见形态。
128
- const wipeout = enforce && cached.nPool > 0 && cached.flaggedIds.size >= cached.nPool;
129
- if (wipeout) {
130
- if (!cached.wipeoutWarned) {
131
- cached.wipeoutWarned = true;
132
- logger?.warn?.("[dsh-mneme] preInjectGate flagged the whole pool — skipping enforcement this frame (fail-open)");
133
- }
134
- return { state: "observed", flaggedIds: cached.flaggedIds, nPool: cached.nPool };
135
- }
136
- return {
137
- state: enforce ? "filtered" : "observed",
138
- flaggedIds: cached.flaggedIds,
139
- nPool: cached.nPool
140
- };
141
- }
142
- // 熔断期:判定器连坏(见文件头),全局停判——本帧原样渲染。
143
- if (Date.now() < breakerUntil) return { state: "pending", flaggedIds: null, nPool: candidates.length };
144
- // 冷却中的 key:上一条流还没死,不再叠新流(宁缓防线不积资源)。
145
- const until = cooldown.get(key);
146
- if (until) {
147
- if (Date.now() < until) return { state: "pending", flaggedIds: null, nPool: candidates.length };
148
- cooldown.delete(key);
149
- }
150
- if (inFlight.has(key)) return { state: "pending", flaggedIds: null, nPool: candidates.length };
151
- prefetch(key, query, candidates);
152
- return { state: "pending", flaggedIds: null, nPool: candidates.length };
153
- }
154
-
155
- // 降级计数:成功即归零(见 prefetch 的 verdict 分支),连坏 FAILURE_BREAKER_N
156
- // 次就全局停判一个冷却期(见文件头)。宿主不守取消契约(流不停)与判定失败
157
- // 同属「判定器不可用」,一起计数——否则每个不同查询都能各叠一条悬挂流,冷却
158
- // 按 key 就兜不住。熔断只停花钱,防线照旧原样注入。
159
- function noteDegraded() {
160
- consecutiveFailures += 1;
161
- if (consecutiveFailures >= FAILURE_BREAKER_N) {
162
- consecutiveFailures = 0;
163
- breakerUntil = Date.now() + COOLDOWN_MS;
164
- logger?.warn?.(`[dsh-mneme] preInjectGate degraded ${FAILURE_BREAKER_N} times in a row — pausing judgment for ${COOLDOWN_MS}ms`);
165
- }
166
- }
167
-
168
- function prefetch(key, query, candidates) {
169
- if (disposed || verdictCache.has(key) || inFlight.has(key)) return;
170
- const promise = judgePool(query, candidates)
171
- .then((outcome) => {
172
- inFlight.delete(key);
173
- // dispose 后在飞的结果不回写:拆机之后还改模块状态是 #381 有、#382 丢了的护栏。
174
- if (disposed) return;
175
- // 流未停止(宿主不响应 abort):该 key 进冷却,冷却内不发新流。
176
- if (outcome.timedOut && !outcome.settled) {
177
- cooldown.set(key, Date.now() + COOLDOWN_MS);
178
- noteDegraded();
179
- return;
180
- }
181
- if (outcome.verdict) {
182
- consecutiveFailures = 0;
183
- verdictCache.set(key, outcome.verdict);
184
- if (verdictCache.size > VERDICT_CACHE_MAX) {
185
- verdictCache.delete(verdictCache.keys().next().value);
186
- }
187
- return;
188
- }
189
- // 判定失败(verdict=null)不缓存:下一帧重试。但连续降级要熔断——判定器
190
- // 系统性坏掉时,逐帧重试等于把成本绑到交互帧数上(见文件头)。
191
- noteDegraded();
192
- })
193
- .catch(() => { inFlight.delete(key); });
194
- inFlight.set(key, promise);
195
- }
196
-
197
- // 单次池级判定(E12 口径:1 次调用判整池,不逐条)。返回
198
- // { verdict, timedOut, settled }:verdict=null 即失败/不可解析/超时(审计行记
199
- // degraded);timedOut 且 !settled = 底层流未随 abort 停止(调用方进冷却)。
200
- async function judgePool(query, candidates) {
201
- let route = {};
202
- try {
203
- const sel = agentDefaultModel?.currentSelection?.();
204
- if (sel?.provider && sel?.model) { route.provider = sel.provider; route.model = sel.model; }
205
- } catch { /* fall through:无路由照常判定、照常落账(model_id 落 "unknown") */ }
206
- const modelId = route.provider && route.model ? `${route.provider}:${route.model}` : "";
207
- const startedAt = Date.now();
208
- const timestamp = new Date(startedAt).toISOString();
209
- const source = { kind: "plugin:dsh-mneme", plugin: "dsh-mneme" };
210
- // 刻意不套 fenceListBody(dream.js:330,Issue #380 的复盘):本闸的 system 契约把
211
- // user 侧形状写死成「你将收到一个 JSON 对象,包含 query 与 memories」(lang.js:57/58),
212
- // 套围栏会让提示词与载荷互相矛盾;而且判定池靠 JSON.parse 直接取 m#<i> 局部序号还原
213
- // 候选(test/pre-inject-gate.test.js 锁着这个形状),外包围栏文本会让解析整条失败。
214
- // 这里的注入防线不是围栏而是**判定结果绝不进模型上下文**:没有任何标记(判到的、
215
- // 没判到的)会被写回提示词,给模型看标记的整条线已被 E2/E3 关闭(见 lang.js:52-55)。
216
- // 但这条「移出候选」的保证是**有条件的**,写清楚免得被读成无条件:
217
- // · 只有 enforce 开着、且本帧缓存命中(gateFrame.state === "filtered")时才真移除候选
218
- // (service.injectCandidates 的移除分支);
219
- // · 观察档(enforce 关)、冷缓存首帧、判定失败/超时、整池被标记的 fail-open 帧,
220
- // 候选原样注入——那些帧里记忆正文会照常到达模型;
221
- // · 判定器识别的是「意见或立场」,它**不**承诺消化正文里任何指令句(越狱式记忆
222
- // 不在本闸的判据里),别把这段读成对注入指令的保证。
223
- // 没有围栏可闭合,也就没有「闭合标记被伪造」这一路风险。
224
- const messages = [
225
- { role: "system", content: [{ type: "text", text: STR.prompts.preInjectGate[language] }], source },
226
- { role: "user", content: [{ type: "text", text: JSON.stringify({ query, memories: candidates.map((c, i) => ({ id: `m#${i}`, content: c.content })) }) }], source },
227
- ];
228
-
229
- let text = null;
230
- let errorMessage = null;
231
- let inputTokens = 0;
232
- let outputTokens = 0;
233
- let timedOut = false;
234
- let settled = false;
235
- // 取消协议:GenerateOptions.signal 是宿主 llm.stream 契约自带的取消通道。
236
- const abort = new AbortController();
237
- const consume = (async () => {
238
- try {
239
- for await (const chunk of llm.stream({ ...route, maxTokens: JUDGE_MAX_TOKENS, messages, signal: abort.signal })) {
240
- if (chunk.type === "text-delta" && typeof chunk.text === "string") text = (text ?? "") + chunk.text;
241
- if (chunk.type === "usage") {
242
- const u = chunk.usage ?? chunk;
243
- const i = u.input_tokens ?? u.inputTokens ?? u.prompt_tokens ?? u.promptTokens;
244
- const o = u.output_tokens ?? u.outputTokens ?? u.completion_tokens ?? u.completionTokens;
245
- if (Number.isFinite(i)) inputTokens = i;
246
- if (Number.isFinite(o)) outputTokens = o;
247
- }
248
- if (chunk.type === "finish" && (chunk.reason?.kind === "error" || chunk.reason?.kind === "aborted")) {
249
- errorMessage = "llm stream aborted or errored";
250
- return;
251
- }
252
- }
253
- } finally {
254
- settled = true;
255
- }
256
- })();
257
- consume.catch(() => { /* 取消/竞态路径的拒绝已由 race 接过一次,这里防未处理拒绝 */ });
258
-
259
- let timeoutTimer;
260
- try {
261
- await Promise.race([
262
- consume,
263
- new Promise((_, reject) => {
264
- timeoutTimer = setTimeout(() => {
265
- timedOut = true;
266
- errorMessage = `judgment timeout (${JUDGE_TIMEOUT}ms)`;
267
- abort.abort(); // 先取消底层流,再等它停下
268
- reject(new Error(errorMessage));
269
- }, JUDGE_TIMEOUT);
270
- })
271
- ]);
272
- clearTimeout(timeoutTimer);
273
- } catch (err) {
274
- clearTimeout(timeoutTimer);
275
- if (!timedOut) errorMessage = String(err?.message ?? err);
276
- logger?.warn?.(`[dsh-mneme] preInjectGate judgment failed: ${errorMessage}`);
277
- }
278
- if (timedOut) {
279
- // 有界等流停止:停了才允许同 key 重试(prefetch 按 settled 决定是否冷却)。
280
- // 定时器必须显式 clear:流提前停下时它还会把进程多按 SETTLE_GRACE_MS(评审)。
281
- let graceTimer;
282
- await Promise.race([
283
- consume,
284
- new Promise((r) => { graceTimer = setTimeout(r, SETTLE_GRACE_MS); })
285
- ]).catch(() => {});
286
- clearTimeout(graceTimer);
287
- }
288
-
289
- // 出参解析:m#<i> → 数组索引 → 真实 UUID。越界/重复/非本池 id 一律忽略
290
- // (解析宽松、应用严格:模型幻觉出的 id 不能滤掉任何真实条目)。
291
- const parsed = errorMessage ? null : extractJson(text);
292
- const flaggedIds = new Set();
293
- let reason = null;
294
- if (parsed && Array.isArray(parsed.ids)) {
295
- for (const raw of parsed.ids) {
296
- const m = /^m#(\d+)$/.exec(String(raw));
297
- const idx = m ? Number(m[1]) : -1;
298
- if (idx >= 0 && idx < candidates.length) flaggedIds.add(candidates[idx].id);
299
- }
300
- if (typeof parsed.reason === "string" && parsed.reason.trim()) reason = parsed.reason.trim().slice(0, 300);
301
- } else if (!errorMessage) {
302
- errorMessage = "unparseable judgment output";
303
- }
304
- const degraded = errorMessage !== null || !parsed;
305
-
306
- // 审计 best-effort(CONTRIBUTING fail-safe 硬约定):写行失败只 warn。
307
- // llmAudit 关闭时 usable 已为 false,这里理论不可达;防御性再判一次。
308
- // 条件里**不含 modelId**:判定生效就必有账(无路由落 "unknown" 占位)。
309
- // session_key 不写(store.js:LLM 调用行恒 NULL;写进去会挤占 write-admission
310
- // 的话题回填窗口)。
311
- if (config?.llmAudit?.enabled !== false && typeof service?.saveLlmAudit === "function") {
312
- try {
313
- service.saveLlmAudit({
314
- timestamp,
315
- trigger_source: GATE_TRIGGER_SOURCE,
316
- operation_type: GATE_OPERATION_TYPE,
317
- model_id: modelId || "unknown",
318
- input_tokens: inputTokens,
319
- output_tokens: outputTokens,
320
- total_tokens: inputTokens + outputTokens,
321
- cost_usd: 0,
322
- duration_ms: Date.now() - startedAt,
323
- status: degraded ? "error" : "success",
324
- error_message: errorMessage,
325
- related_memory_ids: [...flaggedIds],
326
- metadata: {
327
- n_pool: candidates.length,
328
- n_flagged: flaggedIds.size,
329
- ...(reason ? { reason } : {}),
330
- degraded
331
- }
332
- });
333
- } catch (auditError) {
334
- logger?.warn?.(`[dsh-mneme] preInjectGate audit write failed: ${String(auditError)}`);
335
- }
336
- }
337
-
338
- if (degraded) return { verdict: null, timedOut, settled };
339
- return { verdict: { flaggedIds, nPool: candidates.length, reason }, timedOut, settled };
340
- }
341
-
342
- // dispose 钩子(index.js 的 disposers)。置 disposed 后:在飞的判定结果不再回写
343
- // 缓存/冷却,渲染帧直接走 off —— 拆机后模块状态不再变化。
344
- function clear() {
345
- disposed = true;
346
- verdictCache.clear();
347
- inFlight.clear();
348
- cooldown.clear();
349
- consecutiveFailures = 0;
350
- breakerUntil = 0;
351
- }
352
-
353
- return { enabled: usable, enforce, cacheKey, getCached, forFrame, clear };
354
- }
1
+ // Issue #380 注入前判定(preInjectGate):每帧注入候选出池后、进入 system prompt
2
+ // 前,由一次池级 LLM 调用判出「会向本次请求注入意见/立场」的记忆(E12 D1 口径:
3
+ // 判定协议与实验 protocol-preinject.txt 同源;「意见/立场」框架依据 E3 读数 2
4
+ // 「FR 跟随意见内容」,且已被用户采纳为执行期决策)。
5
+ //
6
+ // 两条硬约束决定了本模块的形状:
7
+ // 1. 宿主 systemPrompt 渲染是同步回调(inject.js 的 text callback 不支持 await,
8
+ // service.js Bug4 注释同源)——判定只能 async prefetch + cache、同步消费,与
9
+ // inject.js 的 queryVectorCache 同款。冷缓存首帧降级为不判定原样注入,下一轮
10
+ // 同 key 渲染生效;防线故障永不阻塞注入。
11
+ // 2. 判定只给闸门用,绝不进模型上下文——给模型看任何标记的整条线已被 E2/E3
12
+ // 关闭(E12:判定+标记 D2 比判定+过滤 D1 差 +17.6pp)。
13
+ //
14
+ // 消费点在 service.injectCandidates 内、touchRecalled **之前**(#380 评审:与
15
+ // strictScope 同纪律,见 service.js 的「闸门还必须在 touchRecalled 之前」注释):
16
+ // 滤除若发生在函数返回之后,被滤掉的记忆照样刷温时钟、照样进 recall_runs 曝光账,
17
+ // 反馈环会把闸门想压下的意见记忆重新顶上来。所以判定由 injectCandidates 调用,
18
+ // 帧状态经 gateStats 出参回给调用方做面板快照(不动「返回数组」的既有契约)。
19
+ //
20
+ // 两级语义复用 #254 writeAdmission(enabled 观察档 / enforce 真拦截):
21
+ // enabled=true 才跑判定;enforce=true 且缓存命中时同步滤除被标记候选;
22
+ // observe 档(enforce=false)候选集不动,判定完成时落审计行看真实负载的意见
23
+ // 占比分布(issue:先测分布再决定 enforce)。
24
+ //
25
+ // 审计走 llm_audit_logs:trigger_source="preInjectGate",真实 model_id 与 token
26
+ // 用量(区别于 writeAdmission 的 "skipped"/"-" 占位——本闸有真实 LLM 花费,面板
27
+ // 消费视图自动生效),related_memory_ids=被标记记忆的真实 UUID,metadata 带分布
28
+ // 与降级标记。llmAudit 关闭时本闸不运行(与 write-admission 的 evaluate 早退同
29
+ // 口径:无保留期的表不记账,无账目的判定不可见也不该花钱)。审计行**无条件落**:
30
+ // 无路由时 model_id 落 "unknown"(summarize.js 的无路由占位同款),不许出现
31
+ // 「判定照花钱、账目不见影」(#380 评审)。session_key 不写——store.js 的列语义是
32
+ // 「#254 写入准入的会话键(LLM 调用行恒 NULL)」,写进去会挤占 write-admission
33
+ // 话题回填的 TOPIC_LOOKBACK_ROWS 窗口。
34
+ //
35
+ // 候选映射为 m#<i> 局部序号参与判定、出参映射回真实 UUID——E13b 实测模型抄 36
36
+ // 位 UUID 的错误率足以让整单决策作废,局部短 id 是同源解法。
37
+ //
38
+ // 超时与取消(CodeRabbit on #382):判定挂起用 GenerateOptions.signal(宿主
39
+ // llm.stream 契约自带)真取消底层流,取消发起后**有界等待流真正停止**才放行同
40
+ // key 重试;宿主不理会 signal 时该 key 进冷却期,冷却内不再对该池发新流——宁可
41
+ // 暂缓防线也不累积悬挂流;冷却按 key,跨 key 的悬挂流由熔断兜(不守取消契约与
42
+ // key 无关,同样计入连续降级)。冷却期与宽限期可注入(测试用),默认值不变。
43
+ //
44
+ // 两条资源护栏(#380 评审):
45
+ // - 连续降级熔断:判定器系统性坏掉时(模型持续返回不可解析输出、宿主持续报错)
46
+ // 逐帧重试等于把成本绑到交互帧数上——连续 FAILURE_BREAKER_N 次降级后全局停判
47
+ // 一个冷却期,到期自动恢复。熔断只停花钱,不改防线行为(照旧原样注入)。
48
+ // - 整池被标记时放弃本次 enforce(fail-open):记忆正文是判定模型的可影响输入,
49
+ // 一条指令型记忆可诱导模型标记全池 → 整个记忆块静默失明。该形态本帧退回观察档
50
+ // 原样注入并 warn;判定结果仍在缓存与审计里(n_flagged == n_pool 即此形态)。
51
+ import { STR, langOf } from "./lang.js";
52
+
53
+ export const GATE_TRIGGER_SOURCE = "preInjectGate";
54
+ export const GATE_OPERATION_TYPE = "pre_inject_gate";
55
+ // 判定输出上限:E12 实验同款硬编码(700),池级单次判定足够。
56
+ const JUDGE_MAX_TOKENS = 700;
57
+ // 缓存容量与 inject.js 的 queryVectorCache 同口径(cap 8,命中即刷新序 = LRU:
58
+ // FIFO 下活跃查询会被一次性新池挤出去,之后反复重判)。
59
+ const VERDICT_CACHE_MAX = 8;
60
+ // 流未停止时的 key 冷却期:冷却内不发新流(等旧的死掉/被宿主回收)。
61
+ const DEFAULT_COOLDOWN_MS = 60000;
62
+ // abort 之后的宽限:等底层流停止最多这么久,超过即视为宿主不响应取消。
63
+ const DEFAULT_SETTLE_GRACE_MS = 2000;
64
+ // 连续降级熔断阈值:连坏这么多次就全局停判一个冷却期(见文件头)。
65
+ const FAILURE_BREAKER_N = 3;
66
+
67
+ function extractJson(content) {
68
+ const s = String(content ?? "").trim();
69
+ try { return JSON.parse(s); } catch {}
70
+ const m = s.match(/\{[\s\S]*\}/);
71
+ if (m) { try { return JSON.parse(m[0]); } catch {} }
72
+ return null;
73
+ }
74
+
75
+ export function createPreInjectGate({ llm, agentDefaultModel, service, config, logger, judgeTimeoutMs, cooldownMs, settleGraceMs } = {}) {
76
+ const language = langOf(config);
77
+ const enabled = config?.preInjectGate?.enabled === true;
78
+ const enforce = config?.preInjectGate?.enforce === true;
79
+ // llmAudit 关闭 → 本闸不运行(见文件头:无账目的判定不可见也不该花钱)。
80
+ const auditDisabled = config?.llmAudit?.enabled === false;
81
+ const usable = enabled && !auditDisabled && typeof llm?.stream === "function";
82
+ if (enabled && !usable) {
83
+ logger?.warn?.("[dsh-mneme] preInjectGate enabled but unusable (llm missing or llmAudit disabled) — running as off");
84
+ }
85
+
86
+ const verdictCache = new Map(); // cacheKey -> { flaggedIds: Set<uuid>, nPool, reason }
87
+ const inFlight = new Map(); // cacheKey -> Promise(并行渲染去重,防调用风暴)
88
+ const cooldown = new Map(); // cacheKey -> 冷却截止 ms(流未停止的 key,冷却内不发新流)
89
+ const JUDGE_TIMEOUT = judgeTimeoutMs ?? 60000;
90
+ const COOLDOWN_MS = cooldownMs ?? DEFAULT_COOLDOWN_MS;
91
+ const SETTLE_GRACE_MS = settleGraceMs ?? DEFAULT_SETTLE_GRACE_MS;
92
+ let consecutiveFailures = 0; // 连续降级次数(成功即归零)
93
+ let breakerUntil = 0; // 熔断截止 ms(0 = 未熔断)
94
+ let disposed = false;
95
+
96
+ // cacheKey = 查询 + 排序后的「id:content」:查询变、候选集变、**候选内容变**
97
+ // (updateMemory 就地修正)都换 key——旧判定不沾新内容(CodeRabbit on #382)。
98
+ // 排序保证同池同 key(等价池确定性);内容进 key 换来的是 map 占用略涨,
99
+ // 缓存 cap 8 有界。
100
+ function cacheKey(query, candidates) {
101
+ return `${query}\u0000${candidates.map((c) => `${c.id}:${c.content}`).sort().join("\u0001")}`;
102
+ }
103
+
104
+ function getCached(key) {
105
+ return verdictCache.get(key) ?? null;
106
+ }
107
+
108
+ // 渲染帧的唯一同步入口(text callback 内调用,绝不 await)。返回
109
+ // state: "off" 闸门未启用/不可用/已 dispose/空池,调用方原样
110
+ // "filtered" 缓存命中 + enforce:调用方按 flaggedIds 滤除
111
+ // "observed" 缓存命中 + observe(或整池被标记的 enforce 退让):
112
+ // 候选未动,分布已知(flaggedIds 带回)
113
+ // "pending" 冷缓存/冷却中/熔断中:本帧原样渲染;非冷却时判定已在后台发起
114
+ // flaggedIds 只在缓存命中时非空。豁免(pin 池/graphHint 线索行不进判定集)由
115
+ // 调用方在传入 judgedPart 时完成——本模块只认传入的池。
116
+ function forFrame(query, candidates) {
117
+ if (disposed || !usable || !Array.isArray(candidates) || candidates.length === 0) {
118
+ return { state: "off", flaggedIds: null, nPool: 0 };
119
+ }
120
+ const key = cacheKey(query, candidates);
121
+ const cached = verdictCache.get(key);
122
+ if (cached) {
123
+ // 命中刷序(LRU,见 VERDICT_CACHE_MAX 注释)。
124
+ verdictCache.delete(key);
125
+ verdictCache.set(key, cached);
126
+ // enforce 黑屏保护:整池被标记时放弃本次滤除(fail-open,见文件头)。
127
+ // 判定与审计都保留原样,面板上就是 n_flagged == n_pool 这一可见形态。
128
+ const wipeout = enforce && cached.nPool > 0 && cached.flaggedIds.size >= cached.nPool;
129
+ if (wipeout) {
130
+ if (!cached.wipeoutWarned) {
131
+ cached.wipeoutWarned = true;
132
+ logger?.warn?.("[dsh-mneme] preInjectGate flagged the whole pool — skipping enforcement this frame (fail-open)");
133
+ }
134
+ return { state: "observed", flaggedIds: cached.flaggedIds, nPool: cached.nPool };
135
+ }
136
+ return {
137
+ state: enforce ? "filtered" : "observed",
138
+ flaggedIds: cached.flaggedIds,
139
+ nPool: cached.nPool
140
+ };
141
+ }
142
+ // 熔断期:判定器连坏(见文件头),全局停判——本帧原样渲染。
143
+ if (Date.now() < breakerUntil) return { state: "pending", flaggedIds: null, nPool: candidates.length };
144
+ // 冷却中的 key:上一条流还没死,不再叠新流(宁缓防线不积资源)。
145
+ const until = cooldown.get(key);
146
+ if (until) {
147
+ if (Date.now() < until) return { state: "pending", flaggedIds: null, nPool: candidates.length };
148
+ cooldown.delete(key);
149
+ }
150
+ if (inFlight.has(key)) return { state: "pending", flaggedIds: null, nPool: candidates.length };
151
+ prefetch(key, query, candidates);
152
+ return { state: "pending", flaggedIds: null, nPool: candidates.length };
153
+ }
154
+
155
+ // 降级计数:成功即归零(见 prefetch 的 verdict 分支),连坏 FAILURE_BREAKER_N
156
+ // 次就全局停判一个冷却期(见文件头)。宿主不守取消契约(流不停)与判定失败
157
+ // 同属「判定器不可用」,一起计数——否则每个不同查询都能各叠一条悬挂流,冷却
158
+ // 按 key 就兜不住。熔断只停花钱,防线照旧原样注入。
159
+ function noteDegraded() {
160
+ consecutiveFailures += 1;
161
+ if (consecutiveFailures >= FAILURE_BREAKER_N) {
162
+ consecutiveFailures = 0;
163
+ breakerUntil = Date.now() + COOLDOWN_MS;
164
+ logger?.warn?.(`[dsh-mneme] preInjectGate degraded ${FAILURE_BREAKER_N} times in a row — pausing judgment for ${COOLDOWN_MS}ms`);
165
+ }
166
+ }
167
+
168
+ function prefetch(key, query, candidates) {
169
+ if (disposed || verdictCache.has(key) || inFlight.has(key)) return;
170
+ const promise = judgePool(query, candidates)
171
+ .then((outcome) => {
172
+ inFlight.delete(key);
173
+ // dispose 后在飞的结果不回写:拆机之后还改模块状态是 #381 有、#382 丢了的护栏。
174
+ if (disposed) return;
175
+ // 流未停止(宿主不响应 abort):该 key 进冷却,冷却内不发新流。
176
+ if (outcome.timedOut && !outcome.settled) {
177
+ cooldown.set(key, Date.now() + COOLDOWN_MS);
178
+ noteDegraded();
179
+ return;
180
+ }
181
+ if (outcome.verdict) {
182
+ consecutiveFailures = 0;
183
+ verdictCache.set(key, outcome.verdict);
184
+ if (verdictCache.size > VERDICT_CACHE_MAX) {
185
+ verdictCache.delete(verdictCache.keys().next().value);
186
+ }
187
+ return;
188
+ }
189
+ // 判定失败(verdict=null)不缓存:下一帧重试。但连续降级要熔断——判定器
190
+ // 系统性坏掉时,逐帧重试等于把成本绑到交互帧数上(见文件头)。
191
+ noteDegraded();
192
+ })
193
+ .catch(() => { inFlight.delete(key); });
194
+ inFlight.set(key, promise);
195
+ }
196
+
197
+ // 单次池级判定(E12 口径:1 次调用判整池,不逐条)。返回
198
+ // { verdict, timedOut, settled }:verdict=null 即失败/不可解析/超时(审计行记
199
+ // degraded);timedOut 且 !settled = 底层流未随 abort 停止(调用方进冷却)。
200
+ async function judgePool(query, candidates) {
201
+ let route = {};
202
+ try {
203
+ const sel = agentDefaultModel?.currentSelection?.();
204
+ if (sel?.provider && sel?.model) { route.provider = sel.provider; route.model = sel.model; }
205
+ } catch { /* fall through:无路由照常判定、照常落账(model_id 落 "unknown") */ }
206
+ const modelId = route.provider && route.model ? `${route.provider}:${route.model}` : "";
207
+ const startedAt = Date.now();
208
+ const timestamp = new Date(startedAt).toISOString();
209
+ const source = { kind: "plugin:dsh-mneme", plugin: "dsh-mneme" };
210
+ // 刻意不套 fenceListBody(dream.js:330,Issue #380 的复盘):本闸的 system 契约把
211
+ // user 侧形状写死成「你将收到一个 JSON 对象,包含 query 与 memories」(lang.js:57/58),
212
+ // 套围栏会让提示词与载荷互相矛盾;而且判定池靠 JSON.parse 直接取 m#<i> 局部序号还原
213
+ // 候选(test/pre-inject-gate.test.js 锁着这个形状),外包围栏文本会让解析整条失败。
214
+ // 这里的注入防线不是围栏而是**判定结果绝不进模型上下文**:没有任何标记(判到的、
215
+ // 没判到的)会被写回提示词,给模型看标记的整条线已被 E2/E3 关闭(见 lang.js:52-55)。
216
+ // 但这条「移出候选」的保证是**有条件的**,写清楚免得被读成无条件:
217
+ // · 只有 enforce 开着、且本帧缓存命中(gateFrame.state === "filtered")时才真移除候选
218
+ // (service.injectCandidates 的移除分支);
219
+ // · 观察档(enforce 关)、冷缓存首帧、判定失败/超时、整池被标记的 fail-open 帧,
220
+ // 候选原样注入——那些帧里记忆正文会照常到达模型;
221
+ // · 判定器识别的是「意见或立场」,它**不**承诺消化正文里任何指令句(越狱式记忆
222
+ // 不在本闸的判据里),别把这段读成对注入指令的保证。
223
+ // 没有围栏可闭合,也就没有「闭合标记被伪造」这一路风险。
224
+ const messages = [
225
+ { role: "system", content: [{ type: "text", text: STR.prompts.preInjectGate[language] }], source },
226
+ { role: "user", content: [{ type: "text", text: JSON.stringify({ query, memories: candidates.map((c, i) => ({ id: `m#${i}`, content: c.content })) }) }], source },
227
+ ];
228
+
229
+ let text = null;
230
+ let errorMessage = null;
231
+ let inputTokens = 0;
232
+ let outputTokens = 0;
233
+ let timedOut = false;
234
+ let settled = false;
235
+ // 取消协议:GenerateOptions.signal 是宿主 llm.stream 契约自带的取消通道。
236
+ const abort = new AbortController();
237
+ const consume = (async () => {
238
+ try {
239
+ for await (const chunk of llm.stream({ ...route, maxTokens: JUDGE_MAX_TOKENS, messages, signal: abort.signal })) {
240
+ if (chunk.type === "text-delta" && typeof chunk.text === "string") text = (text ?? "") + chunk.text;
241
+ if (chunk.type === "usage") {
242
+ const u = chunk.usage ?? chunk;
243
+ const i = u.input_tokens ?? u.inputTokens ?? u.prompt_tokens ?? u.promptTokens;
244
+ const o = u.output_tokens ?? u.outputTokens ?? u.completion_tokens ?? u.completionTokens;
245
+ if (Number.isFinite(i)) inputTokens = i;
246
+ if (Number.isFinite(o)) outputTokens = o;
247
+ }
248
+ if (chunk.type === "finish" && (chunk.reason?.kind === "error" || chunk.reason?.kind === "aborted")) {
249
+ errorMessage = "llm stream aborted or errored";
250
+ return;
251
+ }
252
+ }
253
+ } finally {
254
+ settled = true;
255
+ }
256
+ })();
257
+ consume.catch(() => { /* 取消/竞态路径的拒绝已由 race 接过一次,这里防未处理拒绝 */ });
258
+
259
+ let timeoutTimer;
260
+ try {
261
+ await Promise.race([
262
+ consume,
263
+ new Promise((_, reject) => {
264
+ timeoutTimer = setTimeout(() => {
265
+ timedOut = true;
266
+ errorMessage = `judgment timeout (${JUDGE_TIMEOUT}ms)`;
267
+ abort.abort(); // 先取消底层流,再等它停下
268
+ reject(new Error(errorMessage));
269
+ }, JUDGE_TIMEOUT);
270
+ })
271
+ ]);
272
+ clearTimeout(timeoutTimer);
273
+ } catch (err) {
274
+ clearTimeout(timeoutTimer);
275
+ if (!timedOut) errorMessage = String(err?.message ?? err);
276
+ logger?.warn?.(`[dsh-mneme] preInjectGate judgment failed: ${errorMessage}`);
277
+ }
278
+ if (timedOut) {
279
+ // 有界等流停止:停了才允许同 key 重试(prefetch 按 settled 决定是否冷却)。
280
+ // 定时器必须显式 clear:流提前停下时它还会把进程多按 SETTLE_GRACE_MS(评审)。
281
+ let graceTimer;
282
+ await Promise.race([
283
+ consume,
284
+ new Promise((r) => { graceTimer = setTimeout(r, SETTLE_GRACE_MS); })
285
+ ]).catch(() => {});
286
+ clearTimeout(graceTimer);
287
+ }
288
+
289
+ // 出参解析:m#<i> → 数组索引 → 真实 UUID。越界/重复/非本池 id 一律忽略
290
+ // (解析宽松、应用严格:模型幻觉出的 id 不能滤掉任何真实条目)。
291
+ const parsed = errorMessage ? null : extractJson(text);
292
+ const flaggedIds = new Set();
293
+ let reason = null;
294
+ if (parsed && Array.isArray(parsed.ids)) {
295
+ for (const raw of parsed.ids) {
296
+ const m = /^m#(\d+)$/.exec(String(raw));
297
+ const idx = m ? Number(m[1]) : -1;
298
+ if (idx >= 0 && idx < candidates.length) flaggedIds.add(candidates[idx].id);
299
+ }
300
+ if (typeof parsed.reason === "string" && parsed.reason.trim()) reason = parsed.reason.trim().slice(0, 300);
301
+ } else if (!errorMessage) {
302
+ errorMessage = "unparseable judgment output";
303
+ }
304
+ const degraded = errorMessage !== null || !parsed;
305
+
306
+ // 审计 best-effort(CONTRIBUTING fail-safe 硬约定):写行失败只 warn。
307
+ // llmAudit 关闭时 usable 已为 false,这里理论不可达;防御性再判一次。
308
+ // 条件里**不含 modelId**:判定生效就必有账(无路由落 "unknown" 占位)。
309
+ // session_key 不写(store.js:LLM 调用行恒 NULL;写进去会挤占 write-admission
310
+ // 的话题回填窗口)。
311
+ if (config?.llmAudit?.enabled !== false && typeof service?.saveLlmAudit === "function") {
312
+ try {
313
+ service.saveLlmAudit({
314
+ timestamp,
315
+ trigger_source: GATE_TRIGGER_SOURCE,
316
+ operation_type: GATE_OPERATION_TYPE,
317
+ model_id: modelId || "unknown",
318
+ input_tokens: inputTokens,
319
+ output_tokens: outputTokens,
320
+ total_tokens: inputTokens + outputTokens,
321
+ cost_usd: 0,
322
+ duration_ms: Date.now() - startedAt,
323
+ status: degraded ? "error" : "success",
324
+ error_message: errorMessage,
325
+ related_memory_ids: [...flaggedIds],
326
+ metadata: {
327
+ n_pool: candidates.length,
328
+ n_flagged: flaggedIds.size,
329
+ ...(reason ? { reason } : {}),
330
+ degraded
331
+ }
332
+ });
333
+ } catch (auditError) {
334
+ logger?.warn?.(`[dsh-mneme] preInjectGate audit write failed: ${String(auditError)}`);
335
+ }
336
+ }
337
+
338
+ if (degraded) return { verdict: null, timedOut, settled };
339
+ return { verdict: { flaggedIds, nPool: candidates.length, reason }, timedOut, settled };
340
+ }
341
+
342
+ // dispose 钩子(index.js 的 disposers)。置 disposed 后:在飞的判定结果不再回写
343
+ // 缓存/冷却,渲染帧直接走 off —— 拆机后模块状态不再变化。
344
+ function clear() {
345
+ disposed = true;
346
+ verdictCache.clear();
347
+ inFlight.clear();
348
+ cooldown.clear();
349
+ consecutiveFailures = 0;
350
+ breakerUntil = 0;
351
+ }
352
+
353
+ return { enabled: usable, enforce, cacheKey, getCached, forFrame, clear };
354
+ }