@modusensus/dsh-mneme 0.13.0 → 0.14.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +830 -828
- package/assets/icon.svg +3 -3
- package/bin/cli.mjs +964 -964
- package/bin/dsh-mneme-choro.mjs +262 -254
- package/bin/dsh-mneme-mcp.mjs +510 -510
- package/bin/dsh-mneme-serve.mjs +151 -151
- package/cordis.patch.yml +21 -21
- package/docs/CONFIGURATION.md +407 -402
- package/docs/CONTINUITY.md +139 -139
- package/docs/DAEMON.md +57 -57
- package/docs/DIRECTORIES.md +114 -114
- package/docs/DISTILL.md +397 -371
- package/docs/DSH-GO.md +97 -97
- package/docs/ENTITIES.md +247 -247
- package/docs/FORGET.md +77 -77
- package/docs/KB.md +110 -110
- package/docs/LOCAL_MODEL.md +222 -222
- package/docs/SEMANTIC.md +256 -256
- package/docs/SHARE.md +552 -552
- package/docs/SLEEP.md +202 -202
- package/docs/STORAGE.md +122 -122
- package/docs/WORKFLOWS.md +366 -366
- package/lib/api-standalone.js +1007 -1007
- package/lib/api.js +3600 -3394
- package/lib/attachment-text.js +122 -122
- package/lib/bootstrap-http.js +105 -105
- package/lib/bootstrap.js +215 -215
- package/lib/candidates.js +454 -454
- package/lib/capabilities.js +819 -819
- package/lib/choro-command.js +111 -111
- package/lib/client.js +14420 -13436
- package/lib/commands.js +95 -95
- package/lib/config-keys.js +144 -144
- package/lib/config.js +1361 -1317
- package/lib/content-hash.js +54 -54
- package/lib/continuity.js +258 -258
- package/lib/date-hint.js +32 -0
- package/lib/diary/auto.js +190 -190
- package/lib/diary/compose.js +440 -440
- package/lib/diary/dir.js +59 -59
- package/lib/diary/export.js +97 -97
- package/lib/diary/index.js +26 -26
- package/lib/diary/library.js +189 -189
- package/lib/diary/qq-inbox.js +142 -142
- package/lib/diary/rollover.js +61 -61
- package/lib/diary/session-provider.js +122 -122
- package/lib/document-index.js +94 -94
- package/lib/document.js +322 -322
- package/lib/dream/clustering.js +123 -123
- package/lib/dream/decisions.js +945 -907
- package/lib/dream/narratives.js +44 -44
- package/lib/dream/sleep.js +2027 -1887
- package/lib/dream/triage.js +139 -0
- package/lib/dream.js +1879 -1764
- package/lib/dsh-go/host.js +162 -162
- package/lib/dsh-go/panel.js +2091 -2091
- package/lib/dsh-go/text.js +342 -342
- package/lib/dsh-go/views/common.js +68 -68
- package/lib/dsh-go/views/entity.js +48 -48
- package/lib/dsh-go/views/memory.js +198 -198
- package/lib/dsh-go/views/root.js +30 -30
- package/lib/dsh-go/views/settings.js +398 -398
- package/lib/dsh-go/views/status.js +51 -51
- package/lib/embedding.js +342 -342
- package/lib/entities/extractor.js +285 -285
- package/lib/fence.js +24 -24
- package/lib/graph/anchoring.js +184 -184
- package/lib/guide.js +74 -74
- package/lib/heat.js +144 -144
- package/lib/hot-memory.js +119 -107
- package/lib/image-describe.js +158 -0
- package/lib/index.js +852 -822
- package/lib/inject.js +656 -639
- package/lib/kb/index.js +309 -309
- package/lib/kb/routes.js +219 -219
- package/lib/kb/tools.js +148 -148
- package/lib/lang.js +607 -584
- package/lib/local-embedder.js +358 -358
- package/lib/maintenance.js +162 -162
- package/lib/memory-source.js +9 -9
- package/lib/mirror.js +330 -330
- package/lib/organize.js +456 -456
- package/lib/peak-hours.js +172 -172
- package/lib/pipeline-activities.js +178 -178
- package/lib/pipeline-attribution.js +165 -165
- package/lib/pipeline-command.js +279 -279
- package/lib/pipeline-confirm.js +142 -142
- package/lib/pipeline-exec.js +174 -174
- package/lib/pipeline-nudge-auto.js +129 -129
- package/lib/pipeline-nudge.js +1623 -1623
- package/lib/pipeline-observations.js +181 -181
- package/lib/pipeline-observe.js +133 -133
- package/lib/pipeline-runner.js +361 -361
- package/lib/pipeline-runs.js +442 -442
- package/lib/pipeline.js +716 -716
- package/lib/placement.js +13 -13
- package/lib/pre-inject-gate.js +354 -354
- package/lib/profile-extract.js +164 -164
- package/lib/profile-facts.js +356 -356
- package/lib/provenance-http.js +251 -251
- package/lib/quality-filter.js +162 -162
- package/lib/recall-stats.js +190 -190
- package/lib/reclaim-http.js +150 -150
- package/lib/reranker.js +269 -269
- package/lib/runtime/adopt.js +176 -176
- package/lib/runtime/closure.js +172 -172
- package/lib/runtime/download.js +247 -247
- package/lib/runtime/layout.js +265 -265
- package/lib/runtime/loader.js +252 -252
- package/lib/runtime/model-fetch.js +361 -361
- package/lib/runtime/provision.js +237 -237
- package/lib/runtime/tarball.js +130 -130
- package/lib/runtime/verify.js +223 -223
- package/lib/safe-slice.js +18 -18
- package/lib/scope-state.js +277 -277
- package/lib/scope.js +187 -187
- package/lib/search/adaptive.js +60 -60
- package/lib/search/bm25.js +96 -96
- package/lib/semantic.js +572 -572
- package/lib/sensitive-scan.js +209 -209
- package/lib/serve.js +228 -228
- package/lib/service.js +3268 -3196
- package/lib/session-gate.js +91 -91
- package/lib/session-usage.js +350 -350
- package/lib/settings.js +827 -802
- package/lib/share-http.js +281 -281
- package/lib/share-import.js +234 -234
- package/lib/share.js +335 -335
- package/lib/store.js +5895 -5662
- package/lib/summarize.js +1388 -1343
- package/lib/tools.js +1370 -1356
- package/lib/user-settings.js +230 -230
- package/lib/vector-index.js +180 -180
- package/lib/version-check.js +122 -122
- package/lib/wiki/claims.js +267 -0
- package/lib/wiki/cli-hint.js +90 -90
- package/lib/wiki/inject.js +164 -164
- package/lib/wiki/partition.js +200 -200
- package/lib/wiki/plan.js +214 -214
- package/lib/wiki/queries.js +640 -474
- package/lib/wiki/render.js +261 -187
- package/lib/wiki/scan.js +371 -371
- package/lib/wiki/t0.js +500 -463
- package/lib/wiki/t1-jobs.js +232 -232
- package/lib/wiki/t1.js +609 -502
- package/lib/write-admission.js +404 -404
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +15 -1
- package/runtime-manifest.json +526 -526
- package/scripts/benchmark-embed.js +201 -201
- package/scripts/benchmark-recall.js +249 -249
- package/scripts/benchmark-rerank.js +166 -166
- package/scripts/build-runtime-manifest.mjs +224 -224
- package/scripts/changelog-prep.mjs +91 -91
- package/scripts/check-sync.js +44 -44
- package/scripts/e2e-dsh.js +220 -220
- package/scripts/mneme-runtime.mjs +277 -277
- package/scripts/publish-gate.mjs +197 -197
- package/scripts/release-docs.mjs +210 -204
- package/scripts/release-notes.mjs +164 -161
- package/scripts/stress-dsh.js +255 -255
- package/scripts/sync-lib.js +47 -47
- package/scripts/sync-test-badge.mjs +69 -69
- package/scripts/test-count-sync.mjs +48 -48
- package/src/api-standalone.js +1007 -1007
- package/src/api.js +3600 -3394
- package/src/attachment-text.js +122 -122
- package/src/bootstrap-http.js +105 -105
- package/src/bootstrap.js +215 -215
- package/src/candidates.js +454 -454
- package/src/capabilities.js +819 -819
- package/src/choro-command.js +111 -111
- package/src/commands.js +95 -95
- package/src/config-keys.js +144 -144
- package/src/config.js +1361 -1317
- package/src/content-hash.js +54 -54
- package/src/continuity.js +258 -258
- package/src/date-hint.js +32 -0
- package/src/diary/auto.js +190 -190
- package/src/diary/compose.js +440 -440
- package/src/diary/dir.js +59 -59
- package/src/diary/export.js +97 -97
- package/src/diary/index.js +26 -26
- package/src/diary/library.js +189 -189
- package/src/diary/qq-inbox.js +142 -142
- package/src/diary/rollover.js +61 -61
- package/src/diary/session-provider.js +122 -122
- package/src/document-index.js +94 -94
- package/src/document.js +322 -322
- package/src/dream/clustering.js +123 -123
- package/src/dream/decisions.js +945 -907
- package/src/dream/narratives.js +44 -44
- package/src/dream/sleep.js +2027 -1887
- package/src/dream/triage.js +139 -0
- package/src/dream.js +1879 -1764
- package/src/dsh-go/host.js +162 -162
- package/src/dsh-go/panel.js +2091 -2091
- package/src/dsh-go/text.js +342 -342
- package/src/dsh-go/views/common.js +68 -68
- package/src/dsh-go/views/entity.js +48 -48
- package/src/dsh-go/views/memory.js +198 -198
- package/src/dsh-go/views/root.js +30 -30
- package/src/dsh-go/views/settings.js +398 -398
- package/src/dsh-go/views/status.js +51 -51
- package/src/embedding.js +342 -342
- package/src/entities/extractor.js +285 -285
- package/src/fence.js +24 -24
- package/src/graph/anchoring.js +184 -184
- package/src/guide.js +74 -74
- package/src/heat.js +144 -144
- package/src/hot-memory.js +119 -107
- package/src/image-describe.js +158 -0
- package/src/index.js +852 -822
- package/src/inject.js +656 -639
- package/src/kb/index.js +309 -309
- package/src/kb/routes.js +219 -219
- package/src/kb/tools.js +148 -148
- package/src/lang.js +607 -584
- package/src/local-embedder.js +358 -358
- package/src/maintenance.js +162 -162
- package/src/memory-source.js +9 -9
- package/src/mirror.js +330 -330
- package/src/organize.js +456 -456
- package/src/peak-hours.js +172 -172
- package/src/pipeline-activities.js +178 -178
- package/src/pipeline-attribution.js +165 -165
- package/src/pipeline-command.js +279 -279
- package/src/pipeline-confirm.js +142 -142
- package/src/pipeline-exec.js +174 -174
- package/src/pipeline-nudge-auto.js +129 -129
- package/src/pipeline-nudge.js +1623 -1623
- package/src/pipeline-observations.js +181 -181
- package/src/pipeline-observe.js +133 -133
- package/src/pipeline-runner.js +361 -361
- package/src/pipeline-runs.js +442 -442
- package/src/pipeline.js +716 -716
- package/src/placement.js +13 -13
- package/src/pre-inject-gate.js +354 -354
- package/src/profile-extract.js +164 -164
- package/src/profile-facts.js +356 -356
- package/src/provenance-http.js +251 -251
- package/src/quality-filter.js +162 -162
- package/src/recall-stats.js +190 -190
- package/src/reclaim-http.js +150 -150
- package/src/reranker.js +269 -269
- package/src/runtime/adopt.js +176 -176
- package/src/runtime/closure.js +172 -172
- package/src/runtime/download.js +247 -247
- package/src/runtime/layout.js +265 -265
- package/src/runtime/loader.js +252 -252
- package/src/runtime/model-fetch.js +361 -361
- package/src/runtime/provision.js +237 -237
- package/src/runtime/tarball.js +130 -130
- package/src/runtime/verify.js +223 -223
- package/src/safe-slice.js +18 -18
- package/src/scope-state.js +277 -277
- package/src/scope.js +187 -187
- package/src/search/adaptive.js +60 -60
- package/src/search/bm25.js +96 -96
- package/src/semantic.js +572 -572
- package/src/sensitive-scan.js +209 -209
- package/src/serve.js +228 -228
- package/src/service.js +3268 -3196
- package/src/session-gate.js +91 -91
- package/src/session-usage.js +350 -350
- package/src/settings.js +827 -802
- package/src/share-http.js +281 -281
- package/src/share-import.js +234 -234
- package/src/share.js +335 -335
- package/src/store.js +5895 -5662
- package/src/summarize.js +1388 -1343
- package/src/tools.js +1370 -1356
- package/src/user-settings.js +230 -230
- package/src/vector-index.js +180 -180
- package/src/version-check.js +122 -122
- package/src/wiki/claims.js +267 -0
- package/src/wiki/cli-hint.js +90 -90
- package/src/wiki/inject.js +164 -164
- package/src/wiki/partition.js +200 -200
- package/src/wiki/plan.js +214 -214
- package/src/wiki/queries.js +640 -474
- package/src/wiki/render.js +261 -187
- package/src/wiki/scan.js +371 -371
- package/src/wiki/t0.js +500 -463
- package/src/wiki/t1-jobs.js +232 -232
- package/src/wiki/t1.js +609 -502
- package/src/write-admission.js +404 -404
- package/test/adaptive-inject-budget.test.js +85 -85
- package/test/api-hardening.test.js +173 -173
- package/test/api-nudge-route.test.js +259 -259
- package/test/api-routes-gaps.test.js +1092 -1092
- package/test/api.test.js +3007 -2988
- package/test/attachment-text.test.js +139 -139
- package/test/audit.test.js +448 -448
- package/test/baseline-persist.test.js +248 -248
- package/test/benchmark.test.js +94 -94
- package/test/bm25-corpus-cache.test.js +145 -145
- package/test/bootstrap.test.js +228 -228
- package/test/candidates.test.js +345 -345
- package/test/capabilities.test.js +577 -577
- package/test/capacity-warning.test.js +262 -262
- package/test/changelog-prep.test.js +125 -125
- package/test/choro-command.test.js +218 -218
- package/test/cli-enums.test.js +37 -37
- package/test/cli-flag-value.test.js +86 -86
- package/test/cli-receive.test.js +390 -390
- package/test/cli-share.test.js +437 -437
- package/test/client-share.test.js +225 -225
- package/test/client.test.js +4595 -4184
- package/test/clustering.test.js +100 -100
- package/test/commands.test.js +106 -106
- package/test/config-keys-routes.test.js +233 -233
- package/test/config.test.js +153 -153
- package/test/conflict-freeze.test.js +344 -344
- package/test/conflict-ledger-enforce.test.js +452 -0
- package/test/conflict-ledger.test.js +242 -0
- package/test/conflict-reviewed.test.js +79 -79
- package/test/content-hash.test.js +204 -204
- package/test/continuity.test.js +452 -452
- package/test/contrast.test.js +432 -320
- package/test/date-hint.test.js +156 -0
- package/test/dedupe-candidate-window.test.js +180 -180
- package/test/dependency-overrides.test.js +78 -78
- package/test/diary-library.test.js +187 -187
- package/test/diary.test.js +576 -576
- package/test/differentiate-feedback.test.js +248 -248
- package/test/distill-tx-embed-gap.test.js +110 -110
- package/test/docs-config-keys.test.js +65 -65
- package/test/document-dir.test.js +233 -233
- package/test/document.test.js +703 -703
- package/test/dream-archive-guardrail.test.js +116 -116
- package/test/dream-baseline-clamp.test.js +174 -0
- package/test/dream-candidate-hybrid.test.js +281 -281
- package/test/dream-coverage-degrade.test.js +86 -86
- package/test/dream-decision-evidence.test.js +314 -314
- package/test/dream-failure-backoff.test.js +173 -173
- package/test/dream-fence.test.js +207 -207
- package/test/dream-maxtokens-fallback.test.js +132 -132
- package/test/dream-merge-guardrail.test.js +219 -219
- package/test/dream-peak-hours.test.js +194 -194
- package/test/dream.test.js +1528 -1528
- package/test/dsh-go-host.test.js +134 -134
- package/test/dsh-go-panel-actions.test.js +854 -854
- package/test/dsh-go-panel.test.js +251 -251
- package/test/embed-ready-poll.test.js +94 -94
- package/test/embed-text-reasons.test.js +112 -112
- package/test/embedding-cache-f32.test.js +276 -276
- package/test/embedding-ready.test.js +226 -226
- package/test/entities.test.js +711 -711
- package/test/entity-recall.test.js +179 -179
- package/test/epistemic.test.js +325 -325
- package/test/failure-query.test.js +147 -147
- package/test/failure-retention.test.js +105 -105
- package/test/fnew-0112.test.js +311 -311
- package/test/fnew-03.test.js +422 -422
- package/test/forget-corrections.test.js +70 -70
- package/test/graph-anchoring.test.js +184 -184
- package/test/graph-api.test.js +333 -209
- package/test/graph-boosted-relations.test.js +189 -189
- package/test/graph-inject-budget.test.js +103 -103
- package/test/graph-multipath.test.js +269 -269
- package/test/graph-passive-confirm.test.js +91 -91
- package/test/graph-weight-evolution.test.js +104 -104
- package/test/heat.test.js +175 -175
- package/test/helpers/dream-mock.js +86 -86
- package/test/helpers/peer-worker.mjs +25 -25
- package/test/helpers/runtime-source.js +60 -60
- package/test/helpers/tar-builder.js +80 -80
- package/test/helpers/write-admission-samples.js +312 -312
- package/test/hot-memory.test.js +259 -211
- package/test/image-description.test.js +271 -0
- package/test/image-memory.test.js +170 -170
- package/test/inject-content-cap.test.js +91 -91
- package/test/inject-first-round-bm25.test.js +89 -89
- package/test/inject-heat.test.js +77 -77
- package/test/inject-parent-gate.test.js +134 -106
- package/test/inject-pin-explicit.test.js +300 -300
- package/test/inject-pin-vector-crowd.test.js +225 -225
- package/test/inject-pools.test.js +195 -195
- package/test/inject-recall.test.js +172 -172
- package/test/inject-rotation.test.js +161 -161
- package/test/inject.test.js +553 -494
- package/test/issue-617-step1-query-and-fusion.test.js +361 -361
- package/test/kb-api-wiring.test.js +128 -128
- package/test/kb-collections.test.js +272 -272
- package/test/kb-routes.test.js +379 -379
- package/test/lang.test.js +98 -98
- package/test/lastrunat-persist.test.js +101 -101
- package/test/lib-smoke.test.js +116 -116
- package/test/llm-audit.test.js +815 -815
- package/test/llm-message-source.test.js +207 -207
- package/test/local-embedder.test.js +292 -292
- package/test/maintenance.test.js +501 -501
- package/test/mcp-stdio.test.js +385 -385
- package/test/mint-source.test.js +311 -311
- package/test/mirror-dirty.test.js +424 -424
- package/test/mirror-edit-digest.test.js +187 -187
- package/test/mirror-generation.test.js +499 -499
- package/test/mirror-types.test.js +208 -208
- package/test/mirror.test.js +249 -249
- package/test/narrative-bars.test.js +162 -162
- package/test/organize-tool.test.js +232 -232
- package/test/organize.test.js +418 -418
- package/test/panel-chart-scale.test.js +90 -0
- package/test/panel-tokens.test.js +245 -0
- package/test/peak-hours-observability.test.js +70 -70
- package/test/peer-blockers.test.js +193 -193
- package/test/persona-addressing.test.js +83 -83
- package/test/persona-api.test.js +172 -172
- package/test/persona-by-session.test.js +568 -568
- package/test/persona-flag.test.js +36 -36
- package/test/persona-inject.test.js +145 -145
- package/test/persona-settings.test.js +198 -198
- package/test/pipeline-activities.test.js +155 -155
- package/test/pipeline-attribution.test.js +175 -175
- package/test/pipeline-command.test.js +243 -243
- package/test/pipeline-confirm.test.js +148 -148
- package/test/pipeline-exec.test.js +172 -172
- package/test/pipeline-nudge-auto.test.js +248 -248
- package/test/pipeline-nudge.test.js +1278 -1278
- package/test/pipeline-observations.test.js +157 -157
- package/test/pipeline-observe.test.js +127 -127
- package/test/pipeline-r1r2.test.js +278 -278
- package/test/pipeline-runner.test.js +468 -468
- package/test/pipeline-runs.test.js +767 -767
- package/test/pipeline.test.js +636 -636
- package/test/placement.test.js +36 -36
- package/test/policy-epoch.test.js +259 -259
- package/test/pre-inject-gate.test.js +444 -444
- package/test/profile-extract.test.js +269 -269
- package/test/profile-facts.test.js +492 -492
- package/test/prompt-stance.test.js +128 -128
- package/test/provenance-http.test.js +367 -367
- package/test/publish-gate.test.js +272 -272
- package/test/quality-backfill.test.js +290 -290
- package/test/quality-filter.test.js +295 -295
- package/test/readme-claims.test.js +113 -113
- package/test/reasoning-effort.test.js +752 -752
- package/test/recall-anti-update.test.js +82 -82
- package/test/recall-evals.test.js +253 -253
- package/test/recall-layer.test.js +326 -326
- package/test/recall-retention.test.js +128 -128
- package/test/recall-stats.test.js +274 -274
- package/test/receipt-chain.test.js +451 -451
- package/test/reclaim-http.test.js +209 -209
- package/test/reflection-update-gate.test.js +129 -129
- package/test/reflection.test.js +226 -226
- package/test/reindex-backfill.test.js +475 -475
- package/test/reindex-scheduler.test.js +369 -369
- package/test/release-docs.test.js +156 -136
- package/test/release-notes.test.js +265 -240
- package/test/release-workflow.test.js +93 -93
- package/test/reranker-single-logit.test.js +73 -73
- package/test/reranker.test.js +197 -197
- package/test/resident-status.test.js +83 -83
- package/test/route-policy.test.js +113 -113
- package/test/runtime-adopt.test.js +206 -206
- package/test/runtime-cli.test.js +89 -89
- package/test/runtime-closure.test.js +193 -193
- package/test/runtime-download.test.js +302 -302
- package/test/runtime-layout.test.js +163 -163
- package/test/runtime-loader.test.js +255 -255
- package/test/runtime-manifest.test.js +159 -159
- package/test/runtime-model-fetch.test.js +348 -348
- package/test/runtime-provision.test.js +101 -101
- package/test/runtime-tarball.test.js +81 -81
- package/test/runtime-verify.test.js +319 -319
- package/test/scope-candidates.test.js +491 -491
- package/test/scope-distill-stamp.test.js +134 -134
- package/test/scope-explicit.test.js +452 -452
- package/test/scope-inject-soft.test.js +106 -106
- package/test/scope-optional-service.test.js +110 -110
- package/test/scope-retrieval.test.js +197 -197
- package/test/scope-state.test.js +610 -610
- package/test/scope-storage.test.js +255 -255
- package/test/scope-strict.test.js +367 -367
- package/test/search-fusion.test.js +90 -90
- package/test/security-doc.test.js +188 -178
- package/test/semantic.test.js +124 -124
- package/test/sensitive-scan.test.js +251 -251
- package/test/serve-bin.test.js +121 -121
- package/test/serve.test.js +258 -258
- package/test/service-search.test.js +174 -174
- package/test/service.test.js +410 -410
- package/test/session-gate.test.js +264 -264
- package/test/session-usage.test.js +317 -317
- package/test/settings.test.js +273 -273
- package/test/share-foreign-rows.test.js +144 -144
- package/test/share-http.test.js +144 -144
- package/test/share-import.test.js +577 -577
- package/test/share-imported-guard.test.js +311 -311
- package/test/share-imported-identity.test.js +111 -111
- package/test/share-pack.test.js +511 -511
- package/test/share-receive-http.test.js +756 -756
- package/test/share-routes.test.js +337 -337
- package/test/share-store.test.js +133 -133
- package/test/share-validate.test.js +101 -101
- package/test/skip-invalid-restore.test.js +262 -262
- package/test/sleep-action-set.test.js +441 -441
- package/test/sleep-catchup.test.js +315 -315
- package/test/sleep-failure-backoff.test.js +156 -156
- package/test/sleep-heat.test.js +174 -174
- package/test/sleep-scheduler-rearm.test.js +89 -89
- package/test/sleep.test.js +1455 -1418
- package/test/standalone-api.test.js +1344 -1344
- package/test/store-vector-cache.test.js +98 -98
- package/test/store-write-path-columns.test.js +191 -191
- package/test/store.test.js +744 -744
- package/test/stress.test.js +209 -209
- package/test/summarize-occurred-at.test.js +127 -127
- package/test/summarize-parse-diagnostics.test.js +75 -75
- package/test/summarize-reasoning-effort.test.js +189 -189
- package/test/summarize.test.js +2251 -2251
- package/test/supersede-state-tracking.test.js +394 -394
- package/test/test-count-sync.test.js +71 -71
- package/test/tool-state-ignore.test.js +65 -65
- package/test/tools-dto-schema-matrix.test.js +398 -398
- package/test/tools-memory-get-schema.test.js +65 -65
- package/test/tools.test.js +585 -585
- package/test/updated-at-semantics.test.js +113 -113
- package/test/user-settings-preview.test.js +127 -127
- package/test/user-settings-segments.test.js +527 -527
- package/test/vector-index.test.js +334 -334
- package/test/version-check.test.js +152 -152
- package/test/webServer-optional.test.js +82 -82
- package/test/wiki-api.test.js +734 -627
- package/test/wiki-claims.test.js +194 -0
- package/test/wiki-cli-hint.test.js +118 -118
- package/test/wiki-inject.test.js +265 -265
- package/test/wiki-partition.test.js +112 -112
- package/test/wiki-plan.test.js +80 -80
- package/test/wiki-provenance.test.js +202 -0
- package/test/wiki-queries.test.js +398 -277
- package/test/wiki-scan.test.js +208 -208
- package/test/wiki-store.test.js +190 -190
- package/test/wiki-t0.test.js +456 -380
- package/test/wiki-t1-jobs.test.js +125 -125
- package/test/wiki-t1.test.js +460 -235
- package/test/wiki-tools.test.js +176 -176
- package/test/workflow-guards.test.js +155 -155
- package/test/write-admission.test.js +668 -668
package/src/peak-hours.js
CHANGED
|
@@ -1,172 +1,172 @@
|
|
|
1
|
-
// 高峰时段(peak hours)解析与判定——纯函数模块,零依赖。
|
|
2
|
-
//
|
|
3
|
-
// 从 summarize.js 抽出(PR #320 review):#316 之后 summarize.js 反向依赖
|
|
4
|
-
// dream.js(withEffortFallback 复用),dream 镜像错峰时再 import summarize
|
|
5
|
-
// 会成真循环。时段调度本来就不是蒸馏的私有语义,独立成模块后两侧都从这里
|
|
6
|
-
// import,循环消失。re-export 兼容留在 summarize.js,调用方零改动。
|
|
7
|
-
//
|
|
8
|
-
// Issue #239(第 4 项,错峰队列):高峰时段解析与判定。纯函数、可单测——排程
|
|
9
|
-
// 判断不绑死真实时钟,测试才能确定性地覆盖跨零点、多段、星期过滤与非法写法。
|
|
10
|
-
// spec 语法:`[<星期> ]<时段>[,<时段>...]`,星期前缀可省(省 = 每天):
|
|
11
|
-
// "09:00-18:00" 每天 09:00-18:00
|
|
12
|
-
// "mon-fri 08:00-12:00,14:00-18:00" 工作日两段(ISO 1=周一…7=周日,也认 mon..sun)
|
|
13
|
-
// "sat,sun 23:00-06:00" 周末跨零点段
|
|
14
|
-
// 时间取宿主本地时区。任一写法非法 → 整串视为未配置(返回 null):排程是省钱手段,
|
|
15
|
-
// 绝不该因为写错格式把蒸馏/巩固停掉。全角等价符号在解析前归一成半角(#424),
|
|
16
|
-
// 见 parsePeakSpec 上方的 normalizeFullwidth。
|
|
17
|
-
const DAY_NAMES = { mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6, sun: 7 };
|
|
18
|
-
|
|
19
|
-
function parseDayToken(token) {
|
|
20
|
-
const days = new Set();
|
|
21
|
-
const normalize = (value) => (/^\d$/.test(value) ? Number(value) : DAY_NAMES[value] ?? null);
|
|
22
|
-
for (const piece of token.split(",")) {
|
|
23
|
-
const matched = /^([a-z]{3}|\d)(?:-([a-z]{3}|\d))?$/.exec(piece.trim().toLowerCase());
|
|
24
|
-
if (!matched) return null;
|
|
25
|
-
const from = normalize(matched[1]);
|
|
26
|
-
const to = matched[2] === undefined ? from : normalize(matched[2]);
|
|
27
|
-
if (from === null || to === null || from < 1 || from > 7 || to < 1 || to > 7) return null;
|
|
28
|
-
// 支持跨周环绕(fri-mon):从 from 起逐天推进到 to,最多绕一圈。
|
|
29
|
-
for (let day = from; ; day = (day % 7) + 1) {
|
|
30
|
-
days.add(day);
|
|
31
|
-
if (day === to) break;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return days.size > 0 ? [...days].sort((a, b) => a - b) : null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** JS 的 getDay() 是 0=周日…6=周六;这里统一成 ISO(1=周一…7=周日)。 */
|
|
38
|
-
function isoDay(date) {
|
|
39
|
-
const day = date.getDay();
|
|
40
|
-
return day === 0 ? 7 : day;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// #424:全角等价符号归一。中文输入法下写时段串,逗号/冒号/数字跟着输入法走成全角
|
|
44
|
-
// 是常态(#424 实测的失败写法里全角类占四种);而解析失败的语义是「未配置 =
|
|
45
|
-
// 不限制」(见文件头),错一个字符整串静默失效 = 错峰闸门悄悄关掉、巩固回到
|
|
46
|
-
// 全天候——这正是 #424 报的现象。这里只做**等价映射**,不做救错:
|
|
47
|
-
// * U+FF01–FF5D(全角!到全角])与 ASCII 0x21–0x7D 恒差 0xFEE0,整段一起折,
|
|
48
|
-
// 覆盖 ,:0-9-与全角字母(星期前缀一起受益);
|
|
49
|
-
// * 两个例外码不走折叠,直接映成 '-':全角~(U+FF5E)折回半角是 `~`,而 `~` 是
|
|
50
|
-
// 既有契约里故意非法的连接号写法;〜(U+301C)是波浪号的另一半,同理。
|
|
51
|
-
// 合法写法只含 ASCII,归一前合法的串归一后逐字节不变,语义零漂移;
|
|
52
|
-
// `24:00` 这类**值本身写错**的串仍按非法处理(跨零点的正写是 `04:00-00:00`),
|
|
53
|
-
// 归一化不背救错的锅。
|
|
54
|
-
const FULLWIDTH_EQUIVALENTS = /[\uFF01-\uFF5E\u301C]/g;
|
|
55
|
-
|
|
56
|
-
function normalizeFullwidth(spec) {
|
|
57
|
-
return spec.replace(FULLWIDTH_EQUIVALENTS, (ch) => {
|
|
58
|
-
const code = ch.charCodeAt(0);
|
|
59
|
-
if (code === 0xff5e || code === 0x301c) return "-";
|
|
60
|
-
return String.fromCharCode(code - 0xfee0);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function parsePeakSpec(spec) {
|
|
65
|
-
if (typeof spec !== "string" || spec.trim() === "") return null;
|
|
66
|
-
let rest = normalizeFullwidth(spec).trim();
|
|
67
|
-
let days = null;
|
|
68
|
-
// 星期前缀 = 第一个空白之前的部分,但**头段含冒号就不是前缀**(那是时段本身,
|
|
69
|
-
// 例如 "09:00-12:00, 14:00-18:00" 里的逗号空格)。前缀解析失败一律按未配置处理,
|
|
70
|
-
// 不做猜测——宁可不省,也不能误停。
|
|
71
|
-
const sep = rest.search(/\s/);
|
|
72
|
-
if (sep > 0) {
|
|
73
|
-
const head = rest.slice(0, sep);
|
|
74
|
-
if (!head.includes(":")) {
|
|
75
|
-
days = parseDayToken(head);
|
|
76
|
-
if (days === null) return null;
|
|
77
|
-
rest = rest.slice(sep).trim();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const windows = [];
|
|
81
|
-
for (const part of rest.split(",")) {
|
|
82
|
-
const matched = /^\s*(\d{1,2}):(\d{2})\s*-\s*(\d{1,2}):(\d{2})\s*$/.exec(part);
|
|
83
|
-
if (!matched) return null;
|
|
84
|
-
const [sh, sm, eh, em] = [Number(matched[1]), Number(matched[2]), Number(matched[3]), Number(matched[4])];
|
|
85
|
-
if (sh > 23 || eh > 23 || sm > 59 || em > 59) return null;
|
|
86
|
-
const start = sh * 60 + sm;
|
|
87
|
-
const end = eh * 60 + em;
|
|
88
|
-
if (start === end) return null;
|
|
89
|
-
windows.push({ start, end });
|
|
90
|
-
}
|
|
91
|
-
return windows.length > 0 ? { days, windows } : null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// --- 可观测性(issue #622):解析失败不许静默 ---------------------------------
|
|
95
|
-
//
|
|
96
|
-
// 上面的失败语义(整串视为未配置)是对的——排程是省钱手段,绝不该因为写错格式
|
|
97
|
-
// 把蒸馏/巩固停掉。但它的代价是:**配置写错的唯一症状是「功能悄悄没生效」**——
|
|
98
|
-
// 用户明明写了 summarizePeakHours / dreamPeakHours,排程却回到全天候,日志、
|
|
99
|
-
// 面板、审计里没有任何痕迹(#424 主楼的排查只能靠「高峰期还在巩固」倒推)。
|
|
100
|
-
// 归一化类补救(PR #621 的全角等价)收得掉一小类,但 24:00 越界、点号当冒号这类
|
|
101
|
-
// 只能靠告警——归一化不救错,静默失效面永远存在。本节只补**可见性**,不动语义:
|
|
102
|
-
// helper 只转述 parsePeakSpec 的话,解析口径变了(如 #621 落地)告警面自动跟随,
|
|
103
|
-
// 两批无需联动。
|
|
104
|
-
//
|
|
105
|
-
// 两个键名单独成表:告警点(index.js 启动装配)与提示点(/features 答复、面板
|
|
106
|
-
// 输入行)都要认同一份「哪些键是时段键」,不各写一份字符串数组然后各自漂移。
|
|
107
|
-
export const PEAK_SPEC_KEYS = ["summarizePeakHours", "dreamPeakHours"];
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 找出「非空且解析失败」的时段配置。空串是合法的「未配置」(调用方用 `?? ""`
|
|
111
|
-
* 兜底),不进结果——否则每个没配错峰的用户开机都被警告一遍(#622 验收第 2 条)。
|
|
112
|
-
* 非法串映射 key → 原串:告警要带原串才能对上配置文件里那一行;/features 答复
|
|
113
|
-
* 只取 Object.keys(面板只需要「哪个键坏了」,不需要回显值)。
|
|
114
|
-
*/
|
|
115
|
-
export function invalidPeakSpecs(cfg) {
|
|
116
|
-
const out = {};
|
|
117
|
-
if (!cfg || typeof cfg !== "object") return out;
|
|
118
|
-
for (const key of PEAK_SPEC_KEYS) {
|
|
119
|
-
const spec = cfg[key];
|
|
120
|
-
if (typeof spec === "string" && spec.trim() !== "" && parsePeakSpec(spec) === null) {
|
|
121
|
-
out[key] = spec;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return out;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* 该时刻是否落在高峰内。跨零点段(start > end)按「窗口所属的那一天」认星期:
|
|
129
|
-
* `mon-fri 23:00-06:00` 的周六 02:00 属于周五开的那个窗口,仍算高峰。
|
|
130
|
-
*/
|
|
131
|
-
export function isInPeakWindow(date, spec) {
|
|
132
|
-
const parsed = parsePeakSpec(spec);
|
|
133
|
-
if (!parsed) return false;
|
|
134
|
-
const minutes = date.getHours() * 60 + date.getMinutes();
|
|
135
|
-
const today = isoDay(date);
|
|
136
|
-
const yesterday = today === 1 ? 7 : today - 1;
|
|
137
|
-
const allowed = (day) => !parsed.days || parsed.days.includes(day);
|
|
138
|
-
return parsed.windows.some(({ start, end }) => {
|
|
139
|
-
if (start < end) return minutes >= start && minutes < end && allowed(today);
|
|
140
|
-
return (minutes >= start && allowed(today)) || (minutes < end && allowed(yesterday));
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* 高峰内则返回「距当前最近的一个高峰结束时刻」(择时补跑用),否则 null。
|
|
146
|
-
* 落在多个时段重叠处时取最早结束的那个——早跑不亏,晚跑才亏。
|
|
147
|
-
*/
|
|
148
|
-
export function nextOffPeakAt(date, spec) {
|
|
149
|
-
const parsed = parsePeakSpec(spec);
|
|
150
|
-
if (!parsed) return null;
|
|
151
|
-
const minutes = date.getHours() * 60 + date.getMinutes();
|
|
152
|
-
const today = isoDay(date);
|
|
153
|
-
const yesterday = today === 1 ? 7 : today - 1;
|
|
154
|
-
const allowed = (day) => !parsed.days || parsed.days.includes(day);
|
|
155
|
-
let bestDelta = null;
|
|
156
|
-
const consider = (delta) => {
|
|
157
|
-
if (bestDelta === null || delta < bestDelta) bestDelta = delta;
|
|
158
|
-
};
|
|
159
|
-
for (const { start, end } of parsed.windows) {
|
|
160
|
-
if (start < end) {
|
|
161
|
-
if (minutes >= start && minutes < end && allowed(today)) consider(end - minutes);
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
if (minutes >= start && allowed(today)) consider((1440 - minutes) + end);
|
|
165
|
-
else if (minutes < end && allowed(yesterday)) consider(end - minutes);
|
|
166
|
-
}
|
|
167
|
-
if (bestDelta === null) return null;
|
|
168
|
-
const at = new Date(date.getTime());
|
|
169
|
-
at.setSeconds(0, 0);
|
|
170
|
-
at.setMinutes(at.getMinutes() + bestDelta);
|
|
171
|
-
return at;
|
|
172
|
-
}
|
|
1
|
+
// 高峰时段(peak hours)解析与判定——纯函数模块,零依赖。
|
|
2
|
+
//
|
|
3
|
+
// 从 summarize.js 抽出(PR #320 review):#316 之后 summarize.js 反向依赖
|
|
4
|
+
// dream.js(withEffortFallback 复用),dream 镜像错峰时再 import summarize
|
|
5
|
+
// 会成真循环。时段调度本来就不是蒸馏的私有语义,独立成模块后两侧都从这里
|
|
6
|
+
// import,循环消失。re-export 兼容留在 summarize.js,调用方零改动。
|
|
7
|
+
//
|
|
8
|
+
// Issue #239(第 4 项,错峰队列):高峰时段解析与判定。纯函数、可单测——排程
|
|
9
|
+
// 判断不绑死真实时钟,测试才能确定性地覆盖跨零点、多段、星期过滤与非法写法。
|
|
10
|
+
// spec 语法:`[<星期> ]<时段>[,<时段>...]`,星期前缀可省(省 = 每天):
|
|
11
|
+
// "09:00-18:00" 每天 09:00-18:00
|
|
12
|
+
// "mon-fri 08:00-12:00,14:00-18:00" 工作日两段(ISO 1=周一…7=周日,也认 mon..sun)
|
|
13
|
+
// "sat,sun 23:00-06:00" 周末跨零点段
|
|
14
|
+
// 时间取宿主本地时区。任一写法非法 → 整串视为未配置(返回 null):排程是省钱手段,
|
|
15
|
+
// 绝不该因为写错格式把蒸馏/巩固停掉。全角等价符号在解析前归一成半角(#424),
|
|
16
|
+
// 见 parsePeakSpec 上方的 normalizeFullwidth。
|
|
17
|
+
const DAY_NAMES = { mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6, sun: 7 };
|
|
18
|
+
|
|
19
|
+
function parseDayToken(token) {
|
|
20
|
+
const days = new Set();
|
|
21
|
+
const normalize = (value) => (/^\d$/.test(value) ? Number(value) : DAY_NAMES[value] ?? null);
|
|
22
|
+
for (const piece of token.split(",")) {
|
|
23
|
+
const matched = /^([a-z]{3}|\d)(?:-([a-z]{3}|\d))?$/.exec(piece.trim().toLowerCase());
|
|
24
|
+
if (!matched) return null;
|
|
25
|
+
const from = normalize(matched[1]);
|
|
26
|
+
const to = matched[2] === undefined ? from : normalize(matched[2]);
|
|
27
|
+
if (from === null || to === null || from < 1 || from > 7 || to < 1 || to > 7) return null;
|
|
28
|
+
// 支持跨周环绕(fri-mon):从 from 起逐天推进到 to,最多绕一圈。
|
|
29
|
+
for (let day = from; ; day = (day % 7) + 1) {
|
|
30
|
+
days.add(day);
|
|
31
|
+
if (day === to) break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return days.size > 0 ? [...days].sort((a, b) => a - b) : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** JS 的 getDay() 是 0=周日…6=周六;这里统一成 ISO(1=周一…7=周日)。 */
|
|
38
|
+
function isoDay(date) {
|
|
39
|
+
const day = date.getDay();
|
|
40
|
+
return day === 0 ? 7 : day;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// #424:全角等价符号归一。中文输入法下写时段串,逗号/冒号/数字跟着输入法走成全角
|
|
44
|
+
// 是常态(#424 实测的失败写法里全角类占四种);而解析失败的语义是「未配置 =
|
|
45
|
+
// 不限制」(见文件头),错一个字符整串静默失效 = 错峰闸门悄悄关掉、巩固回到
|
|
46
|
+
// 全天候——这正是 #424 报的现象。这里只做**等价映射**,不做救错:
|
|
47
|
+
// * U+FF01–FF5D(全角!到全角])与 ASCII 0x21–0x7D 恒差 0xFEE0,整段一起折,
|
|
48
|
+
// 覆盖 ,:0-9-与全角字母(星期前缀一起受益);
|
|
49
|
+
// * 两个例外码不走折叠,直接映成 '-':全角~(U+FF5E)折回半角是 `~`,而 `~` 是
|
|
50
|
+
// 既有契约里故意非法的连接号写法;〜(U+301C)是波浪号的另一半,同理。
|
|
51
|
+
// 合法写法只含 ASCII,归一前合法的串归一后逐字节不变,语义零漂移;
|
|
52
|
+
// `24:00` 这类**值本身写错**的串仍按非法处理(跨零点的正写是 `04:00-00:00`),
|
|
53
|
+
// 归一化不背救错的锅。
|
|
54
|
+
const FULLWIDTH_EQUIVALENTS = /[\uFF01-\uFF5E\u301C]/g;
|
|
55
|
+
|
|
56
|
+
function normalizeFullwidth(spec) {
|
|
57
|
+
return spec.replace(FULLWIDTH_EQUIVALENTS, (ch) => {
|
|
58
|
+
const code = ch.charCodeAt(0);
|
|
59
|
+
if (code === 0xff5e || code === 0x301c) return "-";
|
|
60
|
+
return String.fromCharCode(code - 0xfee0);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function parsePeakSpec(spec) {
|
|
65
|
+
if (typeof spec !== "string" || spec.trim() === "") return null;
|
|
66
|
+
let rest = normalizeFullwidth(spec).trim();
|
|
67
|
+
let days = null;
|
|
68
|
+
// 星期前缀 = 第一个空白之前的部分,但**头段含冒号就不是前缀**(那是时段本身,
|
|
69
|
+
// 例如 "09:00-12:00, 14:00-18:00" 里的逗号空格)。前缀解析失败一律按未配置处理,
|
|
70
|
+
// 不做猜测——宁可不省,也不能误停。
|
|
71
|
+
const sep = rest.search(/\s/);
|
|
72
|
+
if (sep > 0) {
|
|
73
|
+
const head = rest.slice(0, sep);
|
|
74
|
+
if (!head.includes(":")) {
|
|
75
|
+
days = parseDayToken(head);
|
|
76
|
+
if (days === null) return null;
|
|
77
|
+
rest = rest.slice(sep).trim();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const windows = [];
|
|
81
|
+
for (const part of rest.split(",")) {
|
|
82
|
+
const matched = /^\s*(\d{1,2}):(\d{2})\s*-\s*(\d{1,2}):(\d{2})\s*$/.exec(part);
|
|
83
|
+
if (!matched) return null;
|
|
84
|
+
const [sh, sm, eh, em] = [Number(matched[1]), Number(matched[2]), Number(matched[3]), Number(matched[4])];
|
|
85
|
+
if (sh > 23 || eh > 23 || sm > 59 || em > 59) return null;
|
|
86
|
+
const start = sh * 60 + sm;
|
|
87
|
+
const end = eh * 60 + em;
|
|
88
|
+
if (start === end) return null;
|
|
89
|
+
windows.push({ start, end });
|
|
90
|
+
}
|
|
91
|
+
return windows.length > 0 ? { days, windows } : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- 可观测性(issue #622):解析失败不许静默 ---------------------------------
|
|
95
|
+
//
|
|
96
|
+
// 上面的失败语义(整串视为未配置)是对的——排程是省钱手段,绝不该因为写错格式
|
|
97
|
+
// 把蒸馏/巩固停掉。但它的代价是:**配置写错的唯一症状是「功能悄悄没生效」**——
|
|
98
|
+
// 用户明明写了 summarizePeakHours / dreamPeakHours,排程却回到全天候,日志、
|
|
99
|
+
// 面板、审计里没有任何痕迹(#424 主楼的排查只能靠「高峰期还在巩固」倒推)。
|
|
100
|
+
// 归一化类补救(PR #621 的全角等价)收得掉一小类,但 24:00 越界、点号当冒号这类
|
|
101
|
+
// 只能靠告警——归一化不救错,静默失效面永远存在。本节只补**可见性**,不动语义:
|
|
102
|
+
// helper 只转述 parsePeakSpec 的话,解析口径变了(如 #621 落地)告警面自动跟随,
|
|
103
|
+
// 两批无需联动。
|
|
104
|
+
//
|
|
105
|
+
// 两个键名单独成表:告警点(index.js 启动装配)与提示点(/features 答复、面板
|
|
106
|
+
// 输入行)都要认同一份「哪些键是时段键」,不各写一份字符串数组然后各自漂移。
|
|
107
|
+
export const PEAK_SPEC_KEYS = ["summarizePeakHours", "dreamPeakHours"];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 找出「非空且解析失败」的时段配置。空串是合法的「未配置」(调用方用 `?? ""`
|
|
111
|
+
* 兜底),不进结果——否则每个没配错峰的用户开机都被警告一遍(#622 验收第 2 条)。
|
|
112
|
+
* 非法串映射 key → 原串:告警要带原串才能对上配置文件里那一行;/features 答复
|
|
113
|
+
* 只取 Object.keys(面板只需要「哪个键坏了」,不需要回显值)。
|
|
114
|
+
*/
|
|
115
|
+
export function invalidPeakSpecs(cfg) {
|
|
116
|
+
const out = {};
|
|
117
|
+
if (!cfg || typeof cfg !== "object") return out;
|
|
118
|
+
for (const key of PEAK_SPEC_KEYS) {
|
|
119
|
+
const spec = cfg[key];
|
|
120
|
+
if (typeof spec === "string" && spec.trim() !== "" && parsePeakSpec(spec) === null) {
|
|
121
|
+
out[key] = spec;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 该时刻是否落在高峰内。跨零点段(start > end)按「窗口所属的那一天」认星期:
|
|
129
|
+
* `mon-fri 23:00-06:00` 的周六 02:00 属于周五开的那个窗口,仍算高峰。
|
|
130
|
+
*/
|
|
131
|
+
export function isInPeakWindow(date, spec) {
|
|
132
|
+
const parsed = parsePeakSpec(spec);
|
|
133
|
+
if (!parsed) return false;
|
|
134
|
+
const minutes = date.getHours() * 60 + date.getMinutes();
|
|
135
|
+
const today = isoDay(date);
|
|
136
|
+
const yesterday = today === 1 ? 7 : today - 1;
|
|
137
|
+
const allowed = (day) => !parsed.days || parsed.days.includes(day);
|
|
138
|
+
return parsed.windows.some(({ start, end }) => {
|
|
139
|
+
if (start < end) return minutes >= start && minutes < end && allowed(today);
|
|
140
|
+
return (minutes >= start && allowed(today)) || (minutes < end && allowed(yesterday));
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 高峰内则返回「距当前最近的一个高峰结束时刻」(择时补跑用),否则 null。
|
|
146
|
+
* 落在多个时段重叠处时取最早结束的那个——早跑不亏,晚跑才亏。
|
|
147
|
+
*/
|
|
148
|
+
export function nextOffPeakAt(date, spec) {
|
|
149
|
+
const parsed = parsePeakSpec(spec);
|
|
150
|
+
if (!parsed) return null;
|
|
151
|
+
const minutes = date.getHours() * 60 + date.getMinutes();
|
|
152
|
+
const today = isoDay(date);
|
|
153
|
+
const yesterday = today === 1 ? 7 : today - 1;
|
|
154
|
+
const allowed = (day) => !parsed.days || parsed.days.includes(day);
|
|
155
|
+
let bestDelta = null;
|
|
156
|
+
const consider = (delta) => {
|
|
157
|
+
if (bestDelta === null || delta < bestDelta) bestDelta = delta;
|
|
158
|
+
};
|
|
159
|
+
for (const { start, end } of parsed.windows) {
|
|
160
|
+
if (start < end) {
|
|
161
|
+
if (minutes >= start && minutes < end && allowed(today)) consider(end - minutes);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (minutes >= start && allowed(today)) consider((1440 - minutes) + end);
|
|
165
|
+
else if (minutes < end && allowed(yesterday)) consider(end - minutes);
|
|
166
|
+
}
|
|
167
|
+
if (bestDelta === null) return null;
|
|
168
|
+
const at = new Date(date.getTime());
|
|
169
|
+
at.setSeconds(0, 0);
|
|
170
|
+
at.setMinutes(at.getMinutes() + bestDelta);
|
|
171
|
+
return at;
|
|
172
|
+
}
|