@remnic/core 10.0.69 → 10.0.71
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.js +82 -79
- package/dist/access-authorization-probe.js +83 -80
- package/dist/access-boundary.js +82 -79
- package/dist/access-cli.js +196 -193
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +82 -79
- package/dist/access-http-briefing.js +83 -80
- package/dist/access-http-query.js +84 -81
- package/dist/access-http.js +106 -103
- package/dist/access-identity-continuity-surface.js +82 -79
- package/dist/access-lcm-surface.js +82 -79
- package/dist/access-mcp.js +95 -92
- package/dist/access-namespace-preflight.js +82 -79
- package/dist/access-observe-lcm-archive.d.ts +5 -0
- package/dist/access-observe-lcm-archive.d.ts.map +1 -0
- package/dist/access-observe-lcm-archive.js +9 -0
- package/dist/access-observe-objective-state.d.ts +8 -0
- package/dist/access-observe-objective-state.d.ts.map +1 -0
- package/dist/access-observe-objective-state.js +17 -0
- package/dist/access-observe-stage-timing.d.ts +11 -0
- package/dist/access-observe-stage-timing.d.ts.map +1 -0
- package/dist/access-observe-stage-timing.js +9 -0
- package/dist/access-observe-stage-timing.js.map +1 -0
- package/dist/access-observe-write-category.js +13 -13
- package/dist/access-observe-write-surface.d.ts.map +1 -1
- package/dist/access-observe-write-surface.js +82 -79
- package/dist/access-offline-manifest.js +8 -8
- package/dist/access-operations-batch.js +85 -82
- package/dist/access-operations.js +92 -89
- package/dist/access-recall-concurrency.js +82 -79
- package/dist/access-recall-response.js +82 -79
- package/dist/access-recall-surface.js +82 -79
- package/dist/access-schema.js +12 -12
- package/dist/access-service.d.ts +1 -0
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +82 -79
- package/dist/access-surface-catalog.js +84 -81
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +26 -26
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/briefing.js +17 -17
- package/dist/buffer-turn-helpers.d.ts +10 -1
- package/dist/buffer-turn-helpers.d.ts.map +1 -1
- package/dist/buffer-turn-helpers.js +3 -1
- package/dist/buffer.d.ts +4 -0
- package/dist/buffer.d.ts.map +1 -1
- package/dist/buffer.js +9 -9
- package/dist/calibration.js +4 -4
- package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +22 -22
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +3 -3
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-6WKWL53V.js → chunk-23BA3USG.js} +128 -122
- package/dist/chunk-23BA3USG.js.map +1 -0
- package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-CI3MAAIY.js → chunk-2R44BBPK.js} +2 -2
- package/dist/{chunk-QDZWQBLU.js → chunk-2R7OCBRR.js} +2 -2
- package/dist/{chunk-B3MWELZ3.js → chunk-2WPWEVAH.js} +2 -2
- package/dist/{chunk-PLCYKAWH.js → chunk-3ICAWZUU.js} +4 -4
- package/dist/{chunk-R4ZHF4K2.js → chunk-43FLJLT2.js} +4 -4
- package/dist/{chunk-PMDOGXA2.js → chunk-4PMFSYH7.js} +2 -2
- package/dist/{chunk-HXFIILGF.js → chunk-4RASUAN3.js} +4 -4
- package/dist/{chunk-EI6M5NVK.js → chunk-5JBVPJCT.js} +2 -2
- package/dist/{chunk-4NV7O76Q.js → chunk-5ORJHH43.js} +2 -2
- package/dist/{chunk-74AY3S7U.js → chunk-5RW247F5.js} +10 -10
- package/dist/{chunk-4XINO77L.js → chunk-66JOLMWS.js} +2 -2
- package/dist/{chunk-AYJQ644F.js → chunk-6KBCUIZP.js} +2 -2
- package/dist/{chunk-GKL6QYZO.js → chunk-6L63IN3M.js} +1 -1
- package/dist/{chunk-OPFVXM2D.js → chunk-7P5VBRBS.js} +2 -2
- package/dist/{chunk-4SOWMA55.js → chunk-7WFY2L5J.js} +3 -3
- package/dist/{chunk-GJ4HU6LK.js → chunk-AGRMXXVA.js} +2 -2
- package/dist/{chunk-KOLJJN5G.js → chunk-AQNHEDXP.js} +11 -4
- package/dist/chunk-AQNHEDXP.js.map +1 -0
- package/dist/{chunk-IVUVQPTQ.js → chunk-AZNE2H6D.js} +2 -2
- package/dist/{chunk-HPB5TF55.js → chunk-BCBY5GYZ.js} +3 -3
- package/dist/{chunk-M4IEZGCK.js → chunk-BFBS7OFQ.js} +7 -7
- package/dist/{chunk-4PG5R6BJ.js → chunk-BNU5TQLR.js} +2 -2
- package/dist/{chunk-TVE7W67N.js → chunk-BTU77RB5.js} +2 -2
- package/dist/{chunk-H6VJSITJ.js → chunk-CIL4GHMD.js} +3 -3
- package/dist/{chunk-KZZ5EZ7O.js → chunk-DC3DWF2M.js} +2 -2
- package/dist/{chunk-R2BIVU2Z.js → chunk-DE4U5PTV.js} +50 -46
- package/dist/chunk-DE4U5PTV.js.map +1 -0
- package/dist/{chunk-DDBB55L2.js → chunk-DSSTOV5M.js} +534 -361
- package/dist/chunk-DSSTOV5M.js.map +1 -0
- package/dist/{chunk-4MPAKYZU.js → chunk-EGKDONTC.js} +12 -6
- package/dist/chunk-EGKDONTC.js.map +1 -0
- package/dist/{chunk-AUFPPBC3.js → chunk-ENPJX3BB.js} +3 -3
- package/dist/{chunk-CTH2CQCB.js → chunk-EQ6EMJRG.js} +2 -2
- package/dist/{chunk-63A2BU2B.js → chunk-F4ZHKDBW.js} +5 -5
- package/dist/{chunk-3LBDEGQA.js → chunk-FDUCIUQ5.js} +2 -2
- package/dist/{chunk-OCK4D5PC.js → chunk-FGGLVCHI.js} +2 -2
- package/dist/{chunk-XVZA6VXC.js → chunk-FZVROZKC.js} +4 -4
- package/dist/{chunk-3RHU5MD7.js → chunk-GXAZ4Q2S.js} +2 -2
- package/dist/{chunk-UVVFPCPX.js → chunk-H2X32F2K.js} +2 -2
- package/dist/{chunk-645WOU5I.js → chunk-H7BZBSP6.js} +4 -4
- package/dist/{chunk-W7K5BP3M.js → chunk-HQPFACXO.js} +2 -2
- package/dist/{chunk-TECK5IVY.js → chunk-HZNURRDS.js} +2 -2
- package/dist/{chunk-U2CAGMUD.js → chunk-ICBUTKLL.js} +2 -2
- package/dist/{chunk-B4DXREWC.js → chunk-IQXQ3KWR.js} +2 -2
- package/dist/{chunk-AL3TCNSJ.js → chunk-J6GZ2254.js} +4 -4
- package/dist/{chunk-5MGKGEL7.js → chunk-J72ZY7NU.js} +2 -2
- package/dist/{chunk-OH55F76C.js → chunk-JEX2JL3U.js} +2 -2
- package/dist/{chunk-Y3XPSGNF.js → chunk-JGLZ46JS.js} +2 -2
- package/dist/{chunk-EXQJMFRF.js → chunk-JY4LXMZR.js} +2 -2
- package/dist/{chunk-6P7U3HWG.js → chunk-K36RMINQ.js} +3 -3
- package/dist/{chunk-BUKK5SWA.js → chunk-KQAFEZQX.js} +2 -2
- package/dist/{chunk-LN4X2NDA.js → chunk-KY3KJUNN.js} +10 -10
- package/dist/chunk-L3E7MLMK.js +20 -0
- package/dist/chunk-L3E7MLMK.js.map +1 -0
- package/dist/{chunk-H5DIRCZP.js → chunk-LHMCZ6VW.js} +18 -18
- package/dist/{chunk-GGD2YDS6.js → chunk-LN7CWLAU.js} +2 -2
- package/dist/{chunk-GHHQLSSY.js → chunk-LYOCAUI6.js} +3 -3
- package/dist/{chunk-VA3GNAWQ.js → chunk-LZIOY7ZU.js} +4 -5
- package/dist/chunk-LZIOY7ZU.js.map +1 -0
- package/dist/{chunk-W26D2USW.js → chunk-MUH7RR6T.js} +6 -6
- package/dist/{chunk-IAGVM7EY.js → chunk-N2Y6COEM.js} +2 -2
- package/dist/{chunk-64JLBFCQ.js → chunk-N7NAGECH.js} +2 -2
- package/dist/{chunk-5Y6VA6X2.js → chunk-OGQGDOYK.js} +2 -2
- package/dist/{chunk-U7MIMS2Y.js → chunk-OUBEJWVI.js} +17 -1
- package/dist/chunk-OUBEJWVI.js.map +1 -0
- package/dist/{chunk-JLMOH5JA.js → chunk-OWZXDBV4.js} +2 -2
- package/dist/{chunk-ZORS5BG5.js → chunk-PJAMFIDE.js} +10 -10
- package/dist/chunk-QCFZDYYG.js +34 -0
- package/dist/chunk-QCFZDYYG.js.map +1 -0
- package/dist/{chunk-SXTVZ6JL.js → chunk-QIOQI7QZ.js} +2 -2
- package/dist/{chunk-MMCO4SRG.js → chunk-QNN4FYBD.js} +19 -11
- package/dist/chunk-QNN4FYBD.js.map +1 -0
- package/dist/{chunk-75DCOS6I.js → chunk-QUPTDKSX.js} +67 -67
- package/dist/{chunk-HH73M65H.js → chunk-QVZZE6VH.js} +3 -3
- package/dist/{chunk-XWE3EOWX.js → chunk-RZED7FAU.js} +2 -2
- package/dist/{chunk-IZBIDPMY.js → chunk-S37O4S6E.js} +2 -2
- package/dist/{chunk-NHWDTOBN.js → chunk-S73ZVU37.js} +21 -21
- package/dist/{chunk-YSATDEBY.js → chunk-SBRUTD5O.js} +5 -5
- package/dist/{chunk-WVP2I6C5.js → chunk-SDL22RRW.js} +50 -11
- package/dist/chunk-SDL22RRW.js.map +1 -0
- package/dist/{chunk-PRKNAIGZ.js → chunk-T6T2RMWW.js} +5 -5
- package/dist/{chunk-CIEEYVUB.js → chunk-TPX7XXY4.js} +2 -2
- package/dist/{chunk-L334PSLW.js → chunk-U2A56LLF.js} +8 -8
- package/dist/{chunk-P4TV3TQ2.js → chunk-U6IL4724.js} +5 -5
- package/dist/{chunk-5UM4H64E.js → chunk-UAOJVQXC.js} +2 -2
- package/dist/{chunk-MOIJWWNA.js → chunk-UE5JYPU6.js} +13 -13
- package/dist/{chunk-TGQW3AMK.js → chunk-UIFWZMO7.js} +23 -23
- package/dist/{chunk-UW6LBFYN.js → chunk-USX5YIKB.js} +6 -6
- package/dist/{chunk-QGFR73SN.js → chunk-VK5HKVQ7.js} +2 -2
- package/dist/{chunk-ZOBYTRBU.js → chunk-VNTQU6KB.js} +47 -2
- package/dist/chunk-VNTQU6KB.js.map +1 -0
- package/dist/{chunk-IHCXMYSE.js → chunk-VPEIWMUT.js} +2 -2
- package/dist/{chunk-O5ZINWW3.js → chunk-VSJ3ARFS.js} +2 -2
- package/dist/{chunk-RLVI2M5Q.js → chunk-W4V7P5HP.js} +2 -2
- package/dist/{chunk-ELHJK7LM.js → chunk-WBN66HPI.js} +6 -6
- package/dist/{chunk-BJLCV2P3.js → chunk-WGRAB3KB.js} +2 -2
- package/dist/{chunk-5KDDAQ6K.js → chunk-WN2DASTS.js} +10 -10
- package/dist/{chunk-IG3EZ3TF.js → chunk-WNQ3B3Q5.js} +2 -2
- package/dist/{chunk-V6SBA5O7.js → chunk-WYOHQJKC.js} +7 -7
- package/dist/chunk-WZU5AGO6.js +38 -0
- package/dist/chunk-WZU5AGO6.js.map +1 -0
- package/dist/{chunk-WHROVQ5Q.js → chunk-X4DHAXFO.js} +3 -3
- package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-JLGBJ4AX.js → chunk-XPZM7GP6.js} +10 -10
- package/dist/{chunk-64S6X4IU.js → chunk-XTN54NML.js} +4 -4
- package/dist/{chunk-UYUFDTIX.js → chunk-YIXWY4OZ.js} +2 -2
- package/dist/{chunk-NCZGGGWI.js → chunk-YPYSC3TX.js} +12 -12
- package/dist/{chunk-AVGDYYMA.js → chunk-ZBET3W26.js} +3 -3
- package/dist/{chunk-Y7427SL7.js → chunk-ZG4GNPRE.js} +216 -50
- package/dist/chunk-ZG4GNPRE.js.map +1 -0
- package/dist/chunk-ZODKV27J.js +443 -0
- package/dist/chunk-ZODKV27J.js.map +1 -0
- package/dist/cli.js +133 -130
- package/dist/coding/export-okf-codegraph.js +16 -16
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +18 -18
- package/dist/config.js +26 -26
- package/dist/connectors/codex-materialize-runner.js +19 -19
- package/dist/connectors/codex-materialize.js +2 -2
- package/dist/connectors/index.js +20 -20
- package/dist/consolidation-provenance-check.js +2 -2
- package/dist/convergence-refresh.js +20 -20
- package/dist/corpus-watermark.js +20 -20
- package/dist/deep-recall-http-glue.js +13 -13
- package/dist/deep-recall.js +2 -2
- package/dist/embedding-fallback.js +2 -2
- package/dist/entity-retrieval.js +20 -20
- package/dist/event-order-recall.js +2 -2
- package/dist/external-wiki-access.js +83 -80
- package/dist/extraction-span-materialize.js +2 -2
- package/dist/extraction-window-drain.d.ts.map +1 -1
- package/dist/extraction-window-drain.js +1 -1
- package/dist/extraction.js +32 -32
- package/dist/{graph-edge-decay-OEX5OGMI.js → graph-edge-decay-CXTNJ7IQ.js} +6 -6
- package/dist/graph-edge-decay-CXTNJ7IQ.js.map +1 -0
- package/dist/graph-jsonl.js +4 -4
- package/dist/graph-snapshot.js +4 -4
- package/dist/graph.js +3 -3
- package/dist/harmonic-retrieval.js +17 -17
- package/dist/index.js +281 -278
- package/dist/lcm/archive.d.ts +12 -2
- package/dist/lcm/archive.d.ts.map +1 -1
- package/dist/lcm/archive.js +2 -2
- package/dist/lcm/engine.d.ts +59 -8
- package/dist/lcm/engine.d.ts.map +1 -1
- package/dist/lcm/engine.js +6 -6
- package/dist/lcm/index.js +16 -16
- package/dist/lcm/queue.d.ts +7 -3
- package/dist/lcm/queue.d.ts.map +1 -1
- package/dist/lcm/queue.js +1 -1
- package/dist/lcm/summarizer.d.ts +8 -1
- package/dist/lcm/summarizer.d.ts.map +1 -1
- package/dist/lcm/summarizer.js +3 -3
- package/dist/local-llm.js +4 -4
- package/dist/location/index.js +19 -19
- package/dist/maintenance/memory-governance.js +18 -18
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +17 -17
- package/dist/maintenance/rebuild-memory-projection-runner.js +20 -20
- package/dist/maintenance/rebuild-memory-projection.js +19 -19
- package/dist/memory-browse-operations.js +83 -80
- package/dist/memory-browse.js +6 -6
- package/dist/namespaces/migrate.js +18 -18
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.js +17 -17
- package/dist/offline-sync-changeset-generations.js +5 -5
- package/dist/offline-sync-embedding-generation.js +4 -4
- package/dist/offline-sync-snapshot-stream.js +7 -7
- package/dist/offline-sync.js +9 -9
- package/dist/operator-doctor-corpus.js +21 -21
- package/dist/operator-toolkit.js +41 -41
- package/dist/orchestration/backlog-scheduler.d.ts +151 -0
- package/dist/orchestration/backlog-scheduler.d.ts.map +1 -0
- package/dist/orchestration/backlog-scheduler.js +891 -0
- package/dist/orchestration/backlog-scheduler.js.map +1 -0
- package/dist/orchestration/buffer-drain-runner.d.ts +31 -27
- package/dist/orchestration/buffer-drain-runner.d.ts.map +1 -1
- package/dist/orchestration/buffer-drain-runner.js +202 -404
- package/dist/orchestration/buffer-drain-runner.js.map +1 -1
- package/dist/orchestration/extraction-drain.d.ts +50 -17
- package/dist/orchestration/extraction-drain.d.ts.map +1 -1
- package/dist/orchestration/extraction-queue-coordinator.d.ts +19 -1
- package/dist/orchestration/extraction-queue-coordinator.d.ts.map +1 -1
- package/dist/orchestration/extraction-run.d.ts +8 -0
- package/dist/orchestration/extraction-run.d.ts.map +1 -1
- package/dist/orchestration/extraction-task-queueing.d.ts +44 -0
- package/dist/orchestration/extraction-task-queueing.d.ts.map +1 -0
- package/dist/orchestration/maintenance.js +33 -33
- package/dist/orchestration/turn-ingestion.d.ts +11 -5
- package/dist/orchestration/turn-ingestion.d.ts.map +1 -1
- package/dist/orchestrator.d.ts +13 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +196 -193
- package/dist/qmd.js +4 -4
- package/dist/recall-navigation-operations.js +83 -80
- package/dist/recall-navigation.js +82 -79
- package/dist/recall-state-view-admission.js +6 -6
- package/dist/recall-why-service.js +6 -6
- package/dist/resume-bundles.js +29 -29
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +12 -12
- package/dist/semantic-consolidation.js +25 -25
- package/dist/semantic-rule-promotion.js +17 -17
- package/dist/semantic-rule-verifier.js +17 -17
- package/dist/source-agent-qualifier.js +24 -24
- package/dist/standup/build.js +17 -17
- package/dist/storage/buffer-drain-claims.d.ts +15 -0
- package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
- package/dist/storage/buffer-drain-claims.js +2 -2
- package/dist/storage/buffer-session-files.js +2 -2
- package/dist/storage.js +16 -16
- package/dist/summarizer.js +6 -6
- package/dist/support-passport/index.js +85 -82
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/export-okf.js +17 -17
- package/dist/verified-recall.js +17 -17
- package/dist/who-knows-cli.js +18 -18
- package/dist/who-knows.js +17 -17
- package/package.json +12 -2
- package/src/access-observe-lcm-archive.ts +30 -0
- package/src/access-observe-lcm-fail-closed.test.ts +118 -0
- package/src/access-observe-objective-state.ts +40 -0
- package/src/access-observe-stage-timing.test.ts +246 -0
- package/src/access-observe-stage-timing.ts +58 -0
- package/src/access-observe-write-surface.ts +45 -45
- package/src/access-service.ts +1 -0
- package/src/backlog-scheduler.test.ts +806 -0
- package/src/buffer-drain-handover.test.ts +363 -18
- package/src/buffer-drain-inprocess.test.ts +355 -0
- package/src/buffer-drain-model-boundary.test.ts +1062 -0
- package/src/buffer-drop-extraction-chunk.test.ts +237 -22
- package/src/buffer-turn-helpers.ts +26 -1
- package/src/buffer.ts +8 -0
- package/src/extraction-window-drain.ts +7 -7
- package/src/lcm/archive.ts +59 -7
- package/src/lcm/engine.ts +256 -53
- package/src/lcm/queue.test.ts +32 -14
- package/src/lcm/queue.ts +24 -16
- package/src/lcm/summarizer.ts +15 -2
- package/src/lcm-durability.test.ts +856 -0
- package/src/lcm-engine.test.ts +12 -3
- package/src/orchestration/backlog-scheduler.ts +481 -0
- package/src/orchestration/buffer-drain-runner.ts +545 -180
- package/src/orchestration/extraction-drain.ts +287 -108
- package/src/orchestration/extraction-queue-coordinator.ts +51 -3
- package/src/orchestration/extraction-run.test.ts +41 -0
- package/src/orchestration/extraction-run.ts +24 -5
- package/src/orchestration/extraction-task-queueing.ts +87 -0
- package/src/orchestration/turn-ingestion.ts +46 -59
- package/src/orchestrator-extraction-queue.test.ts +46 -2
- package/src/orchestrator-flush.test.ts +4 -1
- package/src/orchestrator.ts +47 -21
- package/src/storage/buffer-drain-claims.ts +55 -0
- package/dist/chunk-4MPAKYZU.js.map +0 -1
- package/dist/chunk-6WKWL53V.js.map +0 -1
- package/dist/chunk-DDBB55L2.js.map +0 -1
- package/dist/chunk-KOLJJN5G.js.map +0 -1
- package/dist/chunk-MMCO4SRG.js.map +0 -1
- package/dist/chunk-R2BIVU2Z.js.map +0 -1
- package/dist/chunk-U7MIMS2Y.js.map +0 -1
- package/dist/chunk-VA3GNAWQ.js.map +0 -1
- package/dist/chunk-WVP2I6C5.js.map +0 -1
- package/dist/chunk-Y7427SL7.js.map +0 -1
- package/dist/chunk-ZOBYTRBU.js.map +0 -1
- /package/dist/{capsule-crypto-GWVG7LGC.js.map → access-observe-lcm-archive.js.map} +0 -0
- /package/dist/{graph-edge-decay-OEX5OGMI.js.map → access-observe-objective-state.js.map} +0 -0
- /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-CI3MAAIY.js.map → chunk-2R44BBPK.js.map} +0 -0
- /package/dist/{chunk-QDZWQBLU.js.map → chunk-2R7OCBRR.js.map} +0 -0
- /package/dist/{chunk-B3MWELZ3.js.map → chunk-2WPWEVAH.js.map} +0 -0
- /package/dist/{chunk-PLCYKAWH.js.map → chunk-3ICAWZUU.js.map} +0 -0
- /package/dist/{chunk-R4ZHF4K2.js.map → chunk-43FLJLT2.js.map} +0 -0
- /package/dist/{chunk-PMDOGXA2.js.map → chunk-4PMFSYH7.js.map} +0 -0
- /package/dist/{chunk-HXFIILGF.js.map → chunk-4RASUAN3.js.map} +0 -0
- /package/dist/{chunk-EI6M5NVK.js.map → chunk-5JBVPJCT.js.map} +0 -0
- /package/dist/{chunk-4NV7O76Q.js.map → chunk-5ORJHH43.js.map} +0 -0
- /package/dist/{chunk-74AY3S7U.js.map → chunk-5RW247F5.js.map} +0 -0
- /package/dist/{chunk-4XINO77L.js.map → chunk-66JOLMWS.js.map} +0 -0
- /package/dist/{chunk-AYJQ644F.js.map → chunk-6KBCUIZP.js.map} +0 -0
- /package/dist/{chunk-GKL6QYZO.js.map → chunk-6L63IN3M.js.map} +0 -0
- /package/dist/{chunk-OPFVXM2D.js.map → chunk-7P5VBRBS.js.map} +0 -0
- /package/dist/{chunk-4SOWMA55.js.map → chunk-7WFY2L5J.js.map} +0 -0
- /package/dist/{chunk-GJ4HU6LK.js.map → chunk-AGRMXXVA.js.map} +0 -0
- /package/dist/{chunk-IVUVQPTQ.js.map → chunk-AZNE2H6D.js.map} +0 -0
- /package/dist/{chunk-HPB5TF55.js.map → chunk-BCBY5GYZ.js.map} +0 -0
- /package/dist/{chunk-M4IEZGCK.js.map → chunk-BFBS7OFQ.js.map} +0 -0
- /package/dist/{chunk-4PG5R6BJ.js.map → chunk-BNU5TQLR.js.map} +0 -0
- /package/dist/{chunk-TVE7W67N.js.map → chunk-BTU77RB5.js.map} +0 -0
- /package/dist/{chunk-H6VJSITJ.js.map → chunk-CIL4GHMD.js.map} +0 -0
- /package/dist/{chunk-KZZ5EZ7O.js.map → chunk-DC3DWF2M.js.map} +0 -0
- /package/dist/{chunk-AUFPPBC3.js.map → chunk-ENPJX3BB.js.map} +0 -0
- /package/dist/{chunk-CTH2CQCB.js.map → chunk-EQ6EMJRG.js.map} +0 -0
- /package/dist/{chunk-63A2BU2B.js.map → chunk-F4ZHKDBW.js.map} +0 -0
- /package/dist/{chunk-3LBDEGQA.js.map → chunk-FDUCIUQ5.js.map} +0 -0
- /package/dist/{chunk-OCK4D5PC.js.map → chunk-FGGLVCHI.js.map} +0 -0
- /package/dist/{chunk-XVZA6VXC.js.map → chunk-FZVROZKC.js.map} +0 -0
- /package/dist/{chunk-3RHU5MD7.js.map → chunk-GXAZ4Q2S.js.map} +0 -0
- /package/dist/{chunk-UVVFPCPX.js.map → chunk-H2X32F2K.js.map} +0 -0
- /package/dist/{chunk-645WOU5I.js.map → chunk-H7BZBSP6.js.map} +0 -0
- /package/dist/{chunk-W7K5BP3M.js.map → chunk-HQPFACXO.js.map} +0 -0
- /package/dist/{chunk-TECK5IVY.js.map → chunk-HZNURRDS.js.map} +0 -0
- /package/dist/{chunk-U2CAGMUD.js.map → chunk-ICBUTKLL.js.map} +0 -0
- /package/dist/{chunk-B4DXREWC.js.map → chunk-IQXQ3KWR.js.map} +0 -0
- /package/dist/{chunk-AL3TCNSJ.js.map → chunk-J6GZ2254.js.map} +0 -0
- /package/dist/{chunk-5MGKGEL7.js.map → chunk-J72ZY7NU.js.map} +0 -0
- /package/dist/{chunk-OH55F76C.js.map → chunk-JEX2JL3U.js.map} +0 -0
- /package/dist/{chunk-Y3XPSGNF.js.map → chunk-JGLZ46JS.js.map} +0 -0
- /package/dist/{chunk-EXQJMFRF.js.map → chunk-JY4LXMZR.js.map} +0 -0
- /package/dist/{chunk-6P7U3HWG.js.map → chunk-K36RMINQ.js.map} +0 -0
- /package/dist/{chunk-BUKK5SWA.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-LN4X2NDA.js.map → chunk-KY3KJUNN.js.map} +0 -0
- /package/dist/{chunk-H5DIRCZP.js.map → chunk-LHMCZ6VW.js.map} +0 -0
- /package/dist/{chunk-GGD2YDS6.js.map → chunk-LN7CWLAU.js.map} +0 -0
- /package/dist/{chunk-GHHQLSSY.js.map → chunk-LYOCAUI6.js.map} +0 -0
- /package/dist/{chunk-W26D2USW.js.map → chunk-MUH7RR6T.js.map} +0 -0
- /package/dist/{chunk-IAGVM7EY.js.map → chunk-N2Y6COEM.js.map} +0 -0
- /package/dist/{chunk-64JLBFCQ.js.map → chunk-N7NAGECH.js.map} +0 -0
- /package/dist/{chunk-5Y6VA6X2.js.map → chunk-OGQGDOYK.js.map} +0 -0
- /package/dist/{chunk-JLMOH5JA.js.map → chunk-OWZXDBV4.js.map} +0 -0
- /package/dist/{chunk-ZORS5BG5.js.map → chunk-PJAMFIDE.js.map} +0 -0
- /package/dist/{chunk-SXTVZ6JL.js.map → chunk-QIOQI7QZ.js.map} +0 -0
- /package/dist/{chunk-75DCOS6I.js.map → chunk-QUPTDKSX.js.map} +0 -0
- /package/dist/{chunk-HH73M65H.js.map → chunk-QVZZE6VH.js.map} +0 -0
- /package/dist/{chunk-XWE3EOWX.js.map → chunk-RZED7FAU.js.map} +0 -0
- /package/dist/{chunk-IZBIDPMY.js.map → chunk-S37O4S6E.js.map} +0 -0
- /package/dist/{chunk-NHWDTOBN.js.map → chunk-S73ZVU37.js.map} +0 -0
- /package/dist/{chunk-YSATDEBY.js.map → chunk-SBRUTD5O.js.map} +0 -0
- /package/dist/{chunk-PRKNAIGZ.js.map → chunk-T6T2RMWW.js.map} +0 -0
- /package/dist/{chunk-CIEEYVUB.js.map → chunk-TPX7XXY4.js.map} +0 -0
- /package/dist/{chunk-L334PSLW.js.map → chunk-U2A56LLF.js.map} +0 -0
- /package/dist/{chunk-P4TV3TQ2.js.map → chunk-U6IL4724.js.map} +0 -0
- /package/dist/{chunk-5UM4H64E.js.map → chunk-UAOJVQXC.js.map} +0 -0
- /package/dist/{chunk-MOIJWWNA.js.map → chunk-UE5JYPU6.js.map} +0 -0
- /package/dist/{chunk-TGQW3AMK.js.map → chunk-UIFWZMO7.js.map} +0 -0
- /package/dist/{chunk-UW6LBFYN.js.map → chunk-USX5YIKB.js.map} +0 -0
- /package/dist/{chunk-QGFR73SN.js.map → chunk-VK5HKVQ7.js.map} +0 -0
- /package/dist/{chunk-IHCXMYSE.js.map → chunk-VPEIWMUT.js.map} +0 -0
- /package/dist/{chunk-O5ZINWW3.js.map → chunk-VSJ3ARFS.js.map} +0 -0
- /package/dist/{chunk-RLVI2M5Q.js.map → chunk-W4V7P5HP.js.map} +0 -0
- /package/dist/{chunk-ELHJK7LM.js.map → chunk-WBN66HPI.js.map} +0 -0
- /package/dist/{chunk-BJLCV2P3.js.map → chunk-WGRAB3KB.js.map} +0 -0
- /package/dist/{chunk-5KDDAQ6K.js.map → chunk-WN2DASTS.js.map} +0 -0
- /package/dist/{chunk-IG3EZ3TF.js.map → chunk-WNQ3B3Q5.js.map} +0 -0
- /package/dist/{chunk-V6SBA5O7.js.map → chunk-WYOHQJKC.js.map} +0 -0
- /package/dist/{chunk-WHROVQ5Q.js.map → chunk-X4DHAXFO.js.map} +0 -0
- /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-JLGBJ4AX.js.map → chunk-XPZM7GP6.js.map} +0 -0
- /package/dist/{chunk-64S6X4IU.js.map → chunk-XTN54NML.js.map} +0 -0
- /package/dist/{chunk-UYUFDTIX.js.map → chunk-YIXWY4OZ.js.map} +0 -0
- /package/dist/{chunk-NCZGGGWI.js.map → chunk-YPYSC3TX.js.map} +0 -0
- /package/dist/{chunk-AVGDYYMA.js.map → chunk-ZBET3W26.js.map} +0 -0
package/src/lcm/engine.ts
CHANGED
|
@@ -96,6 +96,17 @@ const EMPTY_LCM_STATS = {
|
|
|
96
96
|
maxDepth: -1,
|
|
97
97
|
} as const;
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Upper bound on leaf batches summarized by one background job. Keeps a single
|
|
101
|
+
* job's archive read and wall time bounded; leftover batches stay durable and
|
|
102
|
+
* the job re-queues itself.
|
|
103
|
+
*/
|
|
104
|
+
const LCM_SUMMARIZE_MAX_LEAF_BATCHES_PER_JOB = 16;
|
|
105
|
+
|
|
106
|
+
/** Retry pacing for a failed summary pass: base delay and backoff ceiling. */
|
|
107
|
+
const LCM_SUMMARIZE_RETRY_BASE_MS = 500;
|
|
108
|
+
const LCM_SUMMARIZE_RETRY_MAX_MS = 30_000;
|
|
109
|
+
|
|
99
110
|
export class LcmEngine {
|
|
100
111
|
private db: Database.Database | null = null;
|
|
101
112
|
private archive: LcmArchive | null = null;
|
|
@@ -106,6 +117,17 @@ export class LcmEngine {
|
|
|
106
117
|
private readonly config: LcmEngineConfig;
|
|
107
118
|
private readonly memoryDir: string;
|
|
108
119
|
private initPromise: Promise<void> | null = null;
|
|
120
|
+
private readonly summarizeRetryTimers = new Map<string, NodeJS.Timeout>();
|
|
121
|
+
private readonly summarizeRetryAttempts = new Map<string, number>();
|
|
122
|
+
/** Resolved when the session's pending retry timer has fired or been cleared. */
|
|
123
|
+
private readonly summarizeRetryWaiters = new Map<
|
|
124
|
+
string,
|
|
125
|
+
{ promise: Promise<void>; resolve: () => void }
|
|
126
|
+
>();
|
|
127
|
+
/** Explicit idle waiters per session; referenced only while nonzero. */
|
|
128
|
+
private readonly summarizeRetryIdleWaiters = new Map<string, number>();
|
|
129
|
+
/** Base backoff delay; tests shrink it to keep the retry loop fast. */
|
|
130
|
+
private summarizeRetryBaseMs = LCM_SUMMARIZE_RETRY_BASE_MS;
|
|
109
131
|
private readonly pendingObserveInitCounts = new Map<string, number>();
|
|
110
132
|
private readonly pendingObserveInitWaiters = new Map<string, Array<() => void>>();
|
|
111
133
|
private readonly pendingObserveInitIdleWaiters: Array<() => void> = [];
|
|
@@ -158,8 +180,8 @@ export class LcmEngine {
|
|
|
158
180
|
);
|
|
159
181
|
const observeQueue = new LcmWorkQueue({
|
|
160
182
|
concurrency: this.config.observeConcurrency,
|
|
161
|
-
worker: async (sessionId
|
|
162
|
-
await this.
|
|
183
|
+
worker: async (sessionId) => {
|
|
184
|
+
await this.processSummarizeJob(sessionId);
|
|
163
185
|
},
|
|
164
186
|
hooks: {
|
|
165
187
|
onJobStart: ({ sessionId, depth, inFlight, waitMs }) => {
|
|
@@ -200,12 +222,31 @@ export class LcmEngine {
|
|
|
200
222
|
this.dag = dag;
|
|
201
223
|
this.summarizer = summarizer;
|
|
202
224
|
this.observeQueue = observeQueue;
|
|
225
|
+
this.resumeOutstandingSummaries();
|
|
203
226
|
log.info("LCM engine initialized");
|
|
204
227
|
}
|
|
205
228
|
|
|
206
229
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
230
|
+
* Restart resume: sessions whose durable archive still holds complete
|
|
231
|
+
* unsummarized batches get a summary job without any new observe call.
|
|
232
|
+
* Only session references are queued — the content already lives in SQLite.
|
|
233
|
+
*/
|
|
234
|
+
private resumeOutstandingSummaries(): void {
|
|
235
|
+
if (!this.archive || !this.observeQueue) return;
|
|
236
|
+
const sessions = this.archive.listSessionsWithUnsummarizedTurns(
|
|
237
|
+
this.config.leafBatchSize,
|
|
238
|
+
);
|
|
239
|
+
if (sessions.length === 0) return;
|
|
240
|
+
for (const sessionId of sessions) this.observeQueue.enqueue(sessionId);
|
|
241
|
+
log.info(`LCM resume scheduled summary jobs: sessions=${sessions.length}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Archive observed messages and schedule background summarization.
|
|
246
|
+
*
|
|
247
|
+
* The archive append completes BEFORE this resolves: an accepted observe is
|
|
248
|
+
* already durable in SQLite, so a crash cannot lose it. Only the
|
|
249
|
+
* summarization is background work, and it resumes from the durable archive.
|
|
209
250
|
*/
|
|
210
251
|
async observeMessages(
|
|
211
252
|
sessionId: string,
|
|
@@ -213,53 +254,60 @@ export class LcmEngine {
|
|
|
213
254
|
): Promise<void> {
|
|
214
255
|
const normalizedSessionId = normalizeLcmSessionId(sessionId);
|
|
215
256
|
if (!normalizedSessionId) return;
|
|
216
|
-
this.enqueueObserveMessages(normalizedSessionId, messages);
|
|
257
|
+
await this.enqueueObserveMessages(normalizedSessionId, messages);
|
|
217
258
|
}
|
|
218
259
|
|
|
219
|
-
/**
|
|
220
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Durably archive messages, then queue a metadata-only summary job.
|
|
262
|
+
*
|
|
263
|
+
* Rejects when the archive append fails — callers must not treat a failed
|
|
264
|
+
* archive as an accepted observation.
|
|
265
|
+
*/
|
|
266
|
+
async enqueueObserveMessages(
|
|
221
267
|
sessionId: string,
|
|
222
268
|
messages: LcmObserveMessage[],
|
|
223
|
-
): void {
|
|
224
|
-
|
|
269
|
+
): Promise<void> {
|
|
270
|
+
// LCM off is a feature gate (the caller reports "not archived"); a closed
|
|
271
|
+
// engine is a failure — accepting the turn and archiving nothing would
|
|
272
|
+
// acknowledge a turn the archive never stored.
|
|
273
|
+
if (!this.config.enabled) return;
|
|
274
|
+
if (this.closed) throw new Error("LCM engine is closed");
|
|
225
275
|
const normalizedSessionId = normalizeLcmSessionId(sessionId);
|
|
226
276
|
if (!normalizedSessionId) return;
|
|
227
277
|
if (messages.length === 0) return;
|
|
228
278
|
|
|
229
279
|
this.reservePendingObserveInit(normalizedSessionId);
|
|
230
280
|
|
|
231
|
-
|
|
232
|
-
.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
281
|
+
try {
|
|
282
|
+
await this.ensureInitialized();
|
|
283
|
+
if (this.closed) throw new Error("LCM engine is closed");
|
|
284
|
+
if (!this.archive || !this.observeQueue) {
|
|
285
|
+
throw new Error("LCM engine is not initialized");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const archived = this.archiveObservedMessages(normalizedSessionId, messages);
|
|
289
|
+
this.observeQueue.enqueue(normalizedSessionId);
|
|
290
|
+
log.debug(
|
|
291
|
+
`LCM observe archived: session=${normalizedSessionId}, messages=${archived}, depth=${this.observeQueue.depth}, inFlight=${this.observeQueue.inFlightCount}`,
|
|
292
|
+
);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
log.error(
|
|
295
|
+
`LCM observe archive failed: session=${normalizedSessionId}, error=${err}${nativeBindingRecoveryHint(err)}`,
|
|
296
|
+
);
|
|
297
|
+
throw err;
|
|
298
|
+
} finally {
|
|
299
|
+
this.releasePendingObserveInit(normalizedSessionId);
|
|
300
|
+
}
|
|
248
301
|
}
|
|
249
302
|
|
|
250
|
-
|
|
303
|
+
/** Append observed messages to the archive; returns the number of rows. */
|
|
304
|
+
private archiveObservedMessages(
|
|
251
305
|
sessionId: string,
|
|
252
306
|
messages: LcmObserveMessage[],
|
|
253
|
-
):
|
|
254
|
-
|
|
255
|
-
await this.ensureInitialized();
|
|
256
|
-
if (this.closed || !this.archive || !this.summarizer) return;
|
|
257
|
-
|
|
258
|
-
if (messages.length === 0) return;
|
|
259
|
-
|
|
260
|
-
const currentMax = this.archive.getMaxTurnIndex(sessionId);
|
|
307
|
+
): number {
|
|
308
|
+
const currentMax = this.archive!.getMaxTurnIndex(sessionId);
|
|
261
309
|
const { messagePartsEnabled } = this.config;
|
|
262
|
-
const
|
|
310
|
+
const rows = messages.map((m, i) => ({
|
|
263
311
|
turnIndex: currentMax + 1 + i,
|
|
264
312
|
role: m.role,
|
|
265
313
|
content: m.content,
|
|
@@ -268,16 +316,109 @@ export class LcmEngine {
|
|
|
268
316
|
sourceFormat: messagePartsEnabled ? m.sourceFormat : undefined,
|
|
269
317
|
}));
|
|
270
318
|
|
|
271
|
-
this.archive
|
|
272
|
-
|
|
273
|
-
|
|
319
|
+
this.archive!.appendMessages(sessionId, rows, { messagePartsEnabled });
|
|
320
|
+
return rows.length;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Summarize one session's durable backlog.
|
|
325
|
+
*
|
|
326
|
+
* The pass is capped so a long backlog never holds a job open indefinitely;
|
|
327
|
+
* remaining batches stay durable and the job re-queues itself.
|
|
328
|
+
*/
|
|
329
|
+
private async processSummarizeJob(sessionId: string): Promise<void> {
|
|
330
|
+
if (this.closed) return;
|
|
331
|
+
await this.ensureInitialized();
|
|
332
|
+
if (this.closed || !this.archive || !this.summarizer || !this.observeQueue)
|
|
333
|
+
return;
|
|
274
334
|
|
|
275
|
-
// Trigger incremental summarization inside the worker, after append.
|
|
276
335
|
try {
|
|
277
|
-
await this.summarizer.summarizeIncremental(sessionId
|
|
336
|
+
await this.summarizer.summarizeIncremental(sessionId, {
|
|
337
|
+
maxLeafBatches: LCM_SUMMARIZE_MAX_LEAF_BATCHES_PER_JOB,
|
|
338
|
+
});
|
|
278
339
|
} catch (err) {
|
|
279
|
-
|
|
340
|
+
// The archived turns are durable; a failed pass must not strand them
|
|
341
|
+
// until the next observe or a restart.
|
|
342
|
+
log.error(`LCM incremental summarization failed: session=${sessionId}, error=${err}`);
|
|
343
|
+
this.scheduleSummarizeRetry(sessionId);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
this.clearSummarizeRetry(sessionId);
|
|
348
|
+
|
|
349
|
+
if (this.hasCompleteBatchPending(sessionId)) {
|
|
350
|
+
this.observeQueue.enqueue(sessionId);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Schedule a metadata-only retry for a failed summary pass.
|
|
356
|
+
*
|
|
357
|
+
* The retry keeps trying for as long as the engine is open and complete
|
|
358
|
+
* batches remain — a transient failure must never strand durable work.
|
|
359
|
+
* What is bounded is the RATE, not the ability to retry: one pending timer
|
|
360
|
+
* per session, exponential backoff capped at
|
|
361
|
+
* {@link LCM_SUMMARIZE_RETRY_MAX_MS}, and an unreferenced timer that is
|
|
362
|
+
* cleared when the engine closes.
|
|
363
|
+
*/
|
|
364
|
+
private scheduleSummarizeRetry(sessionId: string): void {
|
|
365
|
+
if (this.closed || this.summarizeRetryTimers.has(sessionId)) return;
|
|
366
|
+
const attempt = (this.summarizeRetryAttempts.get(sessionId) ?? 0) + 1;
|
|
367
|
+
this.summarizeRetryAttempts.set(sessionId, attempt);
|
|
368
|
+
const delayMs = Math.min(
|
|
369
|
+
LCM_SUMMARIZE_RETRY_MAX_MS,
|
|
370
|
+
this.summarizeRetryBaseMs * 2 ** Math.min(attempt - 1, 16),
|
|
371
|
+
);
|
|
372
|
+
// The waiter makes the pending retry visible to idle waits, so a direct
|
|
373
|
+
// flush (pre-compaction) serializes with the retry instead of racing it.
|
|
374
|
+
const retry = Promise.withResolvers<void>();
|
|
375
|
+
this.summarizeRetryWaiters.set(sessionId, retry);
|
|
376
|
+
const timer = setTimeout(() => {
|
|
377
|
+
this.summarizeRetryTimers.delete(sessionId);
|
|
378
|
+
this.summarizeRetryWaiters.delete(sessionId);
|
|
379
|
+
if (this.closed) {
|
|
380
|
+
retry.resolve();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (!this.hasCompleteBatchPending(sessionId)) {
|
|
384
|
+
this.summarizeRetryAttempts.delete(sessionId);
|
|
385
|
+
retry.resolve();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
// Re-queue before releasing the waiter: an idle wait released by the
|
|
389
|
+
// retry must observe the queued job, never run beside it.
|
|
390
|
+
this.observeQueue?.enqueue(sessionId);
|
|
391
|
+
retry.resolve();
|
|
392
|
+
}, delayMs);
|
|
393
|
+
// Pure background: unreferenced, so a one-shot host is never kept alive
|
|
394
|
+
// by a retry loop. An explicit idle waiter (see waitForSessionObserveIdle)
|
|
395
|
+
// references it for the duration of the wait, and close() clears all.
|
|
396
|
+
timer.unref?.();
|
|
397
|
+
this.summarizeRetryTimers.set(sessionId, timer);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** True when the session still holds a complete unsummarized batch. */
|
|
401
|
+
private hasCompleteBatchPending(sessionId: string): boolean {
|
|
402
|
+
if (!this.archive) return false;
|
|
403
|
+
return (
|
|
404
|
+
this.archive.countUnsummarizedMessages(sessionId) >=
|
|
405
|
+
this.config.leafBatchSize
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Drop a pending retry, its attempt counter, and release idle waiters. */
|
|
410
|
+
private clearSummarizeRetry(sessionId: string): void {
|
|
411
|
+
const timer = this.summarizeRetryTimers.get(sessionId);
|
|
412
|
+
if (timer) {
|
|
413
|
+
clearTimeout(timer);
|
|
414
|
+
this.summarizeRetryTimers.delete(sessionId);
|
|
280
415
|
}
|
|
416
|
+
const waiter = this.summarizeRetryWaiters.get(sessionId);
|
|
417
|
+
if (waiter) {
|
|
418
|
+
this.summarizeRetryWaiters.delete(sessionId);
|
|
419
|
+
waiter.resolve();
|
|
420
|
+
}
|
|
421
|
+
this.summarizeRetryAttempts.delete(sessionId);
|
|
281
422
|
}
|
|
282
423
|
|
|
283
424
|
get observeQueueDepth(): number {
|
|
@@ -288,12 +429,22 @@ export class LcmEngine {
|
|
|
288
429
|
return this.observeQueue?.inFlightCount ?? 0;
|
|
289
430
|
}
|
|
290
431
|
|
|
291
|
-
async waitForObserveQueueIdle(): Promise<void> {
|
|
432
|
+
async waitForObserveQueueIdle(abortSignal?: AbortSignal): Promise<void> {
|
|
433
|
+
abortSignal?.throwIfAborted();
|
|
292
434
|
if (!this.config.enabled || this.closed) return;
|
|
293
435
|
await this.ensureInitialized();
|
|
436
|
+
abortSignal?.throwIfAborted();
|
|
294
437
|
if (this.closed) return;
|
|
295
|
-
|
|
296
|
-
|
|
438
|
+
for (;;) {
|
|
439
|
+
abortSignal?.throwIfAborted();
|
|
440
|
+
await this.waitForPendingObserveInitIdle(undefined, abortSignal);
|
|
441
|
+
await this.observeQueue?.whenIdle(abortSignal);
|
|
442
|
+
abortSignal?.throwIfAborted();
|
|
443
|
+
const entry = [...this.summarizeRetryWaiters.entries()][0];
|
|
444
|
+
if (!entry) return;
|
|
445
|
+
await this.awaitSummarizeRetry(entry[0], entry[1], abortSignal);
|
|
446
|
+
// A fired retry may have re-queued a session: wait for the queue again.
|
|
447
|
+
}
|
|
297
448
|
}
|
|
298
449
|
|
|
299
450
|
async waitForSessionObserveIdle(sessionId: string, abortSignal?: AbortSignal): Promise<void> {
|
|
@@ -304,8 +455,42 @@ export class LcmEngine {
|
|
|
304
455
|
await this.ensureInitialized();
|
|
305
456
|
abortSignal?.throwIfAborted();
|
|
306
457
|
if (this.closed) return;
|
|
307
|
-
|
|
308
|
-
|
|
458
|
+
// Serialize with pending summary retries: a direct flush (pre-compaction)
|
|
459
|
+
// must not run beside a retry pass for the same session.
|
|
460
|
+
for (;;) {
|
|
461
|
+
await this.waitForPendingObserveInitIdle(normalizedSessionId, abortSignal);
|
|
462
|
+
await this.observeQueue?.whenSessionIdle(normalizedSessionId, abortSignal) ?? Promise.resolve();
|
|
463
|
+
const waiter = this.summarizeRetryWaiters.get(normalizedSessionId);
|
|
464
|
+
if (!waiter) return;
|
|
465
|
+
await this.awaitSummarizeRetry(normalizedSessionId, waiter, abortSignal);
|
|
466
|
+
// The retry fired and may have re-queued the session: wait again.
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Park an explicit idle wait on a pending retry. While any wait is parked,
|
|
472
|
+
* the retry timer is referenced so a one-shot host cannot exit under the
|
|
473
|
+
* wait; with no waiters the timer stays unreferenced background work.
|
|
474
|
+
*/
|
|
475
|
+
private async awaitSummarizeRetry(
|
|
476
|
+
sessionId: string,
|
|
477
|
+
waiter: { promise: Promise<void>; resolve: () => void },
|
|
478
|
+
abortSignal?: AbortSignal,
|
|
479
|
+
): Promise<void> {
|
|
480
|
+
const waiters = (this.summarizeRetryIdleWaiters.get(sessionId) ?? 0) + 1;
|
|
481
|
+
this.summarizeRetryIdleWaiters.set(sessionId, waiters);
|
|
482
|
+
this.summarizeRetryTimers.get(sessionId)?.ref?.();
|
|
483
|
+
try {
|
|
484
|
+
await abortOnSignal(abortSignal, waiter.promise);
|
|
485
|
+
} finally {
|
|
486
|
+
const remaining = (this.summarizeRetryIdleWaiters.get(sessionId) ?? 1) - 1;
|
|
487
|
+
if (remaining <= 0) {
|
|
488
|
+
this.summarizeRetryIdleWaiters.delete(sessionId);
|
|
489
|
+
this.summarizeRetryTimers.get(sessionId)?.unref?.();
|
|
490
|
+
} else {
|
|
491
|
+
this.summarizeRetryIdleWaiters.set(sessionId, remaining);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
309
494
|
}
|
|
310
495
|
|
|
311
496
|
private reservePendingObserveInit(sessionId: string): void {
|
|
@@ -359,8 +544,18 @@ export class LcmEngine {
|
|
|
359
544
|
}
|
|
360
545
|
|
|
361
546
|
if (this.pendingObserveInitCounts.size === 0) return;
|
|
362
|
-
await new Promise<void>((resolve) => {
|
|
363
|
-
|
|
547
|
+
await new Promise<void>((resolve, reject) => {
|
|
548
|
+
const finish = (): void => {
|
|
549
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
550
|
+
resolve();
|
|
551
|
+
};
|
|
552
|
+
const onAbort = (): void => {
|
|
553
|
+
const remaining = this.pendingObserveInitIdleWaiters.filter((waiter) => waiter !== finish);
|
|
554
|
+
this.pendingObserveInitIdleWaiters.splice(0, this.pendingObserveInitIdleWaiters.length, ...remaining);
|
|
555
|
+
reject(abortSignal?.reason instanceof Error ? abortSignal.reason : new DOMException("Aborted", "AbortError"));
|
|
556
|
+
};
|
|
557
|
+
this.pendingObserveInitIdleWaiters.push(finish);
|
|
558
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
364
559
|
});
|
|
365
560
|
}
|
|
366
561
|
|
|
@@ -697,23 +892,25 @@ export class LcmEngine {
|
|
|
697
892
|
}
|
|
698
893
|
|
|
699
894
|
/** Clear all LCM archive and summary state for one session. */
|
|
700
|
-
async clearSession(sessionId: string): Promise<void> {
|
|
895
|
+
async clearSession(sessionId: string, abortSignal?: AbortSignal): Promise<void> {
|
|
701
896
|
if (!this.config.enabled || this.closed) return;
|
|
702
897
|
const normalizedSessionId = normalizeLcmSessionId(sessionId);
|
|
703
898
|
if (!normalizedSessionId) return;
|
|
704
899
|
await this.ensureInitialized();
|
|
705
900
|
if (this.closed || !this.archive || !this.dag) return;
|
|
706
|
-
await this.waitForSessionObserveIdle(normalizedSessionId);
|
|
901
|
+
await this.waitForSessionObserveIdle(normalizedSessionId, abortSignal);
|
|
902
|
+
abortSignal?.throwIfAborted();
|
|
707
903
|
this.dag.deleteSession(normalizedSessionId);
|
|
708
904
|
this.archive.deleteSession(normalizedSessionId);
|
|
709
905
|
}
|
|
710
906
|
|
|
711
907
|
/** Clear all LCM archive and summary state without closing the engine. */
|
|
712
|
-
async clearAll(): Promise<void> {
|
|
908
|
+
async clearAll(abortSignal?: AbortSignal): Promise<void> {
|
|
713
909
|
if (!this.config.enabled || this.closed) return;
|
|
714
910
|
await this.ensureInitialized();
|
|
715
911
|
if (this.closed || !this.archive || !this.dag) return;
|
|
716
|
-
await this.waitForObserveQueueIdle();
|
|
912
|
+
await this.waitForObserveQueueIdle(abortSignal);
|
|
913
|
+
abortSignal?.throwIfAborted();
|
|
717
914
|
this.dag.deleteAll();
|
|
718
915
|
this.archive.deleteAll();
|
|
719
916
|
}
|
|
@@ -735,6 +932,12 @@ export class LcmEngine {
|
|
|
735
932
|
/** Close the database connection. */
|
|
736
933
|
close(): void {
|
|
737
934
|
this.closed = true;
|
|
935
|
+
for (const timer of this.summarizeRetryTimers.values()) clearTimeout(timer);
|
|
936
|
+
this.summarizeRetryTimers.clear();
|
|
937
|
+
this.summarizeRetryAttempts.clear();
|
|
938
|
+
for (const retry of this.summarizeRetryWaiters.values()) retry.resolve();
|
|
939
|
+
this.summarizeRetryWaiters.clear();
|
|
940
|
+
this.summarizeRetryIdleWaiters.clear();
|
|
738
941
|
if (this.db) {
|
|
739
942
|
this.db.close();
|
|
740
943
|
}
|
package/src/lcm/queue.test.ts
CHANGED
|
@@ -26,9 +26,9 @@ test("LcmWorkQueue coalesces duplicate pending jobs per session", async () => {
|
|
|
26
26
|
},
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
queue.enqueue("blocked"
|
|
30
|
-
queue.enqueue("session-a"
|
|
31
|
-
queue.enqueue("session-a"
|
|
29
|
+
queue.enqueue("blocked");
|
|
30
|
+
queue.enqueue("session-a");
|
|
31
|
+
queue.enqueue("session-a");
|
|
32
32
|
|
|
33
33
|
await Promise.resolve();
|
|
34
34
|
|
|
@@ -56,8 +56,8 @@ test("LcmWorkQueue continues after a worker failure", async () => {
|
|
|
56
56
|
},
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
queue.enqueue("session-a"
|
|
60
|
-
queue.enqueue("session-b"
|
|
59
|
+
queue.enqueue("session-a");
|
|
60
|
+
queue.enqueue("session-b");
|
|
61
61
|
|
|
62
62
|
await queue.whenIdle();
|
|
63
63
|
|
|
@@ -87,14 +87,14 @@ test("LcmWorkQueue preserves first enqueue time when pending jobs coalesce", asy
|
|
|
87
87
|
},
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
queue.enqueue("blocked"
|
|
90
|
+
queue.enqueue("blocked");
|
|
91
91
|
await Promise.resolve();
|
|
92
92
|
|
|
93
93
|
now = 110;
|
|
94
|
-
queue.enqueue("session-a"
|
|
94
|
+
queue.enqueue("session-a");
|
|
95
95
|
|
|
96
96
|
now = 150;
|
|
97
|
-
queue.enqueue("session-a"
|
|
97
|
+
queue.enqueue("session-a");
|
|
98
98
|
|
|
99
99
|
now = 200;
|
|
100
100
|
blocker.resolve();
|
|
@@ -127,10 +127,10 @@ test("LcmWorkQueue does not start a second job for a session already in flight",
|
|
|
127
127
|
},
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
queue.enqueue("session-a"
|
|
130
|
+
queue.enqueue("session-a");
|
|
131
131
|
await Promise.resolve();
|
|
132
|
-
queue.enqueue("session-a"
|
|
133
|
-
queue.enqueue("session-b"
|
|
132
|
+
queue.enqueue("session-a");
|
|
133
|
+
queue.enqueue("session-b");
|
|
134
134
|
|
|
135
135
|
await Promise.resolve();
|
|
136
136
|
await Promise.resolve();
|
|
@@ -159,8 +159,8 @@ test("LcmWorkQueue waits for a specific session without waiting for unrelated id
|
|
|
159
159
|
},
|
|
160
160
|
});
|
|
161
161
|
|
|
162
|
-
queue.enqueue("blocked"
|
|
163
|
-
queue.enqueue("session-a"
|
|
162
|
+
queue.enqueue("blocked");
|
|
163
|
+
queue.enqueue("session-a");
|
|
164
164
|
|
|
165
165
|
await Promise.resolve();
|
|
166
166
|
const sessionIdlePromise = queue.whenSessionIdle("session-a").then(() => {
|
|
@@ -185,7 +185,7 @@ test("#3077 whenSessionIdle abort unregisters the waiter", async () => {
|
|
|
185
185
|
await blocker.promise;
|
|
186
186
|
},
|
|
187
187
|
});
|
|
188
|
-
queue.enqueue("blocked"
|
|
188
|
+
queue.enqueue("blocked");
|
|
189
189
|
await Promise.resolve();
|
|
190
190
|
const abort = new AbortController();
|
|
191
191
|
const waiting = queue.whenSessionIdle("blocked", abort.signal);
|
|
@@ -195,3 +195,21 @@ test("#3077 whenSessionIdle abort unregisters the waiter", async () => {
|
|
|
195
195
|
await queue.whenIdle();
|
|
196
196
|
await queue.whenSessionIdle("blocked");
|
|
197
197
|
});
|
|
198
|
+
|
|
199
|
+
test("whenIdle abort unregisters the waiter while a job is in flight", async () => {
|
|
200
|
+
const blocker = deferred();
|
|
201
|
+
const queue = new LcmWorkQueue({
|
|
202
|
+
concurrency: 1,
|
|
203
|
+
worker: async () => {
|
|
204
|
+
await blocker.promise;
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
queue.enqueue("blocked");
|
|
208
|
+
await Promise.resolve();
|
|
209
|
+
const abort = new AbortController();
|
|
210
|
+
const waiting = queue.whenIdle(abort.signal);
|
|
211
|
+
abort.abort();
|
|
212
|
+
await assert.rejects(waiting, { name: "AbortError" });
|
|
213
|
+
blocker.resolve();
|
|
214
|
+
await queue.whenIdle();
|
|
215
|
+
});
|
package/src/lcm/queue.ts
CHANGED
|
@@ -26,13 +26,12 @@ export interface LcmWorkQueueHooks {
|
|
|
26
26
|
|
|
27
27
|
export interface LcmWorkQueueOptions {
|
|
28
28
|
concurrency?: number;
|
|
29
|
-
worker: (sessionId: string
|
|
29
|
+
worker: (sessionId: string) => Promise<void>;
|
|
30
30
|
hooks?: LcmWorkQueueHooks;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
interface PendingJob {
|
|
34
34
|
sessionId: string;
|
|
35
|
-
messages: LcmObserveMessage[];
|
|
36
35
|
enqueuedAt: number;
|
|
37
36
|
}
|
|
38
37
|
|
|
@@ -42,13 +41,14 @@ interface PendingJob {
|
|
|
42
41
|
* Jobs are coalesced per session while they are pending. A session can have
|
|
43
42
|
* at most one in-flight job at a time, and the queue processes jobs FIFO up to
|
|
44
43
|
* the configured concurrency limit.
|
|
44
|
+
*
|
|
45
|
+
* The queue carries session references only — never message payloads. Observe
|
|
46
|
+
* content is archived in SQLite before it is queued, so a pending job is a
|
|
47
|
+
* "this session has durable unsummarized turns" marker.
|
|
45
48
|
*/
|
|
46
49
|
export class LcmWorkQueue {
|
|
47
50
|
private readonly concurrency: number;
|
|
48
|
-
private readonly worker: (
|
|
49
|
-
sessionId: string,
|
|
50
|
-
messages: LcmObserveMessage[],
|
|
51
|
-
) => Promise<void>;
|
|
51
|
+
private readonly worker: (sessionId: string) => Promise<void>;
|
|
52
52
|
private readonly hooks: LcmWorkQueueHooks;
|
|
53
53
|
private readonly pending = new Map<string, PendingJob>();
|
|
54
54
|
private readonly inFlightSessions = new Set<string>();
|
|
@@ -63,17 +63,14 @@ export class LcmWorkQueue {
|
|
|
63
63
|
this.hooks = options.hooks ?? {};
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
enqueue(sessionId: string
|
|
67
|
-
if (
|
|
66
|
+
enqueue(sessionId: string): void {
|
|
67
|
+
if (sessionId.length === 0) return;
|
|
68
68
|
|
|
69
69
|
const now = Date.now();
|
|
70
70
|
const existing = this.pending.get(sessionId);
|
|
71
|
-
if (existing) {
|
|
72
|
-
existing.messages.push(...messages.map((message) => ({ ...message })));
|
|
73
|
-
} else {
|
|
71
|
+
if (!existing) {
|
|
74
72
|
this.pending.set(sessionId, {
|
|
75
73
|
sessionId,
|
|
76
|
-
messages: messages.map((message) => ({ ...message })),
|
|
77
74
|
enqueuedAt: now,
|
|
78
75
|
});
|
|
79
76
|
}
|
|
@@ -89,10 +86,21 @@ export class LcmWorkQueue {
|
|
|
89
86
|
return this.inFlight;
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
async whenIdle(): Promise<void> {
|
|
89
|
+
async whenIdle(abortSignal?: AbortSignal): Promise<void> {
|
|
90
|
+
abortSignal?.throwIfAborted();
|
|
93
91
|
if (this.depth === 0 && this.inFlight === 0) return;
|
|
94
|
-
await new Promise<void>((resolve) => {
|
|
95
|
-
|
|
92
|
+
await new Promise<void>((resolve, reject) => {
|
|
93
|
+
const finish = (): void => {
|
|
94
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
95
|
+
resolve();
|
|
96
|
+
};
|
|
97
|
+
const onAbort = (): void => {
|
|
98
|
+
const remaining = this.idleWaiters.filter((waiter) => waiter !== finish);
|
|
99
|
+
this.idleWaiters.splice(0, this.idleWaiters.length, ...remaining);
|
|
100
|
+
reject(abortSignal?.reason instanceof Error ? abortSignal.reason : new DOMException("Aborted", "AbortError"));
|
|
101
|
+
};
|
|
102
|
+
this.idleWaiters.push(finish);
|
|
103
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
96
104
|
});
|
|
97
105
|
}
|
|
98
106
|
|
|
@@ -162,7 +170,7 @@ export class LcmWorkQueue {
|
|
|
162
170
|
let error: unknown;
|
|
163
171
|
|
|
164
172
|
try {
|
|
165
|
-
await this.worker(job.sessionId
|
|
173
|
+
await this.worker(job.sessionId);
|
|
166
174
|
} catch (err) {
|
|
167
175
|
error = err;
|
|
168
176
|
} finally {
|
package/src/lcm/summarizer.ts
CHANGED
|
@@ -33,12 +33,25 @@ export class LcmSummarizer {
|
|
|
33
33
|
/**
|
|
34
34
|
* Run incremental summarization for a session.
|
|
35
35
|
* Creates leaf nodes from unsummarized messages, then rolls up as needed.
|
|
36
|
+
*
|
|
37
|
+
* `maxLeafBatches` bounds one pass: the archive read is limited to that
|
|
38
|
+
* many complete batches, so a long backlog never loads the whole
|
|
39
|
+
* unsummarized tail into memory. Remaining batches stay durable in the
|
|
40
|
+
* archive and are picked up by the next pass.
|
|
36
41
|
*/
|
|
37
|
-
async summarizeIncremental(
|
|
42
|
+
async summarizeIncremental(
|
|
43
|
+
sessionId: string,
|
|
44
|
+
options: { maxLeafBatches?: number } = {},
|
|
45
|
+
): Promise<number> {
|
|
38
46
|
let nodesCreated = 0;
|
|
39
47
|
|
|
40
48
|
// Phase 1: Create leaf nodes from unsummarized messages
|
|
41
|
-
const
|
|
49
|
+
const maxLeafBatches = options.maxLeafBatches;
|
|
50
|
+
const readLimit =
|
|
51
|
+
maxLeafBatches === undefined
|
|
52
|
+
? undefined
|
|
53
|
+
: maxLeafBatches * this.config.leafBatchSize;
|
|
54
|
+
const unsummarized = this.archive.getUnsummarizedMessages(sessionId, readLimit);
|
|
42
55
|
if (unsummarized.length >= this.config.leafBatchSize) {
|
|
43
56
|
const batches = chunkArray(unsummarized, this.config.leafBatchSize);
|
|
44
57
|
// Only process complete batches (leave partial for next time)
|