@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
|
@@ -1,285 +1,285 @@
|
|
|
1
|
-
const VALID_TYPES = new Set(["person", "project", "concept", "technology", "organization"]);
|
|
2
|
-
// #619:affective_context **刻意不在**这张白名单里。它不是结构关系,是「某人
|
|
3
|
-
// 在某一次场合对某事的情绪」——判它需要 provenance(这条记忆来自哪个会话),
|
|
4
|
-
// 而实体抽取发生在写入当下、手里没有会话边界,判不了「一次场合」与「反复出现
|
|
5
|
-
// 的特质」。让它在这里也能写,就等于给 entity_relations 开第二个只带 {model}
|
|
6
|
-
// 的写入口,同一张表两种 schema。唯一的写入者是 sleep 的 affective 相位。
|
|
7
|
-
const VALID_RELATIONS = new Set(["uses", "depends_on", "part_of", "related_to"]);
|
|
8
|
-
const MAX_MEMORY_CHARS = 4000;
|
|
9
|
-
|
|
10
|
-
function buildSystemPrompt(config) {
|
|
11
|
-
const maxEntities = config.entityExtractionMaxEntities ?? 10;
|
|
12
|
-
const maxAttrs = config.entityExtractionMaxAttrs ?? 20;
|
|
13
|
-
|
|
14
|
-
return `You are an entity extraction engine. Extract named entities, their attributes, and relations from the given text.
|
|
15
|
-
|
|
16
|
-
RULES:
|
|
17
|
-
- Entities are concrete people, projects, technologies, concepts, or organizations.
|
|
18
|
-
- Attributes are characteristic states of an entity. Only extract attributes explicitly mentioned in the text.
|
|
19
|
-
- Do NOT infer, guess, or hallucinate. Only extract what is clearly stated.
|
|
20
|
-
- Use canonical names (full name / primary name). Do NOT merge synonyms — different spellings of the same person are different entities.
|
|
21
|
-
- Output MUST be strict JSON with this exact structure:
|
|
22
|
-
{
|
|
23
|
-
"entities": [
|
|
24
|
-
{"name": "string", "type": "person|project|concept|technology|organization", "attrs": [{"key": "string", "value": "string", "confidence": 0.9}]}
|
|
25
|
-
],
|
|
26
|
-
"relations": [
|
|
27
|
-
{"from": "entityName", "to": "entityName", "type": "uses|depends_on|part_of|related_to"}
|
|
28
|
-
]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
CONSTRAINTS:
|
|
32
|
-
- "type" must be one of: person, project, concept, technology, organization. If unsure, use "concept".
|
|
33
|
-
- Relation "type" must be one of: uses, depends_on, part_of, related_to. If unsure, use related_to.
|
|
34
|
-
- Do NOT emit emotional or affective relations. An emotion belongs to one occasion and needs session provenance to judge it; that is recorded separately, not here.
|
|
35
|
-
- "relations" from/to must reference entity names from the "entities" list.
|
|
36
|
-
- Maximum ${maxEntities} entities. Maximum ${maxAttrs} attributes per entity. Truncate if exceeded.
|
|
37
|
-
- Only include entity-related attributes. Ignore irrelevant miscellaneous details.
|
|
38
|
-
- Return ONLY the JSON object. No explanations, no markdown, no code fences.`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function buildUserMessage(memoryText) {
|
|
42
|
-
const truncated = memoryText.length > MAX_MEMORY_CHARS
|
|
43
|
-
? memoryText.slice(0, MAX_MEMORY_CHARS) + "..."
|
|
44
|
-
: memoryText;
|
|
45
|
-
return truncated;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function extractJsonFromText(text) {
|
|
49
|
-
if (!text || typeof text !== "string") return null;
|
|
50
|
-
|
|
51
|
-
// Try direct parse first
|
|
52
|
-
try {
|
|
53
|
-
return JSON.parse(text);
|
|
54
|
-
} catch {
|
|
55
|
-
// fall through
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Try to find first {...} block
|
|
59
|
-
const match = text.match(/\{[\s\S]*\}/);
|
|
60
|
-
if (match) {
|
|
61
|
-
try {
|
|
62
|
-
return JSON.parse(match[0]);
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function sanitizeType(type) {
|
|
72
|
-
if (VALID_TYPES.has(type)) return type;
|
|
73
|
-
return "concept";
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function sanitizeConfidence(conf) {
|
|
77
|
-
const num = Number(conf);
|
|
78
|
-
if (Number.isNaN(num)) return 0.9;
|
|
79
|
-
return Math.min(1, Math.max(0, num));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// #619:未知关系类型**不再静默降级成 related_to**。旧行为是「写入成功、不报错、
|
|
83
|
-
// 图上没有那条轴」——最难排查的一种失败:模型抽对了,库里却读不出它抽的是情感边
|
|
84
|
-
// 还是普通关联。现在返回 null,由调用方丢弃并 logger.warn 带原始类型名。
|
|
85
|
-
// 注意这与 sanitizeType(未知实体类型 → concept)刻意不同:实体类型是可容错的
|
|
86
|
-
// 粗分类,关系类型是语义轴本身,猜错一条边就是给图加错误事实。
|
|
87
|
-
function sanitizeRelationType(type) {
|
|
88
|
-
if (typeof type !== "string") return null;
|
|
89
|
-
const normalized = type.trim();
|
|
90
|
-
if (VALID_RELATIONS.has(normalized)) return normalized;
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function sanitizeExtractedData(data, config, logger = null) {
|
|
95
|
-
const maxEntities = config.entityExtractionMaxEntities ?? 10;
|
|
96
|
-
const maxAttrs = config.entityExtractionMaxAttrs ?? 20;
|
|
97
|
-
|
|
98
|
-
if (!data || !Array.isArray(data.entities)) {
|
|
99
|
-
throw new Error("Invalid extraction: missing entities array");
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const entities = [];
|
|
103
|
-
const seenNames = new Set();
|
|
104
|
-
|
|
105
|
-
for (const rawEntity of data.entities.slice(0, maxEntities)) {
|
|
106
|
-
if (!rawEntity || typeof rawEntity.name !== "string" || !rawEntity.name.trim()) continue;
|
|
107
|
-
|
|
108
|
-
const name = rawEntity.name.trim();
|
|
109
|
-
if (seenNames.has(name)) continue;
|
|
110
|
-
seenNames.add(name);
|
|
111
|
-
|
|
112
|
-
const attrs = [];
|
|
113
|
-
if (Array.isArray(rawEntity.attrs)) {
|
|
114
|
-
for (const rawAttr of rawEntity.attrs.slice(0, maxAttrs)) {
|
|
115
|
-
if (!rawAttr || typeof rawAttr.key !== "string" || typeof rawAttr.value !== "string") continue;
|
|
116
|
-
attrs.push({
|
|
117
|
-
key: rawAttr.key.trim(),
|
|
118
|
-
value: rawAttr.value.trim(),
|
|
119
|
-
confidence: sanitizeConfidence(rawAttr.confidence)
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
entities.push({
|
|
125
|
-
name,
|
|
126
|
-
type: sanitizeType(rawEntity.type),
|
|
127
|
-
attrs
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const relations = [];
|
|
132
|
-
const rejectedRelationTypes = [];
|
|
133
|
-
if (Array.isArray(data.relations)) {
|
|
134
|
-
for (const rawRel of data.relations) {
|
|
135
|
-
if (!rawRel || typeof rawRel.from !== "string" || typeof rawRel.to !== "string") continue;
|
|
136
|
-
if (!seenNames.has(rawRel.from) || !seenNames.has(rawRel.to)) continue;
|
|
137
|
-
const relationType = sanitizeRelationType(rawRel.type);
|
|
138
|
-
if (!relationType) {
|
|
139
|
-
// #619:不再降级成 related_to——记下原始类型名,交给调用方 warn。
|
|
140
|
-
rejectedRelationTypes.push(String(rawRel.type));
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
relations.push({
|
|
144
|
-
from: rawRel.from,
|
|
145
|
-
to: rawRel.to,
|
|
146
|
-
type: relationType
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (rejectedRelationTypes.length > 0) {
|
|
151
|
-
logger?.warn?.(
|
|
152
|
-
`[extractor] dropped ${rejectedRelationTypes.length} relation(s) with unknown type(s): ${[...new Set(rejectedRelationTypes)].join(", ")}`
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return { entities, relations, rejectedRelationTypes };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function resolveEntity(entity, store) {
|
|
160
|
-
const existing = store.findEntityByName(entity.name);
|
|
161
|
-
if (existing) {
|
|
162
|
-
store.updateEntity(existing.id, {});
|
|
163
|
-
return existing.id;
|
|
164
|
-
}
|
|
165
|
-
const created = store.createEntity({ name: entity.name, type: entity.type });
|
|
166
|
-
return created.id;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export async function extractEntities(memory, { store, config, callLLM, logger }) {
|
|
170
|
-
try {
|
|
171
|
-
if (!memory || !memory.content) {
|
|
172
|
-
return { ok: false, error: "Invalid memory: missing content" };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const systemPrompt = buildSystemPrompt(config);
|
|
176
|
-
const userText = buildUserMessage(memory.content);
|
|
177
|
-
|
|
178
|
-
const messages = [
|
|
179
|
-
{ role: "system", content: [{ type: "text", text: systemPrompt }], source: { kind: "plugin:dsh-mneme", plugin: "dsh-mneme" } },
|
|
180
|
-
{ role: "user", content: [{ type: "text", text: userText }], source: { kind: "plugin:dsh-mneme", plugin: "dsh-mneme" } }
|
|
181
|
-
];
|
|
182
|
-
|
|
183
|
-
// Issue #109: optional provider/model override + reasoning effort are
|
|
184
|
-
// passed through to callLLM's options; the index.js adapter maps them onto
|
|
185
|
-
// the route (or falls back to the caller's default model). Empty provider/
|
|
186
|
-
// model both mean "use the caller default".
|
|
187
|
-
const options = {};
|
|
188
|
-
if (config.entityExtractionProvider) options.provider = config.entityExtractionProvider;
|
|
189
|
-
if (config.entityExtractionModel) options.model = config.entityExtractionModel;
|
|
190
|
-
const reasoning = config.entityExtractionReasoning;
|
|
191
|
-
if (reasoning && reasoning !== "none") options.reasoningEffort = reasoning;
|
|
192
|
-
// #405: 审计行要能归因到具体记忆。extractor 只把 content 写进 prompt,id 这里顺
|
|
193
|
-
// options 交给 index.js 的 adapter(route 映射只认 provider/model,多余键不会进
|
|
194
|
-
// llm.stream)。拿不到 id 就不写这个键,adapter 侧照旧留空数组,不造虚账。
|
|
195
|
-
if (memory.id) options.memoryId = memory.id;
|
|
196
|
-
const llmResponse = await callLLM(messages, options);
|
|
197
|
-
|
|
198
|
-
if (!llmResponse) {
|
|
199
|
-
return { ok: false, error: "LLM returned empty response" };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const rawData = extractJsonFromText(llmResponse);
|
|
203
|
-
if (!rawData) {
|
|
204
|
-
return { ok: false, error: "Failed to parse JSON from LLM response" };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const { entities, relations, rejectedRelationTypes } = sanitizeExtractedData(rawData, config, logger);
|
|
208
|
-
|
|
209
|
-
const resolvedEntities = [];
|
|
210
|
-
const entityIdMap = new Map();
|
|
211
|
-
let skipCount = 0;
|
|
212
|
-
|
|
213
|
-
// Resolve entities
|
|
214
|
-
for (const entity of entities) {
|
|
215
|
-
try {
|
|
216
|
-
const entityId = await resolveEntity(entity, store);
|
|
217
|
-
entityIdMap.set(entity.name, entityId);
|
|
218
|
-
resolvedEntities.push({ ...entity, entity_id: entityId });
|
|
219
|
-
} catch (err) {
|
|
220
|
-
skipCount++;
|
|
221
|
-
logger?.warn?.(`[extractor] Failed to resolve entity "${entity.name}":`, err.message);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Record attributes
|
|
226
|
-
const attrs = [];
|
|
227
|
-
for (const entity of resolvedEntities) {
|
|
228
|
-
for (const attr of entity.attrs) {
|
|
229
|
-
try {
|
|
230
|
-
const saved = store.saveAttr({
|
|
231
|
-
entity_id: entity.entity_id,
|
|
232
|
-
attr_key: attr.key,
|
|
233
|
-
attr_value: String(attr.value),
|
|
234
|
-
memory_id: memory.id,
|
|
235
|
-
confidence: attr.confidence,
|
|
236
|
-
source: "llm_extract"
|
|
237
|
-
});
|
|
238
|
-
attrs.push(saved);
|
|
239
|
-
} catch (err) {
|
|
240
|
-
skipCount++;
|
|
241
|
-
logger?.warn?.(`[extractor] Failed to save attr "${attr.key}" for entity "${entity.name}":`, err.message);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Record relations
|
|
247
|
-
const savedRelations = [];
|
|
248
|
-
for (const rel of relations) {
|
|
249
|
-
const fromId = entityIdMap.get(rel.from);
|
|
250
|
-
const toId = entityIdMap.get(rel.to);
|
|
251
|
-
if (!fromId || !toId) continue;
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
const saved = store.saveRelation({
|
|
255
|
-
from_entity: fromId,
|
|
256
|
-
to_entity: toId,
|
|
257
|
-
relation_type: rel.type,
|
|
258
|
-
memory_id: memory.id,
|
|
259
|
-
metadata: { model: options.model || "default" },
|
|
260
|
-
// issue #24 块2:LLM 抽取自动建边 → llm 来源(初值 0.4),区别于
|
|
261
|
-
// 人工/确认的满权边。来源进 metadata 之上的独立列,演化与审计可辨。
|
|
262
|
-
source: "llm"
|
|
263
|
-
});
|
|
264
|
-
savedRelations.push(saved);
|
|
265
|
-
} catch (err) {
|
|
266
|
-
skipCount++;
|
|
267
|
-
logger?.warn?.(`[extractor] Failed to save relation "${rel.from} -> ${rel.to}":`, err.message);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return {
|
|
272
|
-
ok: true,
|
|
273
|
-
entities: resolvedEntities,
|
|
274
|
-
attrs,
|
|
275
|
-
relations: savedRelations,
|
|
276
|
-
// #619:被丢弃的未知关系类型(含原始名字)。调用方据此判断「模型没抽」
|
|
277
|
-
// 与「抽了但类型不认识」——两者的运维动作完全不同。
|
|
278
|
-
rejectedRelationTypes,
|
|
279
|
-
skipped: skipCount
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
} catch (err) {
|
|
283
|
-
return { ok: false, error: String(err) };
|
|
284
|
-
}
|
|
285
|
-
}
|
|
1
|
+
const VALID_TYPES = new Set(["person", "project", "concept", "technology", "organization"]);
|
|
2
|
+
// #619:affective_context **刻意不在**这张白名单里。它不是结构关系,是「某人
|
|
3
|
+
// 在某一次场合对某事的情绪」——判它需要 provenance(这条记忆来自哪个会话),
|
|
4
|
+
// 而实体抽取发生在写入当下、手里没有会话边界,判不了「一次场合」与「反复出现
|
|
5
|
+
// 的特质」。让它在这里也能写,就等于给 entity_relations 开第二个只带 {model}
|
|
6
|
+
// 的写入口,同一张表两种 schema。唯一的写入者是 sleep 的 affective 相位。
|
|
7
|
+
const VALID_RELATIONS = new Set(["uses", "depends_on", "part_of", "related_to"]);
|
|
8
|
+
const MAX_MEMORY_CHARS = 4000;
|
|
9
|
+
|
|
10
|
+
function buildSystemPrompt(config) {
|
|
11
|
+
const maxEntities = config.entityExtractionMaxEntities ?? 10;
|
|
12
|
+
const maxAttrs = config.entityExtractionMaxAttrs ?? 20;
|
|
13
|
+
|
|
14
|
+
return `You are an entity extraction engine. Extract named entities, their attributes, and relations from the given text.
|
|
15
|
+
|
|
16
|
+
RULES:
|
|
17
|
+
- Entities are concrete people, projects, technologies, concepts, or organizations.
|
|
18
|
+
- Attributes are characteristic states of an entity. Only extract attributes explicitly mentioned in the text.
|
|
19
|
+
- Do NOT infer, guess, or hallucinate. Only extract what is clearly stated.
|
|
20
|
+
- Use canonical names (full name / primary name). Do NOT merge synonyms — different spellings of the same person are different entities.
|
|
21
|
+
- Output MUST be strict JSON with this exact structure:
|
|
22
|
+
{
|
|
23
|
+
"entities": [
|
|
24
|
+
{"name": "string", "type": "person|project|concept|technology|organization", "attrs": [{"key": "string", "value": "string", "confidence": 0.9}]}
|
|
25
|
+
],
|
|
26
|
+
"relations": [
|
|
27
|
+
{"from": "entityName", "to": "entityName", "type": "uses|depends_on|part_of|related_to"}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
CONSTRAINTS:
|
|
32
|
+
- "type" must be one of: person, project, concept, technology, organization. If unsure, use "concept".
|
|
33
|
+
- Relation "type" must be one of: uses, depends_on, part_of, related_to. If unsure, use related_to.
|
|
34
|
+
- Do NOT emit emotional or affective relations. An emotion belongs to one occasion and needs session provenance to judge it; that is recorded separately, not here.
|
|
35
|
+
- "relations" from/to must reference entity names from the "entities" list.
|
|
36
|
+
- Maximum ${maxEntities} entities. Maximum ${maxAttrs} attributes per entity. Truncate if exceeded.
|
|
37
|
+
- Only include entity-related attributes. Ignore irrelevant miscellaneous details.
|
|
38
|
+
- Return ONLY the JSON object. No explanations, no markdown, no code fences.`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildUserMessage(memoryText) {
|
|
42
|
+
const truncated = memoryText.length > MAX_MEMORY_CHARS
|
|
43
|
+
? memoryText.slice(0, MAX_MEMORY_CHARS) + "..."
|
|
44
|
+
: memoryText;
|
|
45
|
+
return truncated;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractJsonFromText(text) {
|
|
49
|
+
if (!text || typeof text !== "string") return null;
|
|
50
|
+
|
|
51
|
+
// Try direct parse first
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(text);
|
|
54
|
+
} catch {
|
|
55
|
+
// fall through
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Try to find first {...} block
|
|
59
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
60
|
+
if (match) {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(match[0]);
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sanitizeType(type) {
|
|
72
|
+
if (VALID_TYPES.has(type)) return type;
|
|
73
|
+
return "concept";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function sanitizeConfidence(conf) {
|
|
77
|
+
const num = Number(conf);
|
|
78
|
+
if (Number.isNaN(num)) return 0.9;
|
|
79
|
+
return Math.min(1, Math.max(0, num));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// #619:未知关系类型**不再静默降级成 related_to**。旧行为是「写入成功、不报错、
|
|
83
|
+
// 图上没有那条轴」——最难排查的一种失败:模型抽对了,库里却读不出它抽的是情感边
|
|
84
|
+
// 还是普通关联。现在返回 null,由调用方丢弃并 logger.warn 带原始类型名。
|
|
85
|
+
// 注意这与 sanitizeType(未知实体类型 → concept)刻意不同:实体类型是可容错的
|
|
86
|
+
// 粗分类,关系类型是语义轴本身,猜错一条边就是给图加错误事实。
|
|
87
|
+
function sanitizeRelationType(type) {
|
|
88
|
+
if (typeof type !== "string") return null;
|
|
89
|
+
const normalized = type.trim();
|
|
90
|
+
if (VALID_RELATIONS.has(normalized)) return normalized;
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function sanitizeExtractedData(data, config, logger = null) {
|
|
95
|
+
const maxEntities = config.entityExtractionMaxEntities ?? 10;
|
|
96
|
+
const maxAttrs = config.entityExtractionMaxAttrs ?? 20;
|
|
97
|
+
|
|
98
|
+
if (!data || !Array.isArray(data.entities)) {
|
|
99
|
+
throw new Error("Invalid extraction: missing entities array");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const entities = [];
|
|
103
|
+
const seenNames = new Set();
|
|
104
|
+
|
|
105
|
+
for (const rawEntity of data.entities.slice(0, maxEntities)) {
|
|
106
|
+
if (!rawEntity || typeof rawEntity.name !== "string" || !rawEntity.name.trim()) continue;
|
|
107
|
+
|
|
108
|
+
const name = rawEntity.name.trim();
|
|
109
|
+
if (seenNames.has(name)) continue;
|
|
110
|
+
seenNames.add(name);
|
|
111
|
+
|
|
112
|
+
const attrs = [];
|
|
113
|
+
if (Array.isArray(rawEntity.attrs)) {
|
|
114
|
+
for (const rawAttr of rawEntity.attrs.slice(0, maxAttrs)) {
|
|
115
|
+
if (!rawAttr || typeof rawAttr.key !== "string" || typeof rawAttr.value !== "string") continue;
|
|
116
|
+
attrs.push({
|
|
117
|
+
key: rawAttr.key.trim(),
|
|
118
|
+
value: rawAttr.value.trim(),
|
|
119
|
+
confidence: sanitizeConfidence(rawAttr.confidence)
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
entities.push({
|
|
125
|
+
name,
|
|
126
|
+
type: sanitizeType(rawEntity.type),
|
|
127
|
+
attrs
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const relations = [];
|
|
132
|
+
const rejectedRelationTypes = [];
|
|
133
|
+
if (Array.isArray(data.relations)) {
|
|
134
|
+
for (const rawRel of data.relations) {
|
|
135
|
+
if (!rawRel || typeof rawRel.from !== "string" || typeof rawRel.to !== "string") continue;
|
|
136
|
+
if (!seenNames.has(rawRel.from) || !seenNames.has(rawRel.to)) continue;
|
|
137
|
+
const relationType = sanitizeRelationType(rawRel.type);
|
|
138
|
+
if (!relationType) {
|
|
139
|
+
// #619:不再降级成 related_to——记下原始类型名,交给调用方 warn。
|
|
140
|
+
rejectedRelationTypes.push(String(rawRel.type));
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
relations.push({
|
|
144
|
+
from: rawRel.from,
|
|
145
|
+
to: rawRel.to,
|
|
146
|
+
type: relationType
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (rejectedRelationTypes.length > 0) {
|
|
151
|
+
logger?.warn?.(
|
|
152
|
+
`[extractor] dropped ${rejectedRelationTypes.length} relation(s) with unknown type(s): ${[...new Set(rejectedRelationTypes)].join(", ")}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { entities, relations, rejectedRelationTypes };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function resolveEntity(entity, store) {
|
|
160
|
+
const existing = store.findEntityByName(entity.name);
|
|
161
|
+
if (existing) {
|
|
162
|
+
store.updateEntity(existing.id, {});
|
|
163
|
+
return existing.id;
|
|
164
|
+
}
|
|
165
|
+
const created = store.createEntity({ name: entity.name, type: entity.type });
|
|
166
|
+
return created.id;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function extractEntities(memory, { store, config, callLLM, logger }) {
|
|
170
|
+
try {
|
|
171
|
+
if (!memory || !memory.content) {
|
|
172
|
+
return { ok: false, error: "Invalid memory: missing content" };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const systemPrompt = buildSystemPrompt(config);
|
|
176
|
+
const userText = buildUserMessage(memory.content);
|
|
177
|
+
|
|
178
|
+
const messages = [
|
|
179
|
+
{ role: "system", content: [{ type: "text", text: systemPrompt }], source: { kind: "plugin:dsh-mneme", plugin: "dsh-mneme" } },
|
|
180
|
+
{ role: "user", content: [{ type: "text", text: userText }], source: { kind: "plugin:dsh-mneme", plugin: "dsh-mneme" } }
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
// Issue #109: optional provider/model override + reasoning effort are
|
|
184
|
+
// passed through to callLLM's options; the index.js adapter maps them onto
|
|
185
|
+
// the route (or falls back to the caller's default model). Empty provider/
|
|
186
|
+
// model both mean "use the caller default".
|
|
187
|
+
const options = {};
|
|
188
|
+
if (config.entityExtractionProvider) options.provider = config.entityExtractionProvider;
|
|
189
|
+
if (config.entityExtractionModel) options.model = config.entityExtractionModel;
|
|
190
|
+
const reasoning = config.entityExtractionReasoning;
|
|
191
|
+
if (reasoning && reasoning !== "none") options.reasoningEffort = reasoning;
|
|
192
|
+
// #405: 审计行要能归因到具体记忆。extractor 只把 content 写进 prompt,id 这里顺
|
|
193
|
+
// options 交给 index.js 的 adapter(route 映射只认 provider/model,多余键不会进
|
|
194
|
+
// llm.stream)。拿不到 id 就不写这个键,adapter 侧照旧留空数组,不造虚账。
|
|
195
|
+
if (memory.id) options.memoryId = memory.id;
|
|
196
|
+
const llmResponse = await callLLM(messages, options);
|
|
197
|
+
|
|
198
|
+
if (!llmResponse) {
|
|
199
|
+
return { ok: false, error: "LLM returned empty response" };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const rawData = extractJsonFromText(llmResponse);
|
|
203
|
+
if (!rawData) {
|
|
204
|
+
return { ok: false, error: "Failed to parse JSON from LLM response" };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const { entities, relations, rejectedRelationTypes } = sanitizeExtractedData(rawData, config, logger);
|
|
208
|
+
|
|
209
|
+
const resolvedEntities = [];
|
|
210
|
+
const entityIdMap = new Map();
|
|
211
|
+
let skipCount = 0;
|
|
212
|
+
|
|
213
|
+
// Resolve entities
|
|
214
|
+
for (const entity of entities) {
|
|
215
|
+
try {
|
|
216
|
+
const entityId = await resolveEntity(entity, store);
|
|
217
|
+
entityIdMap.set(entity.name, entityId);
|
|
218
|
+
resolvedEntities.push({ ...entity, entity_id: entityId });
|
|
219
|
+
} catch (err) {
|
|
220
|
+
skipCount++;
|
|
221
|
+
logger?.warn?.(`[extractor] Failed to resolve entity "${entity.name}":`, err.message);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Record attributes
|
|
226
|
+
const attrs = [];
|
|
227
|
+
for (const entity of resolvedEntities) {
|
|
228
|
+
for (const attr of entity.attrs) {
|
|
229
|
+
try {
|
|
230
|
+
const saved = store.saveAttr({
|
|
231
|
+
entity_id: entity.entity_id,
|
|
232
|
+
attr_key: attr.key,
|
|
233
|
+
attr_value: String(attr.value),
|
|
234
|
+
memory_id: memory.id,
|
|
235
|
+
confidence: attr.confidence,
|
|
236
|
+
source: "llm_extract"
|
|
237
|
+
});
|
|
238
|
+
attrs.push(saved);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
skipCount++;
|
|
241
|
+
logger?.warn?.(`[extractor] Failed to save attr "${attr.key}" for entity "${entity.name}":`, err.message);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Record relations
|
|
247
|
+
const savedRelations = [];
|
|
248
|
+
for (const rel of relations) {
|
|
249
|
+
const fromId = entityIdMap.get(rel.from);
|
|
250
|
+
const toId = entityIdMap.get(rel.to);
|
|
251
|
+
if (!fromId || !toId) continue;
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const saved = store.saveRelation({
|
|
255
|
+
from_entity: fromId,
|
|
256
|
+
to_entity: toId,
|
|
257
|
+
relation_type: rel.type,
|
|
258
|
+
memory_id: memory.id,
|
|
259
|
+
metadata: { model: options.model || "default" },
|
|
260
|
+
// issue #24 块2:LLM 抽取自动建边 → llm 来源(初值 0.4),区别于
|
|
261
|
+
// 人工/确认的满权边。来源进 metadata 之上的独立列,演化与审计可辨。
|
|
262
|
+
source: "llm"
|
|
263
|
+
});
|
|
264
|
+
savedRelations.push(saved);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
skipCount++;
|
|
267
|
+
logger?.warn?.(`[extractor] Failed to save relation "${rel.from} -> ${rel.to}":`, err.message);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
ok: true,
|
|
273
|
+
entities: resolvedEntities,
|
|
274
|
+
attrs,
|
|
275
|
+
relations: savedRelations,
|
|
276
|
+
// #619:被丢弃的未知关系类型(含原始名字)。调用方据此判断「模型没抽」
|
|
277
|
+
// 与「抽了但类型不认识」——两者的运维动作完全不同。
|
|
278
|
+
rejectedRelationTypes,
|
|
279
|
+
skipped: skipCount
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
} catch (err) {
|
|
283
|
+
return { ok: false, error: String(err) };
|
|
284
|
+
}
|
|
285
|
+
}
|
package/src/fence.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
// dsh-mneme/src/fence.js
|
|
2
|
-
// 围栏正文的「断链」——把内容里自带的 `<<<` / `>>>` 拆散,使它无法提前闭合我们围出来的围栏。
|
|
3
|
-
//
|
|
4
|
-
// 为什么单独成模块(#493 的补充,报告者 §③):断链原先只长在 dream.js 的 fenceListBody
|
|
5
|
-
// 里,于是同一件事在三处各有一条命:dream/sleep 的清单(有断链)、蒸馏转录(没有)、
|
|
6
|
-
// 画像提取的条目(也没有)。两处一旦分叉,下次出问题就得分别查一遍——#494 与 #501/PR
|
|
7
|
-
// #503 的重复劳动就是这么来的。判定逻辑只能有一份实现,这条纪律在本仓已经付过学费
|
|
8
|
-
// (scopeStateGate 注释里「过滤点漂移」那条)。
|
|
9
|
-
//
|
|
10
|
-
// 三个刻意的性质:
|
|
11
|
-
// - **只中和标记,不删内容**:被断链的文本仍逐字可读(零宽空格不占位),只是不再是
|
|
12
|
-
// 一个能匹配围栏串的连续字符序列。吞掉正文会让「这段记忆说了什么」变成不可复原的。
|
|
13
|
-
// - **纯函数、不带 nonce**:重试收到同输入时提示词必须逐字节相同,否则「同输入同错」
|
|
14
|
-
// 这条诊断依据就没了(#493 的重试 20 次现场全靠它)。同一份文本调两次结果相同。
|
|
15
|
-
// - **不认围栏名字**:三对围栏(转录 / 记忆清单 / 画像条目)都以 `<<<` 开头、`>>>` 收尾,
|
|
16
|
-
// 中和这两个前缀/后缀就够了;认名字反而会在新增围栏时漏一处。
|
|
17
|
-
/**
|
|
18
|
-
* 断链:`<<<` / `>>>` 中间插一个零宽空格。
|
|
19
|
-
* @param {string} text 任意正文
|
|
20
|
-
* @returns {string} 断链后的正文(未触及的文本逐字节不变)
|
|
21
|
-
*/
|
|
22
|
-
export function breakFenceMarkers(text) {
|
|
23
|
-
return String(text ?? "").replace(/<<<|>>>/g, (run) => `${run[0]}\u200B${run.slice(1)}`);
|
|
24
|
-
}
|
|
1
|
+
// dsh-mneme/src/fence.js
|
|
2
|
+
// 围栏正文的「断链」——把内容里自带的 `<<<` / `>>>` 拆散,使它无法提前闭合我们围出来的围栏。
|
|
3
|
+
//
|
|
4
|
+
// 为什么单独成模块(#493 的补充,报告者 §③):断链原先只长在 dream.js 的 fenceListBody
|
|
5
|
+
// 里,于是同一件事在三处各有一条命:dream/sleep 的清单(有断链)、蒸馏转录(没有)、
|
|
6
|
+
// 画像提取的条目(也没有)。两处一旦分叉,下次出问题就得分别查一遍——#494 与 #501/PR
|
|
7
|
+
// #503 的重复劳动就是这么来的。判定逻辑只能有一份实现,这条纪律在本仓已经付过学费
|
|
8
|
+
// (scopeStateGate 注释里「过滤点漂移」那条)。
|
|
9
|
+
//
|
|
10
|
+
// 三个刻意的性质:
|
|
11
|
+
// - **只中和标记,不删内容**:被断链的文本仍逐字可读(零宽空格不占位),只是不再是
|
|
12
|
+
// 一个能匹配围栏串的连续字符序列。吞掉正文会让「这段记忆说了什么」变成不可复原的。
|
|
13
|
+
// - **纯函数、不带 nonce**:重试收到同输入时提示词必须逐字节相同,否则「同输入同错」
|
|
14
|
+
// 这条诊断依据就没了(#493 的重试 20 次现场全靠它)。同一份文本调两次结果相同。
|
|
15
|
+
// - **不认围栏名字**:三对围栏(转录 / 记忆清单 / 画像条目)都以 `<<<` 开头、`>>>` 收尾,
|
|
16
|
+
// 中和这两个前缀/后缀就够了;认名字反而会在新增围栏时漏一处。
|
|
17
|
+
/**
|
|
18
|
+
* 断链:`<<<` / `>>>` 中间插一个零宽空格。
|
|
19
|
+
* @param {string} text 任意正文
|
|
20
|
+
* @returns {string} 断链后的正文(未触及的文本逐字节不变)
|
|
21
|
+
*/
|
|
22
|
+
export function breakFenceMarkers(text) {
|
|
23
|
+
return String(text ?? "").replace(/<<<|>>>/g, (run) => `${run[0]}\u200B${run.slice(1)}`);
|
|
24
|
+
}
|