@remnic/core 9.62.0 → 9.62.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/dist/access-admin-ops-surface.d.ts +11 -11
- package/dist/access-admin-ops-surface.js +28 -28
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.d.ts +11 -11
- package/dist/access-authorization-probe.js +29 -29
- package/dist/access-boundary.d.ts +11 -11
- package/dist/access-boundary.js +28 -28
- package/dist/access-cli.js +93 -91
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +11 -11
- package/dist/access-extraction-force-flush.js +28 -28
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +11 -11
- package/dist/access-http-lifecycle-flush.d.ts +11 -11
- package/dist/access-http-offline-stream.d.ts +11 -11
- package/dist/access-http-query.js +28 -28
- package/dist/access-http.d.ts +12 -12
- package/dist/access-http.js +38 -38
- package/dist/access-identity-continuity-surface.d.ts +9 -9
- package/dist/access-identity-continuity-surface.js +28 -28
- package/dist/access-lcm-surface.d.ts +11 -11
- package/dist/access-lcm-surface.js +28 -28
- package/dist/access-mcp.d.ts +11 -11
- package/dist/access-mcp.js +33 -33
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-EMvicnrb.d.ts → access-namespace-preflight-yO4LMGFH.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +28 -28
- package/dist/access-observe-write-surface.d.ts +11 -11
- package/dist/access-observe-write-surface.js +28 -28
- package/dist/access-offline-manifest.d.ts +9 -9
- package/dist/access-offline-manifest.js +2 -2
- package/dist/access-operations-batch.js +29 -29
- package/dist/access-operations.d.ts +11 -11
- package/dist/access-operations.js +32 -32
- package/dist/access-recall-concurrency.d.ts +11 -11
- package/dist/access-recall-concurrency.js +28 -28
- package/dist/access-recall-response.d.ts +11 -11
- package/dist/access-recall-response.js +28 -28
- package/dist/access-recall-surface.d.ts +11 -11
- package/dist/access-recall-surface.js +28 -28
- package/dist/{access-service-BeDYQQ62.d.ts → access-service-M4LgO141.d.ts} +7 -7
- package/dist/access-service-helpers.d.ts +11 -11
- package/dist/access-service.d.ts +11 -11
- package/dist/access-service.js +28 -28
- package/dist/access-surface-catalog.d.ts +11 -11
- package/dist/access-surface-catalog.js +30 -30
- package/dist/access-wearables-meetings-surface.d.ts +3 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-memory-bridge.js +2 -2
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +3 -3
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/{auto-sync-FMWQQB3J.js → auto-sync-Z52VWMNC.js} +3 -3
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-learner.js +2 -2
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +9 -9
- package/dist/briefing-window.d.ts +2 -2
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +13 -12
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-B2SxZ6LU.d.ts → catalog-CyYTf_ok.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-behavior.js +3 -3
- package/dist/causal-chain.js +3 -3
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +16 -16
- package/dist/causal-retrieval.js +3 -3
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/causal-trajectory.js +2 -2
- package/dist/{chunk-B2QHVLJO.js → chunk-256FBCVX.js} +22 -10
- package/dist/chunk-256FBCVX.js.map +1 -0
- package/dist/{chunk-QP2IZTUR.js → chunk-2TYIAKU5.js} +2 -2
- package/dist/{chunk-HQBVQICL.js → chunk-347ZR6D4.js} +2 -2
- package/dist/{chunk-76YNCJ5H.js → chunk-3F3CVVDS.js} +2 -2
- package/dist/{chunk-TE4MKFT2.js → chunk-3MA7JVXN.js} +4 -4
- package/dist/{chunk-KHQ57QLJ.js → chunk-46WM6JX2.js} +7 -3
- package/dist/chunk-46WM6JX2.js.map +1 -0
- package/dist/chunk-47DU4SGJ.js +92 -0
- package/dist/chunk-47DU4SGJ.js.map +1 -0
- package/dist/{chunk-AWRJBAIH.js → chunk-4FFROSFG.js} +37 -14
- package/dist/{chunk-AWRJBAIH.js.map → chunk-4FFROSFG.js.map} +1 -1
- package/dist/{chunk-N4OYPGZS.js → chunk-4HO3542O.js} +2 -2
- package/dist/{chunk-HEEEMJ26.js → chunk-4IZ2IT7D.js} +3 -3
- package/dist/{chunk-4XAVQLBR.js → chunk-4Y2RHVCV.js} +2 -2
- package/dist/{chunk-33JBK2XP.js → chunk-5F6HMNMZ.js} +2 -2
- package/dist/{chunk-5WQMORU5.js → chunk-5PFGMO33.js} +2 -2
- package/dist/{chunk-6HEM6HTQ.js → chunk-6GGTATZF.js} +2 -2
- package/dist/{chunk-W3Y6FYRK.js → chunk-7BJYTX6Q.js} +2 -2
- package/dist/{chunk-2GRLJMKJ.js → chunk-7DRSWPUV.js} +3 -3
- package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
- package/dist/{chunk-HXXGZ4KV.js → chunk-ALNZEWGN.js} +2 -2
- package/dist/{chunk-5WVUO6QT.js → chunk-AR253JBM.js} +1 -1
- package/dist/chunk-AR253JBM.js.map +1 -0
- package/dist/{chunk-S3B3UIQ3.js → chunk-AYIHDC5S.js} +2 -2
- package/dist/{chunk-2RIDW23Y.js → chunk-BBHFAVMP.js} +8 -8
- package/dist/{chunk-MJ4UGUR6.js → chunk-BHO6RHE5.js} +3 -3
- package/dist/{chunk-URN7XWGC.js → chunk-BP6WMYLP.js} +3 -3
- package/dist/{chunk-YUIBXMZY.js → chunk-CALURYWR.js} +284 -249
- package/dist/chunk-CALURYWR.js.map +1 -0
- package/dist/{chunk-D5HDJWNN.js → chunk-CH627RRV.js} +4 -4
- package/dist/{chunk-C3R732UY.js → chunk-CLJ5QS6J.js} +257 -60
- package/dist/chunk-CLJ5QS6J.js.map +1 -0
- package/dist/{chunk-YLHM5BQQ.js → chunk-DCUMFQYM.js} +171 -8
- package/dist/chunk-DCUMFQYM.js.map +1 -0
- package/dist/{chunk-ZBJMUXZH.js → chunk-DJ7HMNSL.js} +15 -8
- package/dist/chunk-DJ7HMNSL.js.map +1 -0
- package/dist/{chunk-XDK6ZFLL.js → chunk-DROAPP6N.js} +3 -3
- package/dist/{chunk-MU3TPZOV.js → chunk-DTLF2SLZ.js} +2 -2
- package/dist/{chunk-I67HKRBF.js → chunk-EFWHCTYJ.js} +2 -2
- package/dist/{chunk-XD4VGLDL.js → chunk-FF6E6HOT.js} +56 -52
- package/dist/chunk-FF6E6HOT.js.map +1 -0
- package/dist/{chunk-NP7FLVZW.js → chunk-G2FCSILA.js} +2 -2
- package/dist/{chunk-3GPBFDXH.js → chunk-H23GIXJC.js} +2 -2
- package/dist/{chunk-WT6UL3UA.js → chunk-H7PRHUBD.js} +2 -2
- package/dist/{chunk-4JGSG7QN.js → chunk-HSBQJRV2.js} +2 -2
- package/dist/{chunk-L33N3BCA.js → chunk-HVKMJAEF.js} +2 -2
- package/dist/{chunk-CSOYVTRX.js → chunk-IIPJKCZB.js} +3 -3
- package/dist/{chunk-E57E5SCS.js → chunk-IRLH4NM2.js} +3 -3
- package/dist/{chunk-H32ANIFP.js → chunk-IZRNVNPV.js} +2 -2
- package/dist/{chunk-XBR5BAKW.js → chunk-JCJGYNIS.js} +3 -3
- package/dist/{chunk-EIQFIJDM.js → chunk-JDI7G6EA.js} +2 -2
- package/dist/{chunk-QWPAQLYV.js → chunk-JFLKSD5V.js} +2 -2
- package/dist/{chunk-KQS72GCR.js → chunk-LJOIZPJH.js} +2 -2
- package/dist/{chunk-N4EPIKSY.js → chunk-LLGDVG52.js} +3 -3
- package/dist/{chunk-7N7DB4L5.js → chunk-LTJTP5YQ.js} +10 -9
- package/dist/chunk-LTJTP5YQ.js.map +1 -0
- package/dist/{chunk-VOELBUS4.js → chunk-NXH34IPL.js} +2 -2
- package/dist/{chunk-XL3TATMJ.js → chunk-OQIRBGUU.js} +21 -12
- package/dist/chunk-OQIRBGUU.js.map +1 -0
- package/dist/chunk-PJKTFXE2.js +119 -0
- package/dist/chunk-PJKTFXE2.js.map +1 -0
- package/dist/{chunk-JGW2EUCQ.js → chunk-PMRUMGK6.js} +2 -2
- package/dist/{chunk-KAB7TZN3.js → chunk-PSNN25DW.js} +2 -2
- package/dist/{chunk-LRGSLV7M.js → chunk-PYHOAXTI.js} +4 -4
- package/dist/{chunk-PA5OPZX2.js → chunk-QCI73Z7C.js} +2 -2
- package/dist/{chunk-EHMMRZOZ.js → chunk-QICOU4RY.js} +2 -2
- package/dist/{chunk-FW73HN33.js → chunk-RFH7U5LT.js} +2 -2
- package/dist/{chunk-4SGDYYWV.js → chunk-RPKJHYVE.js} +2 -2
- package/dist/{chunk-DSA47WBN.js → chunk-RYST72N5.js} +13 -2
- package/dist/chunk-RYST72N5.js.map +1 -0
- package/dist/{chunk-L4DLLKPN.js → chunk-SZ4HFZKB.js} +2 -2
- package/dist/chunk-T3GIETKV.js +152 -0
- package/dist/chunk-T3GIETKV.js.map +1 -0
- package/dist/{chunk-MYZ4NA43.js → chunk-TGSMU3F2.js} +3 -3
- package/dist/{chunk-CM4SZZBN.js → chunk-TIRJV2LW.js} +2 -2
- package/dist/{chunk-EGW4O32Z.js → chunk-UDTME32W.js} +7 -4
- package/dist/chunk-UDTME32W.js.map +1 -0
- package/dist/{chunk-CIA6ZUBA.js → chunk-WMOJIOIG.js} +2 -2
- package/dist/{chunk-W4Q54HM3.js → chunk-WR2YKUIW.js} +2 -2
- package/dist/{chunk-VWDTV6SH.js → chunk-X6E7TT3A.js} +2 -2
- package/dist/{chunk-D4OCJSLV.js → chunk-YDPAE2RD.js} +2 -2
- package/dist/{chunk-G3YBWFUC.js → chunk-YIIC5IDU.js} +35 -18
- package/dist/chunk-YIIC5IDU.js.map +1 -0
- package/dist/{chunk-QRKYABS6.js → chunk-YSR3DXGC.js} +2 -2
- package/dist/{chunk-LBJBNWS2.js → chunk-YTHQHH5U.js} +2 -2
- package/dist/{chunk-GN7HO2LA.js → chunk-ZPGHA7EO.js} +12 -12
- package/dist/{chunk-VUDALPI5.js → chunk-ZQKXRKMR.js} +19 -5
- package/dist/chunk-ZQKXRKMR.js.map +1 -0
- package/dist/{chunk-FQSDU634.js → chunk-ZS4JADYY.js} +3 -3
- package/dist/{cli-DB_uakTc.d.ts → cli-Bw9z-KqX.d.ts} +5 -5
- package/dist/cli.d.ts +13 -13
- package/dist/cli.js +62 -62
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/coding/pre-action-gate.js +3 -3
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +13 -12
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +3 -3
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +13 -12
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +13 -12
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +15 -14
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +1 -1
- package/dist/corpus-watermark.js +15 -14
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-DcOVpK3y.d.ts → dependency-propagation-delivery-BxhvAppj.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer-wiring.js +3 -3
- package/dist/direct-answer.d.ts +1 -1
- package/dist/direct-answer.js +2 -2
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-origin-fields.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +13 -12
- package/dist/entity-schema.d.ts +1 -1
- package/dist/episodic-context.d.ts +92 -0
- package/dist/episodic-context.js +16 -0
- package/dist/episodic-context.js.map +1 -0
- package/dist/explicit-capture.d.ts +9 -9
- package/dist/external-wiki-access.d.ts +11 -11
- package/dist/external-wiki-access.js +29 -29
- package/dist/external-wiki-collection-registration.d.ts +1 -1
- package/dist/external-wiki-collection.d.ts +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +11 -11
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-normalization.d.ts +1 -1
- package/dist/extraction-normalization.js +2 -2
- package/dist/extraction-prompt.d.ts +1 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +18 -18
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{forget-5TWK7CZT.js → forget-SZ2BXDGF.js} +2 -2
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-path-reconstruction.d.ts +1 -1
- package/dist/graph-path-scoring.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/harmonic-construction.js +2 -2
- package/dist/harmonic-retrieval.js +2 -2
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +22 -21
- package/dist/index.js +118 -103
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +8 -2
- package/dist/lcm/engine.js +4 -4
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +9 -9
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +13 -12
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +13 -12
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +14 -13
- package/dist/{maintenance-5c1olVtB.d.ts → maintenance-uJb74j64.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +11 -11
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-snapshot.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +14 -13
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +6 -5
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +13 -12
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +16 -15
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +21 -20
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +18 -17
- package/dist/{orchestrator-HPuY6I4i.d.ts → orchestrator-CFi-5JSR.d.ts} +23 -8
- package/dist/orchestrator.d.ts +9 -9
- package/dist/orchestrator.js +93 -91
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-CvVe1Q2_.d.ts → pipeline-BdF3ferY.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/policy-runtime.js +3 -3
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/provenance.js +2 -2
- package/dist/{public-http-D0JKFkyf.d.ts → public-http-DK-QwaBu.d.ts} +1 -1
- package/dist/{qmd-Bd0Tp76_.d.ts → qmd-D_62bYNl.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-query-policy.js +2 -2
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-tokenization.d.ts +3 -1
- package/dist/recall-tokenization.js +5 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/cursor.d.ts +1 -1
- package/dist/reconcile/manifest.d.ts +10 -1
- package/dist/reconcile/manifest.js +1 -1
- package/dist/reconcile/plan.d.ts +1 -1
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +6 -6
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/{scope-profiles-ClLtm9gK.d.ts → scope-profiles-CeMv7pP0.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +5 -4
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +9 -8
- package/dist/search/lancedb-backend.d.ts +14 -1
- package/dist/search/lancedb-backend.js +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +22 -1
- package/dist/search/orama-backend.js +2 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-B1EkeEDj.d.ts → semantic-consolidation-B-85brYj.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +14 -14
- package/dist/semantic-rule-promotion.js +13 -12
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +13 -13
- package/dist/{service-Cf3ANQj9.d.ts → service-CoO0HA2E.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +15 -14
- package/dist/{storage-DS2O_6Qt.d.ts → storage-BVAVitkO.d.ts} +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +12 -11
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/support-passport/index.d.ts +14 -14
- package/dist/support-passport/index.js +31 -31
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/import-sqlite.js +2 -2
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/trust-zones.js +2 -2
- package/dist/{types-BOANGhCJ.d.ts → types-DWHOxlZP.d.ts} +3 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/user-message-cleaning.d.ts +23 -0
- package/dist/user-message-cleaning.js +10 -0
- package/dist/user-message-cleaning.js.map +1 -0
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +13 -13
- package/dist/whitespace.d.ts +26 -1
- package/dist/whitespace.js +11 -3
- package/dist/work-product-ledger.js +2 -2
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +12 -2
- package/src/access-service-offline-file-content.test.ts +1 -0
- package/src/cli.ts +14 -13
- package/src/config.ts +11 -0
- package/src/entity-retrieval.ts +4 -7
- package/src/episodic-context-recall.integration.test.ts +169 -0
- package/src/episodic-context.test.ts +266 -0
- package/src/episodic-context.ts +274 -0
- package/src/harmonic-construction.test.ts +187 -2
- package/src/harmonic-retrieval.ts +54 -32
- package/src/index.ts +3 -4
- package/src/lcm/engine.ts +18 -1
- package/src/lifecycle/tombstones.test.ts +134 -0
- package/src/lifecycle/tombstones.ts +29 -2
- package/src/orchestration/episodic-context-section.ts +255 -0
- package/src/orchestration/recall-internal-deps.ts +2 -0
- package/src/orchestration/recall-internal.ts +40 -25
- package/src/orchestration/recall-result-formatter.ts +37 -0
- package/src/orchestration/recall-section-coordinator.ts +20 -14
- package/src/recall-tokenization.ts +14 -8
- package/src/reconcile/manifest.test.ts +133 -4
- package/src/reconcile/manifest.ts +43 -7
- package/src/search/factory.ts +1 -0
- package/src/search/lancedb-backend.ts +24 -16
- package/src/search/orama-backend.test.ts +208 -1
- package/src/search/orama-backend.ts +133 -18
- package/src/search/orama-cjk-tokenizer.test.ts +108 -0
- package/src/search/orama-cjk-tokenizer.ts +143 -0
- package/src/storage/tombstone-migration-sources.test.ts +116 -0
- package/src/storage/tombstone-migration-sources.ts +90 -0
- package/src/storage.ts +13 -34
- package/src/types.ts +3 -3
- package/src/user-message-cleaning.ts +139 -0
- package/src/whitespace.test.ts +178 -0
- package/src/whitespace.ts +119 -0
- package/dist/chunk-5WVUO6QT.js.map +0 -1
- package/dist/chunk-6MKAMLQL.js +0 -16
- package/dist/chunk-6MKAMLQL.js.map +0 -1
- package/dist/chunk-7N7DB4L5.js.map +0 -1
- package/dist/chunk-B2QHVLJO.js.map +0 -1
- package/dist/chunk-C3R732UY.js.map +0 -1
- package/dist/chunk-DSA47WBN.js.map +0 -1
- package/dist/chunk-EGW4O32Z.js.map +0 -1
- package/dist/chunk-G3YBWFUC.js.map +0 -1
- package/dist/chunk-KHQ57QLJ.js.map +0 -1
- package/dist/chunk-VUDALPI5.js.map +0 -1
- package/dist/chunk-XD4VGLDL.js.map +0 -1
- package/dist/chunk-XL3TATMJ.js.map +0 -1
- package/dist/chunk-YLHM5BQQ.js.map +0 -1
- package/dist/chunk-YUIBXMZY.js.map +0 -1
- package/dist/chunk-ZBJMUXZH.js.map +0 -1
- /package/dist/{auto-sync-FMWQQB3J.js.map → auto-sync-Z52VWMNC.js.map} +0 -0
- /package/dist/{chunk-QP2IZTUR.js.map → chunk-2TYIAKU5.js.map} +0 -0
- /package/dist/{chunk-HQBVQICL.js.map → chunk-347ZR6D4.js.map} +0 -0
- /package/dist/{chunk-76YNCJ5H.js.map → chunk-3F3CVVDS.js.map} +0 -0
- /package/dist/{chunk-TE4MKFT2.js.map → chunk-3MA7JVXN.js.map} +0 -0
- /package/dist/{chunk-N4OYPGZS.js.map → chunk-4HO3542O.js.map} +0 -0
- /package/dist/{chunk-HEEEMJ26.js.map → chunk-4IZ2IT7D.js.map} +0 -0
- /package/dist/{chunk-4XAVQLBR.js.map → chunk-4Y2RHVCV.js.map} +0 -0
- /package/dist/{chunk-33JBK2XP.js.map → chunk-5F6HMNMZ.js.map} +0 -0
- /package/dist/{chunk-5WQMORU5.js.map → chunk-5PFGMO33.js.map} +0 -0
- /package/dist/{chunk-6HEM6HTQ.js.map → chunk-6GGTATZF.js.map} +0 -0
- /package/dist/{chunk-W3Y6FYRK.js.map → chunk-7BJYTX6Q.js.map} +0 -0
- /package/dist/{chunk-2GRLJMKJ.js.map → chunk-7DRSWPUV.js.map} +0 -0
- /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
- /package/dist/{chunk-HXXGZ4KV.js.map → chunk-ALNZEWGN.js.map} +0 -0
- /package/dist/{chunk-S3B3UIQ3.js.map → chunk-AYIHDC5S.js.map} +0 -0
- /package/dist/{chunk-2RIDW23Y.js.map → chunk-BBHFAVMP.js.map} +0 -0
- /package/dist/{chunk-MJ4UGUR6.js.map → chunk-BHO6RHE5.js.map} +0 -0
- /package/dist/{chunk-URN7XWGC.js.map → chunk-BP6WMYLP.js.map} +0 -0
- /package/dist/{chunk-D5HDJWNN.js.map → chunk-CH627RRV.js.map} +0 -0
- /package/dist/{chunk-XDK6ZFLL.js.map → chunk-DROAPP6N.js.map} +0 -0
- /package/dist/{chunk-MU3TPZOV.js.map → chunk-DTLF2SLZ.js.map} +0 -0
- /package/dist/{chunk-I67HKRBF.js.map → chunk-EFWHCTYJ.js.map} +0 -0
- /package/dist/{chunk-NP7FLVZW.js.map → chunk-G2FCSILA.js.map} +0 -0
- /package/dist/{chunk-3GPBFDXH.js.map → chunk-H23GIXJC.js.map} +0 -0
- /package/dist/{chunk-WT6UL3UA.js.map → chunk-H7PRHUBD.js.map} +0 -0
- /package/dist/{chunk-4JGSG7QN.js.map → chunk-HSBQJRV2.js.map} +0 -0
- /package/dist/{chunk-L33N3BCA.js.map → chunk-HVKMJAEF.js.map} +0 -0
- /package/dist/{chunk-CSOYVTRX.js.map → chunk-IIPJKCZB.js.map} +0 -0
- /package/dist/{chunk-E57E5SCS.js.map → chunk-IRLH4NM2.js.map} +0 -0
- /package/dist/{chunk-H32ANIFP.js.map → chunk-IZRNVNPV.js.map} +0 -0
- /package/dist/{chunk-XBR5BAKW.js.map → chunk-JCJGYNIS.js.map} +0 -0
- /package/dist/{chunk-EIQFIJDM.js.map → chunk-JDI7G6EA.js.map} +0 -0
- /package/dist/{chunk-QWPAQLYV.js.map → chunk-JFLKSD5V.js.map} +0 -0
- /package/dist/{chunk-KQS72GCR.js.map → chunk-LJOIZPJH.js.map} +0 -0
- /package/dist/{chunk-N4EPIKSY.js.map → chunk-LLGDVG52.js.map} +0 -0
- /package/dist/{chunk-VOELBUS4.js.map → chunk-NXH34IPL.js.map} +0 -0
- /package/dist/{chunk-JGW2EUCQ.js.map → chunk-PMRUMGK6.js.map} +0 -0
- /package/dist/{chunk-KAB7TZN3.js.map → chunk-PSNN25DW.js.map} +0 -0
- /package/dist/{chunk-LRGSLV7M.js.map → chunk-PYHOAXTI.js.map} +0 -0
- /package/dist/{chunk-PA5OPZX2.js.map → chunk-QCI73Z7C.js.map} +0 -0
- /package/dist/{chunk-EHMMRZOZ.js.map → chunk-QICOU4RY.js.map} +0 -0
- /package/dist/{chunk-FW73HN33.js.map → chunk-RFH7U5LT.js.map} +0 -0
- /package/dist/{chunk-4SGDYYWV.js.map → chunk-RPKJHYVE.js.map} +0 -0
- /package/dist/{chunk-L4DLLKPN.js.map → chunk-SZ4HFZKB.js.map} +0 -0
- /package/dist/{chunk-MYZ4NA43.js.map → chunk-TGSMU3F2.js.map} +0 -0
- /package/dist/{chunk-CM4SZZBN.js.map → chunk-TIRJV2LW.js.map} +0 -0
- /package/dist/{chunk-CIA6ZUBA.js.map → chunk-WMOJIOIG.js.map} +0 -0
- /package/dist/{chunk-W4Q54HM3.js.map → chunk-WR2YKUIW.js.map} +0 -0
- /package/dist/{chunk-VWDTV6SH.js.map → chunk-X6E7TT3A.js.map} +0 -0
- /package/dist/{chunk-D4OCJSLV.js.map → chunk-YDPAE2RD.js.map} +0 -0
- /package/dist/{chunk-QRKYABS6.js.map → chunk-YSR3DXGC.js.map} +0 -0
- /package/dist/{chunk-LBJBNWS2.js.map → chunk-YTHQHH5U.js.map} +0 -0
- /package/dist/{chunk-GN7HO2LA.js.map → chunk-ZPGHA7EO.js.map} +0 -0
- /package/dist/{chunk-FQSDU634.js.map → chunk-ZS4JADYY.js.map} +0 -0
- /package/dist/{forget-5TWK7CZT.js.map → forget-SZ2BXDGF.js.map} +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, rm, utimes, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { createTombstoneMigrationSourceContents } from "./tombstone-migration-sources.js";
|
|
8
|
+
import type { MemoryFile } from "../types.js";
|
|
9
|
+
|
|
10
|
+
function memory(id: string, content: string): MemoryFile {
|
|
11
|
+
return {
|
|
12
|
+
path: `facts/${id}.md`,
|
|
13
|
+
rawContent: content,
|
|
14
|
+
frontmatter: { id, category: "fact", status: "active" },
|
|
15
|
+
} as unknown as MemoryFile;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Harness {
|
|
19
|
+
dir: string;
|
|
20
|
+
ledgerPath: string;
|
|
21
|
+
collectCalls: number;
|
|
22
|
+
readsByPaths: string[][];
|
|
23
|
+
gateCollect: Promise<void>;
|
|
24
|
+
releaseCollect: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function makeHarness(): Promise<Harness> {
|
|
28
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-migration-race-"));
|
|
29
|
+
const ledgerPath = path.join(dir, "tombstones.jsonl");
|
|
30
|
+
await writeFile(ledgerPath, "", "utf8");
|
|
31
|
+
let gateCollect: (() => void) | undefined;
|
|
32
|
+
const gate = new Promise<void>((resolve) => {
|
|
33
|
+
gateCollect = resolve;
|
|
34
|
+
});
|
|
35
|
+
const harness: Harness = {
|
|
36
|
+
dir,
|
|
37
|
+
ledgerPath,
|
|
38
|
+
collectCalls: 0,
|
|
39
|
+
readsByPaths: [],
|
|
40
|
+
gateCollect: gate,
|
|
41
|
+
releaseCollect: () => gateCollect?.(),
|
|
42
|
+
};
|
|
43
|
+
return harness;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test("an in-flight fill does not publish a stale cache after the ledger advances", async () => {
|
|
47
|
+
const h = await makeHarness();
|
|
48
|
+
try {
|
|
49
|
+
const generations: string[][] = [["facts/old-a.md"], ["facts/new-a.md", "facts/old-a.md"]];
|
|
50
|
+
const contents = new Map<string, string>([
|
|
51
|
+
["facts/old-a.md", "old body"],
|
|
52
|
+
["facts/new-a.md", "new body"],
|
|
53
|
+
]);
|
|
54
|
+
const sourceContents = createTombstoneMigrationSourceContents({
|
|
55
|
+
tombstonesPath: () => h.ledgerPath,
|
|
56
|
+
collectTombstoneMigrationPaths: async () => {
|
|
57
|
+
const callIndex = h.collectCalls + 1;
|
|
58
|
+
h.collectCalls = callIndex;
|
|
59
|
+
if (callIndex === 1) {
|
|
60
|
+
// First fill stalls while a peer retires a legacy memory; the
|
|
61
|
+
// generation is bound at call entry so the stall cannot upgrade
|
|
62
|
+
// it to the fresh revision.
|
|
63
|
+
await h.gateCollect;
|
|
64
|
+
}
|
|
65
|
+
return generations[Math.min(callIndex - 1, generations.length - 1)]!;
|
|
66
|
+
},
|
|
67
|
+
readParsedMemoriesFromPaths: async (filePaths: string[]) => {
|
|
68
|
+
h.readsByPaths.push([...filePaths]);
|
|
69
|
+
return filePaths
|
|
70
|
+
.filter((filePath) => contents.has(filePath))
|
|
71
|
+
.map((filePath) => memory(path.basename(filePath, ".md"), contents.get(filePath)!));
|
|
72
|
+
},
|
|
73
|
+
storedContentIdentityCandidates: (content: string) => [content],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// The first callback requests an id whose path exists only in the new
|
|
77
|
+
// ledger revision, so a stale snapshot would miss it entirely.
|
|
78
|
+
const first = sourceContents(["new-a"]);
|
|
79
|
+
// Advance the ledger mtime so a later callback drops both caches while
|
|
80
|
+
// the first fill is still awaiting its (stale) path snapshot.
|
|
81
|
+
await utimes(h.ledgerPath, new Date(), new Date(Date.now() + 5000));
|
|
82
|
+
const second = sourceContents(["old-a"]);
|
|
83
|
+
h.releaseCollect();
|
|
84
|
+
|
|
85
|
+
// Without the revision guard, the first callback would miss on the
|
|
86
|
+
// stale snapshot, publish a full map built from it, and resolve empty.
|
|
87
|
+
const firstResult = await first;
|
|
88
|
+
assert.deepEqual([...firstResult.keys()], ["new-a"]);
|
|
89
|
+
const secondResult = await second;
|
|
90
|
+
assert.deepEqual([...secondResult.keys()], ["old-a"]);
|
|
91
|
+
assert.equal(h.collectCalls >= 2, true, "restart re-collects paths against the fresh revision");
|
|
92
|
+
} finally {
|
|
93
|
+
await rm(h.dir, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("a stable ledger revision reuses the collected paths across callbacks", async () => {
|
|
98
|
+
const h = await makeHarness();
|
|
99
|
+
try {
|
|
100
|
+
const sourceContents = createTombstoneMigrationSourceContents({
|
|
101
|
+
tombstonesPath: () => h.ledgerPath,
|
|
102
|
+
collectTombstoneMigrationPaths: async () => {
|
|
103
|
+
h.collectCalls += 1;
|
|
104
|
+
return ["facts/a.md"];
|
|
105
|
+
},
|
|
106
|
+
readParsedMemoriesFromPaths: async (filePaths: string[]) =>
|
|
107
|
+
filePaths.map((filePath) => memory(path.basename(filePath, ".md"), "body")),
|
|
108
|
+
storedContentIdentityCandidates: (content: string) => [content],
|
|
109
|
+
});
|
|
110
|
+
await sourceContents(["a"]);
|
|
111
|
+
await sourceContents(["a"]);
|
|
112
|
+
assert.equal(h.collectCalls, 1, "unchanged ledger mtime reuses the path snapshot");
|
|
113
|
+
} finally {
|
|
114
|
+
await rm(h.dir, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { TombstoneStoreOptions } from "../lifecycle/tombstones.js";
|
|
4
|
+
import type { MemoryFile } from "../types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Deps injected by `StorageManager.buildTombstoneStore` so this module owns no
|
|
8
|
+
* storage-god-file imports (issue #1533 ratchet).
|
|
9
|
+
*/
|
|
10
|
+
export interface TombstoneMigrationSourceDeps {
|
|
11
|
+
/** On-disk tombstone ledger path; its mtime scopes both caches below. */
|
|
12
|
+
tombstonesPath: () => string;
|
|
13
|
+
collectTombstoneMigrationPaths: () => Promise<string[]>;
|
|
14
|
+
readParsedMemoriesFromPaths: (filePaths: string[], batchSize?: number) => Promise<MemoryFile[]>;
|
|
15
|
+
storedContentIdentityCandidates: (content: string) => string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build the `sourceContentsForMemoryIds` callback `TombstoneStore` uses to
|
|
20
|
+
* verify pre-Unicode rows against their retired source memories. The corpus
|
|
21
|
+
* path snapshot and the resolved source-content map are memoized for one
|
|
22
|
+
* ledger revision only (issue #2367): a peer process can create and retire a
|
|
23
|
+
* legacy-format memory after this snapshot, and the staleness reload must
|
|
24
|
+
* resolve the new row against a FRESH path list — otherwise the row stays
|
|
25
|
+
* unverified (withheld from the lookup tiers) until restart.
|
|
26
|
+
*/
|
|
27
|
+
export function createTombstoneMigrationSourceContents(
|
|
28
|
+
deps: TombstoneMigrationSourceDeps,
|
|
29
|
+
): NonNullable<TombstoneStoreOptions["sourceContentsForMemoryIds"]> {
|
|
30
|
+
let sourcePathsPromise: Promise<string[]> | undefined;
|
|
31
|
+
let sourceContentsByIdPromise: Promise<Map<string, readonly string[]>> | undefined;
|
|
32
|
+
let ledgerMtimeMs: number | null = null;
|
|
33
|
+
const dropCachesIfLedgerChanged = (): void => {
|
|
34
|
+
let mtimeMs: number | null;
|
|
35
|
+
try {
|
|
36
|
+
mtimeMs = Math.floor(statSync(deps.tombstonesPath()).mtimeMs);
|
|
37
|
+
} catch {
|
|
38
|
+
return; // ENOENT / stat failure — keep whatever is cached.
|
|
39
|
+
}
|
|
40
|
+
if (mtimeMs === ledgerMtimeMs) return;
|
|
41
|
+
ledgerMtimeMs = mtimeMs;
|
|
42
|
+
sourcePathsPromise = undefined;
|
|
43
|
+
sourceContentsByIdPromise = undefined;
|
|
44
|
+
};
|
|
45
|
+
return async (sourceMemoryIds) => {
|
|
46
|
+
for (;;) {
|
|
47
|
+
dropCachesIfLedgerChanged();
|
|
48
|
+
// In-flight fills are bound to the ledger revision they started
|
|
49
|
+
// against: if the ledger advances while a fill awaits, restart
|
|
50
|
+
// against the fresh revision instead of publishing a cache built
|
|
51
|
+
// from a stale path snapshot (issue #2367 review round 2).
|
|
52
|
+
const revision = ledgerMtimeMs;
|
|
53
|
+
const requested = new Set(sourceMemoryIds);
|
|
54
|
+
const sourcePaths = await (sourcePathsPromise ??= deps.collectTombstoneMigrationPaths());
|
|
55
|
+
if (ledgerMtimeMs !== revision) continue;
|
|
56
|
+
const directPaths = sourcePaths.filter((filePath) =>
|
|
57
|
+
requested.has(path.basename(filePath, ".md"))
|
|
58
|
+
);
|
|
59
|
+
const contents = new Map<string, readonly string[]>();
|
|
60
|
+
for (const memory of await deps.readParsedMemoriesFromPaths(directPaths, 50)) {
|
|
61
|
+
const id = memory.frontmatter.id;
|
|
62
|
+
if (requested.has(id) && !contents.has(id)) {
|
|
63
|
+
contents.set(id, deps.storedContentIdentityCandidates(memory.content));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Re-check after the direct-path read too: a concurrent drop during
|
|
67
|
+
// that await must not let this call publish a fill built from the
|
|
68
|
+
// stale snapshot into the freshly cleared slot (issue #2367 round 3).
|
|
69
|
+
if (ledgerMtimeMs !== revision) continue;
|
|
70
|
+
if (contents.size === requested.size) return contents;
|
|
71
|
+
const fill = (sourceContentsByIdPromise ??= (async () => {
|
|
72
|
+
const allContents = new Map<string, readonly string[]>();
|
|
73
|
+
for (const memory of await deps.readParsedMemoriesFromPaths(sourcePaths, 50)) {
|
|
74
|
+
const id = memory.frontmatter.id;
|
|
75
|
+
if (!allContents.has(id)) {
|
|
76
|
+
allContents.set(id, deps.storedContentIdentityCandidates(memory.content));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return allContents;
|
|
80
|
+
})());
|
|
81
|
+
const allContents = await fill;
|
|
82
|
+
if (ledgerMtimeMs !== revision) continue;
|
|
83
|
+
for (const id of requested) {
|
|
84
|
+
const content = allContents.get(id);
|
|
85
|
+
if (content !== undefined) contents.set(id, content);
|
|
86
|
+
}
|
|
87
|
+
return contents;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
package/src/storage.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { resolveSafeStoragePath } from "./storage-paths.js";
|
|
|
26
26
|
import { log } from "./logger.js";
|
|
27
27
|
import { createMemorySnapshot } from "./memory-snapshot.js";
|
|
28
28
|
import { assertMemoryFrontmatterId, warnProjectionFallback } from "./storage-guards.js";
|
|
29
|
+
import { createTombstoneMigrationSourceContents } from "./storage/tombstone-migration-sources.js";
|
|
29
30
|
import { MemoryReadStore, readWindowedMemories, type WindowedMemoryReadOptions, type WindowedMemoryReadResult } from "./storage/memory-read-store.js";
|
|
30
31
|
import { hasSupersessionAudit } from "./storage/supersession-audit.js";
|
|
31
32
|
import { runCommittedInvalidation } from "./storage/committed-invalidation.js";
|
|
@@ -3016,46 +3017,24 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
|
3016
3017
|
}
|
|
3017
3018
|
|
|
3018
3019
|
private buildTombstoneStore(): TombstoneStore {
|
|
3019
|
-
let sourcePathsPromise: Promise<string[]> | undefined;
|
|
3020
|
-
let sourceContentsByIdPromise: Promise<Map<string, readonly string[]>> | undefined;
|
|
3021
3020
|
const options: TombstoneStoreOptions = {
|
|
3022
3021
|
enabled: this.tombstonesConfig.enabled,
|
|
3023
3022
|
semanticMatch: this.tombstonesConfig.semanticMatch,
|
|
3024
3023
|
semanticThreshold: this.tombstonesConfig.semanticThreshold,
|
|
3025
3024
|
hashContent: ContentHashIndex.computeHash,
|
|
3026
3025
|
normalizeText: ContentHashIndex.normalizeContent,
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
if (contents.size === requested.size) return contents;
|
|
3042
|
-
sourceContentsByIdPromise ??= (async () => {
|
|
3043
|
-
const allContents = new Map<string, readonly string[]>();
|
|
3044
|
-
for (const memory of await this.readParsedMemoriesFromPaths(sourcePaths, 50)) {
|
|
3045
|
-
const id = memory.frontmatter.id;
|
|
3046
|
-
if (!allContents.has(id)) {
|
|
3047
|
-
allContents.set(id, this.storedContentIdentityCandidates(memory.content));
|
|
3048
|
-
}
|
|
3049
|
-
}
|
|
3050
|
-
return allContents;
|
|
3051
|
-
})();
|
|
3052
|
-
const allContents = await sourceContentsByIdPromise;
|
|
3053
|
-
for (const id of requested) {
|
|
3054
|
-
const content = allContents.get(id);
|
|
3055
|
-
if (content !== undefined) contents.set(id, content);
|
|
3056
|
-
}
|
|
3057
|
-
return contents;
|
|
3058
|
-
},
|
|
3026
|
+
// Corpus-path + source-content caches are scoped to one ledger revision
|
|
3027
|
+
// so a peer-appended legacy row is verified on the next staleness reload
|
|
3028
|
+
// (issue #2367). Implementation lives in storage/tombstone-migration-sources.ts.
|
|
3029
|
+
sourceContentsForMemoryIds: createTombstoneMigrationSourceContents({
|
|
3030
|
+
tombstonesPath: () => this.tombstonesPath,
|
|
3031
|
+
collectTombstoneMigrationPaths: () =>
|
|
3032
|
+
this.memoryReadStore.collectTombstoneMigrationPaths(),
|
|
3033
|
+
readParsedMemoriesFromPaths: (filePaths, batchSize) =>
|
|
3034
|
+
this.readParsedMemoriesFromPaths(filePaths, batchSize),
|
|
3035
|
+
storedContentIdentityCandidates: (content) =>
|
|
3036
|
+
this.storedContentIdentityCandidates(content),
|
|
3037
|
+
}),
|
|
3059
3038
|
};
|
|
3060
3039
|
const io: TombstoneFileIo = {
|
|
3061
3040
|
read: (filePath) => this.readStorageSecureFile(filePath),
|
package/src/types.ts
CHANGED
|
@@ -631,7 +631,6 @@ export interface DependencyPropagationConfig {
|
|
|
631
631
|
dryRun: boolean;
|
|
632
632
|
}
|
|
633
633
|
|
|
634
|
-
|
|
635
634
|
export interface HeartbeatConfig {
|
|
636
635
|
enabled: boolean;
|
|
637
636
|
journalPath: string;
|
|
@@ -1408,6 +1407,7 @@ export interface PluginConfig extends BoundedJsonlStateConfig, SecurityConfig {
|
|
|
1408
1407
|
harmonicRetrievalEnabled: boolean;
|
|
1409
1408
|
abstractionAnchorsEnabled: boolean;
|
|
1410
1409
|
abstractionNodeStoreDir: string;
|
|
1410
|
+
episodicContextEnabled: boolean;
|
|
1411
1411
|
// Episodic/semantic split foundation
|
|
1412
1412
|
verifiedRecallEnabled: boolean;
|
|
1413
1413
|
semanticRulePromotionEnabled: boolean;
|
|
@@ -1870,6 +1870,8 @@ export interface PluginConfig extends BoundedJsonlStateConfig, SecurityConfig {
|
|
|
1870
1870
|
oramaEnabled: boolean;
|
|
1871
1871
|
oramaDbPath?: string;
|
|
1872
1872
|
oramaEmbeddingDimension?: number;
|
|
1873
|
+
/** Segment space-free scripts (CJK/Thai) in the Orama lexical index (issue #2187). Default `true`. */
|
|
1874
|
+
oramaCjkSegmentation?: boolean;
|
|
1873
1875
|
|
|
1874
1876
|
// QMD daemon mode
|
|
1875
1877
|
qmdDaemonEnabled: boolean;
|
|
@@ -2908,12 +2910,10 @@ export interface MemoryFrontmatter extends SourceConnectorProvenance, OriginMeta
|
|
|
2908
2910
|
/** Last time this memory was accessed (ISO 8601) */
|
|
2909
2911
|
lastAccessed?: string;
|
|
2910
2912
|
// Memory Worth counters (issue #560)
|
|
2911
|
-
//
|
|
2912
2913
|
// Per-fact outcome counters used to derive a dynamic utility score —
|
|
2913
2914
|
// `p(success | retrieved)` — as a complement to the static `importance`
|
|
2914
2915
|
// field. Absent on legacy memories written before #560; readers must treat
|
|
2915
2916
|
// `undefined` as zero observations (uniform Beta(1,1) prior).
|
|
2916
|
-
//
|
|
2917
2917
|
// Both values must be non-negative integers on write. PR 1 wires only the
|
|
2918
2918
|
// schema + storage round-trip — no increments, scoring, or filtering yet.
|
|
2919
2919
|
/** Number of sessions where this memory was retrieved and the outcome was judged a success. */
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-agnostic user-message cleaning (issue #2331).
|
|
3
|
+
*
|
|
4
|
+
* Owns the generic part of the cleaner the OpenClaw wiring has always used
|
|
5
|
+
* (`src/user-message-cleaning.ts` in the root host bundle): stripping the
|
|
6
|
+
* structured host-injected memory wrappers and the leading memory-context
|
|
7
|
+
* preamble from a user message. The host wrapper keeps only the
|
|
8
|
+
* platform-header pattern (channel envelopes are an OpenClaw concept) and
|
|
9
|
+
* delegates here, so core consumers — the episodic-context recall section
|
|
10
|
+
* re-rendering archived user turns — share ONE implementation instead of a
|
|
11
|
+
* second cleaner drifting beside it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Never-matching header pattern for callers with no platform envelope. */
|
|
15
|
+
const NO_PLATFORM_HEADER = /$a/;
|
|
16
|
+
|
|
17
|
+
const WRAPPER_OPEN = "<supermemory-context";
|
|
18
|
+
const WRAPPER_CLOSE = "</supermemory-context>";
|
|
19
|
+
const WRAPPER_MAX_ATTRIBUTE_CHARS = 256;
|
|
20
|
+
|
|
21
|
+
function isWhitespaceCode(code: number): boolean {
|
|
22
|
+
return code === 32 || (code >= 9 && code <= 13);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Remove `<supermemory-context …>…</supermemory-context>` wrappers. A
|
|
27
|
+
* single left-to-right scan with string searches — no regex on uncontrolled
|
|
28
|
+
* input — so adversarial repeats of the tag literal stay linear (CodeQL
|
|
29
|
+
* redos rule, issue #2331 review). Malformed or oversized tag heads are
|
|
30
|
+
* treated as plain text, and an unterminated wrapper preserves the rest.
|
|
31
|
+
*/
|
|
32
|
+
function stripMemoryWrappers(content: string): string {
|
|
33
|
+
let out = "";
|
|
34
|
+
let cursor = 0;
|
|
35
|
+
for (;;) {
|
|
36
|
+
const open = content.indexOf(WRAPPER_OPEN, cursor);
|
|
37
|
+
if (open === -1) return out + content.slice(cursor);
|
|
38
|
+
const headEnd = content.indexOf(">", open);
|
|
39
|
+
if (headEnd === -1) return out + content.slice(cursor);
|
|
40
|
+
const head = content.slice(open + WRAPPER_OPEN.length, headEnd);
|
|
41
|
+
const headIsPlain =
|
|
42
|
+
head.length > 0 &&
|
|
43
|
+
(head.length > WRAPPER_MAX_ATTRIBUTE_CHARS + 1 || headIsNotAttributeRun(head));
|
|
44
|
+
if (head !== "" && headIsPlain) {
|
|
45
|
+
// Not a wrapper open tag: keep the literal and continue after it.
|
|
46
|
+
out += content.slice(cursor, open + WRAPPER_OPEN.length);
|
|
47
|
+
cursor = open + WRAPPER_OPEN.length;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const close = content.indexOf(WRAPPER_CLOSE, headEnd);
|
|
51
|
+
if (close === -1) return out + content.slice(cursor);
|
|
52
|
+
let after = close + WRAPPER_CLOSE.length;
|
|
53
|
+
while (after < content.length && isWhitespaceCode(content.charCodeAt(after))) {
|
|
54
|
+
after += 1;
|
|
55
|
+
}
|
|
56
|
+
// Keep everything before the wrapper; only the wrapper (plus its
|
|
57
|
+
// trailing whitespace) is removed.
|
|
58
|
+
out += content.slice(cursor, open);
|
|
59
|
+
cursor = after;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Remove a trailing `[message_id: …]` marker (with surrounding whitespace)
|
|
65
|
+
* using a single right-anchored string scan — no regex on uncontrolled
|
|
66
|
+
* input (CodeQL redos rule). Returns null when no marker terminates the
|
|
67
|
+
* content, leaving the caller's string untouched.
|
|
68
|
+
*/
|
|
69
|
+
function stripTrailingMessageId(content: string): string | null {
|
|
70
|
+
const marker = "[message_id:";
|
|
71
|
+
const start = content.lastIndexOf(marker);
|
|
72
|
+
if (start === -1) return null;
|
|
73
|
+
let index = start + marker.length;
|
|
74
|
+
while (index < content.length && isWhitespaceCode(content.charCodeAt(index))) {
|
|
75
|
+
index += 1;
|
|
76
|
+
}
|
|
77
|
+
const idStart = index;
|
|
78
|
+
while (index < content.length && content.charCodeAt(index) !== 93 /* ] */) {
|
|
79
|
+
index += 1;
|
|
80
|
+
}
|
|
81
|
+
if (index === idStart || index === content.length) return null;
|
|
82
|
+
index += 1; // past the closing bracket
|
|
83
|
+
while (index < content.length && isWhitespaceCode(content.charCodeAt(index))) {
|
|
84
|
+
index += 1;
|
|
85
|
+
}
|
|
86
|
+
if (index !== content.length) return null;
|
|
87
|
+
let boundary = start;
|
|
88
|
+
while (boundary > 0 && isWhitespaceCode(content.charCodeAt(boundary - 1))) {
|
|
89
|
+
boundary -= 1;
|
|
90
|
+
}
|
|
91
|
+
return content.slice(0, boundary);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** True when the tag-head text cannot be a whitespace-led attribute run. */
|
|
95
|
+
function headIsNotAttributeRun(head: string): boolean {
|
|
96
|
+
if (head.charCodeAt(0) !== 32 && !(head.charCodeAt(0) >= 9 && head.charCodeAt(0) <= 13)) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
for (let index = 0; index < head.length; index += 1) {
|
|
100
|
+
const code = head.charCodeAt(index);
|
|
101
|
+
if (code === 60 || code === 62) return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function cleanUserMessageWithPattern(
|
|
107
|
+
content: string,
|
|
108
|
+
platformHeaderPattern: RegExp,
|
|
109
|
+
): string {
|
|
110
|
+
let cleaned = stripMemoryWrappers(content);
|
|
111
|
+
const platformHeader = cleaned.match(platformHeaderPattern);
|
|
112
|
+
const hasPlatformHeader = platformHeader !== null;
|
|
113
|
+
if (platformHeader) {
|
|
114
|
+
cleaned = cleaned.slice(platformHeader[0].length);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Remove markdown memory context only when it is a leading preamble. If a
|
|
118
|
+
// user writes a section with this title later in their message, preserve it.
|
|
119
|
+
cleaned = cleaned.replace(
|
|
120
|
+
/^\s*## Memory Context \((?:Engram|Remnic)\)[\s\S]*?(?=\n## |\n$)/i,
|
|
121
|
+
"",
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (hasPlatformHeader) {
|
|
125
|
+
cleaned = stripTrailingMessageId(cleaned) ?? cleaned;
|
|
126
|
+
}
|
|
127
|
+
return cleaned.trim();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Clean an archived user turn for re-injection (issue #2331): strips the
|
|
132
|
+
* memory wrappers this system itself injects, so an archived echo of a
|
|
133
|
+
* previous recall output cannot re-enter the context through the
|
|
134
|
+
* episodic-context section. No platform envelope stripping — core has no
|
|
135
|
+
* host channel-envelope concept.
|
|
136
|
+
*/
|
|
137
|
+
export function cleanArchivedUserMessage(content: string): string {
|
|
138
|
+
return cleanUserMessageWithPattern(content, NO_PLATFORM_HEADER);
|
|
139
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
collapseWhitespace,
|
|
6
|
+
displayWidth,
|
|
7
|
+
graphemeUnits,
|
|
8
|
+
padEndDisplay,
|
|
9
|
+
truncateCodePointSafe,
|
|
10
|
+
truncateGraphemeSafe,
|
|
11
|
+
} from "./whitespace.js";
|
|
12
|
+
|
|
13
|
+
function hasLoneSurrogate(value: string): boolean {
|
|
14
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
15
|
+
const code = value.charCodeAt(index);
|
|
16
|
+
const isHigh = code >= 0xd800 && code <= 0xdbff;
|
|
17
|
+
const isLow = code >= 0xdc00 && code <= 0xdfff;
|
|
18
|
+
if (isHigh) {
|
|
19
|
+
const next = value.charCodeAt(index + 1);
|
|
20
|
+
if (next < 0xdc00 || next > 0xdfff) return true;
|
|
21
|
+
index += 1;
|
|
22
|
+
} else if (isLow) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isGraphemePrefix(prefix: string, value: string): boolean {
|
|
30
|
+
let accumulated = "";
|
|
31
|
+
for (const unit of graphemeUnits(value)) {
|
|
32
|
+
if (accumulated === prefix) return true;
|
|
33
|
+
accumulated += unit;
|
|
34
|
+
}
|
|
35
|
+
return accumulated === prefix;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Deterministic pseudo-random source so failures replay exactly.
|
|
39
|
+
function lcg(seed: number): () => number {
|
|
40
|
+
let state = seed >>> 0;
|
|
41
|
+
return () => {
|
|
42
|
+
state = (state * 1664525 + 1013904223) >>> 0;
|
|
43
|
+
return state / 0x1_0000_0000;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Every entry is one or more whole grapheme clusters covering the cases named
|
|
48
|
+
// in issue #2195: astral emoji, ZWJ sequences, skin-tone modifiers,
|
|
49
|
+
// regional-indicator flags, Hangul (precomposed and jamo), combining marks,
|
|
50
|
+
// and astral kanji.
|
|
51
|
+
const GRAPHEME_PIECES = [
|
|
52
|
+
"a",
|
|
53
|
+
"b",
|
|
54
|
+
" ",
|
|
55
|
+
"7",
|
|
56
|
+
"😀",
|
|
57
|
+
"👨👩👧👦",
|
|
58
|
+
"👍🏽",
|
|
59
|
+
"🇯🇵",
|
|
60
|
+
"🇺🇸",
|
|
61
|
+
"🇰🇷",
|
|
62
|
+
"한",
|
|
63
|
+
"한",
|
|
64
|
+
"e\u0301",
|
|
65
|
+
"k\u0331\u0301",
|
|
66
|
+
"क़",
|
|
67
|
+
"𠮷",
|
|
68
|
+
"日",
|
|
69
|
+
"本",
|
|
70
|
+
"語",
|
|
71
|
+
] as const;
|
|
72
|
+
|
|
73
|
+
test("truncateGraphemeSafe: property - valid Unicode, grapheme-aligned, within budget", () => {
|
|
74
|
+
const random = lcg(20260816);
|
|
75
|
+
for (let iteration = 0; iteration < 500; iteration += 1) {
|
|
76
|
+
let input = "";
|
|
77
|
+
const pieceCount = 1 + Math.floor(random() * 12);
|
|
78
|
+
for (let piece = 0; piece < pieceCount; piece += 1) {
|
|
79
|
+
input += GRAPHEME_PIECES[Math.floor(random() * GRAPHEME_PIECES.length)]!;
|
|
80
|
+
}
|
|
81
|
+
const maxChars = Math.floor(random() * (input.length + 2));
|
|
82
|
+
const output = truncateGraphemeSafe(input, maxChars);
|
|
83
|
+
assert.ok(
|
|
84
|
+
output.length <= maxChars,
|
|
85
|
+
`budget exceeded for ${JSON.stringify(input)} @ ${maxChars}: ${JSON.stringify(output)}`,
|
|
86
|
+
);
|
|
87
|
+
assert.ok(
|
|
88
|
+
!hasLoneSurrogate(output),
|
|
89
|
+
`lone surrogate for ${JSON.stringify(input)} @ ${maxChars}: ${JSON.stringify(output)}`,
|
|
90
|
+
);
|
|
91
|
+
assert.ok(
|
|
92
|
+
isGraphemePrefix(output, input),
|
|
93
|
+
`split grapheme for ${JSON.stringify(input)} @ ${maxChars}: ${JSON.stringify(output)}`,
|
|
94
|
+
);
|
|
95
|
+
if (input.length <= maxChars) {
|
|
96
|
+
assert.equal(output, input, `fits must pass through @ ${maxChars}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("truncateGraphemeSafe: emoji, ZWJ, flags, jamo, and combining marks stay whole", () => {
|
|
102
|
+
assert.equal(truncateGraphemeSafe("hello world", 5), "hello");
|
|
103
|
+
assert.equal(truncateGraphemeSafe("hello", 50), "hello");
|
|
104
|
+
assert.equal(truncateGraphemeSafe("hello", 0), "");
|
|
105
|
+
assert.equal(truncateGraphemeSafe("👍👍", 2), "👍");
|
|
106
|
+
assert.equal(truncateGraphemeSafe("👍", 1), "", "a split emoji is dropped, never half-cut");
|
|
107
|
+
assert.equal(truncateGraphemeSafe("a👍b", 2), "a");
|
|
108
|
+
assert.equal(truncateGraphemeSafe("🇯🇵🇺🇸", 4), "🇯🇵");
|
|
109
|
+
assert.equal(truncateGraphemeSafe("🇯🇵🇺🇸", 3), "", "a split flag is dropped, never half-cut");
|
|
110
|
+
const family = "👨👩👧👦";
|
|
111
|
+
assert.equal(truncateGraphemeSafe(`${family}!`, family.length + 1), `${family}!`);
|
|
112
|
+
assert.equal(truncateGraphemeSafe(`${family}!`, family.length), family);
|
|
113
|
+
assert.equal(truncateGraphemeSafe(family, family.length - 1), "", "ZWJ sequence is atomic");
|
|
114
|
+
assert.equal(truncateGraphemeSafe("한글", 3), "한", "jamo run is one cluster");
|
|
115
|
+
assert.equal(truncateGraphemeSafe("e\u0301x", 2), "e\u0301", "base plus mark is one cluster");
|
|
116
|
+
assert.equal(truncateGraphemeSafe("e\u0301x", 1), "");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("truncateCodePointSafe and collapseWhitespace keep prior behavior", () => {
|
|
120
|
+
assert.equal(collapseWhitespace(" a \n b "), "a b");
|
|
121
|
+
assert.equal(truncateCodePointSafe("abcd", 2), "ab");
|
|
122
|
+
assert.equal(truncateCodePointSafe("ab", 9), "ab");
|
|
123
|
+
assert.equal(truncateCodePointSafe("abc", 0), "");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("displayWidth: CJK wide, fullwidth, halfwidth, marks, and emoji", () => {
|
|
127
|
+
assert.equal(displayWidth(""), 0);
|
|
128
|
+
assert.equal(displayWidth("abc"), 3);
|
|
129
|
+
assert.equal(displayWidth("日本語"), 6);
|
|
130
|
+
assert.equal(displayWidth("ハングル"), 8);
|
|
131
|
+
assert.equal(displayWidth("한국어"), 6);
|
|
132
|
+
assert.equal(displayWidth("ア"), 1, "halfwidth katakana is narrow");
|
|
133
|
+
assert.equal(displayWidth("full"), 8, "fullwidth ASCII is wide");
|
|
134
|
+
assert.equal(displayWidth("e\u0301"), 1, "combining mark is zero width");
|
|
135
|
+
assert.equal(displayWidth("😀"), 2);
|
|
136
|
+
assert.equal(displayWidth("中日 memo"), 9, "2+2+1+4");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("displayWidth: emoji sequences measure as one two-cell grapheme", () => {
|
|
140
|
+
assert.equal(displayWidth("👨👩👧👦"), 2, "ZWJ family emoji is one cluster");
|
|
141
|
+
assert.equal(displayWidth("🇯🇵"), 2, "regional-indicator flag is one cluster");
|
|
142
|
+
assert.equal(displayWidth("👍🏽"), 2, "skin-tone modifier joins the cluster");
|
|
143
|
+
assert.equal(displayWidth("❤️"), 2, "VS16 requests emoji presentation");
|
|
144
|
+
assert.equal(displayWidth("1️⃣"), 2, "keycap sequence is one cluster");
|
|
145
|
+
assert.equal(displayWidth("👨👩👧👦 文"), 5, "clusters compose: 2 + 1 + 2");
|
|
146
|
+
assert.equal(
|
|
147
|
+
padEndDisplay("👨👩👧👦", 6),
|
|
148
|
+
"👨👩👧👦" + " ".repeat(4),
|
|
149
|
+
"emoji rows pad by display width, not code units",
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("padEndDisplay: CJK rows align to one computed width", () => {
|
|
154
|
+
assert.equal(padEndDisplay("abc", 6), "abc ");
|
|
155
|
+
assert.equal(padEndDisplay("abc", 2), "abc", "narrow target never truncates");
|
|
156
|
+
assert.equal(padEndDisplay("日本", 10), "日本" + " ".repeat(6));
|
|
157
|
+
const rows = ["機械学習", "nlp", "한국어", "x"];
|
|
158
|
+
const padded = rows.map((row) => padEndDisplay(row, 12));
|
|
159
|
+
for (const row of padded) {
|
|
160
|
+
assert.equal(displayWidth(row), 12);
|
|
161
|
+
}
|
|
162
|
+
assert.deepEqual(padded, [
|
|
163
|
+
"機械学習 ",
|
|
164
|
+
"nlp ",
|
|
165
|
+
"한국어 ",
|
|
166
|
+
"x ",
|
|
167
|
+
]);
|
|
168
|
+
// Snapshot-style: the CLI topics table shape keeps its score column aligned
|
|
169
|
+
// for CJK rows, which plain padEnd misaligns.
|
|
170
|
+
const table = rows.slice(0, 3).map((term) => ` ${padEndDisplay(term, 12)} 0.123`);
|
|
171
|
+
assert.deepEqual(table, [
|
|
172
|
+
" 機械学習 0.123",
|
|
173
|
+
" nlp 0.123",
|
|
174
|
+
" 한국어 0.123",
|
|
175
|
+
]);
|
|
176
|
+
const tableWidth = displayWidth(table[0]!);
|
|
177
|
+
assert.ok(table.every((line) => displayWidth(line) === tableWidth));
|
|
178
|
+
});
|