@remnic/core 10.0.36 → 10.0.38
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 +84 -84
- package/dist/access-authorization-probe.js +85 -85
- package/dist/access-boundary.js +84 -84
- package/dist/access-citation-namespace.js +2 -2
- package/dist/access-cli.js +178 -176
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +84 -84
- package/dist/access-http-briefing.js +85 -85
- package/dist/access-http-query.js +86 -86
- package/dist/access-http.js +108 -108
- package/dist/access-identity-continuity-surface.js +84 -84
- package/dist/access-lcm-surface.js +84 -84
- package/dist/access-mcp.js +97 -97
- package/dist/access-namespace-preflight.js +84 -84
- package/dist/access-observe-write-category.js +13 -13
- package/dist/access-observe-write-surface.js +84 -84
- package/dist/access-offline-manifest.js +12 -12
- package/dist/access-operations-batch.js +88 -88
- package/dist/access-operations.js +95 -95
- package/dist/access-recall-concurrency.js +84 -84
- package/dist/access-recall-diagnostics.js +2 -2
- package/dist/access-recall-response.js +84 -84
- package/dist/access-recall-surface.js +84 -84
- package/dist/access-schema.js +8 -8
- package/dist/access-service.js +84 -84
- package/dist/access-surface-catalog.js +86 -86
- package/dist/active-recall.js +43 -43
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/{auto-sync-VPHR3FBI.js → auto-sync-432USQDH.js} +9 -9
- package/dist/briefing.js +32 -32
- package/dist/buffer-eviction.d.ts +16 -0
- package/dist/buffer-eviction.d.ts.map +1 -0
- package/dist/buffer-eviction.js +8 -0
- package/dist/buffer-persistence.d.ts +45 -0
- package/dist/buffer-persistence.d.ts.map +1 -0
- package/dist/buffer-persistence.js +8 -0
- package/dist/buffer.d.ts +5 -2
- package/dist/buffer.d.ts.map +1 -1
- package/dist/buffer.js +13 -11
- package/dist/calibration.js +1 -1
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +35 -35
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-FL6SLVAJ.js → chunk-2AKIVSHP.js} +2 -2
- package/dist/{chunk-ORWDCX5Q.js → chunk-3AODCBKX.js} +11 -11
- package/dist/{chunk-XFDHL252.js → chunk-3K5IEJTK.js} +4 -4
- package/dist/{chunk-6FIFEXFF.js → chunk-3PLAL2KP.js} +21 -7
- package/dist/chunk-3PLAL2KP.js.map +1 -0
- package/dist/{chunk-XVZA6VXC.js → chunk-3SL52JKD.js} +4 -4
- package/dist/{chunk-ZSAQSLKL.js → chunk-3XVBGGF3.js} +2 -2
- package/dist/{chunk-XO4HOBM2.js → chunk-4B37KNMP.js} +2 -2
- package/dist/{chunk-NIANATGL.js → chunk-4IZ6AC2U.js} +2 -2
- package/dist/chunk-4OFXLQJD.js +58 -0
- package/dist/chunk-4OFXLQJD.js.map +1 -0
- package/dist/{chunk-S2PDFSXS.js → chunk-4P4VLPGB.js} +1 -1
- package/dist/{chunk-G5UR4DXQ.js → chunk-53BLBDV6.js} +2 -2
- package/dist/{chunk-PDYP7SUC.js → chunk-5ANUG4JZ.js} +2 -2
- package/dist/{chunk-2ZYIAUYU.js → chunk-5CWH2LKL.js} +4 -4
- package/dist/{chunk-S2S4U4G3.js → chunk-5DCS4C4S.js} +4 -4
- package/dist/{chunk-7WTTE5FW.js → chunk-5K23NQRI.js} +2 -2
- package/dist/{chunk-V3JZZJ2E.js → chunk-5UPOKIFP.js} +12 -12
- package/dist/{chunk-K567EIW3.js → chunk-63YPKHAU.js} +2 -2
- package/dist/{chunk-G3T6YMBO.js → chunk-64S6X4IU.js} +7 -7
- package/dist/{chunk-HJM23E65.js → chunk-6DTIYAAN.js} +2 -2
- package/dist/{chunk-G7XAZU2L.js → chunk-6F5BGIAF.js} +3 -3
- package/dist/{chunk-IHTKBQBS.js → chunk-6JEEY42N.js} +2 -2
- package/dist/{chunk-NHB4RC3G.js → chunk-6QOL4G76.js} +2 -2
- package/dist/{chunk-AD3ZB72W.js → chunk-6SYS5YX3.js} +2 -2
- package/dist/{chunk-DFP37GUN.js → chunk-7HXZOWTR.js} +2 -2
- package/dist/{chunk-DGBWPSM6.js → chunk-7VFNGQ4V.js} +2 -2
- package/dist/{chunk-77ZK533I.js → chunk-7ZRRJ6RG.js} +2 -2
- package/dist/{chunk-GMMZ45C4.js → chunk-AVVCPA7U.js} +6 -8
- package/dist/chunk-AVVCPA7U.js.map +1 -0
- package/dist/{chunk-QISYYK4L.js → chunk-AYBAC2N4.js} +3 -3
- package/dist/{chunk-V44DWTFA.js → chunk-AYXRK264.js} +6 -6
- package/dist/{chunk-LJSBF6PW.js → chunk-C2KOW2UQ.js} +2 -2
- package/dist/{chunk-GY4R4P2A.js → chunk-CUWOSRCS.js} +2 -2
- package/dist/{chunk-VNAIUNXI.js → chunk-CVMAOPAH.js} +10 -10
- package/dist/{chunk-CJKHLACF.js → chunk-DG4H56SK.js} +2 -2
- package/dist/{chunk-BQ6DGL6A.js → chunk-DKHX2FHI.js} +2 -2
- package/dist/{chunk-77IXBNV6.js → chunk-E24RUQEN.js} +2 -2
- package/dist/{chunk-HRT2SJPC.js → chunk-E3NBMTTX.js} +2 -2
- package/dist/{chunk-PTQZPVRQ.js → chunk-E5PVMB3I.js} +2 -2
- package/dist/{chunk-AHS3QEEA.js → chunk-EEOYF3IB.js} +2 -2
- package/dist/{chunk-5XTSIZ3L.js → chunk-EGNONTVT.js} +6 -6
- package/dist/{chunk-PH33R7GN.js → chunk-EHIKY2IA.js} +2 -2
- package/dist/{chunk-4ISHIPOC.js → chunk-FGEY2AGV.js} +2 -2
- package/dist/{chunk-O6YJI5VX.js → chunk-FKTMWDN2.js} +2 -2
- package/dist/{chunk-KQUN4RSX.js → chunk-FQFMLOAC.js} +18 -18
- package/dist/{chunk-5LWODP5R.js → chunk-FRAL3EU4.js} +2 -2
- package/dist/{chunk-AAANTXCQ.js → chunk-FZVZYU5C.js} +2 -2
- package/dist/{chunk-TGAXRT7N.js → chunk-GFYFKXWZ.js} +2 -2
- package/dist/{chunk-ID2PLENW.js → chunk-GGI6FLO6.js} +2 -2
- package/dist/{chunk-GDYLZ6FR.js → chunk-HJ3UTGIW.js} +3 -3
- package/dist/{chunk-X3IFRQKS.js → chunk-I73ZSORJ.js} +4 -4
- package/dist/{chunk-X73SCHKR.js → chunk-I75BWXRZ.js} +2 -2
- package/dist/{chunk-M3GKSCFN.js → chunk-J7G656MY.js} +4 -4
- package/dist/{chunk-EH6KLYIE.js → chunk-JF3MEIHO.js} +4 -4
- package/dist/{chunk-XUB4I2N6.js → chunk-JFI3ATK4.js} +2 -2
- package/dist/{chunk-R4SATHCV.js → chunk-JH2ALMPR.js} +2 -2
- package/dist/{chunk-TKBW4YLS.js → chunk-JH4FCVWJ.js} +2 -2
- package/dist/{chunk-DG5UKN2Y.js → chunk-JIA435P3.js} +2 -2
- package/dist/chunk-KASJJGRG.js +29 -0
- package/dist/chunk-KASJJGRG.js.map +1 -0
- package/dist/{chunk-4Z7O2QVJ.js → chunk-KOAVX2IT.js} +2 -2
- package/dist/{chunk-6NXHX6MI.js → chunk-KOY25HUB.js} +2 -2
- package/dist/{chunk-TRW7N4FS.js → chunk-L2JQSG5M.js} +2 -2
- package/dist/{chunk-QQ4T622E.js → chunk-L7PGPH4E.js} +2 -2
- package/dist/{chunk-TYT4TEQV.js → chunk-LDWE7OZS.js} +6 -2
- package/dist/chunk-LDWE7OZS.js.map +1 -0
- package/dist/{chunk-23VGZ65V.js → chunk-LHBOFROT.js} +2 -2
- package/dist/{chunk-VKB2FGNW.js → chunk-LOMSBYNS.js} +90 -90
- package/dist/{chunk-NDJ42GKB.js → chunk-MCPSMWUC.js} +6 -6
- package/dist/{chunk-ERMT5WQM.js → chunk-MKE5DP3S.js} +2 -2
- package/dist/{chunk-64IO6ZEE.js → chunk-MZWHIAOL.js} +2 -2
- package/dist/{chunk-LDXS746V.js → chunk-N7DVBHU3.js} +3 -3
- package/dist/{chunk-F2JLBXMG.js → chunk-NH6GZEQN.js} +8 -8
- package/dist/{chunk-7FG2JSU6.js → chunk-NMKETOAX.js} +2 -2
- package/dist/{chunk-GAARPWMN.js → chunk-OKKBOTRA.js} +2 -2
- package/dist/{chunk-PCIIKQXW.js → chunk-OLH4XCLU.js} +2 -2
- package/dist/{chunk-XA32GZ6B.js → chunk-OLHN4ZNT.js} +3 -3
- package/dist/{chunk-UR25IQVT.js → chunk-P5CUEB2Q.js} +8 -8
- package/dist/{chunk-LO775P3S.js → chunk-POGPI5GR.js} +2 -2
- package/dist/{chunk-FXO43NBW.js → chunk-PSQ2P2T3.js} +3 -3
- package/dist/{chunk-KRAMCBIW.js → chunk-QKXPOLQD.js} +4 -4
- package/dist/{chunk-65JLKIVA.js → chunk-QO5KDQGG.js} +8 -8
- package/dist/{chunk-A4B7WL5N.js → chunk-QOUKZ2N3.js} +2 -2
- package/dist/{chunk-ZABEDRGQ.js → chunk-RGKXF4W3.js} +2 -2
- package/dist/{chunk-IKCGNTGS.js → chunk-RWRHGE75.js} +2 -2
- package/dist/{chunk-NBH7IRME.js → chunk-SWZOAQUM.js} +54 -54
- package/dist/{chunk-FQ52AJXX.js → chunk-SZPPCEYT.js} +2 -2
- package/dist/{chunk-4YYIZ475.js → chunk-TAEJ2MRR.js} +52 -52
- package/dist/{chunk-3ODEGTVV.js → chunk-TDCR6PQL.js} +2 -2
- package/dist/{chunk-QCP75JGO.js → chunk-TFA6HPOA.js} +5 -5
- package/dist/{chunk-BBDASWNT.js → chunk-TJ5YGCPV.js} +81 -76
- package/dist/chunk-TJ5YGCPV.js.map +1 -0
- package/dist/{chunk-SE3JAD22.js → chunk-TPKB7T6Z.js} +2 -2
- package/dist/{chunk-6VYY2XFK.js → chunk-TVIGN3XZ.js} +19 -19
- package/dist/{chunk-OHMGR6QN.js → chunk-TXP7Q7T5.js} +2 -2
- package/dist/{chunk-ZG3HZZ6K.js → chunk-TYGLV6ZG.js} +5 -1
- package/dist/{chunk-ZG3HZZ6K.js.map → chunk-TYGLV6ZG.js.map} +1 -1
- package/dist/{chunk-AI2VXOSO.js → chunk-TYLNWE4D.js} +2 -2
- package/dist/{chunk-WI6MIJYK.js → chunk-U7HJBODU.js} +5 -5
- package/dist/{chunk-IS4HKNWP.js → chunk-UBN5AOLT.js} +2 -2
- package/dist/{chunk-A6PKEU7T.js → chunk-UFM3PJDT.js} +1 -1
- package/dist/chunk-UFM3PJDT.js.map +1 -0
- package/dist/{chunk-JNELS2TW.js → chunk-UJDTXWMT.js} +2 -2
- package/dist/{chunk-DJMC4W6N.js → chunk-VC3X7XP3.js} +2 -2
- package/dist/{chunk-A2D3WICT.js → chunk-VGFNTY3Y.js} +2 -2
- package/dist/{chunk-X544TW54.js → chunk-VGRM43X6.js} +2 -2
- package/dist/{chunk-CSGFGQBI.js → chunk-VPO53QMG.js} +2 -2
- package/dist/{chunk-BRZRP5WD.js → chunk-VSSQIBVU.js} +2 -2
- package/dist/{chunk-O3VKQMWC.js → chunk-WCBLPKG3.js} +366 -168
- package/dist/chunk-WCBLPKG3.js.map +1 -0
- package/dist/{chunk-GKNX4QO3.js → chunk-WCIDCJOQ.js} +3 -3
- package/dist/{chunk-7VJEZQYF.js → chunk-WEK3OUGO.js} +3 -3
- package/dist/{chunk-IVYAPAHG.js → chunk-WRDFYSUC.js} +2 -2
- package/dist/{chunk-5EILJ6W2.js → chunk-WXPBDEMW.js} +2 -2
- package/dist/{chunk-QZP3RMYP.js → chunk-X247CYL6.js} +4 -4
- package/dist/{chunk-UAYA4NJN.js → chunk-XHVO3CGX.js} +28 -36
- package/dist/chunk-XHVO3CGX.js.map +1 -0
- package/dist/{chunk-YLF42JQ7.js → chunk-XHX67LX6.js} +2 -2
- package/dist/{chunk-C6S2Y7HE.js → chunk-XKDQTDXN.js} +4 -4
- package/dist/{chunk-4AHKNZW7.js → chunk-XLEP7SS3.js} +2 -2
- package/dist/{chunk-B6L7BKA7.js → chunk-XRFLR3XF.js} +4 -4
- package/dist/{chunk-H7QC7WCU.js → chunk-XTBEXZFN.js} +4 -4
- package/dist/{chunk-CIJRM6SQ.js → chunk-Y5ZHZKZD.js} +2 -2
- package/dist/{chunk-H6SKBCNI.js → chunk-YC2C67F7.js} +2 -2
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-YOL56HY5.js → chunk-YQ6SLFNZ.js} +3 -3
- package/dist/{chunk-SIIWHIBK.js → chunk-YYGZ4EIY.js} +2 -2
- package/dist/{chunk-G22A6WJQ.js → chunk-Z2T5BPOZ.js} +2 -2
- package/dist/{chunk-RYUSWAAU.js → chunk-ZAP7BKTZ.js} +6 -6
- package/dist/cli.js +136 -136
- package/dist/coding/export-okf-codegraph.js +33 -33
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +34 -34
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -43
- package/dist/connectors/codex-materialize-runner.js +32 -32
- package/dist/connectors/index.js +32 -32
- package/dist/contradiction/index.js +5 -5
- package/dist/convergence-refresh.js +34 -34
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +34 -34
- package/dist/dashboard-runtime.js +2 -2
- package/dist/deep-recall-http-glue.js +9 -9
- package/dist/deep-recall-policy-llm.js +2 -2
- package/dist/entity-retrieval.js +34 -34
- package/dist/explicit-capture.js +7 -7
- package/dist/external-wiki-access.js +85 -85
- package/dist/extraction-faithfulness.js +2 -2
- package/dist/extraction-judge.d.ts +42 -2
- package/dist/extraction-judge.d.ts.map +1 -1
- package/dist/extraction-judge.js +12 -2
- package/dist/extraction-span-materialize.js +2 -2
- package/dist/extraction.js +47 -47
- package/dist/fallback-llm.d.ts +1 -0
- package/dist/fallback-llm.d.ts.map +1 -1
- package/dist/fallback-llm.js +1 -1
- package/dist/{graph-edge-decay-IU75NV42.js → graph-edge-decay-2BZTY2IM.js} +5 -5
- package/dist/graph-edge-decay-2BZTY2IM.js.map +1 -0
- package/dist/graph-jsonl.js +3 -3
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.js +2 -2
- package/dist/harmonic-retrieval.js +33 -33
- package/dist/harmonic-source-lookup.js +2 -2
- package/dist/index.js +236 -234
- package/dist/lcm/index.js +3 -3
- package/dist/location/index.js +35 -35
- package/dist/maintenance/memory-governance.js +32 -32
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +32 -32
- package/dist/maintenance/rebuild-memory-projection-runner.js +34 -34
- package/dist/maintenance/rebuild-memory-projection.js +33 -33
- package/dist/memory-browse-operations.js +85 -85
- package/dist/memory-browse.js +5 -5
- package/dist/memory-subject.js +3 -3
- package/dist/namespaces/migrate.js +33 -33
- package/dist/namespaces/search.js +11 -11
- package/dist/namespaces/storage.js +32 -32
- package/dist/offline-sync-changeset-generations.js +3 -3
- package/dist/offline-sync-embedding-generation.js +2 -2
- package/dist/offline-sync-file-io.js +2 -2
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/offline-sync-runtime-state.d.ts.map +1 -1
- package/dist/offline-sync-runtime-state.js +1 -1
- package/dist/offline-sync-snapshot-stream.js +8 -8
- package/dist/offline-sync-upload-staging.js +3 -3
- package/dist/offline-sync.js +8 -8
- package/dist/operator-doctor-corpus.js +35 -35
- package/dist/operator-toolkit.js +54 -54
- package/dist/orchestration/maintenance.js +44 -44
- package/dist/orchestrator.js +183 -181
- package/dist/qmd.js +4 -4
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.js +2 -2
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-navigation-operations.js +85 -85
- package/dist/recall-navigation.js +84 -84
- package/dist/recall-planner-llm.js +1 -1
- package/dist/recall-state-view-admission.js +5 -5
- package/dist/recall-why-service.js +5 -5
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/manifest.js +3 -3
- package/dist/{resolution-V6JBMW7U.js → resolution-4ZEQPXUZ.js} +6 -6
- package/dist/resolution-4ZEQPXUZ.js.map +1 -0
- package/dist/resume-bundles.js +44 -44
- package/dist/retrieval-agents.js +2 -2
- package/dist/salvage-envelope.js +5 -5
- package/dist/search/factory.js +10 -10
- package/dist/search/index.js +14 -14
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/secure-store/index.js +2 -2
- package/dist/semantic-consolidation.js +33 -33
- package/dist/semantic-rule-promotion.js +32 -32
- package/dist/semantic-rule-verifier.js +32 -32
- package/dist/source-agent-qualifier.js +37 -37
- package/dist/standup/build.js +32 -32
- package/dist/storage/buffer-session-files.d.ts +69 -0
- package/dist/storage/buffer-session-files.d.ts.map +1 -0
- package/dist/storage.d.ts +5 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +31 -31
- package/dist/subject-config.js +4 -4
- package/dist/summarizer.js +2 -2
- package/dist/support-passport/index.js +87 -87
- package/dist/transfer/export-okf.js +33 -33
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +32 -32
- package/dist/who-knows-cli.js +33 -33
- package/dist/who-knows.js +32 -32
- package/dist/write-envelope.js +4 -4
- package/package.json +2 -2
- package/src/buffer-eviction.ts +51 -0
- package/src/buffer-incremental-persistence.test.ts +256 -0
- package/src/buffer-persistence.ts +89 -0
- package/src/buffer.ts +32 -61
- package/src/config.test.ts +16 -0
- package/src/config.ts +14 -0
- package/src/extraction-judge-chain.test.ts +298 -2
- package/src/extraction-judge.ts +147 -82
- package/src/fallback-llm.test.ts +94 -0
- package/src/fallback-llm.ts +7 -7
- package/src/offline-sync-runtime-state.ts +24 -2
- package/src/offline-sync.test.ts +9 -0
- package/src/secure-store/secure-fs.ts +11 -0
- package/src/secure-store/secure-store.test.ts +22 -0
- package/src/storage/buffer-session-files.ts +249 -0
- package/src/storage-buffer-sessions.test.ts +369 -0
- package/src/storage.ts +11 -16
- package/src/types.ts +12 -0
- package/dist/chunk-6FIFEXFF.js.map +0 -1
- package/dist/chunk-A6PKEU7T.js.map +0 -1
- package/dist/chunk-BBDASWNT.js.map +0 -1
- package/dist/chunk-GMMZ45C4.js.map +0 -1
- package/dist/chunk-O3VKQMWC.js.map +0 -1
- package/dist/chunk-TYT4TEQV.js.map +0 -1
- package/dist/chunk-UAYA4NJN.js.map +0 -1
- /package/dist/{auto-sync-VPHR3FBI.js.map → auto-sync-432USQDH.js.map} +0 -0
- /package/dist/{graph-edge-decay-IU75NV42.js.map → buffer-eviction.js.map} +0 -0
- /package/dist/{resolution-V6JBMW7U.js.map → buffer-persistence.js.map} +0 -0
- /package/dist/{chunk-FL6SLVAJ.js.map → chunk-2AKIVSHP.js.map} +0 -0
- /package/dist/{chunk-ORWDCX5Q.js.map → chunk-3AODCBKX.js.map} +0 -0
- /package/dist/{chunk-XFDHL252.js.map → chunk-3K5IEJTK.js.map} +0 -0
- /package/dist/{chunk-XVZA6VXC.js.map → chunk-3SL52JKD.js.map} +0 -0
- /package/dist/{chunk-ZSAQSLKL.js.map → chunk-3XVBGGF3.js.map} +0 -0
- /package/dist/{chunk-XO4HOBM2.js.map → chunk-4B37KNMP.js.map} +0 -0
- /package/dist/{chunk-NIANATGL.js.map → chunk-4IZ6AC2U.js.map} +0 -0
- /package/dist/{chunk-S2PDFSXS.js.map → chunk-4P4VLPGB.js.map} +0 -0
- /package/dist/{chunk-G5UR4DXQ.js.map → chunk-53BLBDV6.js.map} +0 -0
- /package/dist/{chunk-PDYP7SUC.js.map → chunk-5ANUG4JZ.js.map} +0 -0
- /package/dist/{chunk-2ZYIAUYU.js.map → chunk-5CWH2LKL.js.map} +0 -0
- /package/dist/{chunk-S2S4U4G3.js.map → chunk-5DCS4C4S.js.map} +0 -0
- /package/dist/{chunk-7WTTE5FW.js.map → chunk-5K23NQRI.js.map} +0 -0
- /package/dist/{chunk-V3JZZJ2E.js.map → chunk-5UPOKIFP.js.map} +0 -0
- /package/dist/{chunk-K567EIW3.js.map → chunk-63YPKHAU.js.map} +0 -0
- /package/dist/{chunk-G3T6YMBO.js.map → chunk-64S6X4IU.js.map} +0 -0
- /package/dist/{chunk-HJM23E65.js.map → chunk-6DTIYAAN.js.map} +0 -0
- /package/dist/{chunk-G7XAZU2L.js.map → chunk-6F5BGIAF.js.map} +0 -0
- /package/dist/{chunk-IHTKBQBS.js.map → chunk-6JEEY42N.js.map} +0 -0
- /package/dist/{chunk-NHB4RC3G.js.map → chunk-6QOL4G76.js.map} +0 -0
- /package/dist/{chunk-AD3ZB72W.js.map → chunk-6SYS5YX3.js.map} +0 -0
- /package/dist/{chunk-DFP37GUN.js.map → chunk-7HXZOWTR.js.map} +0 -0
- /package/dist/{chunk-DGBWPSM6.js.map → chunk-7VFNGQ4V.js.map} +0 -0
- /package/dist/{chunk-77ZK533I.js.map → chunk-7ZRRJ6RG.js.map} +0 -0
- /package/dist/{chunk-QISYYK4L.js.map → chunk-AYBAC2N4.js.map} +0 -0
- /package/dist/{chunk-V44DWTFA.js.map → chunk-AYXRK264.js.map} +0 -0
- /package/dist/{chunk-LJSBF6PW.js.map → chunk-C2KOW2UQ.js.map} +0 -0
- /package/dist/{chunk-GY4R4P2A.js.map → chunk-CUWOSRCS.js.map} +0 -0
- /package/dist/{chunk-VNAIUNXI.js.map → chunk-CVMAOPAH.js.map} +0 -0
- /package/dist/{chunk-CJKHLACF.js.map → chunk-DG4H56SK.js.map} +0 -0
- /package/dist/{chunk-BQ6DGL6A.js.map → chunk-DKHX2FHI.js.map} +0 -0
- /package/dist/{chunk-77IXBNV6.js.map → chunk-E24RUQEN.js.map} +0 -0
- /package/dist/{chunk-HRT2SJPC.js.map → chunk-E3NBMTTX.js.map} +0 -0
- /package/dist/{chunk-PTQZPVRQ.js.map → chunk-E5PVMB3I.js.map} +0 -0
- /package/dist/{chunk-AHS3QEEA.js.map → chunk-EEOYF3IB.js.map} +0 -0
- /package/dist/{chunk-5XTSIZ3L.js.map → chunk-EGNONTVT.js.map} +0 -0
- /package/dist/{chunk-PH33R7GN.js.map → chunk-EHIKY2IA.js.map} +0 -0
- /package/dist/{chunk-4ISHIPOC.js.map → chunk-FGEY2AGV.js.map} +0 -0
- /package/dist/{chunk-O6YJI5VX.js.map → chunk-FKTMWDN2.js.map} +0 -0
- /package/dist/{chunk-KQUN4RSX.js.map → chunk-FQFMLOAC.js.map} +0 -0
- /package/dist/{chunk-5LWODP5R.js.map → chunk-FRAL3EU4.js.map} +0 -0
- /package/dist/{chunk-AAANTXCQ.js.map → chunk-FZVZYU5C.js.map} +0 -0
- /package/dist/{chunk-TGAXRT7N.js.map → chunk-GFYFKXWZ.js.map} +0 -0
- /package/dist/{chunk-ID2PLENW.js.map → chunk-GGI6FLO6.js.map} +0 -0
- /package/dist/{chunk-GDYLZ6FR.js.map → chunk-HJ3UTGIW.js.map} +0 -0
- /package/dist/{chunk-X3IFRQKS.js.map → chunk-I73ZSORJ.js.map} +0 -0
- /package/dist/{chunk-X73SCHKR.js.map → chunk-I75BWXRZ.js.map} +0 -0
- /package/dist/{chunk-M3GKSCFN.js.map → chunk-J7G656MY.js.map} +0 -0
- /package/dist/{chunk-EH6KLYIE.js.map → chunk-JF3MEIHO.js.map} +0 -0
- /package/dist/{chunk-XUB4I2N6.js.map → chunk-JFI3ATK4.js.map} +0 -0
- /package/dist/{chunk-R4SATHCV.js.map → chunk-JH2ALMPR.js.map} +0 -0
- /package/dist/{chunk-TKBW4YLS.js.map → chunk-JH4FCVWJ.js.map} +0 -0
- /package/dist/{chunk-DG5UKN2Y.js.map → chunk-JIA435P3.js.map} +0 -0
- /package/dist/{chunk-4Z7O2QVJ.js.map → chunk-KOAVX2IT.js.map} +0 -0
- /package/dist/{chunk-6NXHX6MI.js.map → chunk-KOY25HUB.js.map} +0 -0
- /package/dist/{chunk-TRW7N4FS.js.map → chunk-L2JQSG5M.js.map} +0 -0
- /package/dist/{chunk-QQ4T622E.js.map → chunk-L7PGPH4E.js.map} +0 -0
- /package/dist/{chunk-23VGZ65V.js.map → chunk-LHBOFROT.js.map} +0 -0
- /package/dist/{chunk-VKB2FGNW.js.map → chunk-LOMSBYNS.js.map} +0 -0
- /package/dist/{chunk-NDJ42GKB.js.map → chunk-MCPSMWUC.js.map} +0 -0
- /package/dist/{chunk-ERMT5WQM.js.map → chunk-MKE5DP3S.js.map} +0 -0
- /package/dist/{chunk-64IO6ZEE.js.map → chunk-MZWHIAOL.js.map} +0 -0
- /package/dist/{chunk-LDXS746V.js.map → chunk-N7DVBHU3.js.map} +0 -0
- /package/dist/{chunk-F2JLBXMG.js.map → chunk-NH6GZEQN.js.map} +0 -0
- /package/dist/{chunk-7FG2JSU6.js.map → chunk-NMKETOAX.js.map} +0 -0
- /package/dist/{chunk-GAARPWMN.js.map → chunk-OKKBOTRA.js.map} +0 -0
- /package/dist/{chunk-PCIIKQXW.js.map → chunk-OLH4XCLU.js.map} +0 -0
- /package/dist/{chunk-XA32GZ6B.js.map → chunk-OLHN4ZNT.js.map} +0 -0
- /package/dist/{chunk-UR25IQVT.js.map → chunk-P5CUEB2Q.js.map} +0 -0
- /package/dist/{chunk-LO775P3S.js.map → chunk-POGPI5GR.js.map} +0 -0
- /package/dist/{chunk-FXO43NBW.js.map → chunk-PSQ2P2T3.js.map} +0 -0
- /package/dist/{chunk-KRAMCBIW.js.map → chunk-QKXPOLQD.js.map} +0 -0
- /package/dist/{chunk-65JLKIVA.js.map → chunk-QO5KDQGG.js.map} +0 -0
- /package/dist/{chunk-A4B7WL5N.js.map → chunk-QOUKZ2N3.js.map} +0 -0
- /package/dist/{chunk-ZABEDRGQ.js.map → chunk-RGKXF4W3.js.map} +0 -0
- /package/dist/{chunk-IKCGNTGS.js.map → chunk-RWRHGE75.js.map} +0 -0
- /package/dist/{chunk-NBH7IRME.js.map → chunk-SWZOAQUM.js.map} +0 -0
- /package/dist/{chunk-FQ52AJXX.js.map → chunk-SZPPCEYT.js.map} +0 -0
- /package/dist/{chunk-4YYIZ475.js.map → chunk-TAEJ2MRR.js.map} +0 -0
- /package/dist/{chunk-3ODEGTVV.js.map → chunk-TDCR6PQL.js.map} +0 -0
- /package/dist/{chunk-QCP75JGO.js.map → chunk-TFA6HPOA.js.map} +0 -0
- /package/dist/{chunk-SE3JAD22.js.map → chunk-TPKB7T6Z.js.map} +0 -0
- /package/dist/{chunk-6VYY2XFK.js.map → chunk-TVIGN3XZ.js.map} +0 -0
- /package/dist/{chunk-OHMGR6QN.js.map → chunk-TXP7Q7T5.js.map} +0 -0
- /package/dist/{chunk-AI2VXOSO.js.map → chunk-TYLNWE4D.js.map} +0 -0
- /package/dist/{chunk-WI6MIJYK.js.map → chunk-U7HJBODU.js.map} +0 -0
- /package/dist/{chunk-IS4HKNWP.js.map → chunk-UBN5AOLT.js.map} +0 -0
- /package/dist/{chunk-JNELS2TW.js.map → chunk-UJDTXWMT.js.map} +0 -0
- /package/dist/{chunk-DJMC4W6N.js.map → chunk-VC3X7XP3.js.map} +0 -0
- /package/dist/{chunk-A2D3WICT.js.map → chunk-VGFNTY3Y.js.map} +0 -0
- /package/dist/{chunk-X544TW54.js.map → chunk-VGRM43X6.js.map} +0 -0
- /package/dist/{chunk-CSGFGQBI.js.map → chunk-VPO53QMG.js.map} +0 -0
- /package/dist/{chunk-BRZRP5WD.js.map → chunk-VSSQIBVU.js.map} +0 -0
- /package/dist/{chunk-GKNX4QO3.js.map → chunk-WCIDCJOQ.js.map} +0 -0
- /package/dist/{chunk-7VJEZQYF.js.map → chunk-WEK3OUGO.js.map} +0 -0
- /package/dist/{chunk-IVYAPAHG.js.map → chunk-WRDFYSUC.js.map} +0 -0
- /package/dist/{chunk-5EILJ6W2.js.map → chunk-WXPBDEMW.js.map} +0 -0
- /package/dist/{chunk-QZP3RMYP.js.map → chunk-X247CYL6.js.map} +0 -0
- /package/dist/{chunk-YLF42JQ7.js.map → chunk-XHX67LX6.js.map} +0 -0
- /package/dist/{chunk-C6S2Y7HE.js.map → chunk-XKDQTDXN.js.map} +0 -0
- /package/dist/{chunk-4AHKNZW7.js.map → chunk-XLEP7SS3.js.map} +0 -0
- /package/dist/{chunk-B6L7BKA7.js.map → chunk-XRFLR3XF.js.map} +0 -0
- /package/dist/{chunk-H7QC7WCU.js.map → chunk-XTBEXZFN.js.map} +0 -0
- /package/dist/{chunk-CIJRM6SQ.js.map → chunk-Y5ZHZKZD.js.map} +0 -0
- /package/dist/{chunk-H6SKBCNI.js.map → chunk-YC2C67F7.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-YOL56HY5.js.map → chunk-YQ6SLFNZ.js.map} +0 -0
- /package/dist/{chunk-SIIWHIBK.js.map → chunk-YYGZ4EIY.js.map} +0 -0
- /package/dist/{chunk-G22A6WJQ.js.map → chunk-Z2T5BPOZ.js.map} +0 -0
- /package/dist/{chunk-RYUSWAAU.js.map → chunk-ZAP7BKTZ.js.map} +0 -0
package/src/buffer.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { log } from "./logger.js";
|
|
2
2
|
import { throwIfAborted } from "./abort-error.js";
|
|
3
3
|
import { scanSignals } from "./signal.js";
|
|
4
|
-
import type
|
|
4
|
+
import { BufferSessionPersistence, type BufferStorage } from "./buffer-persistence.js";
|
|
5
|
+
import { selectEvictedBufferKeys } from "./buffer-eviction.js";
|
|
5
6
|
import type {
|
|
6
7
|
BufferEntryState,
|
|
7
8
|
BufferState,
|
|
@@ -175,9 +176,12 @@ export class SmartBuffer {
|
|
|
175
176
|
* activity (see BUFFER_SAVE_MAX_DEFER_MULTIPLIER). */
|
|
176
177
|
private firstPendingAtMs: number | null = null;
|
|
177
178
|
|
|
179
|
+
/** Which sessions' persisted files are stale (per-session incremental saves). */
|
|
180
|
+
private readonly persistence = new BufferSessionPersistence();
|
|
181
|
+
|
|
178
182
|
constructor(
|
|
179
183
|
private readonly config: PluginConfig,
|
|
180
|
-
private readonly storage:
|
|
184
|
+
private readonly storage: BufferStorage,
|
|
181
185
|
surpriseProbe: BufferSurpriseProbe | null = null,
|
|
182
186
|
) {
|
|
183
187
|
this.state = { turns: [], lastExtractionAt: null, extractionCount: 0 };
|
|
@@ -261,60 +265,20 @@ export class SmartBuffer {
|
|
|
261
265
|
);
|
|
262
266
|
}
|
|
263
267
|
|
|
268
|
+
/** Evict oldest entries past MAX_BUFFER_ENTRY_COUNT and queue their session files for deletion. */
|
|
264
269
|
private pruneEntries(retainKeys: string[]): void {
|
|
265
270
|
const entries = this.state.entries;
|
|
266
271
|
if (!entries) return;
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
turns: [],
|
|
274
|
-
lastExtractionAt: null,
|
|
275
|
-
extractionCount: 0,
|
|
276
|
-
});
|
|
277
|
-
const rightAt = this.entryActivityAt(entries[right] ?? {
|
|
278
|
-
turns: [],
|
|
279
|
-
lastExtractionAt: null,
|
|
280
|
-
extractionCount: 0,
|
|
281
|
-
});
|
|
282
|
-
if (leftAt !== rightAt) return leftAt - rightAt;
|
|
283
|
-
return (insertionOrder.get(left) ?? 0) - (insertionOrder.get(right) ?? 0);
|
|
284
|
-
};
|
|
285
|
-
const candidates = keys.filter(
|
|
286
|
-
(key) =>
|
|
287
|
-
key !== "default" &&
|
|
288
|
-
!retainKeys.includes(key) &&
|
|
289
|
-
// #3251: accepted captures exit via refusal/backpressure, never eviction.
|
|
290
|
-
!this.entryHoldsAcceptedCapture(entries[key]),
|
|
291
|
-
);
|
|
292
|
-
const emptyRemovable = candidates
|
|
293
|
-
.filter((key) => (entries[key]?.turns.length ?? 0) === 0)
|
|
294
|
-
.sort(byOldestActivity);
|
|
295
|
-
|
|
296
|
-
const removableCount = Math.max(0, keys.length - MAX_BUFFER_ENTRY_COUNT);
|
|
297
|
-
let evicted = emptyRemovable.slice(0, removableCount);
|
|
298
|
-
if (evicted.length < removableCount) {
|
|
299
|
-
// Retained failed extractions (#1908 retry retention) keep their turns in
|
|
300
|
-
// the buffer, so empty entries alone may not bring the map back under the
|
|
301
|
-
// cap during a provider outage. The cap is the documented bound — evict
|
|
302
|
-
// the oldest NON-empty sessions too rather than growing unboundedly
|
|
303
|
-
// (codex review).
|
|
304
|
-
const nonEmptyRemovable = candidates
|
|
305
|
-
.filter((key) => (entries[key]?.turns.length ?? 0) > 0)
|
|
306
|
-
.sort(byOldestActivity);
|
|
307
|
-
evicted = evicted.concat(nonEmptyRemovable.slice(0, removableCount - evicted.length));
|
|
308
|
-
}
|
|
309
|
-
for (const key of evicted) {
|
|
310
|
-
delete entries[key];
|
|
311
|
-
}
|
|
272
|
+
const evicted = selectEvictedBufferKeys(entries, retainKeys, MAX_BUFFER_ENTRY_COUNT, {
|
|
273
|
+
activityAt: (entry) => this.entryActivityAt(entry),
|
|
274
|
+
holdsAcceptedCapture: (entry) => this.entryHoldsAcceptedCapture(entry),
|
|
275
|
+
});
|
|
276
|
+
for (const key of evicted) delete entries[key];
|
|
277
|
+
this.persistence.markEvicted(evicted);
|
|
312
278
|
if (evicted.length > 0) {
|
|
313
|
-
// Loud degradation (extraction hot-loop hardening, Step 7):
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
// MAX_BUFFER_ENTRY_COUNT is bounded and expected, but must be observable —
|
|
317
|
-
// not silent — so operators can see buffer pressure.
|
|
279
|
+
// Loud degradation (extraction hot-loop hardening, Step 7): buffer
|
|
280
|
+
// pressure during a provider outage is bounded and expected, but must
|
|
281
|
+
// be observable, not silent.
|
|
318
282
|
log.warn(
|
|
319
283
|
`buffer: pruned ${evicted.length} oldest session entr${evicted.length === 1 ? "y" : "ies"} ` +
|
|
320
284
|
`past MAX_BUFFER_ENTRY_COUNT=${MAX_BUFFER_ENTRY_COUNT}; buffered turn data for those sessions was dropped`,
|
|
@@ -376,12 +340,15 @@ export class SmartBuffer {
|
|
|
376
340
|
* so the buffer can still accept new turns for the rest of the session.
|
|
377
341
|
*/
|
|
378
342
|
resetToEmpty(): void {
|
|
343
|
+
for (const key of Object.keys(this.state.entries ?? {})) {
|
|
344
|
+
this.persistence.markDeleted(key);
|
|
345
|
+
}
|
|
379
346
|
this.state = { turns: [], lastExtractionAt: null, extractionCount: 0 };
|
|
380
347
|
this.loaded = true;
|
|
381
348
|
}
|
|
382
349
|
|
|
383
350
|
private async saveUnlocked(): Promise<void> {
|
|
384
|
-
await this.
|
|
351
|
+
await this.persistence.persist(this.storage, this.state, (key) => this.peekEntry(key) ?? undefined);
|
|
385
352
|
}
|
|
386
353
|
|
|
387
354
|
async save(): Promise<void> {
|
|
@@ -495,13 +462,16 @@ export class SmartBuffer {
|
|
|
495
462
|
clearTimeout(this.saveTimer);
|
|
496
463
|
this.saveTimer = null;
|
|
497
464
|
}
|
|
498
|
-
|
|
465
|
+
// resetToEmpty-only deletions are pending work too, even with no
|
|
466
|
+
// pending-save flag set.
|
|
467
|
+
if (!this.pendingSave && !this.persistence.hasPending()) return;
|
|
499
468
|
try {
|
|
500
469
|
await this.enqueueMutation(async () => {
|
|
501
470
|
// Re-check under the serializer: a concurrent flush may have already
|
|
502
471
|
// persisted this pending save. Skip the redundant write (no phantom
|
|
503
|
-
// writes).
|
|
504
|
-
|
|
472
|
+
// writes). resetToEmpty-only deletions also land here without a
|
|
473
|
+
// pending-save flag.
|
|
474
|
+
if (!this.pendingSave && !this.persistence.hasPending()) return;
|
|
505
475
|
await this.saveUnlocked();
|
|
506
476
|
// Clear the pending flag INSIDE the same serialized mutation as the
|
|
507
477
|
// write (issue #1909 review). Clearing it after the enqueue await
|
|
@@ -579,6 +549,7 @@ export class SmartBuffer {
|
|
|
579
549
|
for (const key of emptyRemovable) {
|
|
580
550
|
if (Object.keys(entries).length < MAX_BUFFER_ENTRY_COUNT) break;
|
|
581
551
|
delete entries[key];
|
|
552
|
+
this.persistence.markDeleted(key);
|
|
582
553
|
}
|
|
583
554
|
}
|
|
584
555
|
|
|
@@ -714,6 +685,7 @@ export class SmartBuffer {
|
|
|
714
685
|
const decision = this.evaluate(entry, signal.level);
|
|
715
686
|
const turnCountInWindow = entry.turns.length;
|
|
716
687
|
|
|
688
|
+
this.persistence.markDirty(bufferKey);
|
|
717
689
|
this.pruneEntries([bufferKey]);
|
|
718
690
|
if (decision === "keep_buffering" && this.config.bufferSaveDebounceMs > 0) {
|
|
719
691
|
// Steady-state buffering: coalesce the whole-state serialize onto a
|
|
@@ -792,11 +764,7 @@ export class SmartBuffer {
|
|
|
792
764
|
private async emitSurpriseEventSafe(
|
|
793
765
|
params: SurpriseTelemetryQueueEntry,
|
|
794
766
|
): Promise<void> {
|
|
795
|
-
const storage = this.storage
|
|
796
|
-
appendBufferSurpriseEvents?: (
|
|
797
|
-
events: BufferSurpriseEvent[],
|
|
798
|
-
) => Promise<number>;
|
|
799
|
-
};
|
|
767
|
+
const storage = this.storage;
|
|
800
768
|
if (typeof storage.appendBufferSurpriseEvents !== "function") {
|
|
801
769
|
// Older StorageManager / test double without the telemetry sink.
|
|
802
770
|
// Silently skip — core path is still covered by the log line above.
|
|
@@ -955,6 +923,7 @@ export class SmartBuffer {
|
|
|
955
923
|
);
|
|
956
924
|
if (retained.length > 0) entry.retainedTurns = retained;
|
|
957
925
|
else delete entry.retainedTurns;
|
|
926
|
+
this.persistence.markDirty(bufferKey);
|
|
958
927
|
await this.saveUnlocked();
|
|
959
928
|
});
|
|
960
929
|
}
|
|
@@ -1062,6 +1031,7 @@ export class SmartBuffer {
|
|
|
1062
1031
|
if (remainingTurns.length > 0) entry.retainedTurns = remainingTurns;
|
|
1063
1032
|
else delete entry.retainedTurns;
|
|
1064
1033
|
if (bufferKey === "default") this.state.turns = entry.turns;
|
|
1034
|
+
this.persistence.markDirty(bufferKey);
|
|
1065
1035
|
}
|
|
1066
1036
|
if (changed || hadPendingSave) {
|
|
1067
1037
|
assertLifecycle();
|
|
@@ -1172,6 +1142,7 @@ export class SmartBuffer {
|
|
|
1172
1142
|
this.state.lastExtractionAt = entry.lastExtractionAt;
|
|
1173
1143
|
this.state.extractionCount = entry.extractionCount;
|
|
1174
1144
|
}
|
|
1145
|
+
this.persistence.markDirty(bufferKey);
|
|
1175
1146
|
this.pruneEntries([bufferKey]);
|
|
1176
1147
|
await this.saveNowRetainingPendingOnFailure("clearAfterExtraction");
|
|
1177
1148
|
});
|
package/src/config.test.ts
CHANGED
|
@@ -1060,6 +1060,22 @@ test("parseConfig localLlmTimeoutMs clamps invalid values to a positive timeout"
|
|
|
1060
1060
|
assert.equal(parseConfig({ localLlmTimeoutMs: Number.NaN }).localLlmTimeoutMs, 180_000);
|
|
1061
1061
|
});
|
|
1062
1062
|
|
|
1063
|
+
test("parseConfig extractionJudgeTimeoutMs defaults to 20000 — compact verdict measures 2.1-2.5 s on a small fast judge model", () => {
|
|
1064
|
+
assert.equal(parseConfig({}).extractionJudgeTimeoutMs, 20_000);
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
test("parseConfig extractionJudgeTimeoutMs accepts numbers and CLI-style numeric strings", () => {
|
|
1068
|
+
assert.equal(parseConfig({ extractionJudgeTimeoutMs: 45_000 }).extractionJudgeTimeoutMs, 45_000);
|
|
1069
|
+
assert.equal(parseConfig({ extractionJudgeTimeoutMs: "90000" }).extractionJudgeTimeoutMs, 90_000);
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
test("parseConfig extractionJudgeTimeoutMs falls back to the default on unusable values and caps the top", () => {
|
|
1073
|
+
for (const value of [0, -1, Number.NaN, "abc"]) {
|
|
1074
|
+
assert.equal(parseConfig({ extractionJudgeTimeoutMs: value }).extractionJudgeTimeoutMs, 20_000);
|
|
1075
|
+
}
|
|
1076
|
+
assert.equal(parseConfig({ extractionJudgeTimeoutMs: 10_000_000 }).extractionJudgeTimeoutMs, 3_600_000);
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1063
1079
|
test("parseConfig validates localLlmMaxContext as a usable context window", () => {
|
|
1064
1080
|
assert.equal(parseConfig({ localLlmMaxContext: 4096 }).localLlmMaxContext, 4096);
|
|
1065
1081
|
assert.equal(parseConfig({ localLlmMaxContext: "8192" }).localLlmMaxContext, 8192);
|
package/src/config.ts
CHANGED
|
@@ -2731,6 +2731,20 @@ export function parseConfig(
|
|
|
2731
2731
|
typeof cfg.extractionJudgeBatchSize === "number" && Number.isFinite(cfg.extractionJudgeBatchSize)
|
|
2732
2732
|
? Math.max(1, Math.round(cfg.extractionJudgeBatchSize))
|
|
2733
2733
|
: 20,
|
|
2734
|
+
// Judge LLM timeout (issue #376 follow-up, Studio 2026-10-29; tightened in
|
|
2735
|
+
// PR 3264): the judge requests a compact verdict with a batch-scaled
|
|
2736
|
+
// completion budget (24 tokens per candidate, up to 2048). Measured
|
|
2737
|
+
// 2.1-2.5 s per call on a small fast judge
|
|
2738
|
+
// model; the big shared core model measured 13-22 s per compact call,
|
|
2739
|
+
// 41 s verbose. The 20s default assumes the fast model, stays fail-open
|
|
2740
|
+
// on timeout, and needs a raise if extractionJudgeModel points at the
|
|
2741
|
+
// slow core model. Non-positive / unusable values fall back to the
|
|
2742
|
+
// default rather than a 1ms timeout that would silently disable judging.
|
|
2743
|
+
extractionJudgeTimeoutMs: ((): number => {
|
|
2744
|
+
const coerced = coerceNumber(cfg.extractionJudgeTimeoutMs);
|
|
2745
|
+
if (coerced === undefined || coerced < 1) return 20_000;
|
|
2746
|
+
return Math.min(3_600_000, Math.floor(coerced));
|
|
2747
|
+
})(),
|
|
2734
2748
|
extractionJudgeShadow: cfg.extractionJudgeShadow === true,
|
|
2735
2749
|
// Defer cap (issue #562 PR 2): max re-deferrals for the same candidate
|
|
2736
2750
|
// text before the verdict is forcibly converted to reject.
|
|
@@ -2,8 +2,11 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
|
|
4
4
|
import { parseConfig } from "./config.js";
|
|
5
|
-
import { judgeFactDurability } from "./extraction-judge.js";
|
|
6
|
-
import
|
|
5
|
+
import { judgeFactDurability, JUDGE_SYSTEM_PROMPT, judgeMaxTokens, MAX_JUDGE_BATCH, MAX_JUDGE_TOKENS } from "./extraction-judge.js";
|
|
6
|
+
import { FallbackLlmClient } from "./fallback-llm.js";
|
|
7
|
+
import type { FallbackLlmOptions, FallbackLlmResponse } from "./fallback-llm.js";
|
|
8
|
+
import { LocalLlmClient } from "./local-llm.js";
|
|
9
|
+
import type { LocalLlmChatCompletionOptions, LocalLlmChatCompletionResult } from "./local-llm-helpers.js";
|
|
7
10
|
|
|
8
11
|
// Regression for cursor review on #1425: the extraction judge must route gateway
|
|
9
12
|
// fallback calls through the SAME precedence as ExtractionEngine.withGatewayAgent
|
|
@@ -53,3 +56,296 @@ test("judge falls back to gatewayAgentId when no taskModelChain is set", async (
|
|
|
53
56
|
assert.equal(captured[0]?.agentId, "persona-agent");
|
|
54
57
|
assert.equal(captured[0]?.modelChain, undefined);
|
|
55
58
|
});
|
|
59
|
+
|
|
60
|
+
type ChatOptions = LocalLlmChatCompletionOptions;
|
|
61
|
+
|
|
62
|
+
/** A real FallbackLlmClient whose gateway call is replaced: records the options the judge passes. */
|
|
63
|
+
class RecordingFallbackLlm extends FallbackLlmClient {
|
|
64
|
+
constructor(private readonly sink: FallbackLlmOptions[]) {
|
|
65
|
+
super();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override isAvailable(): boolean {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
override async chatCompletion(
|
|
73
|
+
_messages: Array<{ role: "system" | "user" | "assistant"; content: string }>,
|
|
74
|
+
options: FallbackLlmOptions = {},
|
|
75
|
+
): Promise<FallbackLlmResponse | null> {
|
|
76
|
+
this.sink.push(options);
|
|
77
|
+
return { content: JSON.stringify([{ index: 0, durable: true, reason: "ok" }]), modelUsed: "recording-fake" };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A real LocalLlmClient whose network call is replaced: records the options the judge passes. */
|
|
82
|
+
class RecordingLocalLlm extends LocalLlmClient {
|
|
83
|
+
constructor(
|
|
84
|
+
private readonly sink: ChatOptions[],
|
|
85
|
+
private readonly reply: string,
|
|
86
|
+
) {
|
|
87
|
+
super(parseConfig({}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override async chatCompletion(
|
|
91
|
+
_messages: Array<{ role: string; content: string }>,
|
|
92
|
+
options: ChatOptions = {},
|
|
93
|
+
): Promise<LocalLlmChatCompletionResult | null> {
|
|
94
|
+
this.sink.push(options);
|
|
95
|
+
return { content: this.reply };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface ExampleVerdict {
|
|
100
|
+
index?: number;
|
|
101
|
+
kind?: string;
|
|
102
|
+
reason?: string;
|
|
103
|
+
durable?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Regression (Studio 2026-10-29): the judge hardcoded timeoutMs: 1500 while the
|
|
107
|
+
// deployed local model needs tens of seconds — 94 straight aborts, 0 completions.
|
|
108
|
+
// The timeout must come from config so operators can set one the model can meet.
|
|
109
|
+
|
|
110
|
+
test("judge forwards the configured timeout to the local LLM call", async () => {
|
|
111
|
+
const config = parseConfig({ extractionJudgeTimeoutMs: 45_000 });
|
|
112
|
+
const captured: ChatOptions[] = [];
|
|
113
|
+
const localLlm = new RecordingLocalLlm(captured, JSON.stringify([{ index: 0, durable: true, reason: "ok" }]));
|
|
114
|
+
|
|
115
|
+
await judgeFactDurability([candidate], config, localLlm, null, new Map(), new Map());
|
|
116
|
+
|
|
117
|
+
assert.equal(captured.length, 1, "judge should call the local LLM once");
|
|
118
|
+
assert.equal(captured[0]?.timeoutMs, 45_000);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Compact verdict request (PR 3264 follow-up): the judge asks for a minimal
|
|
123
|
+
// `[{"index":N,"kind":"accept|reject|defer"}]` reply with a batch-scaled
|
|
124
|
+
// completion budget (24 tokens per candidate, up to 2048) instead of a flat 2048. The big budget let
|
|
125
|
+
// a verbose 59-305s reply hide inside the old 150s timeout; the compact one
|
|
126
|
+
// measures 2.1-2.5 s per call on a small fast judge model (13-22 s, 41 s
|
|
127
|
+
// verbose on the big shared core model), inside the 20s default.
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
function captureLocal(captured: ChatOptions[], content?: string): LocalLlmClient {
|
|
131
|
+
return new RecordingLocalLlm(captured, content ?? JSON.stringify([{ index: 0, kind: "reject" }]));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const EIGHT_CANDIDATES = Array.from({ length: 8 }, (_, i) => ({
|
|
135
|
+
text: `sample candidate fact ${i}`,
|
|
136
|
+
category: "preference",
|
|
137
|
+
confidence: 0.5,
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
test("judge uses a compact completion budget scaled to the batch size", async () => {
|
|
141
|
+
const config = parseConfig({});
|
|
142
|
+
const captured: ChatOptions[] = [];
|
|
143
|
+
|
|
144
|
+
await judgeFactDurability(EIGHT_CANDIDATES, config, captureLocal(captured), null, new Map(), new Map());
|
|
145
|
+
|
|
146
|
+
assert.equal(captured.length, 1, "judge should call the local LLM once");
|
|
147
|
+
assert.equal(captured[0]?.maxTokens, judgeMaxTokens(8));
|
|
148
|
+
assert.ok(
|
|
149
|
+
(captured[0]?.maxTokens ?? Number.POSITIVE_INFINITY) <= judgeMaxTokens(8),
|
|
150
|
+
`completion budget must stay small, got ${captured[0]?.maxTokens}`,
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("judgeMaxTokens leaves room for one verdict per candidate, up to a cap", () => {
|
|
155
|
+
assert.equal(judgeMaxTokens(1), 64);
|
|
156
|
+
assert.equal(judgeMaxTokens(8), 192);
|
|
157
|
+
assert.ok(judgeMaxTokens(50) >= 50 * 16, "a 50-candidate batch needs a verdict array that fits");
|
|
158
|
+
assert.equal(judgeMaxTokens(500), 2048, "cap");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("judge bounds the whole local call, retries included, with one deadline signal", async () => {
|
|
162
|
+
const config = parseConfig({ extractionJudgeTimeoutMs: 45_000 });
|
|
163
|
+
const captured: ChatOptions[] = [];
|
|
164
|
+
|
|
165
|
+
await judgeFactDurability([candidate], config, captureLocal(captured), null, new Map(), new Map());
|
|
166
|
+
|
|
167
|
+
assert.ok(captured[0]?.signal instanceof AbortSignal, "judge passes an AbortSignal so retries share one deadline");
|
|
168
|
+
assert.equal(captured[0]?.signal?.aborted, false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
test("system prompt requests the compact verdict shape only", () => {
|
|
173
|
+
const lines = JUDGE_SYSTEM_PROMPT.split("\n");
|
|
174
|
+
const exampleIdx = lines.findIndex((l) => l.trim() === "Example output:");
|
|
175
|
+
assert.ok(exampleIdx >= 0, "prompt must contain an 'Example output:' line");
|
|
176
|
+
// SAFETY: the line is the example JSON checked into the prompt text this test pins.
|
|
177
|
+
const example = JSON.parse(lines[exampleIdx + 1].trim()) as { verdicts?: ExampleVerdict[] };
|
|
178
|
+
assert.ok(
|
|
179
|
+
Array.isArray(example.verdicts) && example.verdicts.length > 0,
|
|
180
|
+
'example must use the {"verdicts":[...]} wrapper — response_format json_object rejects a bare top-level array',
|
|
181
|
+
);
|
|
182
|
+
for (const item of example.verdicts) {
|
|
183
|
+
assert.ok(Number.isInteger(item.index), "example index must be an integer");
|
|
184
|
+
assert.ok(
|
|
185
|
+
item.kind === "accept" || item.kind === "reject" || item.kind === "defer",
|
|
186
|
+
`example kind must be a parser-accepted verdict, got ${item.kind}`,
|
|
187
|
+
);
|
|
188
|
+
assert.equal(item.reason, undefined, "compact request must not ask for reasons");
|
|
189
|
+
assert.equal(item.durable, undefined, "compact request must not ask for the durable flag");
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("judge still accepts the legacy verbose verdict shape", async () => {
|
|
194
|
+
const config = parseConfig({});
|
|
195
|
+
const captured: ChatOptions[] = [];
|
|
196
|
+
const llm = captureLocal(
|
|
197
|
+
captured,
|
|
198
|
+
JSON.stringify([
|
|
199
|
+
{ index: 0, kind: "accept", durable: true, reason: "Stable preference" },
|
|
200
|
+
{ index: 1, durable: false, reason: "Ephemeral build status" },
|
|
201
|
+
{ index: 2, kind: "defer", durable: false, reason: "Ambiguous pronoun" },
|
|
202
|
+
]),
|
|
203
|
+
);
|
|
204
|
+
const candidates = [
|
|
205
|
+
{ text: "user prefers dark mode", category: "preference", confidence: 0.5 },
|
|
206
|
+
{ text: "the build is running now", category: "task", confidence: 0.5 },
|
|
207
|
+
{ text: "he said they'd follow up", category: "task", confidence: 0.5 },
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
const result = await judgeFactDurability(candidates, config, llm, null, new Map(), new Map());
|
|
211
|
+
|
|
212
|
+
assert.deepEqual(
|
|
213
|
+
{ ...result.verdicts.get(0) },
|
|
214
|
+
{ durable: true, reason: "Stable preference", kind: "accept" },
|
|
215
|
+
);
|
|
216
|
+
assert.deepEqual(
|
|
217
|
+
{ ...result.verdicts.get(1) },
|
|
218
|
+
{ durable: false, reason: "Ephemeral build status" },
|
|
219
|
+
);
|
|
220
|
+
assert.deepEqual(
|
|
221
|
+
{ ...result.verdicts.get(2) },
|
|
222
|
+
{ durable: false, reason: "Ambiguous pronoun", kind: "defer" },
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("judge parses the {\"verdicts\":[...]} wrapper object the prompt requests", async () => {
|
|
227
|
+
const config = parseConfig({});
|
|
228
|
+
const captured: ChatOptions[] = [];
|
|
229
|
+
const llm = captureLocal(
|
|
230
|
+
captured,
|
|
231
|
+
JSON.stringify({
|
|
232
|
+
verdicts: [
|
|
233
|
+
{ index: 0, kind: "accept" },
|
|
234
|
+
{ index: 1, kind: "reject" },
|
|
235
|
+
],
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
const candidates = [
|
|
239
|
+
{ text: "user prefers dark mode", category: "preference", confidence: 0.5 },
|
|
240
|
+
{ text: "the build is running now", category: "task", confidence: 0.5 },
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
const result = await judgeFactDurability(candidates, config, llm, null, new Map(), new Map());
|
|
244
|
+
|
|
245
|
+
assert.equal(result.judged, 2, "both verdicts must resolve from the wrapper");
|
|
246
|
+
assert.equal(result.verdicts.get(0)?.kind, "accept");
|
|
247
|
+
assert.equal(result.verdicts.get(1)?.kind, "reject");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("judge still parses the compact bare-array shape", async () => {
|
|
251
|
+
const config = parseConfig({});
|
|
252
|
+
const captured: ChatOptions[] = [];
|
|
253
|
+
const llm = captureLocal(
|
|
254
|
+
captured,
|
|
255
|
+
JSON.stringify([
|
|
256
|
+
{ index: 0, kind: "reject" },
|
|
257
|
+
{ index: 1, kind: "accept" },
|
|
258
|
+
]),
|
|
259
|
+
);
|
|
260
|
+
const candidates = [
|
|
261
|
+
{ text: "the build is running now", category: "task", confidence: 0.5 },
|
|
262
|
+
{ text: "user prefers dark mode", category: "preference", confidence: 0.5 },
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
const result = await judgeFactDurability(candidates, config, llm, null, new Map(), new Map());
|
|
266
|
+
|
|
267
|
+
assert.equal(result.judged, 2, "both verdicts must resolve from the bare array");
|
|
268
|
+
assert.equal(result.verdicts.get(0)?.kind, "reject");
|
|
269
|
+
assert.equal(result.verdicts.get(1)?.kind, "accept");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("judge treats a single verdict object as one verdict at its index, not the whole batch", async () => {
|
|
273
|
+
const config = parseConfig({});
|
|
274
|
+
const captured: ChatOptions[] = [];
|
|
275
|
+
const llm = captureLocal(captured, JSON.stringify({ index: 1, kind: "reject" }));
|
|
276
|
+
const candidates = [
|
|
277
|
+
{ text: "user prefers dark mode", category: "preference", confidence: 0.5 },
|
|
278
|
+
{ text: "the build is running now", category: "task", confidence: 0.5 },
|
|
279
|
+
{ text: "he said they'd follow up", category: "task", confidence: 0.5 },
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
const result = await judgeFactDurability(candidates, config, llm, null, new Map(), new Map());
|
|
283
|
+
|
|
284
|
+
assert.equal(result.judged, 1, "only the indexed candidate comes from the LLM");
|
|
285
|
+
assert.equal(result.verdicts.get(1)?.kind, "reject");
|
|
286
|
+
assert.equal(result.verdicts.get(1)?.durable, false);
|
|
287
|
+
assert.equal(result.verdicts.get(0)?.kind, undefined, "index 0 must not inherit the reject");
|
|
288
|
+
assert.equal(result.verdicts.get(0)?.durable, true, "index 0 fail-open approves");
|
|
289
|
+
assert.equal(result.verdicts.get(2)?.kind, undefined, "index 2 must not inherit the reject");
|
|
290
|
+
assert.equal(result.verdicts.get(2)?.durable, true, "index 2 fail-open approves");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// Judge model override: extractionJudgeModel must reach the LLM call options
|
|
295
|
+
// (options.model) on both the local and fallback paths, and stay absent when
|
|
296
|
+
// unset so routing keeps the default model.
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
test("judge forwards extractionJudgeModel to the local LLM options", async () => {
|
|
300
|
+
const config = parseConfig({ extractionJudgeModel: "qwen3.8-27b-64k-nothink" });
|
|
301
|
+
const captured: ChatOptions[] = [];
|
|
302
|
+
|
|
303
|
+
await judgeFactDurability([candidate], config, captureLocal(captured), null, new Map(), new Map());
|
|
304
|
+
|
|
305
|
+
assert.equal(captured[0]?.model, "qwen3.8-27b-64k-nothink");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("judge omits model from local LLM options when extractionJudgeModel is unset", async () => {
|
|
309
|
+
const config = parseConfig({});
|
|
310
|
+
const captured: ChatOptions[] = [];
|
|
311
|
+
|
|
312
|
+
await judgeFactDurability([candidate], config, captureLocal(captured), null, new Map(), new Map());
|
|
313
|
+
|
|
314
|
+
assert.equal(captured[0]?.model, undefined);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("judge forwards extractionJudgeModel to the fallback LLM options in gateway mode", async () => {
|
|
318
|
+
const config = parseConfig({ modelSource: "gateway", extractionJudgeModel: "fast-judge" });
|
|
319
|
+
const captured: FallbackLlmOptions[] = [];
|
|
320
|
+
|
|
321
|
+
await judgeFactDurability([candidate], config, null, new RecordingFallbackLlm(captured), new Map(), new Map());
|
|
322
|
+
|
|
323
|
+
assert.equal(captured[0]?.model, "fast-judge");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("judge defaults to a 20s timeout when none is configured", async () => {
|
|
327
|
+
const config = parseConfig({});
|
|
328
|
+
const captured: FallbackLlmOptions[] = [];
|
|
329
|
+
|
|
330
|
+
await judgeFactDurability([candidate], config, null, new RecordingFallbackLlm(captured), new Map(), new Map());
|
|
331
|
+
|
|
332
|
+
assert.equal(captured[0]?.timeoutMs, 20_000);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("a configured batch size above what the token cap can answer is split", async () => {
|
|
336
|
+
const config = parseConfig({ extractionJudgeBatchSize: 200 });
|
|
337
|
+
const captured: ChatOptions[] = [];
|
|
338
|
+
const candidates = Array.from({ length: 200 }, (_, i) => ({
|
|
339
|
+
text: `distinct candidate fact number ${i}`,
|
|
340
|
+
category: "preference",
|
|
341
|
+
confidence: 0.5,
|
|
342
|
+
}));
|
|
343
|
+
|
|
344
|
+
await judgeFactDurability(candidates, config, captureLocal(captured), null, new Map(), new Map());
|
|
345
|
+
|
|
346
|
+
assert.equal(captured.length, 3, "200 candidates at 85 per request");
|
|
347
|
+
for (const call of captured) {
|
|
348
|
+
assert.ok((call.maxTokens ?? 0) <= MAX_JUDGE_TOKENS, "never above the cap");
|
|
349
|
+
}
|
|
350
|
+
assert.equal(MAX_JUDGE_BATCH * 24 <= MAX_JUDGE_TOKENS, true, "a full batch fits its own budget");
|
|
351
|
+
});
|