@modusensus/dsh-mneme 0.14.0-beta.1 → 0.14.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +830 -830
- package/assets/icon.svg +3 -3
- package/bin/cli.mjs +964 -964
- package/bin/dsh-mneme-choro.mjs +262 -262
- 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 -404
- 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 -3600
- 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 -14322
- package/lib/commands.js +95 -95
- package/lib/config-keys.js +144 -144
- package/lib/config.js +1361 -1344
- 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 -945
- package/lib/dream/narratives.js +44 -44
- package/lib/dream/sleep.js +2027 -2027
- package/lib/dream/triage.js +139 -139
- package/lib/dream.js +1879 -1879
- 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 -119
- package/lib/image-describe.js +158 -0
- package/lib/index.js +852 -852
- package/lib/inject.js +656 -651
- 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 -595
- 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 -3268
- package/lib/session-gate.js +91 -91
- package/lib/session-usage.js +350 -350
- package/lib/settings.js +827 -817
- 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 -5895
- 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 -267
- 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 -640
- package/lib/wiki/render.js +261 -261
- package/lib/wiki/scan.js +371 -371
- package/lib/wiki/t0.js +500 -500
- package/lib/wiki/t1-jobs.js +232 -232
- package/lib/wiki/t1.js +609 -609
- package/lib/write-admission.js +404 -404
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +1 -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 -210
- package/scripts/release-notes.mjs +164 -164
- 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 -3600
- 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 -1344
- 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 -945
- package/src/dream/narratives.js +44 -44
- package/src/dream/sleep.js +2027 -2027
- package/src/dream/triage.js +139 -139
- package/src/dream.js +1879 -1879
- 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 -119
- package/src/image-describe.js +158 -0
- package/src/index.js +852 -852
- package/src/inject.js +656 -651
- 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 -595
- 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 -3268
- package/src/session-gate.js +91 -91
- package/src/session-usage.js +350 -350
- package/src/settings.js +827 -817
- 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 -5895
- 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 -267
- 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 -640
- package/src/wiki/render.js +261 -261
- package/src/wiki/scan.js +371 -371
- package/src/wiki/t0.js +500 -500
- package/src/wiki/t1-jobs.js +232 -232
- package/src/wiki/t1.js +609 -609
- 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 -2998
- 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 -4425
- 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 -452
- package/test/conflict-ledger.test.js +242 -242
- 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 -432
- 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 -174
- 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 -333
- 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 -259
- 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 -109
- 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 -553
- 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 -90
- package/test/panel-tokens.test.js +245 -245
- 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 -156
- package/test/release-notes.test.js +265 -265
- 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 -188
- 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 -1455
- 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 -734
- package/test/wiki-claims.test.js +194 -194
- 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 -202
- package/test/wiki-queries.test.js +398 -398
- package/test/wiki-scan.test.js +208 -208
- package/test/wiki-store.test.js +190 -190
- package/test/wiki-t0.test.js +456 -456
- package/test/wiki-t1-jobs.test.js +125 -125
- package/test/wiki-t1.test.js +460 -460
- 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/lib/settings.js
CHANGED
|
@@ -1,817 +1,827 @@
|
|
|
1
|
-
// User-configurable settings: profile (user self-description), rules (behavior
|
|
2
|
-
// rules the agent must follow), and custom slash commands. Stored in the same
|
|
3
|
-
// SQLite database via dedicated tables, isolated from the memories store.
|
|
4
|
-
import { randomUUID } from "node:crypto";
|
|
5
|
-
|
|
6
|
-
const SCHEMA = `
|
|
7
|
-
CREATE TABLE IF NOT EXISTS user_settings (
|
|
8
|
-
key TEXT PRIMARY KEY,
|
|
9
|
-
value TEXT NOT NULL
|
|
10
|
-
);
|
|
11
|
-
CREATE TABLE IF NOT EXISTS custom_commands (
|
|
12
|
-
id TEXT PRIMARY KEY,
|
|
13
|
-
name TEXT NOT NULL UNIQUE,
|
|
14
|
-
description TEXT NOT NULL DEFAULT '',
|
|
15
|
-
instruction TEXT NOT NULL,
|
|
16
|
-
created_at TEXT NOT NULL,
|
|
17
|
-
updated_at TEXT NOT NULL
|
|
18
|
-
);
|
|
19
|
-
`;
|
|
20
|
-
|
|
21
|
-
// DSH command names must match this (lowercase, start with a letter).
|
|
22
|
-
const COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
|
|
23
|
-
|
|
24
|
-
/** Parse a JSON array out of a stored string, tolerant of corruption. */
|
|
25
|
-
function parseList(raw) {
|
|
26
|
-
try {
|
|
27
|
-
const value = JSON.parse(raw);
|
|
28
|
-
return Array.isArray(value) ? value : [];
|
|
29
|
-
} catch {
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// persona 对象的字段白名单:写入时只挑这些键,未知字段丢弃(设计稿①:字段从简,
|
|
35
|
-
// beta 收到真实反馈再加;provenance 是 v1.5 协作式人格的预留位,v1 不写管道)。
|
|
36
|
-
// 读侧损坏即回空对象——同一容错口径照 parseList(坏值不抛错、面板显示为空可重写)。
|
|
37
|
-
// scenario / mesExample(#606):对标 Character Card V2 的同名字段,是「人格卡能装
|
|
38
|
-
// 下完整角色」所需的最小补充。两者都可选、都不写时渲染零输出(老用户逐字节不变)。
|
|
39
|
-
const PERSONA_FIELDS = ["name", "personality", "replyStyle", "scenario", "mesExample", "enabled", "provenance"];
|
|
40
|
-
|
|
41
|
-
function parsePersona(raw) {
|
|
42
|
-
try {
|
|
43
|
-
const value = JSON.parse(raw);
|
|
44
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
|
|
45
|
-
const out = {};
|
|
46
|
-
for (const key of PERSONA_FIELDS) {
|
|
47
|
-
if (value[key] !== undefined) out[key] = value[key];
|
|
48
|
-
}
|
|
49
|
-
return out;
|
|
50
|
-
} catch {
|
|
51
|
-
return {};
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// 多卡模型(v1.2):存储形为 { default: <卡>, byAgent: { <agentId>: <卡>, … } }。
|
|
56
|
-
// 兼容层:v1 存的是单对象(有 PERSONA_FIELDS 键、无 default/byAgent 键),读侧
|
|
57
|
-
// 判形状——单对象原样落 default 位,旧数据零迁移。byAgent 的键是 agent 身份
|
|
58
|
-
// id(注入时按会话身份寻址,取不到落 default)。default 卡不可删(兜底位)。
|
|
59
|
-
//
|
|
60
|
-
// 绑会话(v1.3,#606):加第三维 bySession: { <sessionId>: <卡> }。为什么需要它——
|
|
61
|
-
// byAgent 的键来自 session.header.agentPreset,而 DS Bot 这类宿主把它写成常量
|
|
62
|
-
// (所有 bot 会话都是 "standard",实测 43/50),按 bot 名字建卡永远不命中。会话 id
|
|
63
|
-
// 才是那个「一个 bot 一张卡」真正能分开的键。bySession 同样是纯加法:没有这个键
|
|
64
|
-
// 的老数据读成 {},注入文本逐字节不变。
|
|
65
|
-
function parsePersonaStore(raw) {
|
|
66
|
-
try {
|
|
67
|
-
const value = JSON.parse(raw);
|
|
68
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
69
|
-
return { default: {}, byAgent: {}, bySession: {} };
|
|
70
|
-
}
|
|
71
|
-
// 新形:含 default / byAgent / bySession 任一键 → 按多卡解析
|
|
72
|
-
if ("default" in value || "byAgent" in value || "bySession" in value) {
|
|
73
|
-
const byAgent = {};
|
|
74
|
-
if (value.byAgent && typeof value.byAgent === "object" && !Array.isArray(value.byAgent)) {
|
|
75
|
-
for (const [agentId, card] of Object.entries(value.byAgent)) {
|
|
76
|
-
const parsed = parsePersona(JSON.stringify(card ?? {}));
|
|
77
|
-
if (Object.keys(parsed).length) byAgent[String(agentId)] = parsed;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const bySession = {};
|
|
81
|
-
if (value.bySession && typeof value.bySession === "object" && !Array.isArray(value.bySession)) {
|
|
82
|
-
for (const [sessionId, card] of Object.entries(value.bySession)) {
|
|
83
|
-
const parsed = parsePersona(JSON.stringify(card ?? {}));
|
|
84
|
-
if (Object.keys(parsed).length) bySession[String(sessionId)] = parsed;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return { default: parsePersona(JSON.stringify(value.default ?? {})), byAgent, bySession };
|
|
88
|
-
}
|
|
89
|
-
// 旧形:v1 单对象 → 整体作 default(字段白名单在 parsePersona 内裁)
|
|
90
|
-
return { default: parsePersona(raw), byAgent: {}, bySession: {} };
|
|
91
|
-
} catch {
|
|
92
|
-
return { default: {}, byAgent: {}, bySession: {} };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function serializePersonaStore(store) {
|
|
97
|
-
const out = {
|
|
98
|
-
default: parsePersona(JSON.stringify(store?.default ?? {})),
|
|
99
|
-
byAgent: {},
|
|
100
|
-
bySession: {}
|
|
101
|
-
};
|
|
102
|
-
if (store?.byAgent && typeof store.byAgent === "object") {
|
|
103
|
-
for (const [agentId, card] of Object.entries(store.byAgent)) {
|
|
104
|
-
const parsed = parsePersona(JSON.stringify(card ?? {}));
|
|
105
|
-
if (Object.keys(parsed).length) out.byAgent[String(agentId)] = parsed;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (store?.bySession && typeof store.bySession === "object") {
|
|
109
|
-
for (const [sessionId, card] of Object.entries(store.bySession)) {
|
|
110
|
-
const parsed = parsePersona(JSON.stringify(card ?? {}));
|
|
111
|
-
if (Object.keys(parsed).length) out.bySession[String(sessionId)] = parsed;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return JSON.stringify(out);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// --- feature flags(功能开关)白名单 -----------------------------------------
|
|
118
|
-
// 面板可逐项开关的后端能力。设计约束:
|
|
119
|
-
// 1. 键名与类型必须和 config.js schema 同名同型,这份白名单是唯一校验源
|
|
120
|
-
// (api.js 复用它计算 effective),schema 增删能力键时要同步改这里。
|
|
121
|
-
// 2. 持久化(kv "feature_flags")只落白名单键;读到未知键、类型损坏或越界的
|
|
122
|
-
// 值一律丢弃而不是报错——kv 会残留旧版本写入的键,读路径必须向前兼容。
|
|
123
|
-
// 3. 写入是逐键校验的合并写:未知键/类型/范围不符抛 TypeError(消息含键名,
|
|
124
|
-
// 供 API 透传给前端定位),校验不通过不落库,坏值永远进不了 kv。
|
|
125
|
-
const FEATURE_FLAG_BOOLEANS = [
|
|
126
|
-
"autoInject",
|
|
127
|
-
"autoSummarize",
|
|
128
|
-
"hotMemoryEnabled",
|
|
129
|
-
// Issue #34(v0.7.2 引入;v0.7.11 误删后随 #333 恢复):对话开始注入当前时间。
|
|
130
|
-
// 顶层扁平键,随白名单回面板(FEATURE_GROUPS 的 group.core)。
|
|
131
|
-
"injectTimePrefix",
|
|
132
|
-
// Issue #239 第 5 项:注入条数的查询自适应(确定性强则收缩注入条数,默认关)。
|
|
133
|
-
"injectUncertaintyAdaptive",
|
|
134
|
-
// Issue #249:能力说明(工具描述判断指引 + order 150 总则段)。第二批起它是
|
|
135
|
-
// 注入父开关 autoInject 的子项、默认开(父关时不生效,闸门见 config.js)。
|
|
136
|
-
"injectGuidanceEnabled",
|
|
137
|
-
// Issue #249 N3:压缩边缘双落点——上下文即将精简前落一条连续性提案 + 往
|
|
138
|
-
// 序列末尾追加同一份快照。默认关(新注入表面,见 config.js),父开关 autoInject。
|
|
139
|
-
"continuityRescueEnabled",
|
|
140
|
-
"entityExtractionEnabled",
|
|
141
|
-
// Issue #219:图召回轴——查询命中实体名时把挂联记忆并入检索融合池
|
|
142
|
-
// (默认关;依赖实体抽取产出,lightMode 强制关闭)。
|
|
143
|
-
"entityRecallEnabled",
|
|
144
|
-
// Issue #24 块1:图谱锚定层级联——命中实体作种子、沿关系表向邻居扩散
|
|
145
|
-
// (默认关;依赖实体抽取产出,lightMode 强制关闭)。
|
|
146
|
-
"graphAnchoringEnabled",
|
|
147
|
-
// Issue #24 块2:边权重演化总闸——关着时任何触达都不改边权(默认关;
|
|
148
|
-
// 依赖实体抽取产出,lightMode 强制关闭)。块4 的被动确认是它底下的一个通道。
|
|
149
|
-
"graphWeightEnabled",
|
|
150
|
-
// Issue #24 块3:关联提示防幻觉——图召回线索行以「[检索线索]」标注进注入
|
|
151
|
-
// (默认关=线索只参与排序不进块;依赖实体抽取产出,lightMode 强制关闭)。
|
|
152
|
-
"graphInjectHint",
|
|
153
|
-
// Issue #24 块4:被动确认通道——正常触达即对该记忆挂联的边 bump 一格
|
|
154
|
-
// (默认关;需总闸 graphWeightEnabled 同开)。
|
|
155
|
-
"graphPassiveConfirm",
|
|
156
|
-
// Issue #164:叙述条——dream 期间按 tag 主题簇合成叙述落库(source=
|
|
157
|
-
// narrative,evidence 回链簇内记忆;按需检索不常驻注入;默认关)。
|
|
158
|
-
"dreamNarrativeEnabled",
|
|
159
|
-
// Issue #230:document 型记忆——agent 产长文档的指针行(注册校验/摘要+
|
|
160
|
-
// doc_path 落库/C2 去重/supersede 记账;全文归 agent;默认关,lightMode 强制关)。
|
|
161
|
-
"documentMemoryEnabled",
|
|
162
|
-
// 日记本:跨天惰性判定 + 启动补写,正文落 <memoryDir>/diaries/<项目键>/。
|
|
163
|
-
// 默认关,进 LIGHT_MODE_OFF(要调模型,与低资源档互斥)。与 documentMemoryEnabled
|
|
164
|
-
// 成对但不相依:关掉文档记忆只影响检索,正文照样写。
|
|
165
|
-
"diaryEnabled",
|
|
166
|
-
"codingRetrospect",
|
|
167
|
-
"autoDream",
|
|
168
|
-
// Issue #292:autoDream 连续失败退避(默认关)。基数是 dreamMinIntervalMinutes,
|
|
169
|
-
// 有效间隔 = 基数 × 2^连续失败数(成功清零),封顶 30 分钟;面板可启停。
|
|
170
|
-
"autoDreamFailureBackoff",
|
|
171
|
-
// Issue #557:dream 审计行输入快照瘦身(默认关)。开启后 auto 轮只落骨架
|
|
172
|
-
// (不含 content),organize 轮的重放载荷不受影响;面板可启停。
|
|
173
|
-
"dreamInputSummaryEnabled",
|
|
174
|
-
"sleepModeEnabled",
|
|
175
|
-
// Issue #529:sleep 连续失败退避(默认关)。基数是 sleepMinIntervalHours,
|
|
176
|
-
// 有效间隔 = 基数 × 2^连续失败数(成功清零),封顶 max(基数, 24h);面板可启停。
|
|
177
|
-
"sleepFailureBackoff",
|
|
178
|
-
// Issue #619:情感归因相位(默认关)。把「某人某场合的情绪」写成 occasion-bound
|
|
179
|
-
// 的 affective_context 关系,只有跨够 sleepAffectiveTraitMinSessions 个独立会话
|
|
180
|
-
// 才允许升成稳定特质。要调模型且依赖实体抽取,所以必须显式打开、可随时关掉;
|
|
181
|
-
// 它是 sleepModeEnabled 的子相:父开关关着时整个 sleep 不跑。
|
|
182
|
-
"sleepAffectiveEnabled",
|
|
183
|
-
"heatEnabled",
|
|
184
|
-
// issue #218 / E5:注入侧 heat 独立开关(默认关,老用户 injectHeatEnabled=true 回滚)。
|
|
185
|
-
"injectHeatEnabled",
|
|
186
|
-
"hybridInject",
|
|
187
|
-
"selectiveInjectEnabled",
|
|
188
|
-
"searchSemanticDedup",
|
|
189
|
-
"rerankEnabled",
|
|
190
|
-
"resilientModelDownload",
|
|
191
|
-
"adaptiveThresholdEnabled",
|
|
192
|
-
"reflectionUpdateEnabled",
|
|
193
|
-
"reflectionFailureTracking",
|
|
194
|
-
"bm25SearchEnabled",
|
|
195
|
-
"conflictFreezeEnabled",
|
|
196
|
-
// Issue #603 第 4 层:自动裁决档(默认关)。相似度极高的同 scope 重复对直接
|
|
197
|
-
// 保留新条、归档旧条,不进人工队列;模糊地带照旧留给人。会归档记忆,所以必须
|
|
198
|
-
// 显式打开,面板可启停。阈值见 FEATURE_FLAG_NUMBER_RANGES 的
|
|
199
|
-
// conflictAutoResolveMinSimilarity。
|
|
200
|
-
"conflictAutoResolveEnabled",
|
|
201
|
-
// Issue #641(beta 第一阶段):conflictLedgerMode / conflictLedgerDupMinSimilarity
|
|
202
|
-
// 不在本表——前者是枚举键(FEATURE_FLAG_ENUMS),后者是数值键
|
|
203
|
-
// (FEATURE_FLAG_NUMBER_RANGES),flagKeysFromSpec 会把三张表合成一份白名单,
|
|
204
|
-
// 在这里重复登记会让同键被收两次(config-keys 的覆盖完整性锁抓过)。
|
|
205
|
-
"trustEpistemicWeighting",
|
|
206
|
-
// Plan #2: attach per-source {keyword, vector, bm25, final} signals to each
|
|
207
|
-
// search result for transparency/debugging. Default off, purely decorative.
|
|
208
|
-
"signalTransparency",
|
|
209
|
-
// Issue #89:宽容校验回归(默认开)+ 跨类型合并显式放宽(默认关)。
|
|
210
|
-
"dreamSkipInvalid",
|
|
211
|
-
"allowCrossTypeMerge",
|
|
212
|
-
// Issue #339 / E8:guarded 类型 merge 护栏(默认开)——合并对象命中
|
|
213
|
-
// 长保留类型的 merge 决策整条跳过。关掉 = guarded 类型的 merge 不再被跳过,
|
|
214
|
-
// 「更精炼的摘要」重新成为约束失真的主通道(面板可关,供需要旧行为的库回退)。
|
|
215
|
-
"dreamMergeGuard",
|
|
216
|
-
// Issue #17(v0.8.0 A1):scope 隔离存储层总开关。开启后写入标注
|
|
217
|
-
// agent_scope/workspace_scope、去重键扩展(含 sensitivity);检索侧过滤在
|
|
218
|
-
// A2/A3。默认关=行为与 A1 前逐字节一致。
|
|
219
|
-
"scopeEnabled",
|
|
220
|
-
// Issue #17(v0.8.0 A3):strictScope 硬过滤——他 scope 完全不可见(关闭时
|
|
221
|
-
// 为 A2 软隔离:降权保留可见)。
|
|
222
|
-
"strictScope",
|
|
223
|
-
// Issue #471:政策轴——作用域级可逆闸门(scopeState)。与上面两个键是两条不同的
|
|
224
|
-
// 轴:那两个管「这条记忆属于谁」(事实轴),这两个管「这个作用域现在通不通」
|
|
225
|
-
// (政策轴,按作用域整体按下、跨会话存续、松开即恢复)。两级语义同
|
|
226
|
-
// writeAdmission/preInjectGate——enabled 跑判定(观察档,留审计行但照常注入)、
|
|
227
|
-
// enforce 真拦(不注入);都默认关。点号键平铺存、合并时展开回 scopeState 对象。
|
|
228
|
-
"scopeState.enabled",
|
|
229
|
-
"scopeState.enforce",
|
|
230
|
-
// 工具暴露开关(v0.8.5):记忆已每轮自动注入,memory_search/memory_archive
|
|
231
|
-
// 在慢/轻量模型上是多余往返,面板可关(默认关=行为不变)。
|
|
232
|
-
"disableMemorySearch",
|
|
233
|
-
"disableMemoryArchive",
|
|
234
|
-
// Issue #231 第二批:agent 主动整理(memory_organize 工具)的暴露闸。与上面两个
|
|
235
|
-
// 反向——那两个藏默认在场的工具,本键开默认不在场的能力(默认关=工具根本不存在,
|
|
236
|
-
// 行为逐字节不变)。面板可启停=线上回滚开关。
|
|
237
|
-
"organizeEnabled",
|
|
238
|
-
// chorography(仓库地志)的暴露闸:开默认不在场的三个只读工具 + 面板浏览页
|
|
239
|
-
// (默认关=模型工具列表里根本没有它们,行为逐字节不变)。同 organizeEnabled
|
|
240
|
-
// 的反向语义——见 config.js 的键注释。
|
|
241
|
-
"chorographyEnabled",
|
|
242
|
-
// 嵌套对象开关:config.js 里是 memoryQualityFilter / llmAudit 对象的 enabled
|
|
243
|
-
// 子字段。kv 按点号键平铺存("memoryQualityFilter.enabled": false),index.js
|
|
244
|
-
// 合并时展开回嵌套对象,api.js 的 effective 从对象子字段取值。
|
|
245
|
-
"memoryQualityFilter.enabled",
|
|
246
|
-
"llmAudit.enabled",
|
|
247
|
-
// Issue #254:写入准入(第 1 级确定性拒绝)。两个键分层——enabled 跑判据、
|
|
248
|
-
// enforce 真拦;都默认关。同上,点号键平铺存、合并时展开回
|
|
249
|
-
// writeAdmission 对象。
|
|
250
|
-
"writeAdmission.enabled",
|
|
251
|
-
"writeAdmission.enforce",
|
|
252
|
-
// Issue #380:注入前判定(preInjectGate)。两级语义同 writeAdmission——
|
|
253
|
-
// enabled 跑判定(观察档)、enforce 缓存命中帧真滤除;都默认关。点号键平铺存、
|
|
254
|
-
// 合并时展开回 preInjectGate 对象。
|
|
255
|
-
"preInjectGate.enabled",
|
|
256
|
-
"preInjectGate.enforce",
|
|
257
|
-
// Issue #164 A2:写入边界的密钥 / PII 判据(src/sensitive-scan.js)。与上面两个
|
|
258
|
-
// 键分层——本键决定「这类判据参不参与」(默认关),命中之后是仅告警还是真拦仍由
|
|
259
|
-
// writeAdmission.enforce 决定(#164 口径:默认仅告警、拦截 opt-in)。
|
|
260
|
-
"sensitiveScanEnabled",
|
|
261
|
-
// Issue #399:蒸馏原文留存 + 记忆级溯源。enabled 决定蒸馏时是否把公开正文原样
|
|
262
|
-
// 落盘(distill_records)并给记忆挂窗口链接(memory_provenance);默认关。
|
|
263
|
-
// 点号键平铺存、合并时展开回 distillRecords 对象(同 llmAudit.enabled)。
|
|
264
|
-
// retentionDays 是 int,走下面的 INT_RANGES 表,不进开关白名单。
|
|
265
|
-
"distillRecords.enabled",
|
|
266
|
-
// v0.9.0:M.O.(Modus Operandi,beta)客户端视图开关。客户端读 /features
|
|
267
|
-
// 的 effective
|
|
268
|
-
// 决定是否注册座位(会话头图标 / 浮层 / 右栏 / 对话行卡片),服务端只负责把
|
|
269
|
-
// 这个键稳定暴露出来——键若不在 effective 里,客户端静默少键、一个座位都不注册。
|
|
270
|
-
// 默认关(beta、opt-in);顶层扁平键,因此不需要 NESTED_FLAG_PATHS 条目。
|
|
271
|
-
"actionPipelineEnabled",
|
|
272
|
-
// v0.9.0 第二批:M.O. 执行记账(`/mo-run` 开窗 → 窗口内命令被记成一条 run)。
|
|
273
|
-
// 与上面那个键分开:那个管「看图的座位」,这个管「往账本里写」。默认关(beta、
|
|
274
|
-
// opt-in),顶层扁平键,不需要 NESTED_FLAG_PATHS 条目。
|
|
275
|
-
"actionRunEnabled",
|
|
276
|
-
// v0.9.0 第三批:M.O. 自动沉淀(会话跑完一轮 → 挖这个项目自己的会话日志 → 落提议)。
|
|
277
|
-
// 与前两个分开:这个管「从日志里长出候选」,那个管记账、另一个管看图。默认关
|
|
278
|
-
// (beta、opt-in);顶层扁平键,不需要 NESTED_FLAG_PATHS 条目。进 LIGHT_MODE_OFF。
|
|
279
|
-
"actionNudgeEnabled",
|
|
280
|
-
// 人格定义页(beta):注入器渲染人格段的顶层总闸(双闸门的 config 半边;对象内
|
|
281
|
-
// 的 persona.enabled 是 per-agent 半边,存 user_settings 的 "persona" 键,见
|
|
282
|
-
// getPersona/setPersona)。默认关(beta、opt-in,老用户行为逐字节不变);不进
|
|
283
|
-
// LIGHT_MODE_OFF——人格段是用户自写的一小段轻量常驻文本,不引入任何重型后台
|
|
284
|
-
// 路径,轻量档用户显式勾选仍然赢(同 actionPipelineEnabled 的定性)。顶层扁平
|
|
285
|
-
// 键,不需要 NESTED_FLAG_PATHS 条目。
|
|
286
|
-
"personaEnabled",
|
|
287
|
-
// 人格寻址口径(上一条打开时才有意义):开 = 身份没建卡/卡被关时不注入人格,
|
|
288
|
-
// default 只兜解析不到身份的会话。默认关(历史行为,升级零变化)。
|
|
289
|
-
"personaStrictAddressing",
|
|
290
|
-
// Issue #497:画像提取草稿(POST /profile/extract)。默认关——每次调用一次 LLM 且
|
|
291
|
-
// 读上百行记忆;产物只回草稿,绝不写 user_settings。进 LIGHT_MODE_OFF(预设关)。
|
|
292
|
-
"profileExtractEnabled",
|
|
293
|
-
// 「Agent 眼中的你」自动事实(2026-11-06):dream 后逐卡维护 + 手动轮 + 注入段,
|
|
294
|
-
// 事实住 profile_facts 独立三表,绝不写 user_settings(#497 口径不变)。默认关,
|
|
295
|
-
// 进 LIGHT_MODE_OFF(自动维护轮与低资源档互斥)。顶层扁平键,与 config.js schema
|
|
296
|
-
// 成对出现(计数锁 test/api.test.js 钉住)。
|
|
297
|
-
"profileFactsEnabled",
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
"
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// Issue #
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
//
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
// Issue #
|
|
350
|
-
|
|
351
|
-
// Issue #
|
|
352
|
-
|
|
353
|
-
// Issue #
|
|
354
|
-
|
|
355
|
-
// Issue #
|
|
356
|
-
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
"
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// Issue #
|
|
389
|
-
|
|
390
|
-
// Issue #
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
"
|
|
409
|
-
"
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
"
|
|
413
|
-
"
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
"
|
|
417
|
-
"
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
"
|
|
421
|
-
|
|
422
|
-
"
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
//
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
function
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
return
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
if (
|
|
506
|
-
throw new TypeError(`feature flag "${key}" must be
|
|
507
|
-
}
|
|
508
|
-
return
|
|
509
|
-
}
|
|
510
|
-
if (
|
|
511
|
-
if (typeof value !== "string") {
|
|
512
|
-
throw new TypeError(`feature flag "${key}" must be a string`);
|
|
513
|
-
}
|
|
514
|
-
const trimmed = value.trim();
|
|
515
|
-
if (trimmed
|
|
516
|
-
throw new TypeError(`feature flag "${key}" must be
|
|
517
|
-
}
|
|
518
|
-
return trimmed; // 空串合法 = 跟随默认
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
for (const key of
|
|
551
|
-
if (typeof raw[key] === "
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
));
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
return
|
|
655
|
-
},
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
return
|
|
687
|
-
},
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
},
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
*
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1
|
+
// User-configurable settings: profile (user self-description), rules (behavior
|
|
2
|
+
// rules the agent must follow), and custom slash commands. Stored in the same
|
|
3
|
+
// SQLite database via dedicated tables, isolated from the memories store.
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
|
|
6
|
+
const SCHEMA = `
|
|
7
|
+
CREATE TABLE IF NOT EXISTS user_settings (
|
|
8
|
+
key TEXT PRIMARY KEY,
|
|
9
|
+
value TEXT NOT NULL
|
|
10
|
+
);
|
|
11
|
+
CREATE TABLE IF NOT EXISTS custom_commands (
|
|
12
|
+
id TEXT PRIMARY KEY,
|
|
13
|
+
name TEXT NOT NULL UNIQUE,
|
|
14
|
+
description TEXT NOT NULL DEFAULT '',
|
|
15
|
+
instruction TEXT NOT NULL,
|
|
16
|
+
created_at TEXT NOT NULL,
|
|
17
|
+
updated_at TEXT NOT NULL
|
|
18
|
+
);
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
// DSH command names must match this (lowercase, start with a letter).
|
|
22
|
+
const COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
|
|
23
|
+
|
|
24
|
+
/** Parse a JSON array out of a stored string, tolerant of corruption. */
|
|
25
|
+
function parseList(raw) {
|
|
26
|
+
try {
|
|
27
|
+
const value = JSON.parse(raw);
|
|
28
|
+
return Array.isArray(value) ? value : [];
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// persona 对象的字段白名单:写入时只挑这些键,未知字段丢弃(设计稿①:字段从简,
|
|
35
|
+
// beta 收到真实反馈再加;provenance 是 v1.5 协作式人格的预留位,v1 不写管道)。
|
|
36
|
+
// 读侧损坏即回空对象——同一容错口径照 parseList(坏值不抛错、面板显示为空可重写)。
|
|
37
|
+
// scenario / mesExample(#606):对标 Character Card V2 的同名字段,是「人格卡能装
|
|
38
|
+
// 下完整角色」所需的最小补充。两者都可选、都不写时渲染零输出(老用户逐字节不变)。
|
|
39
|
+
const PERSONA_FIELDS = ["name", "personality", "replyStyle", "scenario", "mesExample", "enabled", "provenance"];
|
|
40
|
+
|
|
41
|
+
function parsePersona(raw) {
|
|
42
|
+
try {
|
|
43
|
+
const value = JSON.parse(raw);
|
|
44
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const key of PERSONA_FIELDS) {
|
|
47
|
+
if (value[key] !== undefined) out[key] = value[key];
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
} catch {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 多卡模型(v1.2):存储形为 { default: <卡>, byAgent: { <agentId>: <卡>, … } }。
|
|
56
|
+
// 兼容层:v1 存的是单对象(有 PERSONA_FIELDS 键、无 default/byAgent 键),读侧
|
|
57
|
+
// 判形状——单对象原样落 default 位,旧数据零迁移。byAgent 的键是 agent 身份
|
|
58
|
+
// id(注入时按会话身份寻址,取不到落 default)。default 卡不可删(兜底位)。
|
|
59
|
+
//
|
|
60
|
+
// 绑会话(v1.3,#606):加第三维 bySession: { <sessionId>: <卡> }。为什么需要它——
|
|
61
|
+
// byAgent 的键来自 session.header.agentPreset,而 DS Bot 这类宿主把它写成常量
|
|
62
|
+
// (所有 bot 会话都是 "standard",实测 43/50),按 bot 名字建卡永远不命中。会话 id
|
|
63
|
+
// 才是那个「一个 bot 一张卡」真正能分开的键。bySession 同样是纯加法:没有这个键
|
|
64
|
+
// 的老数据读成 {},注入文本逐字节不变。
|
|
65
|
+
function parsePersonaStore(raw) {
|
|
66
|
+
try {
|
|
67
|
+
const value = JSON.parse(raw);
|
|
68
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
69
|
+
return { default: {}, byAgent: {}, bySession: {} };
|
|
70
|
+
}
|
|
71
|
+
// 新形:含 default / byAgent / bySession 任一键 → 按多卡解析
|
|
72
|
+
if ("default" in value || "byAgent" in value || "bySession" in value) {
|
|
73
|
+
const byAgent = {};
|
|
74
|
+
if (value.byAgent && typeof value.byAgent === "object" && !Array.isArray(value.byAgent)) {
|
|
75
|
+
for (const [agentId, card] of Object.entries(value.byAgent)) {
|
|
76
|
+
const parsed = parsePersona(JSON.stringify(card ?? {}));
|
|
77
|
+
if (Object.keys(parsed).length) byAgent[String(agentId)] = parsed;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const bySession = {};
|
|
81
|
+
if (value.bySession && typeof value.bySession === "object" && !Array.isArray(value.bySession)) {
|
|
82
|
+
for (const [sessionId, card] of Object.entries(value.bySession)) {
|
|
83
|
+
const parsed = parsePersona(JSON.stringify(card ?? {}));
|
|
84
|
+
if (Object.keys(parsed).length) bySession[String(sessionId)] = parsed;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { default: parsePersona(JSON.stringify(value.default ?? {})), byAgent, bySession };
|
|
88
|
+
}
|
|
89
|
+
// 旧形:v1 单对象 → 整体作 default(字段白名单在 parsePersona 内裁)
|
|
90
|
+
return { default: parsePersona(raw), byAgent: {}, bySession: {} };
|
|
91
|
+
} catch {
|
|
92
|
+
return { default: {}, byAgent: {}, bySession: {} };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function serializePersonaStore(store) {
|
|
97
|
+
const out = {
|
|
98
|
+
default: parsePersona(JSON.stringify(store?.default ?? {})),
|
|
99
|
+
byAgent: {},
|
|
100
|
+
bySession: {}
|
|
101
|
+
};
|
|
102
|
+
if (store?.byAgent && typeof store.byAgent === "object") {
|
|
103
|
+
for (const [agentId, card] of Object.entries(store.byAgent)) {
|
|
104
|
+
const parsed = parsePersona(JSON.stringify(card ?? {}));
|
|
105
|
+
if (Object.keys(parsed).length) out.byAgent[String(agentId)] = parsed;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (store?.bySession && typeof store.bySession === "object") {
|
|
109
|
+
for (const [sessionId, card] of Object.entries(store.bySession)) {
|
|
110
|
+
const parsed = parsePersona(JSON.stringify(card ?? {}));
|
|
111
|
+
if (Object.keys(parsed).length) out.bySession[String(sessionId)] = parsed;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return JSON.stringify(out);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- feature flags(功能开关)白名单 -----------------------------------------
|
|
118
|
+
// 面板可逐项开关的后端能力。设计约束:
|
|
119
|
+
// 1. 键名与类型必须和 config.js schema 同名同型,这份白名单是唯一校验源
|
|
120
|
+
// (api.js 复用它计算 effective),schema 增删能力键时要同步改这里。
|
|
121
|
+
// 2. 持久化(kv "feature_flags")只落白名单键;读到未知键、类型损坏或越界的
|
|
122
|
+
// 值一律丢弃而不是报错——kv 会残留旧版本写入的键,读路径必须向前兼容。
|
|
123
|
+
// 3. 写入是逐键校验的合并写:未知键/类型/范围不符抛 TypeError(消息含键名,
|
|
124
|
+
// 供 API 透传给前端定位),校验不通过不落库,坏值永远进不了 kv。
|
|
125
|
+
const FEATURE_FLAG_BOOLEANS = [
|
|
126
|
+
"autoInject",
|
|
127
|
+
"autoSummarize",
|
|
128
|
+
"hotMemoryEnabled",
|
|
129
|
+
// Issue #34(v0.7.2 引入;v0.7.11 误删后随 #333 恢复):对话开始注入当前时间。
|
|
130
|
+
// 顶层扁平键,随白名单回面板(FEATURE_GROUPS 的 group.core)。
|
|
131
|
+
"injectTimePrefix",
|
|
132
|
+
// Issue #239 第 5 项:注入条数的查询自适应(确定性强则收缩注入条数,默认关)。
|
|
133
|
+
"injectUncertaintyAdaptive",
|
|
134
|
+
// Issue #249:能力说明(工具描述判断指引 + order 150 总则段)。第二批起它是
|
|
135
|
+
// 注入父开关 autoInject 的子项、默认开(父关时不生效,闸门见 config.js)。
|
|
136
|
+
"injectGuidanceEnabled",
|
|
137
|
+
// Issue #249 N3:压缩边缘双落点——上下文即将精简前落一条连续性提案 + 往
|
|
138
|
+
// 序列末尾追加同一份快照。默认关(新注入表面,见 config.js),父开关 autoInject。
|
|
139
|
+
"continuityRescueEnabled",
|
|
140
|
+
"entityExtractionEnabled",
|
|
141
|
+
// Issue #219:图召回轴——查询命中实体名时把挂联记忆并入检索融合池
|
|
142
|
+
// (默认关;依赖实体抽取产出,lightMode 强制关闭)。
|
|
143
|
+
"entityRecallEnabled",
|
|
144
|
+
// Issue #24 块1:图谱锚定层级联——命中实体作种子、沿关系表向邻居扩散
|
|
145
|
+
// (默认关;依赖实体抽取产出,lightMode 强制关闭)。
|
|
146
|
+
"graphAnchoringEnabled",
|
|
147
|
+
// Issue #24 块2:边权重演化总闸——关着时任何触达都不改边权(默认关;
|
|
148
|
+
// 依赖实体抽取产出,lightMode 强制关闭)。块4 的被动确认是它底下的一个通道。
|
|
149
|
+
"graphWeightEnabled",
|
|
150
|
+
// Issue #24 块3:关联提示防幻觉——图召回线索行以「[检索线索]」标注进注入
|
|
151
|
+
// (默认关=线索只参与排序不进块;依赖实体抽取产出,lightMode 强制关闭)。
|
|
152
|
+
"graphInjectHint",
|
|
153
|
+
// Issue #24 块4:被动确认通道——正常触达即对该记忆挂联的边 bump 一格
|
|
154
|
+
// (默认关;需总闸 graphWeightEnabled 同开)。
|
|
155
|
+
"graphPassiveConfirm",
|
|
156
|
+
// Issue #164:叙述条——dream 期间按 tag 主题簇合成叙述落库(source=
|
|
157
|
+
// narrative,evidence 回链簇内记忆;按需检索不常驻注入;默认关)。
|
|
158
|
+
"dreamNarrativeEnabled",
|
|
159
|
+
// Issue #230:document 型记忆——agent 产长文档的指针行(注册校验/摘要+
|
|
160
|
+
// doc_path 落库/C2 去重/supersede 记账;全文归 agent;默认关,lightMode 强制关)。
|
|
161
|
+
"documentMemoryEnabled",
|
|
162
|
+
// 日记本:跨天惰性判定 + 启动补写,正文落 <memoryDir>/diaries/<项目键>/。
|
|
163
|
+
// 默认关,进 LIGHT_MODE_OFF(要调模型,与低资源档互斥)。与 documentMemoryEnabled
|
|
164
|
+
// 成对但不相依:关掉文档记忆只影响检索,正文照样写。
|
|
165
|
+
"diaryEnabled",
|
|
166
|
+
"codingRetrospect",
|
|
167
|
+
"autoDream",
|
|
168
|
+
// Issue #292:autoDream 连续失败退避(默认关)。基数是 dreamMinIntervalMinutes,
|
|
169
|
+
// 有效间隔 = 基数 × 2^连续失败数(成功清零),封顶 30 分钟;面板可启停。
|
|
170
|
+
"autoDreamFailureBackoff",
|
|
171
|
+
// Issue #557:dream 审计行输入快照瘦身(默认关)。开启后 auto 轮只落骨架
|
|
172
|
+
// (不含 content),organize 轮的重放载荷不受影响;面板可启停。
|
|
173
|
+
"dreamInputSummaryEnabled",
|
|
174
|
+
"sleepModeEnabled",
|
|
175
|
+
// Issue #529:sleep 连续失败退避(默认关)。基数是 sleepMinIntervalHours,
|
|
176
|
+
// 有效间隔 = 基数 × 2^连续失败数(成功清零),封顶 max(基数, 24h);面板可启停。
|
|
177
|
+
"sleepFailureBackoff",
|
|
178
|
+
// Issue #619:情感归因相位(默认关)。把「某人某场合的情绪」写成 occasion-bound
|
|
179
|
+
// 的 affective_context 关系,只有跨够 sleepAffectiveTraitMinSessions 个独立会话
|
|
180
|
+
// 才允许升成稳定特质。要调模型且依赖实体抽取,所以必须显式打开、可随时关掉;
|
|
181
|
+
// 它是 sleepModeEnabled 的子相:父开关关着时整个 sleep 不跑。
|
|
182
|
+
"sleepAffectiveEnabled",
|
|
183
|
+
"heatEnabled",
|
|
184
|
+
// issue #218 / E5:注入侧 heat 独立开关(默认关,老用户 injectHeatEnabled=true 回滚)。
|
|
185
|
+
"injectHeatEnabled",
|
|
186
|
+
"hybridInject",
|
|
187
|
+
"selectiveInjectEnabled",
|
|
188
|
+
"searchSemanticDedup",
|
|
189
|
+
"rerankEnabled",
|
|
190
|
+
"resilientModelDownload",
|
|
191
|
+
"adaptiveThresholdEnabled",
|
|
192
|
+
"reflectionUpdateEnabled",
|
|
193
|
+
"reflectionFailureTracking",
|
|
194
|
+
"bm25SearchEnabled",
|
|
195
|
+
"conflictFreezeEnabled",
|
|
196
|
+
// Issue #603 第 4 层:自动裁决档(默认关)。相似度极高的同 scope 重复对直接
|
|
197
|
+
// 保留新条、归档旧条,不进人工队列;模糊地带照旧留给人。会归档记忆,所以必须
|
|
198
|
+
// 显式打开,面板可启停。阈值见 FEATURE_FLAG_NUMBER_RANGES 的
|
|
199
|
+
// conflictAutoResolveMinSimilarity。
|
|
200
|
+
"conflictAutoResolveEnabled",
|
|
201
|
+
// Issue #641(beta 第一阶段):conflictLedgerMode / conflictLedgerDupMinSimilarity
|
|
202
|
+
// 不在本表——前者是枚举键(FEATURE_FLAG_ENUMS),后者是数值键
|
|
203
|
+
// (FEATURE_FLAG_NUMBER_RANGES),flagKeysFromSpec 会把三张表合成一份白名单,
|
|
204
|
+
// 在这里重复登记会让同键被收两次(config-keys 的覆盖完整性锁抓过)。
|
|
205
|
+
"trustEpistemicWeighting",
|
|
206
|
+
// Plan #2: attach per-source {keyword, vector, bm25, final} signals to each
|
|
207
|
+
// search result for transparency/debugging. Default off, purely decorative.
|
|
208
|
+
"signalTransparency",
|
|
209
|
+
// Issue #89:宽容校验回归(默认开)+ 跨类型合并显式放宽(默认关)。
|
|
210
|
+
"dreamSkipInvalid",
|
|
211
|
+
"allowCrossTypeMerge",
|
|
212
|
+
// Issue #339 / E8:guarded 类型 merge 护栏(默认开)——合并对象命中
|
|
213
|
+
// 长保留类型的 merge 决策整条跳过。关掉 = guarded 类型的 merge 不再被跳过,
|
|
214
|
+
// 「更精炼的摘要」重新成为约束失真的主通道(面板可关,供需要旧行为的库回退)。
|
|
215
|
+
"dreamMergeGuard",
|
|
216
|
+
// Issue #17(v0.8.0 A1):scope 隔离存储层总开关。开启后写入标注
|
|
217
|
+
// agent_scope/workspace_scope、去重键扩展(含 sensitivity);检索侧过滤在
|
|
218
|
+
// A2/A3。默认关=行为与 A1 前逐字节一致。
|
|
219
|
+
"scopeEnabled",
|
|
220
|
+
// Issue #17(v0.8.0 A3):strictScope 硬过滤——他 scope 完全不可见(关闭时
|
|
221
|
+
// 为 A2 软隔离:降权保留可见)。
|
|
222
|
+
"strictScope",
|
|
223
|
+
// Issue #471:政策轴——作用域级可逆闸门(scopeState)。与上面两个键是两条不同的
|
|
224
|
+
// 轴:那两个管「这条记忆属于谁」(事实轴),这两个管「这个作用域现在通不通」
|
|
225
|
+
// (政策轴,按作用域整体按下、跨会话存续、松开即恢复)。两级语义同
|
|
226
|
+
// writeAdmission/preInjectGate——enabled 跑判定(观察档,留审计行但照常注入)、
|
|
227
|
+
// enforce 真拦(不注入);都默认关。点号键平铺存、合并时展开回 scopeState 对象。
|
|
228
|
+
"scopeState.enabled",
|
|
229
|
+
"scopeState.enforce",
|
|
230
|
+
// 工具暴露开关(v0.8.5):记忆已每轮自动注入,memory_search/memory_archive
|
|
231
|
+
// 在慢/轻量模型上是多余往返,面板可关(默认关=行为不变)。
|
|
232
|
+
"disableMemorySearch",
|
|
233
|
+
"disableMemoryArchive",
|
|
234
|
+
// Issue #231 第二批:agent 主动整理(memory_organize 工具)的暴露闸。与上面两个
|
|
235
|
+
// 反向——那两个藏默认在场的工具,本键开默认不在场的能力(默认关=工具根本不存在,
|
|
236
|
+
// 行为逐字节不变)。面板可启停=线上回滚开关。
|
|
237
|
+
"organizeEnabled",
|
|
238
|
+
// chorography(仓库地志)的暴露闸:开默认不在场的三个只读工具 + 面板浏览页
|
|
239
|
+
// (默认关=模型工具列表里根本没有它们,行为逐字节不变)。同 organizeEnabled
|
|
240
|
+
// 的反向语义——见 config.js 的键注释。
|
|
241
|
+
"chorographyEnabled",
|
|
242
|
+
// 嵌套对象开关:config.js 里是 memoryQualityFilter / llmAudit 对象的 enabled
|
|
243
|
+
// 子字段。kv 按点号键平铺存("memoryQualityFilter.enabled": false),index.js
|
|
244
|
+
// 合并时展开回嵌套对象,api.js 的 effective 从对象子字段取值。
|
|
245
|
+
"memoryQualityFilter.enabled",
|
|
246
|
+
"llmAudit.enabled",
|
|
247
|
+
// Issue #254:写入准入(第 1 级确定性拒绝)。两个键分层——enabled 跑判据、
|
|
248
|
+
// enforce 真拦;都默认关。同上,点号键平铺存、合并时展开回
|
|
249
|
+
// writeAdmission 对象。
|
|
250
|
+
"writeAdmission.enabled",
|
|
251
|
+
"writeAdmission.enforce",
|
|
252
|
+
// Issue #380:注入前判定(preInjectGate)。两级语义同 writeAdmission——
|
|
253
|
+
// enabled 跑判定(观察档)、enforce 缓存命中帧真滤除;都默认关。点号键平铺存、
|
|
254
|
+
// 合并时展开回 preInjectGate 对象。
|
|
255
|
+
"preInjectGate.enabled",
|
|
256
|
+
"preInjectGate.enforce",
|
|
257
|
+
// Issue #164 A2:写入边界的密钥 / PII 判据(src/sensitive-scan.js)。与上面两个
|
|
258
|
+
// 键分层——本键决定「这类判据参不参与」(默认关),命中之后是仅告警还是真拦仍由
|
|
259
|
+
// writeAdmission.enforce 决定(#164 口径:默认仅告警、拦截 opt-in)。
|
|
260
|
+
"sensitiveScanEnabled",
|
|
261
|
+
// Issue #399:蒸馏原文留存 + 记忆级溯源。enabled 决定蒸馏时是否把公开正文原样
|
|
262
|
+
// 落盘(distill_records)并给记忆挂窗口链接(memory_provenance);默认关。
|
|
263
|
+
// 点号键平铺存、合并时展开回 distillRecords 对象(同 llmAudit.enabled)。
|
|
264
|
+
// retentionDays 是 int,走下面的 INT_RANGES 表,不进开关白名单。
|
|
265
|
+
"distillRecords.enabled",
|
|
266
|
+
// v0.9.0:M.O.(Modus Operandi,beta)客户端视图开关。客户端读 /features
|
|
267
|
+
// 的 effective
|
|
268
|
+
// 决定是否注册座位(会话头图标 / 浮层 / 右栏 / 对话行卡片),服务端只负责把
|
|
269
|
+
// 这个键稳定暴露出来——键若不在 effective 里,客户端静默少键、一个座位都不注册。
|
|
270
|
+
// 默认关(beta、opt-in);顶层扁平键,因此不需要 NESTED_FLAG_PATHS 条目。
|
|
271
|
+
"actionPipelineEnabled",
|
|
272
|
+
// v0.9.0 第二批:M.O. 执行记账(`/mo-run` 开窗 → 窗口内命令被记成一条 run)。
|
|
273
|
+
// 与上面那个键分开:那个管「看图的座位」,这个管「往账本里写」。默认关(beta、
|
|
274
|
+
// opt-in),顶层扁平键,不需要 NESTED_FLAG_PATHS 条目。
|
|
275
|
+
"actionRunEnabled",
|
|
276
|
+
// v0.9.0 第三批:M.O. 自动沉淀(会话跑完一轮 → 挖这个项目自己的会话日志 → 落提议)。
|
|
277
|
+
// 与前两个分开:这个管「从日志里长出候选」,那个管记账、另一个管看图。默认关
|
|
278
|
+
// (beta、opt-in);顶层扁平键,不需要 NESTED_FLAG_PATHS 条目。进 LIGHT_MODE_OFF。
|
|
279
|
+
"actionNudgeEnabled",
|
|
280
|
+
// 人格定义页(beta):注入器渲染人格段的顶层总闸(双闸门的 config 半边;对象内
|
|
281
|
+
// 的 persona.enabled 是 per-agent 半边,存 user_settings 的 "persona" 键,见
|
|
282
|
+
// getPersona/setPersona)。默认关(beta、opt-in,老用户行为逐字节不变);不进
|
|
283
|
+
// LIGHT_MODE_OFF——人格段是用户自写的一小段轻量常驻文本,不引入任何重型后台
|
|
284
|
+
// 路径,轻量档用户显式勾选仍然赢(同 actionPipelineEnabled 的定性)。顶层扁平
|
|
285
|
+
// 键,不需要 NESTED_FLAG_PATHS 条目。
|
|
286
|
+
"personaEnabled",
|
|
287
|
+
// 人格寻址口径(上一条打开时才有意义):开 = 身份没建卡/卡被关时不注入人格,
|
|
288
|
+
// default 只兜解析不到身份的会话。默认关(历史行为,升级零变化)。
|
|
289
|
+
"personaStrictAddressing",
|
|
290
|
+
// Issue #497:画像提取草稿(POST /profile/extract)。默认关——每次调用一次 LLM 且
|
|
291
|
+
// 读上百行记忆;产物只回草稿,绝不写 user_settings。进 LIGHT_MODE_OFF(预设关)。
|
|
292
|
+
"profileExtractEnabled",
|
|
293
|
+
// 「Agent 眼中的你」自动事实(2026-11-06):dream 后逐卡维护 + 手动轮 + 注入段,
|
|
294
|
+
// 事实住 profile_facts 独立三表,绝不写 user_settings(#497 口径不变)。默认关,
|
|
295
|
+
// 进 LIGHT_MODE_OFF(自动维护轮与低资源档互斥)。顶层扁平键,与 config.js schema
|
|
296
|
+
// 成对出现(计数锁 test/api.test.js 钉住)。
|
|
297
|
+
"profileFactsEnabled",
|
|
298
|
+
// 图像描述档(docs/DISTILL.md:346):蒸馏前给窗口里的图出描述。默认关,进
|
|
299
|
+
// LIGHT_MODE_OFF —— 它是一次额外的视觉调用,与低资源档互斥。路由两个键走
|
|
300
|
+
// FEATURE_FLAG_STRINGS(留空 = 不描述,理由写在 config.js 那三个键的注释里)。
|
|
301
|
+
"imageDescribeEnabled",
|
|
302
|
+
// #539:逐会话无痕默认关闭;子代理开关受总开关约束。手动 session id 名单留在配置,
|
|
303
|
+
// 不进入只支持标量的 feature_flags 白名单。
|
|
304
|
+
"sessionGateEnabled",
|
|
305
|
+
"sessionGateSubagents",
|
|
306
|
+
// 2026-11-08 kb-qoder parity(契约 §3.1):chorography 面板闭环的两个授权闸 +
|
|
307
|
+
// kb 子系统总闸。三条都与 config.js schema 成对出现,计数锁 test/api.test.js 钉住。
|
|
308
|
+
// 不进 LIGHT_MODE_OFF —— 它们是「谁按下了按钮」而不是「每轮付多少」,理由写在
|
|
309
|
+
// config.js 那三个键的注释里。kbEnabled 本车道只登记、不接线(B 线做子系统)。
|
|
310
|
+
"chorographyAutoRefresh",
|
|
311
|
+
"chorographyT1FromPanel",
|
|
312
|
+
"kbEnabled"
|
|
313
|
+
];
|
|
314
|
+
// 整数开关的闭区间,与 config.js 里 z.natural().min().max() 对齐。
|
|
315
|
+
const FEATURE_FLAG_INT_RANGES = {
|
|
316
|
+
distillRateLimitIntervalMs: [0, 60000],
|
|
317
|
+
distillRateLimitRetries: [0, 10],
|
|
318
|
+
distillRateLimitBaseDelayMs: [100, 60000],
|
|
319
|
+
distillMaxChars: [1000, 200000],
|
|
320
|
+
codingBoostFactor: [1, 5],
|
|
321
|
+
dreamMinIntervalMinutes: [0, 10080],
|
|
322
|
+
// Issue #637:注入跨轮轮换的窗口(0 = 关,#205 既有语义;config.js 的
|
|
323
|
+
// z.natural().min(0).max(20) 与此成对)。此前不在白名单——报告者想开只能写
|
|
324
|
+
// 宿主 patch 文件(cordis.patch.yml),面板上根本看不见这个键的存在。
|
|
325
|
+
injectRotationTurns: [0, 20],
|
|
326
|
+
dreamMaxTokens: [256, 131072],
|
|
327
|
+
// Issue #127:autoSummarize 节流三键(0 = 零行为变化,等同现状)。
|
|
328
|
+
summarizeMinIntervalMinutes: [0, 10080],
|
|
329
|
+
summarizeMaxEntriesPerRun: [0, 50],
|
|
330
|
+
summarizeDedupeWindowHours: [0, 168],
|
|
331
|
+
// Issue #239:蒸馏零 LLM 预判(窗口最小字符数)与每会话 run 预算(0 = 零行为变化)。
|
|
332
|
+
summarizeMinWindowChars: [0, 100000],
|
|
333
|
+
summarizeMaxRunsPerSession: [0, 1000],
|
|
334
|
+
// Issue #493:连续解析失败几次后放弃当前窗口(0 = 关)。与 config.js 的
|
|
335
|
+
// z.natural().min(0).max(10) 成对。
|
|
336
|
+
summarizeParseFailureMaxRetries: [0, 10],
|
|
337
|
+
// Issue #239 第 4 项:高峰顺延上限(分钟,0 = 不设上限)。
|
|
338
|
+
summarizePeakMaxDeferMinutes: [0, 1440],
|
|
339
|
+
// Issue #239 第 4 项镜像到巩固:同一口径(分钟,0 = 不设上限)。
|
|
340
|
+
dreamPeakMaxDeferMinutes: [0, 1440],
|
|
341
|
+
// v0.13 日记定时写:每隔 N 分钟唤醒一次跨天判定(0 = 关,只在 turn/end 与
|
|
342
|
+
// 启动时判)。上限一天——判定单位是天,更长的间隔没有意义。
|
|
343
|
+
diaryWriteEveryMinutes: [0, 1440],
|
|
344
|
+
// Issue #125:hybrid 候选量上限(0 = 复用 dreamMaxSnapshotSize)。
|
|
345
|
+
dreamCandidateMax: [0, 5000],
|
|
346
|
+
// #135 成本审计派生:单轮 merge 决策上限(merge 是唯一回吐正文的动作,默认 40
|
|
347
|
+
// = 实测分布上沿;与 config.js 的 z.natural().min(1).max(200) 对齐)。
|
|
348
|
+
dreamMaxMergePerRun: [1, 200],
|
|
349
|
+
// Issue #164①:注入单条正文截断上限(默认 300 = 既有行为)。
|
|
350
|
+
injectContentMaxChars: [60, 4000],
|
|
351
|
+
// Issue #164:叙述条成簇门槛(共享同一 tag 的记忆数下限)。
|
|
352
|
+
dreamNarrativeMinCluster: [2, 20],
|
|
353
|
+
// Issue #230:document 摘要行的注入预算(次优先档内最多几条指针行)。
|
|
354
|
+
documentInjectBudget: [1, 5],
|
|
355
|
+
// Issue #249 第一批:B1 pin 池(约束/偏好)的独立条数预算(0 = 关闭/现状)。
|
|
356
|
+
pinnedInjectBudget: [0, 5],
|
|
357
|
+
// Issue #257:sleep 冲突/模式阶段的 LLM 输出预算(原硬编码 2048,实测不足)。
|
|
358
|
+
sleepMaxTokens: [256, 131072],
|
|
359
|
+
// Issue #619:情感归因的两个闸——升特质所需的最小独立会话数(下界 2:N=1 就是
|
|
360
|
+
// 单次场合,正是必须禁止升特质的那个情形),以及单轮可写的 affective_context
|
|
361
|
+
// 关系条数上限(0 = 关闭关系通道)。闭区间与 config.js 的 z.natural() 对齐。
|
|
362
|
+
sleepAffectiveTraitMinSessions: [2, 10],
|
|
363
|
+
sleepAffectiveMaxRelationsPerRun: [0, 50],
|
|
364
|
+
// Issue #529:sleep 冲突阶段的输入侧预算(此前完全没有——全部候选对一次进一个
|
|
365
|
+
// prompt,实测 163,054 token 撞服务端 163,840 上限)。两个键都是 0 = 不设上限,
|
|
366
|
+
// 与 config.js 的 z.natural().min(0).max() 成对。
|
|
367
|
+
sleepConflictMaxPairs: [0, 500],
|
|
368
|
+
sleepConflictMaxPairsPerRun: [0, 5000],
|
|
369
|
+
// Issue #258:总览(dream_summarize)输入条数硬上限(0 = 不设上限)。
|
|
370
|
+
dreamSummaryMaxInputs: [0, 100000],
|
|
371
|
+
// Issue #24 块1:锚定种子上限与级联深度(闭区间与 config.js 的 z.natural() 对齐)。
|
|
372
|
+
graphSeedCap: [1, 30],
|
|
373
|
+
graphCascadeDepth: [1, 3],
|
|
374
|
+
// Issue #24 块3:图线索行的独立注入预算(0 = 线索不进块)。
|
|
375
|
+
graphInjectBudget: [0, 5],
|
|
376
|
+
// Issue #399:蒸馏原文记录的保留天数(与 config.js 的 z.natural().min(1).max(3650)
|
|
377
|
+
// 对齐;同 llmAudit.retentionDays,是 int 而非开关)。
|
|
378
|
+
"distillRecords.retentionDays": [1, 3650],
|
|
379
|
+
// #275 容量预警阈值(嵌套点号键,必须同时进 api.js 的 NESTED_FLAG_PATHS)。
|
|
380
|
+
// 0 = 显式关掉那一条检查(阈值判级的启用条件写在 service.getCapacityHealth)。
|
|
381
|
+
// 上限与 config.js 的 z.natural().min(0).max() 成对。
|
|
382
|
+
"capacityWarning.dbSizeWarnMb": [0, 1048576],
|
|
383
|
+
"capacityWarning.archivedRowsWarn": [0, 10000000]
|
|
384
|
+
};
|
|
385
|
+
// 浮点开关的闭区间(与 config.js 的 z.number().min().max() 对齐)。与整数开关
|
|
386
|
+
// 分开:面板的整数控件要求 Number.isInteger,而余弦相似度阈值必须允许小数。
|
|
387
|
+
const FEATURE_FLAG_NUMBER_RANGES = {
|
|
388
|
+
// Issue #127:vector 去重档的并入阈值。
|
|
389
|
+
summarizeDedupeMinSim: [0.5, 0.99],
|
|
390
|
+
// Issue #125:hybrid 判"高相似"的阈值。
|
|
391
|
+
dreamCandidateMinSim: [0.5, 0.99],
|
|
392
|
+
// Issue #24 块2:单次有效触达的边权抬升幅度(与 config.js 的闭区间对齐)。
|
|
393
|
+
graphWeightDelta: [0, 1],
|
|
394
|
+
// Issue #603 第 4 层:自动裁决的相似度门槛。下界 0.85 与冲突检测阈值同源——
|
|
395
|
+
// 低于它就不是「重复」而是「相似」,不该自动归档任何一条。
|
|
396
|
+
conflictAutoResolveMinSimilarity: [0.85, 1],
|
|
397
|
+
// Issue #641:T1(dup)档的相似度下限。检测阈值(normal 0.85)与自动档 autoMin
|
|
398
|
+
// (0.95,#616 重测在现库覆盖为 0)之间的空带由它接管,下界与检测阈值同源。
|
|
399
|
+
conflictLedgerDupMinSimilarity: [0.85, 1],
|
|
400
|
+
// #275 容量预警的归档占比阈值(0 = 关):健康形态本身就落在 0.8~0.9,默认
|
|
401
|
+
// 0.95 起才算「归档区吞掉活跃区」——0.9 会对着健康库天天报警(依据见 config.js
|
|
402
|
+
// capacityWarning 的注释)。浮点键,同 conflictAutoResolveMinSimilarity 的缘由。
|
|
403
|
+
"capacityWarning.archivedRatioWarn": [0, 1]
|
|
404
|
+
};
|
|
405
|
+
// 自由字符串开关(与 config.js 的 z.string() 同名同型):trim 后 ≤200 字符,
|
|
406
|
+
// 空串合法(= 跟随主对话模型/默认路径,面板显示 placeholder)。
|
|
407
|
+
const FEATURE_FLAG_STRINGS = [
|
|
408
|
+
"dreamProvider",
|
|
409
|
+
"dreamModel",
|
|
410
|
+
// 睡眠侧专用路由(sleep.js 的 config-first 第三层):面板下拉随
|
|
411
|
+
// /llm-providers 端点一起提供,留空 = 用巩固模型或当前模型。
|
|
412
|
+
"sleepProvider",
|
|
413
|
+
"sleepModel",
|
|
414
|
+
// 实体抽取侧专用路由(issue #109):provider/model 显式指定,
|
|
415
|
+
// 留空 = 用当前默认模型。
|
|
416
|
+
"entityExtractionProvider",
|
|
417
|
+
"entityExtractionModel",
|
|
418
|
+
// Issue #258:总览(dream_summarize)专用路由,留空 = 用巩固模型。
|
|
419
|
+
"dreamSummaryProvider",
|
|
420
|
+
"dreamSummaryModel",
|
|
421
|
+
"localEmbedModel",
|
|
422
|
+
"ollamaModel",
|
|
423
|
+
// Issue #239 第 4 项:高峰时段串("09:00-18:00",空串 = 关闭)。
|
|
424
|
+
"summarizePeakHours",
|
|
425
|
+
// Issue #239 第 4 项镜像到巩固:同一份时段语法,空串 = 关闭(行为与现状一致)。
|
|
426
|
+
"dreamPeakHours",
|
|
427
|
+
// 图像描述档专用路由(docs/DISTILL.md:346):与上面几条不同,这两条留空不是
|
|
428
|
+
// 「跟随当前模型」而是「整档不描述」——蒸馏路由与会话主模型都可能是纯文本
|
|
429
|
+
// 模型,跟着它们跑只会拿回一行 textOnly 占位。所以面板上它们是空串 + 说明,
|
|
430
|
+
// 不是下拉默认值。
|
|
431
|
+
"imageDescribeProvider",
|
|
432
|
+
"imageDescribeModel"
|
|
433
|
+
];
|
|
434
|
+
// URL 字符串开关:trim 后必须为空或合法 http/https URL(new URL() 校验协议,
|
|
435
|
+
// 拒绝其余协议——这是 SSRF 防线的一部分)。
|
|
436
|
+
const FEATURE_FLAG_URLS = ["ollamaBaseUrl"];
|
|
437
|
+
// 枚举开关(与 config.js 的 z.union(z.const(...)) 对齐):仅允许列出的值。
|
|
438
|
+
const FEATURE_FLAG_ENUMS = {
|
|
439
|
+
embedProvider: ["openai", "local", "ollama"],
|
|
440
|
+
// Plan #1: recall fusion recipe. blend = legacy (default); rrf / minmax are
|
|
441
|
+
// rank/scale-aware alternatives selected by the panel.
|
|
442
|
+
recallFusion: ["blend", "rrf", "minmax"],
|
|
443
|
+
// 实体抽取思考强度(issue #109):与 dreamReasoningEffort 枚举对齐。
|
|
444
|
+
entityExtractionReasoning: ["low", "medium", "high", "none"],
|
|
445
|
+
// 蒸馏思考强度(issue #315):与 entityExtractionReasoning 枚举对齐,
|
|
446
|
+
// 多一个 off(显式关思考,思考型模型蒸馏防推理烧预算)。
|
|
447
|
+
summarizeReasoningEffort: ["off", "low", "medium", "high", "none"],
|
|
448
|
+
// Issue #127:落库前去重档位(off 默认,等同现状)。
|
|
449
|
+
summarizeDedupeMode: ["off", "title", "vector"],
|
|
450
|
+
// Issue #126:sleep 冲突阶段的动作集(conflict 默认 = 现状;full = 六分支)。
|
|
451
|
+
sleepActionSet: ["conflict", "full"],
|
|
452
|
+
// Issue #125:dream 候选集构造方式(window 默认 = 现状;hybrid 并入向量组)。
|
|
453
|
+
dreamCandidateMode: ["window", "hybrid"],
|
|
454
|
+
// 本地嵌入池化:auto = 按模型族判定(BGE → cls),可显式覆盖为 cls / mean。
|
|
455
|
+
localEmbedPooling: ["auto", "cls", "mean"],
|
|
456
|
+
// M3 开局注入档位(spec §7):轻量档在消费侧压成 off,见 wiki/inject.js 的
|
|
457
|
+
// resolveInjectTier——LIGHT_MODE_OFF 对平铺键一律赋 false,枚举值进去会静默失效。
|
|
458
|
+
chorographyInject: ["off", "light", "full"],
|
|
459
|
+
// Issue #641:冲突分诊账本档位。off(默认,零行为)/ shadow(只记账)/ enforce
|
|
460
|
+
// (确定性处置:T1 信息量优先 supersede,T2-T4 记账不停车不送 LLM)。三个档位
|
|
461
|
+
// 都有真实行为——「设了但没行为」的档位不进枚举(#616「假成功」教训)。
|
|
462
|
+
conflictLedgerMode: ["off", "shadow", "enforce"]
|
|
463
|
+
};
|
|
464
|
+
const FEATURE_FLAG_STRING_MAX = 200;
|
|
465
|
+
|
|
466
|
+
// 供 api.js 复用同一份白名单(effective 只在白名单键上计算)。
|
|
467
|
+
export const FEATURE_FLAG_SPEC = {
|
|
468
|
+
booleans: FEATURE_FLAG_BOOLEANS,
|
|
469
|
+
ints: FEATURE_FLAG_INT_RANGES,
|
|
470
|
+
numbers: FEATURE_FLAG_NUMBER_RANGES,
|
|
471
|
+
strings: FEATURE_FLAG_STRINGS,
|
|
472
|
+
urls: FEATURE_FLAG_URLS,
|
|
473
|
+
enums: FEATURE_FLAG_ENUMS
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
/** ollamaBaseUrl 的协议白名单:只接受 http/https(SSRF 防线的一部分)。 */
|
|
477
|
+
function isHttpUrl(value) {
|
|
478
|
+
try {
|
|
479
|
+
const protocol = new URL(value).protocol;
|
|
480
|
+
return protocol === "http:" || protocol === "https:";
|
|
481
|
+
} catch {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/** 校验单个开关值;不合法抛 TypeError(消息含键名)。 */
|
|
487
|
+
function validateFlag(key, value) {
|
|
488
|
+
if (FEATURE_FLAG_BOOLEANS.includes(key)) {
|
|
489
|
+
if (typeof value !== "boolean") {
|
|
490
|
+
throw new TypeError(`feature flag "${key}" must be a boolean`);
|
|
491
|
+
}
|
|
492
|
+
return value;
|
|
493
|
+
}
|
|
494
|
+
const range = FEATURE_FLAG_INT_RANGES[key];
|
|
495
|
+
if (range) {
|
|
496
|
+
const [min, max] = range;
|
|
497
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
498
|
+
throw new TypeError(`feature flag "${key}" must be an integer in [${min}, ${max}]`);
|
|
499
|
+
}
|
|
500
|
+
return value;
|
|
501
|
+
}
|
|
502
|
+
const numRange = FEATURE_FLAG_NUMBER_RANGES[key];
|
|
503
|
+
if (numRange) {
|
|
504
|
+
const [min, max] = numRange;
|
|
505
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) {
|
|
506
|
+
throw new TypeError(`feature flag "${key}" must be a number in [${min}, ${max}]`);
|
|
507
|
+
}
|
|
508
|
+
return value;
|
|
509
|
+
}
|
|
510
|
+
if (FEATURE_FLAG_STRINGS.includes(key)) {
|
|
511
|
+
if (typeof value !== "string") {
|
|
512
|
+
throw new TypeError(`feature flag "${key}" must be a string`);
|
|
513
|
+
}
|
|
514
|
+
const trimmed = value.trim();
|
|
515
|
+
if (trimmed.length > FEATURE_FLAG_STRING_MAX) {
|
|
516
|
+
throw new TypeError(`feature flag "${key}" must be at most ${FEATURE_FLAG_STRING_MAX} characters`);
|
|
517
|
+
}
|
|
518
|
+
return trimmed; // 空串合法 = 跟随默认
|
|
519
|
+
}
|
|
520
|
+
if (FEATURE_FLAG_URLS.includes(key)) {
|
|
521
|
+
if (typeof value !== "string") {
|
|
522
|
+
throw new TypeError(`feature flag "${key}" must be a string`);
|
|
523
|
+
}
|
|
524
|
+
const trimmed = value.trim();
|
|
525
|
+
if (trimmed && !isHttpUrl(trimmed)) {
|
|
526
|
+
throw new TypeError(`feature flag "${key}" must be empty or a valid http(s) URL`);
|
|
527
|
+
}
|
|
528
|
+
return trimmed; // 空串合法 = 跟随默认
|
|
529
|
+
}
|
|
530
|
+
const allowed = FEATURE_FLAG_ENUMS[key];
|
|
531
|
+
if (allowed) {
|
|
532
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
533
|
+
throw new TypeError(`feature flag "${key}" must be one of: ${allowed.join(", ")}`);
|
|
534
|
+
}
|
|
535
|
+
return value;
|
|
536
|
+
}
|
|
537
|
+
throw new TypeError(`unknown feature flag "${key}"`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** 清洗已存的 feature_flags 对象:只保留白名单键,类型/范围损坏的键丢弃。 */
|
|
541
|
+
function sanitizeFlags(raw) {
|
|
542
|
+
const out = {};
|
|
543
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return out;
|
|
544
|
+
for (const key of FEATURE_FLAG_BOOLEANS) {
|
|
545
|
+
if (typeof raw[key] === "boolean") out[key] = raw[key];
|
|
546
|
+
}
|
|
547
|
+
for (const [key, [min, max]] of Object.entries(FEATURE_FLAG_INT_RANGES)) {
|
|
548
|
+
if (Number.isInteger(raw[key]) && raw[key] >= min && raw[key] <= max) out[key] = raw[key];
|
|
549
|
+
}
|
|
550
|
+
for (const [key, [min, max]] of Object.entries(FEATURE_FLAG_NUMBER_RANGES)) {
|
|
551
|
+
if (typeof raw[key] === "number" && Number.isFinite(raw[key]) && raw[key] >= min && raw[key] <= max) {
|
|
552
|
+
out[key] = raw[key];
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
for (const key of FEATURE_FLAG_STRINGS) {
|
|
556
|
+
if (typeof raw[key] === "string" && raw[key].trim().length <= FEATURE_FLAG_STRING_MAX) {
|
|
557
|
+
out[key] = raw[key].trim();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
for (const key of FEATURE_FLAG_URLS) {
|
|
561
|
+
if (typeof raw[key] === "string") {
|
|
562
|
+
const trimmed = raw[key].trim();
|
|
563
|
+
if (!trimmed || isHttpUrl(trimmed)) out[key] = trimmed;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
for (const [key, allowed] of Object.entries(FEATURE_FLAG_ENUMS)) {
|
|
567
|
+
if (allowed.includes(raw[key])) out[key] = raw[key];
|
|
568
|
+
}
|
|
569
|
+
return out;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function createSettings(db) {
|
|
573
|
+
db.exec(SCHEMA);
|
|
574
|
+
|
|
575
|
+
function getSetting(key) {
|
|
576
|
+
const row = db.prepare("SELECT value FROM user_settings WHERE key = ?").get(key);
|
|
577
|
+
return row?.value ?? undefined;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function setSetting(key, value) {
|
|
581
|
+
db.prepare(
|
|
582
|
+
`INSERT INTO user_settings (key, value) VALUES (?, ?)
|
|
583
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
584
|
+
).run(key, value);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function toCommand(row) {
|
|
588
|
+
if (!row) return undefined;
|
|
589
|
+
return {
|
|
590
|
+
id: row.id,
|
|
591
|
+
name: row.name,
|
|
592
|
+
description: row.description,
|
|
593
|
+
instruction: row.instruction,
|
|
594
|
+
created_at: row.created_at,
|
|
595
|
+
updated_at: row.updated_at
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return {
|
|
600
|
+
/** The user's self-description (free text) or "" when unset. */
|
|
601
|
+
getProfile() {
|
|
602
|
+
return getSetting("profile") ?? "";
|
|
603
|
+
},
|
|
604
|
+
setProfile(text) {
|
|
605
|
+
setSetting("profile", String(text ?? ""));
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
/** Behavior rules as an array of strings. */
|
|
609
|
+
getRules() {
|
|
610
|
+
return parseList(getSetting("rules") ?? "[]").filter((r) => typeof r === "string");
|
|
611
|
+
},
|
|
612
|
+
setRules(rules) {
|
|
613
|
+
const list = Array.isArray(rules) ? rules.filter((r) => typeof r === "string") : [];
|
|
614
|
+
setSetting("rules", JSON.stringify(list));
|
|
615
|
+
},
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Agent persona (name / personality / replyStyle + enabled gate), stored as
|
|
619
|
+
* one JSON key. v1.2 多卡:存储形 { default, byAgent },旧 v1 单对象读作
|
|
620
|
+
* default 兼容。getPersona 返回全量多卡形(面板与注入寻址都用它)。
|
|
621
|
+
* 寻址与兜底口径不在这里——住 src/user-settings.js 的 resolvePersonaCard,
|
|
622
|
+
* 注入与面板预览共用那一份;这里若再留一个 getPersonaFor,就是两张皮。
|
|
623
|
+
*/
|
|
624
|
+
getPersona() {
|
|
625
|
+
return parsePersonaStore(getSetting("persona") ?? "{}");
|
|
626
|
+
},
|
|
627
|
+
setPersona(input) {
|
|
628
|
+
// 双形兼容:单对象形 = 只更新 default、byAgent/bySession 保留(旧面板/旧调用方
|
|
629
|
+
// 合并语义);多卡形 = 全量替换。坏体(非对象)在此不落库不抛错,保值由调用方守卫。
|
|
630
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return;
|
|
631
|
+
if ("default" in input || "byAgent" in input || "bySession" in input) {
|
|
632
|
+
setSetting("persona", serializePersonaStore(input));
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const current = parsePersonaStore(getSetting("persona") ?? "{}");
|
|
636
|
+
current.default = parsePersona(JSON.stringify(input));
|
|
637
|
+
setSetting("persona", serializePersonaStore(current));
|
|
638
|
+
},
|
|
639
|
+
/** Replace one byAgent card wholesale (panel save); default 不可经此改。 */
|
|
640
|
+
setPersonaFor(agentId, card) {
|
|
641
|
+
const id = typeof agentId === "string" && agentId.trim() ? agentId.trim() : "";
|
|
642
|
+
if (!id || id === "default") return false;
|
|
643
|
+
const store = parsePersonaStore(getSetting("persona") ?? "{}");
|
|
644
|
+
const clean = parsePersona(JSON.stringify(
|
|
645
|
+
typeof card === "object" && card !== null && !Array.isArray(card) ? card : {}
|
|
646
|
+
));
|
|
647
|
+
if (Object.keys(clean).length) store.byAgent[id] = clean;
|
|
648
|
+
else delete store.byAgent[id];
|
|
649
|
+
setSetting("persona", serializePersonaStore(store));
|
|
650
|
+
return true;
|
|
651
|
+
},
|
|
652
|
+
/** byAgent 键列表(面板卡列表用,default 不在其中)。 */
|
|
653
|
+
listPersonas() {
|
|
654
|
+
return Object.keys(parsePersonaStore(getSetting("persona") ?? "{}").byAgent);
|
|
655
|
+
},
|
|
656
|
+
/** 删一张 byAgent 卡;default 不可删(返回 false)。 */
|
|
657
|
+
removePersona(agentId) {
|
|
658
|
+
const id = typeof agentId === "string" && agentId.trim() ? agentId.trim() : "";
|
|
659
|
+
if (!id || id === "default") return false;
|
|
660
|
+
const store = parsePersonaStore(getSetting("persona") ?? "{}");
|
|
661
|
+
if (!(id in store.byAgent)) return false;
|
|
662
|
+
delete store.byAgent[id];
|
|
663
|
+
setSetting("persona", serializePersonaStore(store));
|
|
664
|
+
return true;
|
|
665
|
+
},
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* 绑会话卡(#606):按 sessionId 存/删/列。形状与 setPersonaFor 一族逐一对应,
|
|
669
|
+
* 只是换了一个维度——为什么需要第二维见 parsePersonaStore 的注释。
|
|
670
|
+
* sessionId 为空或 "default" 拒绝(与 byAgent 同一条纪律:兜底位不可被顶替)。
|
|
671
|
+
*/
|
|
672
|
+
setPersonaForSession(sessionId, card) {
|
|
673
|
+
const id = typeof sessionId === "string" && sessionId.trim() ? sessionId.trim() : "";
|
|
674
|
+
if (!id || id === "default") return false;
|
|
675
|
+
const store = parsePersonaStore(getSetting("persona") ?? "{}");
|
|
676
|
+
const clean = parsePersona(JSON.stringify(
|
|
677
|
+
typeof card === "object" && card !== null && !Array.isArray(card) ? card : {}
|
|
678
|
+
));
|
|
679
|
+
if (Object.keys(clean).length) store.bySession[id] = clean;
|
|
680
|
+
else delete store.bySession[id];
|
|
681
|
+
setSetting("persona", serializePersonaStore(store));
|
|
682
|
+
return true;
|
|
683
|
+
},
|
|
684
|
+
/** bySession 键列表(面板「按会话建的卡」用,default 不在其中)。 */
|
|
685
|
+
listPersonaSessions() {
|
|
686
|
+
return Object.keys(parsePersonaStore(getSetting("persona") ?? "{}").bySession);
|
|
687
|
+
},
|
|
688
|
+
/** 删一张 bySession 卡;不存在返回 false。 */
|
|
689
|
+
removePersonaForSession(sessionId) {
|
|
690
|
+
const id = typeof sessionId === "string" && sessionId.trim() ? sessionId.trim() : "";
|
|
691
|
+
if (!id || id === "default") return false;
|
|
692
|
+
const store = parsePersonaStore(getSetting("persona") ?? "{}");
|
|
693
|
+
if (!(id in store.bySession)) return false;
|
|
694
|
+
delete store.bySession[id];
|
|
695
|
+
setSetting("persona", serializePersonaStore(store));
|
|
696
|
+
return true;
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
/** All custom commands, sorted by name. */
|
|
700
|
+
listCommands() {
|
|
701
|
+
const rows = db.prepare("SELECT * FROM custom_commands ORDER BY name ASC").all();
|
|
702
|
+
return rows.map(toCommand);
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Add or replace a custom command by name.
|
|
707
|
+
* @returns the stored command.
|
|
708
|
+
* @throws when name is invalid or does not match DSH's command-name grammar.
|
|
709
|
+
*/
|
|
710
|
+
addCommand({ name, description = "", instruction }) {
|
|
711
|
+
const cmdName = String(name ?? "").trim();
|
|
712
|
+
if (!COMMAND_NAME.test(cmdName)) {
|
|
713
|
+
throw new Error(`invalid command name "${cmdName}": must match /^[a-z][a-z0-9_-]*$/`);
|
|
714
|
+
}
|
|
715
|
+
if (typeof instruction !== "string" || !instruction.trim()) {
|
|
716
|
+
throw new Error("command instruction must be a non-empty string");
|
|
717
|
+
}
|
|
718
|
+
const now = new Date().toISOString();
|
|
719
|
+
const existing = db.prepare("SELECT id FROM custom_commands WHERE name = ?").get(cmdName);
|
|
720
|
+
if (existing) {
|
|
721
|
+
db.prepare(
|
|
722
|
+
"UPDATE custom_commands SET description = ?, instruction = ?, updated_at = ? WHERE id = ?"
|
|
723
|
+
).run(String(description ?? ""), instruction, now, existing.id);
|
|
724
|
+
return toCommand(db.prepare("SELECT * FROM custom_commands WHERE id = ?").get(existing.id));
|
|
725
|
+
}
|
|
726
|
+
const id = randomUUID();
|
|
727
|
+
db.prepare(
|
|
728
|
+
`INSERT INTO custom_commands (id, name, description, instruction, created_at, updated_at)
|
|
729
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
730
|
+
).run(id, cmdName, String(description ?? ""), instruction, now, now);
|
|
731
|
+
return toCommand(db.prepare("SELECT * FROM custom_commands WHERE id = ?").get(id));
|
|
732
|
+
},
|
|
733
|
+
|
|
734
|
+
/** Remove a custom command by id; returns true when removed. */
|
|
735
|
+
removeCommand(id) {
|
|
736
|
+
const result = db.prepare("DELETE FROM custom_commands WHERE id = ?").run(id);
|
|
737
|
+
return result.changes > 0;
|
|
738
|
+
},
|
|
739
|
+
|
|
740
|
+
/** Vector-search provider config (OpenAI-compatible embeddings endpoint). */
|
|
741
|
+
getVectorConfig() {
|
|
742
|
+
const raw = getSetting("vector");
|
|
743
|
+
if (!raw) return undefined;
|
|
744
|
+
try {
|
|
745
|
+
const cfg = JSON.parse(raw);
|
|
746
|
+
return typeof cfg === "object" && cfg !== null ? cfg : undefined;
|
|
747
|
+
} catch {
|
|
748
|
+
return undefined;
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
setVectorConfig({ enabled, baseUrl, apiKey, model }) {
|
|
752
|
+
const cfg = {
|
|
753
|
+
enabled: enabled === true || enabled === 1,
|
|
754
|
+
baseUrl: String(baseUrl ?? "").trim().replace(/\/+$/, ""),
|
|
755
|
+
apiKey: String(apiKey ?? "").trim(),
|
|
756
|
+
model: String(model ?? "").trim()
|
|
757
|
+
};
|
|
758
|
+
setSetting("vector", JSON.stringify(cfg));
|
|
759
|
+
return cfg;
|
|
760
|
+
},
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Standalone external API settings (kv "external_api"): {enabled, port,
|
|
764
|
+
* token}. The Bearer token is auto-generated on first boot and persisted
|
|
765
|
+
* here. Partial writes preserve the keys they don't mention.
|
|
766
|
+
*/
|
|
767
|
+
getExternalApi() {
|
|
768
|
+
const raw = getSetting("external_api");
|
|
769
|
+
if (!raw) return undefined;
|
|
770
|
+
try {
|
|
771
|
+
const cfg = JSON.parse(raw);
|
|
772
|
+
return typeof cfg === "object" && cfg !== null ? cfg : undefined;
|
|
773
|
+
} catch {
|
|
774
|
+
return undefined;
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
setExternalApi(patch = {}) {
|
|
778
|
+
const prev = this.getExternalApi() ?? {};
|
|
779
|
+
const port = Number(patch.port ?? prev.port);
|
|
780
|
+
const host = typeof patch.host === "string" && patch.host.trim() ? patch.host.trim() : (prev.host ?? "127.0.0.1");
|
|
781
|
+
const cfg = {
|
|
782
|
+
enabled: patch.enabled !== undefined ? patch.enabled === true : prev.enabled === true,
|
|
783
|
+
port: Number.isInteger(port) && port > 0 ? port : 8790,
|
|
784
|
+
host,
|
|
785
|
+
token: String(patch.token ?? prev.token ?? "")
|
|
786
|
+
};
|
|
787
|
+
setSetting("external_api", JSON.stringify(cfg));
|
|
788
|
+
return cfg;
|
|
789
|
+
},
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Web panel mode (kv "panel_mode"): "light" (low-resource preset) or
|
|
793
|
+
* "standard" (full feature set). Unset reads as "standard".
|
|
794
|
+
*/
|
|
795
|
+
getPanelMode() {
|
|
796
|
+
return getSetting("panel_mode") === "light" ? "light" : "standard";
|
|
797
|
+
},
|
|
798
|
+
setPanelMode(mode) {
|
|
799
|
+
setSetting("panel_mode", mode === "light" ? "light" : "standard");
|
|
800
|
+
},
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Feature flags(kv "feature_flags"):面板对后端能力的显式覆盖。读取只
|
|
804
|
+
* 返回白名单内的合法键(默认 {}),写入是逐键校验后的合并持久化。
|
|
805
|
+
*/
|
|
806
|
+
getFeatureFlags() {
|
|
807
|
+
const raw = getSetting("feature_flags");
|
|
808
|
+
if (!raw) return {};
|
|
809
|
+
try {
|
|
810
|
+
return sanitizeFlags(JSON.parse(raw));
|
|
811
|
+
} catch {
|
|
812
|
+
return {};
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
setFeatureFlags(patch) {
|
|
816
|
+
if (typeof patch !== "object" || patch === null || Array.isArray(patch)) {
|
|
817
|
+
throw new TypeError("feature flags patch must be a plain object");
|
|
818
|
+
}
|
|
819
|
+
const merged = this.getFeatureFlags();
|
|
820
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
821
|
+
merged[key] = validateFlag(key, value);
|
|
822
|
+
}
|
|
823
|
+
setSetting("feature_flags", JSON.stringify(merged));
|
|
824
|
+
return merged;
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
}
|