@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.
- package/LICENSE +21 -21
- package/README.md +830 -828
- package/assets/icon.svg +3 -3
- package/bin/cli.mjs +964 -964
- package/bin/dsh-mneme-choro.mjs +262 -254
- package/bin/dsh-mneme-mcp.mjs +510 -510
- package/bin/dsh-mneme-serve.mjs +151 -151
- package/cordis.patch.yml +21 -21
- package/docs/CONFIGURATION.md +407 -402
- package/docs/CONTINUITY.md +139 -139
- package/docs/DAEMON.md +57 -57
- package/docs/DIRECTORIES.md +114 -114
- package/docs/DISTILL.md +397 -371
- package/docs/DSH-GO.md +97 -97
- package/docs/ENTITIES.md +247 -247
- package/docs/FORGET.md +77 -77
- package/docs/KB.md +110 -110
- package/docs/LOCAL_MODEL.md +222 -222
- package/docs/SEMANTIC.md +256 -256
- package/docs/SHARE.md +552 -552
- package/docs/SLEEP.md +202 -202
- package/docs/STORAGE.md +122 -122
- package/docs/WORKFLOWS.md +366 -366
- package/lib/api-standalone.js +1007 -1007
- package/lib/api.js +3600 -3394
- package/lib/attachment-text.js +122 -122
- package/lib/bootstrap-http.js +105 -105
- package/lib/bootstrap.js +215 -215
- package/lib/candidates.js +454 -454
- package/lib/capabilities.js +819 -819
- package/lib/choro-command.js +111 -111
- package/lib/client.js +14420 -13436
- package/lib/commands.js +95 -95
- package/lib/config-keys.js +144 -144
- package/lib/config.js +1361 -1317
- package/lib/content-hash.js +54 -54
- package/lib/continuity.js +258 -258
- package/lib/date-hint.js +32 -0
- package/lib/diary/auto.js +190 -190
- package/lib/diary/compose.js +440 -440
- package/lib/diary/dir.js +59 -59
- package/lib/diary/export.js +97 -97
- package/lib/diary/index.js +26 -26
- package/lib/diary/library.js +189 -189
- package/lib/diary/qq-inbox.js +142 -142
- package/lib/diary/rollover.js +61 -61
- package/lib/diary/session-provider.js +122 -122
- package/lib/document-index.js +94 -94
- package/lib/document.js +322 -322
- package/lib/dream/clustering.js +123 -123
- package/lib/dream/decisions.js +945 -907
- package/lib/dream/narratives.js +44 -44
- package/lib/dream/sleep.js +2027 -1887
- package/lib/dream/triage.js +139 -0
- package/lib/dream.js +1879 -1764
- package/lib/dsh-go/host.js +162 -162
- package/lib/dsh-go/panel.js +2091 -2091
- package/lib/dsh-go/text.js +342 -342
- package/lib/dsh-go/views/common.js +68 -68
- package/lib/dsh-go/views/entity.js +48 -48
- package/lib/dsh-go/views/memory.js +198 -198
- package/lib/dsh-go/views/root.js +30 -30
- package/lib/dsh-go/views/settings.js +398 -398
- package/lib/dsh-go/views/status.js +51 -51
- package/lib/embedding.js +342 -342
- package/lib/entities/extractor.js +285 -285
- package/lib/fence.js +24 -24
- package/lib/graph/anchoring.js +184 -184
- package/lib/guide.js +74 -74
- package/lib/heat.js +144 -144
- package/lib/hot-memory.js +119 -107
- package/lib/image-describe.js +158 -0
- package/lib/index.js +852 -822
- package/lib/inject.js +656 -639
- package/lib/kb/index.js +309 -309
- package/lib/kb/routes.js +219 -219
- package/lib/kb/tools.js +148 -148
- package/lib/lang.js +607 -584
- package/lib/local-embedder.js +358 -358
- package/lib/maintenance.js +162 -162
- package/lib/memory-source.js +9 -9
- package/lib/mirror.js +330 -330
- package/lib/organize.js +456 -456
- package/lib/peak-hours.js +172 -172
- package/lib/pipeline-activities.js +178 -178
- package/lib/pipeline-attribution.js +165 -165
- package/lib/pipeline-command.js +279 -279
- package/lib/pipeline-confirm.js +142 -142
- package/lib/pipeline-exec.js +174 -174
- package/lib/pipeline-nudge-auto.js +129 -129
- package/lib/pipeline-nudge.js +1623 -1623
- package/lib/pipeline-observations.js +181 -181
- package/lib/pipeline-observe.js +133 -133
- package/lib/pipeline-runner.js +361 -361
- package/lib/pipeline-runs.js +442 -442
- package/lib/pipeline.js +716 -716
- package/lib/placement.js +13 -13
- package/lib/pre-inject-gate.js +354 -354
- package/lib/profile-extract.js +164 -164
- package/lib/profile-facts.js +356 -356
- package/lib/provenance-http.js +251 -251
- package/lib/quality-filter.js +162 -162
- package/lib/recall-stats.js +190 -190
- package/lib/reclaim-http.js +150 -150
- package/lib/reranker.js +269 -269
- package/lib/runtime/adopt.js +176 -176
- package/lib/runtime/closure.js +172 -172
- package/lib/runtime/download.js +247 -247
- package/lib/runtime/layout.js +265 -265
- package/lib/runtime/loader.js +252 -252
- package/lib/runtime/model-fetch.js +361 -361
- package/lib/runtime/provision.js +237 -237
- package/lib/runtime/tarball.js +130 -130
- package/lib/runtime/verify.js +223 -223
- package/lib/safe-slice.js +18 -18
- package/lib/scope-state.js +277 -277
- package/lib/scope.js +187 -187
- package/lib/search/adaptive.js +60 -60
- package/lib/search/bm25.js +96 -96
- package/lib/semantic.js +572 -572
- package/lib/sensitive-scan.js +209 -209
- package/lib/serve.js +228 -228
- package/lib/service.js +3268 -3196
- package/lib/session-gate.js +91 -91
- package/lib/session-usage.js +350 -350
- package/lib/settings.js +827 -802
- package/lib/share-http.js +281 -281
- package/lib/share-import.js +234 -234
- package/lib/share.js +335 -335
- package/lib/store.js +5895 -5662
- package/lib/summarize.js +1388 -1343
- package/lib/tools.js +1370 -1356
- package/lib/user-settings.js +230 -230
- package/lib/vector-index.js +180 -180
- package/lib/version-check.js +122 -122
- package/lib/wiki/claims.js +267 -0
- package/lib/wiki/cli-hint.js +90 -90
- package/lib/wiki/inject.js +164 -164
- package/lib/wiki/partition.js +200 -200
- package/lib/wiki/plan.js +214 -214
- package/lib/wiki/queries.js +640 -474
- package/lib/wiki/render.js +261 -187
- package/lib/wiki/scan.js +371 -371
- package/lib/wiki/t0.js +500 -463
- package/lib/wiki/t1-jobs.js +232 -232
- package/lib/wiki/t1.js +609 -502
- package/lib/write-admission.js +404 -404
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +15 -1
- package/runtime-manifest.json +526 -526
- package/scripts/benchmark-embed.js +201 -201
- package/scripts/benchmark-recall.js +249 -249
- package/scripts/benchmark-rerank.js +166 -166
- package/scripts/build-runtime-manifest.mjs +224 -224
- package/scripts/changelog-prep.mjs +91 -91
- package/scripts/check-sync.js +44 -44
- package/scripts/e2e-dsh.js +220 -220
- package/scripts/mneme-runtime.mjs +277 -277
- package/scripts/publish-gate.mjs +197 -197
- package/scripts/release-docs.mjs +210 -204
- package/scripts/release-notes.mjs +164 -161
- package/scripts/stress-dsh.js +255 -255
- package/scripts/sync-lib.js +47 -47
- package/scripts/sync-test-badge.mjs +69 -69
- package/scripts/test-count-sync.mjs +48 -48
- package/src/api-standalone.js +1007 -1007
- package/src/api.js +3600 -3394
- package/src/attachment-text.js +122 -122
- package/src/bootstrap-http.js +105 -105
- package/src/bootstrap.js +215 -215
- package/src/candidates.js +454 -454
- package/src/capabilities.js +819 -819
- package/src/choro-command.js +111 -111
- package/src/commands.js +95 -95
- package/src/config-keys.js +144 -144
- package/src/config.js +1361 -1317
- package/src/content-hash.js +54 -54
- package/src/continuity.js +258 -258
- package/src/date-hint.js +32 -0
- package/src/diary/auto.js +190 -190
- package/src/diary/compose.js +440 -440
- package/src/diary/dir.js +59 -59
- package/src/diary/export.js +97 -97
- package/src/diary/index.js +26 -26
- package/src/diary/library.js +189 -189
- package/src/diary/qq-inbox.js +142 -142
- package/src/diary/rollover.js +61 -61
- package/src/diary/session-provider.js +122 -122
- package/src/document-index.js +94 -94
- package/src/document.js +322 -322
- package/src/dream/clustering.js +123 -123
- package/src/dream/decisions.js +945 -907
- package/src/dream/narratives.js +44 -44
- package/src/dream/sleep.js +2027 -1887
- package/src/dream/triage.js +139 -0
- package/src/dream.js +1879 -1764
- package/src/dsh-go/host.js +162 -162
- package/src/dsh-go/panel.js +2091 -2091
- package/src/dsh-go/text.js +342 -342
- package/src/dsh-go/views/common.js +68 -68
- package/src/dsh-go/views/entity.js +48 -48
- package/src/dsh-go/views/memory.js +198 -198
- package/src/dsh-go/views/root.js +30 -30
- package/src/dsh-go/views/settings.js +398 -398
- package/src/dsh-go/views/status.js +51 -51
- package/src/embedding.js +342 -342
- package/src/entities/extractor.js +285 -285
- package/src/fence.js +24 -24
- package/src/graph/anchoring.js +184 -184
- package/src/guide.js +74 -74
- package/src/heat.js +144 -144
- package/src/hot-memory.js +119 -107
- package/src/image-describe.js +158 -0
- package/src/index.js +852 -822
- package/src/inject.js +656 -639
- package/src/kb/index.js +309 -309
- package/src/kb/routes.js +219 -219
- package/src/kb/tools.js +148 -148
- package/src/lang.js +607 -584
- package/src/local-embedder.js +358 -358
- package/src/maintenance.js +162 -162
- package/src/memory-source.js +9 -9
- package/src/mirror.js +330 -330
- package/src/organize.js +456 -456
- package/src/peak-hours.js +172 -172
- package/src/pipeline-activities.js +178 -178
- package/src/pipeline-attribution.js +165 -165
- package/src/pipeline-command.js +279 -279
- package/src/pipeline-confirm.js +142 -142
- package/src/pipeline-exec.js +174 -174
- package/src/pipeline-nudge-auto.js +129 -129
- package/src/pipeline-nudge.js +1623 -1623
- package/src/pipeline-observations.js +181 -181
- package/src/pipeline-observe.js +133 -133
- package/src/pipeline-runner.js +361 -361
- package/src/pipeline-runs.js +442 -442
- package/src/pipeline.js +716 -716
- package/src/placement.js +13 -13
- package/src/pre-inject-gate.js +354 -354
- package/src/profile-extract.js +164 -164
- package/src/profile-facts.js +356 -356
- package/src/provenance-http.js +251 -251
- package/src/quality-filter.js +162 -162
- package/src/recall-stats.js +190 -190
- package/src/reclaim-http.js +150 -150
- package/src/reranker.js +269 -269
- package/src/runtime/adopt.js +176 -176
- package/src/runtime/closure.js +172 -172
- package/src/runtime/download.js +247 -247
- package/src/runtime/layout.js +265 -265
- package/src/runtime/loader.js +252 -252
- package/src/runtime/model-fetch.js +361 -361
- package/src/runtime/provision.js +237 -237
- package/src/runtime/tarball.js +130 -130
- package/src/runtime/verify.js +223 -223
- package/src/safe-slice.js +18 -18
- package/src/scope-state.js +277 -277
- package/src/scope.js +187 -187
- package/src/search/adaptive.js +60 -60
- package/src/search/bm25.js +96 -96
- package/src/semantic.js +572 -572
- package/src/sensitive-scan.js +209 -209
- package/src/serve.js +228 -228
- package/src/service.js +3268 -3196
- package/src/session-gate.js +91 -91
- package/src/session-usage.js +350 -350
- package/src/settings.js +827 -802
- package/src/share-http.js +281 -281
- package/src/share-import.js +234 -234
- package/src/share.js +335 -335
- package/src/store.js +5895 -5662
- package/src/summarize.js +1388 -1343
- package/src/tools.js +1370 -1356
- package/src/user-settings.js +230 -230
- package/src/vector-index.js +180 -180
- package/src/version-check.js +122 -122
- package/src/wiki/claims.js +267 -0
- package/src/wiki/cli-hint.js +90 -90
- package/src/wiki/inject.js +164 -164
- package/src/wiki/partition.js +200 -200
- package/src/wiki/plan.js +214 -214
- package/src/wiki/queries.js +640 -474
- package/src/wiki/render.js +261 -187
- package/src/wiki/scan.js +371 -371
- package/src/wiki/t0.js +500 -463
- package/src/wiki/t1-jobs.js +232 -232
- package/src/wiki/t1.js +609 -502
- package/src/write-admission.js +404 -404
- package/test/adaptive-inject-budget.test.js +85 -85
- package/test/api-hardening.test.js +173 -173
- package/test/api-nudge-route.test.js +259 -259
- package/test/api-routes-gaps.test.js +1092 -1092
- package/test/api.test.js +3007 -2988
- package/test/attachment-text.test.js +139 -139
- package/test/audit.test.js +448 -448
- package/test/baseline-persist.test.js +248 -248
- package/test/benchmark.test.js +94 -94
- package/test/bm25-corpus-cache.test.js +145 -145
- package/test/bootstrap.test.js +228 -228
- package/test/candidates.test.js +345 -345
- package/test/capabilities.test.js +577 -577
- package/test/capacity-warning.test.js +262 -262
- package/test/changelog-prep.test.js +125 -125
- package/test/choro-command.test.js +218 -218
- package/test/cli-enums.test.js +37 -37
- package/test/cli-flag-value.test.js +86 -86
- package/test/cli-receive.test.js +390 -390
- package/test/cli-share.test.js +437 -437
- package/test/client-share.test.js +225 -225
- package/test/client.test.js +4595 -4184
- package/test/clustering.test.js +100 -100
- package/test/commands.test.js +106 -106
- package/test/config-keys-routes.test.js +233 -233
- package/test/config.test.js +153 -153
- package/test/conflict-freeze.test.js +344 -344
- package/test/conflict-ledger-enforce.test.js +452 -0
- package/test/conflict-ledger.test.js +242 -0
- package/test/conflict-reviewed.test.js +79 -79
- package/test/content-hash.test.js +204 -204
- package/test/continuity.test.js +452 -452
- package/test/contrast.test.js +432 -320
- package/test/date-hint.test.js +156 -0
- package/test/dedupe-candidate-window.test.js +180 -180
- package/test/dependency-overrides.test.js +78 -78
- package/test/diary-library.test.js +187 -187
- package/test/diary.test.js +576 -576
- package/test/differentiate-feedback.test.js +248 -248
- package/test/distill-tx-embed-gap.test.js +110 -110
- package/test/docs-config-keys.test.js +65 -65
- package/test/document-dir.test.js +233 -233
- package/test/document.test.js +703 -703
- package/test/dream-archive-guardrail.test.js +116 -116
- package/test/dream-baseline-clamp.test.js +174 -0
- package/test/dream-candidate-hybrid.test.js +281 -281
- package/test/dream-coverage-degrade.test.js +86 -86
- package/test/dream-decision-evidence.test.js +314 -314
- package/test/dream-failure-backoff.test.js +173 -173
- package/test/dream-fence.test.js +207 -207
- package/test/dream-maxtokens-fallback.test.js +132 -132
- package/test/dream-merge-guardrail.test.js +219 -219
- package/test/dream-peak-hours.test.js +194 -194
- package/test/dream.test.js +1528 -1528
- package/test/dsh-go-host.test.js +134 -134
- package/test/dsh-go-panel-actions.test.js +854 -854
- package/test/dsh-go-panel.test.js +251 -251
- package/test/embed-ready-poll.test.js +94 -94
- package/test/embed-text-reasons.test.js +112 -112
- package/test/embedding-cache-f32.test.js +276 -276
- package/test/embedding-ready.test.js +226 -226
- package/test/entities.test.js +711 -711
- package/test/entity-recall.test.js +179 -179
- package/test/epistemic.test.js +325 -325
- package/test/failure-query.test.js +147 -147
- package/test/failure-retention.test.js +105 -105
- package/test/fnew-0112.test.js +311 -311
- package/test/fnew-03.test.js +422 -422
- package/test/forget-corrections.test.js +70 -70
- package/test/graph-anchoring.test.js +184 -184
- package/test/graph-api.test.js +333 -209
- package/test/graph-boosted-relations.test.js +189 -189
- package/test/graph-inject-budget.test.js +103 -103
- package/test/graph-multipath.test.js +269 -269
- package/test/graph-passive-confirm.test.js +91 -91
- package/test/graph-weight-evolution.test.js +104 -104
- package/test/heat.test.js +175 -175
- package/test/helpers/dream-mock.js +86 -86
- package/test/helpers/peer-worker.mjs +25 -25
- package/test/helpers/runtime-source.js +60 -60
- package/test/helpers/tar-builder.js +80 -80
- package/test/helpers/write-admission-samples.js +312 -312
- package/test/hot-memory.test.js +259 -211
- package/test/image-description.test.js +271 -0
- package/test/image-memory.test.js +170 -170
- package/test/inject-content-cap.test.js +91 -91
- package/test/inject-first-round-bm25.test.js +89 -89
- package/test/inject-heat.test.js +77 -77
- package/test/inject-parent-gate.test.js +134 -106
- package/test/inject-pin-explicit.test.js +300 -300
- package/test/inject-pin-vector-crowd.test.js +225 -225
- package/test/inject-pools.test.js +195 -195
- package/test/inject-recall.test.js +172 -172
- package/test/inject-rotation.test.js +161 -161
- package/test/inject.test.js +553 -494
- package/test/issue-617-step1-query-and-fusion.test.js +361 -361
- package/test/kb-api-wiring.test.js +128 -128
- package/test/kb-collections.test.js +272 -272
- package/test/kb-routes.test.js +379 -379
- package/test/lang.test.js +98 -98
- package/test/lastrunat-persist.test.js +101 -101
- package/test/lib-smoke.test.js +116 -116
- package/test/llm-audit.test.js +815 -815
- package/test/llm-message-source.test.js +207 -207
- package/test/local-embedder.test.js +292 -292
- package/test/maintenance.test.js +501 -501
- package/test/mcp-stdio.test.js +385 -385
- package/test/mint-source.test.js +311 -311
- package/test/mirror-dirty.test.js +424 -424
- package/test/mirror-edit-digest.test.js +187 -187
- package/test/mirror-generation.test.js +499 -499
- package/test/mirror-types.test.js +208 -208
- package/test/mirror.test.js +249 -249
- package/test/narrative-bars.test.js +162 -162
- package/test/organize-tool.test.js +232 -232
- package/test/organize.test.js +418 -418
- package/test/panel-chart-scale.test.js +90 -0
- package/test/panel-tokens.test.js +245 -0
- package/test/peak-hours-observability.test.js +70 -70
- package/test/peer-blockers.test.js +193 -193
- package/test/persona-addressing.test.js +83 -83
- package/test/persona-api.test.js +172 -172
- package/test/persona-by-session.test.js +568 -568
- package/test/persona-flag.test.js +36 -36
- package/test/persona-inject.test.js +145 -145
- package/test/persona-settings.test.js +198 -198
- package/test/pipeline-activities.test.js +155 -155
- package/test/pipeline-attribution.test.js +175 -175
- package/test/pipeline-command.test.js +243 -243
- package/test/pipeline-confirm.test.js +148 -148
- package/test/pipeline-exec.test.js +172 -172
- package/test/pipeline-nudge-auto.test.js +248 -248
- package/test/pipeline-nudge.test.js +1278 -1278
- package/test/pipeline-observations.test.js +157 -157
- package/test/pipeline-observe.test.js +127 -127
- package/test/pipeline-r1r2.test.js +278 -278
- package/test/pipeline-runner.test.js +468 -468
- package/test/pipeline-runs.test.js +767 -767
- package/test/pipeline.test.js +636 -636
- package/test/placement.test.js +36 -36
- package/test/policy-epoch.test.js +259 -259
- package/test/pre-inject-gate.test.js +444 -444
- package/test/profile-extract.test.js +269 -269
- package/test/profile-facts.test.js +492 -492
- package/test/prompt-stance.test.js +128 -128
- package/test/provenance-http.test.js +367 -367
- package/test/publish-gate.test.js +272 -272
- package/test/quality-backfill.test.js +290 -290
- package/test/quality-filter.test.js +295 -295
- package/test/readme-claims.test.js +113 -113
- package/test/reasoning-effort.test.js +752 -752
- package/test/recall-anti-update.test.js +82 -82
- package/test/recall-evals.test.js +253 -253
- package/test/recall-layer.test.js +326 -326
- package/test/recall-retention.test.js +128 -128
- package/test/recall-stats.test.js +274 -274
- package/test/receipt-chain.test.js +451 -451
- package/test/reclaim-http.test.js +209 -209
- package/test/reflection-update-gate.test.js +129 -129
- package/test/reflection.test.js +226 -226
- package/test/reindex-backfill.test.js +475 -475
- package/test/reindex-scheduler.test.js +369 -369
- package/test/release-docs.test.js +156 -136
- package/test/release-notes.test.js +265 -240
- package/test/release-workflow.test.js +93 -93
- package/test/reranker-single-logit.test.js +73 -73
- package/test/reranker.test.js +197 -197
- package/test/resident-status.test.js +83 -83
- package/test/route-policy.test.js +113 -113
- package/test/runtime-adopt.test.js +206 -206
- package/test/runtime-cli.test.js +89 -89
- package/test/runtime-closure.test.js +193 -193
- package/test/runtime-download.test.js +302 -302
- package/test/runtime-layout.test.js +163 -163
- package/test/runtime-loader.test.js +255 -255
- package/test/runtime-manifest.test.js +159 -159
- package/test/runtime-model-fetch.test.js +348 -348
- package/test/runtime-provision.test.js +101 -101
- package/test/runtime-tarball.test.js +81 -81
- package/test/runtime-verify.test.js +319 -319
- package/test/scope-candidates.test.js +491 -491
- package/test/scope-distill-stamp.test.js +134 -134
- package/test/scope-explicit.test.js +452 -452
- package/test/scope-inject-soft.test.js +106 -106
- package/test/scope-optional-service.test.js +110 -110
- package/test/scope-retrieval.test.js +197 -197
- package/test/scope-state.test.js +610 -610
- package/test/scope-storage.test.js +255 -255
- package/test/scope-strict.test.js +367 -367
- package/test/search-fusion.test.js +90 -90
- package/test/security-doc.test.js +188 -178
- package/test/semantic.test.js +124 -124
- package/test/sensitive-scan.test.js +251 -251
- package/test/serve-bin.test.js +121 -121
- package/test/serve.test.js +258 -258
- package/test/service-search.test.js +174 -174
- package/test/service.test.js +410 -410
- package/test/session-gate.test.js +264 -264
- package/test/session-usage.test.js +317 -317
- package/test/settings.test.js +273 -273
- package/test/share-foreign-rows.test.js +144 -144
- package/test/share-http.test.js +144 -144
- package/test/share-import.test.js +577 -577
- package/test/share-imported-guard.test.js +311 -311
- package/test/share-imported-identity.test.js +111 -111
- package/test/share-pack.test.js +511 -511
- package/test/share-receive-http.test.js +756 -756
- package/test/share-routes.test.js +337 -337
- package/test/share-store.test.js +133 -133
- package/test/share-validate.test.js +101 -101
- package/test/skip-invalid-restore.test.js +262 -262
- package/test/sleep-action-set.test.js +441 -441
- package/test/sleep-catchup.test.js +315 -315
- package/test/sleep-failure-backoff.test.js +156 -156
- package/test/sleep-heat.test.js +174 -174
- package/test/sleep-scheduler-rearm.test.js +89 -89
- package/test/sleep.test.js +1455 -1418
- package/test/standalone-api.test.js +1344 -1344
- package/test/store-vector-cache.test.js +98 -98
- package/test/store-write-path-columns.test.js +191 -191
- package/test/store.test.js +744 -744
- package/test/stress.test.js +209 -209
- package/test/summarize-occurred-at.test.js +127 -127
- package/test/summarize-parse-diagnostics.test.js +75 -75
- package/test/summarize-reasoning-effort.test.js +189 -189
- package/test/summarize.test.js +2251 -2251
- package/test/supersede-state-tracking.test.js +394 -394
- package/test/test-count-sync.test.js +71 -71
- package/test/tool-state-ignore.test.js +65 -65
- package/test/tools-dto-schema-matrix.test.js +398 -398
- package/test/tools-memory-get-schema.test.js +65 -65
- package/test/tools.test.js +585 -585
- package/test/updated-at-semantics.test.js +113 -113
- package/test/user-settings-preview.test.js +127 -127
- package/test/user-settings-segments.test.js +527 -527
- package/test/vector-index.test.js +334 -334
- package/test/version-check.test.js +152 -152
- package/test/webServer-optional.test.js +82 -82
- package/test/wiki-api.test.js +734 -627
- package/test/wiki-claims.test.js +194 -0
- package/test/wiki-cli-hint.test.js +118 -118
- package/test/wiki-inject.test.js +265 -265
- package/test/wiki-partition.test.js +112 -112
- package/test/wiki-plan.test.js +80 -80
- package/test/wiki-provenance.test.js +202 -0
- package/test/wiki-queries.test.js +398 -277
- package/test/wiki-scan.test.js +208 -208
- package/test/wiki-store.test.js +190 -190
- package/test/wiki-t0.test.js +456 -380
- package/test/wiki-t1-jobs.test.js +125 -125
- package/test/wiki-t1.test.js +460 -235
- package/test/wiki-tools.test.js +176 -176
- package/test/workflow-guards.test.js +155 -155
- package/test/write-admission.test.js +668 -668
package/test/hot-memory.test.js
CHANGED
|
@@ -1,211 +1,259 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { createHotMemory, estimateTokens, roundsFromEvents } from "../src/hot-memory.js";
|
|
4
|
-
import { createStore } from "../src/store.js";
|
|
5
|
-
import { createService } from "../src/service.js";
|
|
6
|
-
import { createVectorIndex } from "../src/vector-index.js";
|
|
7
|
-
|
|
8
|
-
// --- hot memory buffer ---
|
|
9
|
-
|
|
10
|
-
test("hot memory keeps the latest rounds within maxRounds", () => {
|
|
11
|
-
const hot = createHotMemory({ maxRounds: 2, maxTokens: 10000 });
|
|
12
|
-
hot.add({ query: "第一轮", response: "答一" });
|
|
13
|
-
hot.add({ query: "第二轮", response: "答二" });
|
|
14
|
-
hot.add({ query: "第三轮", response: "答三" });
|
|
15
|
-
assert.equal(hot.rounds().length, 2);
|
|
16
|
-
assert.ok(hot.getContext().includes("第三轮"));
|
|
17
|
-
assert.ok(!hot.getContext().includes("第一轮"));
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("hot memory enforces the token budget", () => {
|
|
21
|
-
const hot = createHotMemory({ maxRounds: 10, maxTokens: 30 });
|
|
22
|
-
hot.add({ query: "很长的第一轮问题".repeat(10), response: "很长的回答".repeat(10) });
|
|
23
|
-
hot.add({ query: "第二轮", response: "答二" });
|
|
24
|
-
// The first round alone blows the budget; the newest round survives and
|
|
25
|
-
// the buffer never empties completely.
|
|
26
|
-
const rounds = hot.rounds();
|
|
27
|
-
assert.ok(rounds.length >= 1);
|
|
28
|
-
assert.equal(rounds[rounds.length - 1].query, "第二轮");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("hot memory getContext uses the Q/A round format", () => {
|
|
32
|
-
const hot = createHotMemory({ maxRounds: 5, maxTokens: 10000 });
|
|
33
|
-
hot.add({ query: "Q1", response: "A1" });
|
|
34
|
-
hot.add({ query: "Q2", response: "A2" });
|
|
35
|
-
assert.equal(hot.getContext(), "Q: Q1\nA: A1\n\nQ: Q2\nA: A2");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("hot memory ignores empty rounds and clears", () => {
|
|
39
|
-
const hot = createHotMemory({ maxRounds: 5, maxTokens: 10000 });
|
|
40
|
-
hot.add({ query: "", response: "x" });
|
|
41
|
-
hot.add(null);
|
|
42
|
-
assert.equal(hot.rounds().length, 0);
|
|
43
|
-
hot.add({ query: "q" });
|
|
44
|
-
hot.clear();
|
|
45
|
-
assert.equal(hot.getContext(), "");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("estimateTokens counts CJK heavier than ASCII", () => {
|
|
49
|
-
assert.ok(estimateTokens("中文内容") > estimateTokens("abcd"));
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// --- service-level: BM25 fusion + semantic dedup + selective injection ---
|
|
53
|
-
|
|
54
|
-
function toyVec(text) {
|
|
55
|
-
const v = new Array(64).fill(0);
|
|
56
|
-
for (const t of String(text).toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean)) {
|
|
57
|
-
let h = 0;
|
|
58
|
-
for (const ch of t) h = (h * 31 + ch.codePointAt(0)) >>> 0;
|
|
59
|
-
v[h % 64] += 1;
|
|
60
|
-
}
|
|
61
|
-
const norm = Math.sqrt(v.reduce((s, x) => s + x * x, 0)) || 1;
|
|
62
|
-
return v.map((x) => x / norm);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function setup(overrides = {}) {
|
|
66
|
-
const store = createStore(":memory:");
|
|
67
|
-
const config = {
|
|
68
|
-
entitySearchEnabled: false,
|
|
69
|
-
bm25SearchEnabled: true,
|
|
70
|
-
adaptiveThresholdEnabled: false,
|
|
71
|
-
searchSemanticDedup: true,
|
|
72
|
-
selectiveInjectEnabled: true,
|
|
73
|
-
...overrides
|
|
74
|
-
};
|
|
75
|
-
const service = createService({ store, mirror: null, config, logger: null });
|
|
76
|
-
service.setVectorIndex(createVectorIndex({ store, logger: null }));
|
|
77
|
-
service.setEmbedder({ embedSingle: async (t) => toyVec(t) });
|
|
78
|
-
return { store, service };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
test("searchMemories fuses BM25: scattered-term queries recall both rows", async () => {
|
|
82
|
-
const { store, service } = setup();
|
|
83
|
-
const a = store.save({ type: "project", title: "异步并发模式", content: "async runtime 选用 tokio", importance: 3 });
|
|
84
|
-
const b = store.save({ type: "decision", title: "语言迁移", content: "编译模块迁移到 Rust", importance: 3 });
|
|
85
|
-
store.save({ type: "project", title: "无关", content: "夜间 ETL 脚本", importance: 3 });
|
|
86
|
-
|
|
87
|
-
// "rust 异步" is not a substring of either row — LIKE misses both; BM25
|
|
88
|
-
// must surface both rows in the merged result.
|
|
89
|
-
const results = await service.searchMemories("rust 异步", { mode: "auto", topK: 5 });
|
|
90
|
-
const ids = results.map((r) => r.id);
|
|
91
|
-
assert.ok(ids.includes(a.id), "async row must be recalled via BM25");
|
|
92
|
-
assert.ok(ids.includes(b.id), "rust row must be recalled via BM25");
|
|
93
|
-
assert.equal(results.find((r) => r.id === a.id)?.source, "bm25");
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("bm25SearchEnabled=false restores the two-path behavior", async () => {
|
|
97
|
-
const { store, service } = setup({ bm25SearchEnabled: false });
|
|
98
|
-
const a = store.save({ type: "project", title: "异步并发模式", content: "async runtime 选用 tokio", importance: 3 });
|
|
99
|
-
const results = await service.searchMemories("rust 异步", { mode: "auto", topK: 5 });
|
|
100
|
-
assert.ok(!results.some((r) => r.id === a.id), "no BM25 → scattered-term miss is back");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("search-time semantic dedup drops near-identical embeddings", async () => {
|
|
104
|
-
const { store, service } = setup();
|
|
105
|
-
const a = store.save({ type: "project", title: "偏好 A", content: "用户喜欢深色主题编辑器", importance: 3 });
|
|
106
|
-
const b = store.save({ type: "project", title: "偏好 A 备份", content: "用户喜欢深色主题编辑器(备份)", importance: 3 });
|
|
107
|
-
store.setEmbedding(a.id, toyVec("用户喜欢深色主题编辑器"));
|
|
108
|
-
store.setEmbedding(b.id, toyVec("用户喜欢深色主题编辑器"));
|
|
109
|
-
|
|
110
|
-
// auto (not keyword): keyword mode is the documented text-only path and is
|
|
111
|
-
// exempt from dedup by design; auto exercises the dedup the way production
|
|
112
|
-
// searches run.
|
|
113
|
-
const results = await service.searchMemories("深色主题", { mode: "auto", topK: 5 });
|
|
114
|
-
const ids = results.map((r) => r.id);
|
|
115
|
-
assert.ok(ids.includes(a.id) !== ids.includes(b.id), "one of the near-duplicate pair is dropped");
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("searchSemanticDedup=false keeps duplicate embeddings", async () => {
|
|
119
|
-
const { store, service } = setup({ searchSemanticDedup: false });
|
|
120
|
-
const a = store.save({ type: "project", title: "偏好 A", content: "用户喜欢深色主题编辑器", importance: 3 });
|
|
121
|
-
const b = store.save({ type: "project", title: "偏好 A 备份", content: "用户喜欢深色主题编辑器(备份)", importance: 3 });
|
|
122
|
-
store.setEmbedding(a.id, toyVec("用户喜欢深色主题编辑器"));
|
|
123
|
-
store.setEmbedding(b.id, toyVec("用户喜欢深色主题编辑器"));
|
|
124
|
-
const results = await service.searchMemories("深色主题", { mode: "keyword", topK: 5 });
|
|
125
|
-
assert.equal(results.length, 2);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("selective injection re-orders candidates by query similarity", () => {
|
|
129
|
-
const { store, service } = setup();
|
|
130
|
-
const thesis = store.save({ type: "project", title: "湿地论文", content: "毕业论文研究城市湿地公园", importance: 5 });
|
|
131
|
-
const plugin = store.save({ type: "project", title: "插件项目", content: "dsh-mneme 记忆插件开发", importance: 5 });
|
|
132
|
-
store.setEmbedding(thesis.id, toyVec("毕业论文研究城市湿地公园"));
|
|
133
|
-
store.setEmbedding(plugin.id, toyVec("dsh-mneme 记忆插件开发"));
|
|
134
|
-
|
|
135
|
-
// Rule-based order would put both at equal importance; the query vector is
|
|
136
|
-
// about the thesis, so topic ranking must put the thesis memory first.
|
|
137
|
-
const picked = service.injectCandidates({
|
|
138
|
-
query: "论文写作",
|
|
139
|
-
queryVector: toyVec("毕业论文研究城市湿地公园"),
|
|
140
|
-
maxItems: 2,
|
|
141
|
-
threshold: 3
|
|
142
|
-
});
|
|
143
|
-
assert.equal(picked[0].id, thesis.id);
|
|
144
|
-
assert.ok(picked.some((m) => m.id === plugin.id));
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// --- roundsFromEvents:surface 投影(rewind 撤回内容不得回流热记忆) ---
|
|
148
|
-
|
|
149
|
-
function userEvent(seq, text, kind) {
|
|
150
|
-
return { seq, type: "user/message", data: { source: kind ? { kind } : undefined, content: [text] } };
|
|
151
|
-
}
|
|
152
|
-
function assistantEvent(seq, text) {
|
|
153
|
-
return { seq, type: "assistant/message", data: { message: { content: [{ type: "text", text }] } } };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
test("roundsFromEvents pairs user/assistant rounds and keeps the trailing pending query", () => {
|
|
157
|
-
const events = [
|
|
158
|
-
userEvent(1, "第一问"),
|
|
159
|
-
assistantEvent(2, "第一答"),
|
|
160
|
-
userEvent(3, "第二问")
|
|
161
|
-
];
|
|
162
|
-
const rounds = roundsFromEvents(events, 5);
|
|
163
|
-
assert.equal(rounds.length, 2);
|
|
164
|
-
assert.equal(rounds[0].query, "第一问");
|
|
165
|
-
assert.equal(rounds[0].response, "第一答");
|
|
166
|
-
assert.equal(rounds[1].query, "第二问");
|
|
167
|
-
assert.equal(rounds[1].response, "");
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("roundsFromEvents excludes events shadowed off the surface (rewind/compact)", () => {
|
|
171
|
-
// append-only 日志里被撤回的轮次仍在,但 surface 只剩可见 seq——
|
|
172
|
-
// 热记忆必须与模型同源,被遮蔽的轮次不得回流。
|
|
173
|
-
const events = [
|
|
174
|
-
userEvent(1, "已被撤回的问题"),
|
|
175
|
-
assistantEvent(2, "已被撤回的回答"),
|
|
176
|
-
{ seq: 3, type: "user/message", data: { source: { kind: "dsh-rewind" }, content: [""] } },
|
|
177
|
-
userEvent(4, "撤回后的新问题"),
|
|
178
|
-
assistantEvent(5, "撤回后的新回答")
|
|
179
|
-
];
|
|
180
|
-
const visible = new Set([3, 4, 5]);
|
|
181
|
-
const rounds = roundsFromEvents(events, 5, visible);
|
|
182
|
-
assert.equal(rounds.length, 1);
|
|
183
|
-
assert.equal(rounds[0].query, "撤回后的新问题");
|
|
184
|
-
assert.equal(rounds[0].response, "撤回后的新回答");
|
|
185
|
-
assert.ok(!JSON.stringify(rounds).includes("已被撤回"));
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("roundsFromEvents with no surface info keeps the legacy full-log behavior", () => {
|
|
189
|
-
const events = [userEvent(1, "旧宿主问题"), assistantEvent(2, "旧宿主回答")];
|
|
190
|
-
const rounds = roundsFromEvents(events, 5, null);
|
|
191
|
-
assert.equal(rounds.length, 1);
|
|
192
|
-
assert.equal(rounds[0].query, "旧宿主问题");
|
|
193
|
-
assert.equal(rounds[0].response, "旧宿主回答");
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test("roundsFromEvents skips rewind markers as queries even when on the surface", () => {
|
|
197
|
-
const events = [
|
|
198
|
-
{ seq: 1, type: "user/message", data: { source: { kind: "dsh-rewind" }, content: [""] } },
|
|
199
|
-
userEvent(2, "标记后的第一问"),
|
|
200
|
-
assistantEvent(3, "标记后的第一答")
|
|
201
|
-
];
|
|
202
|
-
const visible = new Set([1, 2, 3]);
|
|
203
|
-
const rounds = roundsFromEvents(events, 5, visible);
|
|
204
|
-
assert.equal(rounds.length, 1);
|
|
205
|
-
assert.equal(rounds[0].query, "标记后的第一问");
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test("roundsFromEvents with an empty surface yields no rounds (model sees nothing)", () => {
|
|
209
|
-
const events = [userEvent(1, "问题"), assistantEvent(2, "回答")];
|
|
210
|
-
assert.equal(roundsFromEvents(events, 5, new Set()).length, 0);
|
|
211
|
-
});
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createHotMemory, estimateTokens, roundsFromEvents } from "../src/hot-memory.js";
|
|
4
|
+
import { createStore } from "../src/store.js";
|
|
5
|
+
import { createService } from "../src/service.js";
|
|
6
|
+
import { createVectorIndex } from "../src/vector-index.js";
|
|
7
|
+
|
|
8
|
+
// --- hot memory buffer ---
|
|
9
|
+
|
|
10
|
+
test("hot memory keeps the latest rounds within maxRounds", () => {
|
|
11
|
+
const hot = createHotMemory({ maxRounds: 2, maxTokens: 10000 });
|
|
12
|
+
hot.add({ query: "第一轮", response: "答一" });
|
|
13
|
+
hot.add({ query: "第二轮", response: "答二" });
|
|
14
|
+
hot.add({ query: "第三轮", response: "答三" });
|
|
15
|
+
assert.equal(hot.rounds().length, 2);
|
|
16
|
+
assert.ok(hot.getContext().includes("第三轮"));
|
|
17
|
+
assert.ok(!hot.getContext().includes("第一轮"));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("hot memory enforces the token budget", () => {
|
|
21
|
+
const hot = createHotMemory({ maxRounds: 10, maxTokens: 30 });
|
|
22
|
+
hot.add({ query: "很长的第一轮问题".repeat(10), response: "很长的回答".repeat(10) });
|
|
23
|
+
hot.add({ query: "第二轮", response: "答二" });
|
|
24
|
+
// The first round alone blows the budget; the newest round survives and
|
|
25
|
+
// the buffer never empties completely.
|
|
26
|
+
const rounds = hot.rounds();
|
|
27
|
+
assert.ok(rounds.length >= 1);
|
|
28
|
+
assert.equal(rounds[rounds.length - 1].query, "第二轮");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("hot memory getContext uses the Q/A round format", () => {
|
|
32
|
+
const hot = createHotMemory({ maxRounds: 5, maxTokens: 10000 });
|
|
33
|
+
hot.add({ query: "Q1", response: "A1" });
|
|
34
|
+
hot.add({ query: "Q2", response: "A2" });
|
|
35
|
+
assert.equal(hot.getContext(), "Q: Q1\nA: A1\n\nQ: Q2\nA: A2");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("hot memory ignores empty rounds and clears", () => {
|
|
39
|
+
const hot = createHotMemory({ maxRounds: 5, maxTokens: 10000 });
|
|
40
|
+
hot.add({ query: "", response: "x" });
|
|
41
|
+
hot.add(null);
|
|
42
|
+
assert.equal(hot.rounds().length, 0);
|
|
43
|
+
hot.add({ query: "q" });
|
|
44
|
+
hot.clear();
|
|
45
|
+
assert.equal(hot.getContext(), "");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("estimateTokens counts CJK heavier than ASCII", () => {
|
|
49
|
+
assert.ok(estimateTokens("中文内容") > estimateTokens("abcd"));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// --- service-level: BM25 fusion + semantic dedup + selective injection ---
|
|
53
|
+
|
|
54
|
+
function toyVec(text) {
|
|
55
|
+
const v = new Array(64).fill(0);
|
|
56
|
+
for (const t of String(text).toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean)) {
|
|
57
|
+
let h = 0;
|
|
58
|
+
for (const ch of t) h = (h * 31 + ch.codePointAt(0)) >>> 0;
|
|
59
|
+
v[h % 64] += 1;
|
|
60
|
+
}
|
|
61
|
+
const norm = Math.sqrt(v.reduce((s, x) => s + x * x, 0)) || 1;
|
|
62
|
+
return v.map((x) => x / norm);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setup(overrides = {}) {
|
|
66
|
+
const store = createStore(":memory:");
|
|
67
|
+
const config = {
|
|
68
|
+
entitySearchEnabled: false,
|
|
69
|
+
bm25SearchEnabled: true,
|
|
70
|
+
adaptiveThresholdEnabled: false,
|
|
71
|
+
searchSemanticDedup: true,
|
|
72
|
+
selectiveInjectEnabled: true,
|
|
73
|
+
...overrides
|
|
74
|
+
};
|
|
75
|
+
const service = createService({ store, mirror: null, config, logger: null });
|
|
76
|
+
service.setVectorIndex(createVectorIndex({ store, logger: null }));
|
|
77
|
+
service.setEmbedder({ embedSingle: async (t) => toyVec(t) });
|
|
78
|
+
return { store, service };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
test("searchMemories fuses BM25: scattered-term queries recall both rows", async () => {
|
|
82
|
+
const { store, service } = setup();
|
|
83
|
+
const a = store.save({ type: "project", title: "异步并发模式", content: "async runtime 选用 tokio", importance: 3 });
|
|
84
|
+
const b = store.save({ type: "decision", title: "语言迁移", content: "编译模块迁移到 Rust", importance: 3 });
|
|
85
|
+
store.save({ type: "project", title: "无关", content: "夜间 ETL 脚本", importance: 3 });
|
|
86
|
+
|
|
87
|
+
// "rust 异步" is not a substring of either row — LIKE misses both; BM25
|
|
88
|
+
// must surface both rows in the merged result.
|
|
89
|
+
const results = await service.searchMemories("rust 异步", { mode: "auto", topK: 5 });
|
|
90
|
+
const ids = results.map((r) => r.id);
|
|
91
|
+
assert.ok(ids.includes(a.id), "async row must be recalled via BM25");
|
|
92
|
+
assert.ok(ids.includes(b.id), "rust row must be recalled via BM25");
|
|
93
|
+
assert.equal(results.find((r) => r.id === a.id)?.source, "bm25");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("bm25SearchEnabled=false restores the two-path behavior", async () => {
|
|
97
|
+
const { store, service } = setup({ bm25SearchEnabled: false });
|
|
98
|
+
const a = store.save({ type: "project", title: "异步并发模式", content: "async runtime 选用 tokio", importance: 3 });
|
|
99
|
+
const results = await service.searchMemories("rust 异步", { mode: "auto", topK: 5 });
|
|
100
|
+
assert.ok(!results.some((r) => r.id === a.id), "no BM25 → scattered-term miss is back");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("search-time semantic dedup drops near-identical embeddings", async () => {
|
|
104
|
+
const { store, service } = setup();
|
|
105
|
+
const a = store.save({ type: "project", title: "偏好 A", content: "用户喜欢深色主题编辑器", importance: 3 });
|
|
106
|
+
const b = store.save({ type: "project", title: "偏好 A 备份", content: "用户喜欢深色主题编辑器(备份)", importance: 3 });
|
|
107
|
+
store.setEmbedding(a.id, toyVec("用户喜欢深色主题编辑器"));
|
|
108
|
+
store.setEmbedding(b.id, toyVec("用户喜欢深色主题编辑器"));
|
|
109
|
+
|
|
110
|
+
// auto (not keyword): keyword mode is the documented text-only path and is
|
|
111
|
+
// exempt from dedup by design; auto exercises the dedup the way production
|
|
112
|
+
// searches run.
|
|
113
|
+
const results = await service.searchMemories("深色主题", { mode: "auto", topK: 5 });
|
|
114
|
+
const ids = results.map((r) => r.id);
|
|
115
|
+
assert.ok(ids.includes(a.id) !== ids.includes(b.id), "one of the near-duplicate pair is dropped");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("searchSemanticDedup=false keeps duplicate embeddings", async () => {
|
|
119
|
+
const { store, service } = setup({ searchSemanticDedup: false });
|
|
120
|
+
const a = store.save({ type: "project", title: "偏好 A", content: "用户喜欢深色主题编辑器", importance: 3 });
|
|
121
|
+
const b = store.save({ type: "project", title: "偏好 A 备份", content: "用户喜欢深色主题编辑器(备份)", importance: 3 });
|
|
122
|
+
store.setEmbedding(a.id, toyVec("用户喜欢深色主题编辑器"));
|
|
123
|
+
store.setEmbedding(b.id, toyVec("用户喜欢深色主题编辑器"));
|
|
124
|
+
const results = await service.searchMemories("深色主题", { mode: "keyword", topK: 5 });
|
|
125
|
+
assert.equal(results.length, 2);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("selective injection re-orders candidates by query similarity", () => {
|
|
129
|
+
const { store, service } = setup();
|
|
130
|
+
const thesis = store.save({ type: "project", title: "湿地论文", content: "毕业论文研究城市湿地公园", importance: 5 });
|
|
131
|
+
const plugin = store.save({ type: "project", title: "插件项目", content: "dsh-mneme 记忆插件开发", importance: 5 });
|
|
132
|
+
store.setEmbedding(thesis.id, toyVec("毕业论文研究城市湿地公园"));
|
|
133
|
+
store.setEmbedding(plugin.id, toyVec("dsh-mneme 记忆插件开发"));
|
|
134
|
+
|
|
135
|
+
// Rule-based order would put both at equal importance; the query vector is
|
|
136
|
+
// about the thesis, so topic ranking must put the thesis memory first.
|
|
137
|
+
const picked = service.injectCandidates({
|
|
138
|
+
query: "论文写作",
|
|
139
|
+
queryVector: toyVec("毕业论文研究城市湿地公园"),
|
|
140
|
+
maxItems: 2,
|
|
141
|
+
threshold: 3
|
|
142
|
+
});
|
|
143
|
+
assert.equal(picked[0].id, thesis.id);
|
|
144
|
+
assert.ok(picked.some((m) => m.id === plugin.id));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// --- roundsFromEvents:surface 投影(rewind 撤回内容不得回流热记忆) ---
|
|
148
|
+
|
|
149
|
+
function userEvent(seq, text, kind) {
|
|
150
|
+
return { seq, type: "user/message", data: { source: kind ? { kind } : undefined, content: [text] } };
|
|
151
|
+
}
|
|
152
|
+
function assistantEvent(seq, text) {
|
|
153
|
+
return { seq, type: "assistant/message", data: { message: { content: [{ type: "text", text }] } } };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
test("roundsFromEvents pairs user/assistant rounds and keeps the trailing pending query", () => {
|
|
157
|
+
const events = [
|
|
158
|
+
userEvent(1, "第一问"),
|
|
159
|
+
assistantEvent(2, "第一答"),
|
|
160
|
+
userEvent(3, "第二问")
|
|
161
|
+
];
|
|
162
|
+
const rounds = roundsFromEvents(events, 5);
|
|
163
|
+
assert.equal(rounds.length, 2);
|
|
164
|
+
assert.equal(rounds[0].query, "第一问");
|
|
165
|
+
assert.equal(rounds[0].response, "第一答");
|
|
166
|
+
assert.equal(rounds[1].query, "第二问");
|
|
167
|
+
assert.equal(rounds[1].response, "");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("roundsFromEvents excludes events shadowed off the surface (rewind/compact)", () => {
|
|
171
|
+
// append-only 日志里被撤回的轮次仍在,但 surface 只剩可见 seq——
|
|
172
|
+
// 热记忆必须与模型同源,被遮蔽的轮次不得回流。
|
|
173
|
+
const events = [
|
|
174
|
+
userEvent(1, "已被撤回的问题"),
|
|
175
|
+
assistantEvent(2, "已被撤回的回答"),
|
|
176
|
+
{ seq: 3, type: "user/message", data: { source: { kind: "dsh-rewind" }, content: [""] } },
|
|
177
|
+
userEvent(4, "撤回后的新问题"),
|
|
178
|
+
assistantEvent(5, "撤回后的新回答")
|
|
179
|
+
];
|
|
180
|
+
const visible = new Set([3, 4, 5]);
|
|
181
|
+
const rounds = roundsFromEvents(events, 5, visible);
|
|
182
|
+
assert.equal(rounds.length, 1);
|
|
183
|
+
assert.equal(rounds[0].query, "撤回后的新问题");
|
|
184
|
+
assert.equal(rounds[0].response, "撤回后的新回答");
|
|
185
|
+
assert.ok(!JSON.stringify(rounds).includes("已被撤回"));
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("roundsFromEvents with no surface info keeps the legacy full-log behavior", () => {
|
|
189
|
+
const events = [userEvent(1, "旧宿主问题"), assistantEvent(2, "旧宿主回答")];
|
|
190
|
+
const rounds = roundsFromEvents(events, 5, null);
|
|
191
|
+
assert.equal(rounds.length, 1);
|
|
192
|
+
assert.equal(rounds[0].query, "旧宿主问题");
|
|
193
|
+
assert.equal(rounds[0].response, "旧宿主回答");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("roundsFromEvents skips rewind markers as queries even when on the surface", () => {
|
|
197
|
+
const events = [
|
|
198
|
+
{ seq: 1, type: "user/message", data: { source: { kind: "dsh-rewind" }, content: [""] } },
|
|
199
|
+
userEvent(2, "标记后的第一问"),
|
|
200
|
+
assistantEvent(3, "标记后的第一答")
|
|
201
|
+
];
|
|
202
|
+
const visible = new Set([1, 2, 3]);
|
|
203
|
+
const rounds = roundsFromEvents(events, 5, visible);
|
|
204
|
+
assert.equal(rounds.length, 1);
|
|
205
|
+
assert.equal(rounds[0].query, "标记后的第一问");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("roundsFromEvents with an empty surface yields no rounds (model sees nothing)", () => {
|
|
209
|
+
const events = [userEvent(1, "问题"), assistantEvent(2, "回答")];
|
|
210
|
+
assert.equal(roundsFromEvents(events, 5, new Set()).length, 0);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// --- Issue #637:omitPendingRound —— 注入侧不回放「正在组装的这一轮」 ---
|
|
214
|
+
|
|
215
|
+
test("omitPendingRound drops the trailing in-flight round (the #637 empty-A churn source)", () => {
|
|
216
|
+
// 本轮刚开了头(提问已落日志、回答还没来):默认行为渲染空 A: 行——
|
|
217
|
+
// 宿主「文本变了就追加快照」的契机会让回答落地后这一行抖成正文、再多追一份。
|
|
218
|
+
const events = [userEvent(1, "第一问"), assistantEvent(2, "第一答"), userEvent(3, "第二问")];
|
|
219
|
+
const rounds = roundsFromEvents(events, 5, null, { omitPendingRound: true });
|
|
220
|
+
assert.equal(rounds.length, 1, "the in-flight round is not replayed");
|
|
221
|
+
assert.equal(rounds[0].query, "第一问");
|
|
222
|
+
assert.ok(!JSON.stringify(rounds).includes("第二问"), "the current turn's query never reaches hot memory");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("omitPendingRound keeps a closed round — the block changes exactly once per turn", () => {
|
|
226
|
+
// 回答落地(轮闭合)是热块在一轮内唯一该变化的时刻:此前所有组装看到同一份
|
|
227
|
+
// 文本,此刻变成「历史轮 + 刚闭合的轮」→ 宿主恰好追一份快照。
|
|
228
|
+
const open = [userEvent(1, "第一问"), assistantEvent(2, "第一答"), userEvent(3, "第二问")];
|
|
229
|
+
const closed = [userEvent(1, "第一问"), assistantEvent(2, "第一答"), userEvent(3, "第二问"), assistantEvent(4, "第二答")];
|
|
230
|
+
const before = roundsFromEvents(open, 5, null, { omitPendingRound: true });
|
|
231
|
+
const after = roundsFromEvents(closed, 5, null, { omitPendingRound: true });
|
|
232
|
+
assert.equal(before.length, 1);
|
|
233
|
+
assert.equal(after.length, 2);
|
|
234
|
+
assert.deepEqual(after[1], { query: "第二问", response: "第二答" });
|
|
235
|
+
// 轮与轮之间(下一轮开头的组装)块不再变:第三轮的提问此刻是 pending,被省略。
|
|
236
|
+
const nextTurn = roundsFromEvents([...closed, userEvent(5, "第三问")], 5, null, { omitPendingRound: true });
|
|
237
|
+
assert.deepEqual(nextTurn, after, "the block is stable across the turn boundary — no extra snapshot");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("omitPendingRound only touches the tail — mid-log abandoned rounds still render empty A", () => {
|
|
241
|
+
// 双发消息(第二问把第一问顶掉):第一问是已被放弃的历史提问,渲染成空 A:
|
|
242
|
+
// 是既有语义,不属于「正在组装的这一轮」。
|
|
243
|
+
const events = [userEvent(1, "被顶掉的问题"), userEvent(2, "第二轮的问题"), assistantEvent(3, "第二轮的回答"), userEvent(4, "第三问")];
|
|
244
|
+
const rounds = roundsFromEvents(events, 5, null, { omitPendingRound: true });
|
|
245
|
+
assert.deepEqual(rounds, [
|
|
246
|
+
{ query: "被顶掉的问题", response: "" },
|
|
247
|
+
{ query: "第二轮的问题", response: "第二轮的回答" }
|
|
248
|
+
]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("omitPendingRound works with the surface projection (rewind-shadowed tail not special)", () => {
|
|
252
|
+
// 撤回遮蔽过的事件不参与配对,也不影响「尾部 pending」的判定:可见面上最后
|
|
253
|
+
// 一条 user/message 是第二问,它才是本轮。
|
|
254
|
+
const events = [userEvent(1, "已被撤回的问题"), assistantEvent(2, "已被撤回的回答"), userEvent(3, "第一问"), assistantEvent(4, "第一答"), userEvent(5, "第二问")];
|
|
255
|
+
const visible = new Set([3, 4, 5]);
|
|
256
|
+
const rounds = roundsFromEvents(events, 5, visible, { omitPendingRound: true });
|
|
257
|
+
assert.deepEqual(rounds, [{ query: "第一问", response: "第一答" }]);
|
|
258
|
+
});
|
|
259
|
+
|