@remnic/core 9.69.85 → 9.69.87
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 +45 -44
- package/dist/access-authorization-probe.js +46 -45
- package/dist/access-boundary.js +45 -44
- package/dist/access-cli.js +111 -110
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.d.ts.map +1 -1
- package/dist/access-extraction-force-flush.js +45 -44
- package/dist/access-http-briefing.js +46 -45
- package/dist/access-http-query.js +47 -46
- package/dist/access-http.js +67 -66
- package/dist/access-identity-continuity-surface.js +45 -44
- package/dist/access-lcm-surface.js +45 -44
- package/dist/access-mcp.d.ts.map +1 -1
- package/dist/access-mcp.js +59 -58
- package/dist/access-namespace-preflight.js +45 -44
- package/dist/access-observe-write-category.js +4 -4
- package/dist/access-observe-write-surface.js +45 -44
- package/dist/access-operations-batch.js +49 -48
- package/dist/access-operations.js +56 -55
- package/dist/access-recall-concurrency.js +45 -44
- package/dist/access-recall-diagnostics.js +4 -4
- package/dist/access-recall-response.js +45 -44
- package/dist/access-recall-surface.js +45 -44
- package/dist/access-schema.js +3 -3
- package/dist/access-service-helpers.d.ts +1 -0
- package/dist/access-service-helpers.d.ts.map +1 -1
- package/dist/access-service-helpers.js +1 -1
- package/dist/access-service.js +45 -44
- package/dist/access-surface-catalog.js +47 -46
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +13 -12
- package/dist/active-recall.js.map +1 -1
- package/dist/{auto-sync-WEUDMXUW.js → auto-sync-6IPX2BAC.js} +5 -5
- package/dist/background-generation.js +2 -2
- package/dist/briefing.js +7 -7
- package/dist/buffer.js +4 -3
- package/dist/calibration.js +2 -2
- package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +10 -10
- 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-J3IWHCMF.js → chunk-2DT7PIDZ.js} +3 -3
- package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-MFZACYZ6.js → chunk-37S3IMYB.js} +2 -2
- package/dist/{chunk-IBGEHKQ6.js → chunk-3C3J243X.js} +2 -2
- package/dist/{chunk-55FCXCXJ.js → chunk-3GQC3NMB.js} +4 -4
- package/dist/{chunk-QOPB6KE3.js → chunk-3TB2XQ3C.js} +2 -2
- package/dist/{chunk-3XQYM4RE.js → chunk-4BZP2B4U.js} +2 -2
- package/dist/{chunk-FYAU4S6E.js → chunk-4CDYONWO.js} +3 -3
- package/dist/{chunk-CRKWSSS4.js → chunk-4IEOGIPB.js} +2 -2
- package/dist/{chunk-W75BBBSY.js → chunk-4IIMMZZK.js} +11 -11
- package/dist/{chunk-Q3THVX75.js → chunk-4RL3JOB3.js} +2 -2
- package/dist/{chunk-7G6WUOLB.js → chunk-66DKTOBY.js} +2 -2
- package/dist/{chunk-IP5VLSLO.js → chunk-6L3PBSAR.js} +2 -2
- package/dist/{chunk-AZ2X5LNA.js → chunk-6LYAEM54.js} +2 -2
- package/dist/{chunk-RCQKAOOB.js → chunk-6NQ4LSMS.js} +40 -40
- package/dist/{chunk-WB2I2EQK.js → chunk-6TT6HRE4.js} +2 -2
- package/dist/{chunk-JX6USXZR.js → chunk-72JNNJJJ.js} +2 -2
- package/dist/{chunk-BUKK5SWA.js → chunk-7LCGFUMB.js} +4 -4
- package/dist/{chunk-OEBLZZKK.js → chunk-7XG7KMIW.js} +2 -2
- package/dist/{chunk-3GN2PEOT.js → chunk-AQK54Q7O.js} +2 -2
- package/dist/{chunk-3SG4FGDV.js → chunk-AWD36R4D.js} +5 -2
- package/dist/chunk-AWD36R4D.js.map +1 -0
- package/dist/{chunk-BOUFVFV2.js → chunk-AZSGSRIM.js} +2 -2
- package/dist/{chunk-N3MNDVJO.js → chunk-BFO3IR56.js} +2 -2
- package/dist/{chunk-2UPTRDGM.js → chunk-BJRAELDH.js} +3 -3
- package/dist/{chunk-IMAP236M.js → chunk-CS52MFDV.js} +2 -2
- package/dist/{chunk-UNJOB4DY.js → chunk-CUCWX3MO.js} +2 -2
- package/dist/{chunk-QB75LKQQ.js → chunk-CWHV574S.js} +32 -13
- package/dist/chunk-CWHV574S.js.map +1 -0
- package/dist/{chunk-DQISIT63.js → chunk-DZ42MDZE.js} +3 -3
- package/dist/{chunk-ML2MVAEZ.js → chunk-DZ525SNQ.js} +1 -1
- package/dist/{chunk-5ELENZTA.js → chunk-EDIH6X2P.js} +2 -2
- package/dist/{chunk-S42HZILI.js → chunk-EHS5MJEF.js} +2 -2
- package/dist/{chunk-IA3X2Y4K.js → chunk-EQ6YOIKO.js} +2 -2
- package/dist/{chunk-4V44AA7O.js → chunk-FAN73COB.js} +2 -2
- package/dist/{chunk-KERNN6BC.js → chunk-FB64XE3R.js} +2 -2
- package/dist/{chunk-SVZWVZ7E.js → chunk-FOH7AMMI.js} +72 -55
- package/dist/chunk-FOH7AMMI.js.map +1 -0
- package/dist/{chunk-S55WTPFJ.js → chunk-FRCESVV6.js} +7 -7
- package/dist/{chunk-GEX5NI36.js → chunk-HBBHL4YF.js} +2 -2
- package/dist/{chunk-2I6VHX6Q.js → chunk-HESDNYRB.js} +2 -2
- package/dist/{chunk-2U3R3DDH.js → chunk-ICZQG4XE.js} +9 -9
- package/dist/{chunk-DGAWEJVI.js → chunk-JGSQ6P4D.js} +2 -2
- package/dist/{chunk-TIQZNDZ5.js → chunk-JKMHZF3Q.js} +2 -2
- package/dist/{chunk-NAFPHFDM.js → chunk-JNLEJE4A.js} +2 -2
- package/dist/{chunk-IFCKHPY4.js → chunk-K4J4E7WV.js} +2 -2
- package/dist/{chunk-2QG4SLZI.js → chunk-KF7PQ7G2.js} +2 -2
- package/dist/{chunk-JXMECXMF.js → chunk-KFCMMDHH.js} +2 -2
- package/dist/{chunk-SQDKX4RW.js → chunk-KP7SANCF.js} +2 -2
- package/dist/{chunk-OM5BIPBR.js → chunk-L6UZEAID.js} +2 -2
- package/dist/{chunk-TEV2JGU5.js → chunk-LF5IGOOD.js} +15 -15
- package/dist/{chunk-QWUJWBZ5.js → chunk-LQG5O5EP.js} +2 -2
- package/dist/{chunk-NKFLHW7J.js → chunk-LS3XH3UE.js} +2 -2
- package/dist/{chunk-BJX3VQWZ.js → chunk-M536WNTQ.js} +2 -2
- package/dist/{chunk-EU3N5MK3.js → chunk-MQJL6CDZ.js} +3 -3
- package/dist/{chunk-VZBW6CDG.js → chunk-N4S4NUX5.js} +1 -1
- package/dist/{chunk-L2LBMKSI.js → chunk-NE6PHPLG.js} +10 -10
- package/dist/{chunk-VTPASHTO.js → chunk-NE7I2RCT.js} +2 -2
- package/dist/{chunk-OAOBL6OY.js → chunk-NR2ASHBL.js} +5 -5
- package/dist/{chunk-7UUKRS3J.js → chunk-P2WZHLUF.js} +3 -3
- package/dist/{chunk-IVH7INFM.js → chunk-P6WFK4ZJ.js} +6 -6
- package/dist/{chunk-CHDGJJV6.js → chunk-PUKTBULQ.js} +2 -2
- package/dist/{chunk-NAYBHNGN.js → chunk-QEV26L2F.js} +2 -2
- package/dist/{chunk-I4G4UWQA.js → chunk-QI2E6ISQ.js} +1 -1
- package/dist/{chunk-CK5U7JGH.js → chunk-QVS22WTO.js} +2 -2
- package/dist/{chunk-B47OHVFT.js → chunk-QWW2JDDX.js} +7 -7
- package/dist/{chunk-EJPF6XDQ.js → chunk-RDJ6C7NZ.js} +3 -3
- package/dist/{chunk-C2WWQ4OR.js → chunk-RMMA44HE.js} +1 -1
- package/dist/chunk-RMMA44HE.js.map +1 -0
- package/dist/{chunk-I53NAHZ4.js → chunk-S7GCT7NU.js} +3 -2
- package/dist/chunk-S7GCT7NU.js.map +1 -0
- package/dist/{chunk-RDEUG2M2.js → chunk-SOZUJCME.js} +6 -6
- package/dist/{chunk-T4QGUXCV.js → chunk-SPSRGEDQ.js} +2 -2
- package/dist/{chunk-TY446V34.js → chunk-SRZW7FNU.js} +2 -2
- package/dist/{chunk-SV72F4DS.js → chunk-SY5KDWGU.js} +2 -2
- package/dist/{chunk-H36X4NRH.js → chunk-TDYAIXHK.js} +5 -5
- package/dist/{chunk-ZTHGFTMW.js → chunk-TKC5LZ43.js} +2 -2
- package/dist/{chunk-6DTJBTHL.js → chunk-TM3RXL4G.js} +14 -2
- package/dist/chunk-TM3RXL4G.js.map +1 -0
- package/dist/{chunk-AXDOF4VI.js → chunk-UA5DX5C3.js} +2 -2
- package/dist/{chunk-JS3S2HVG.js → chunk-V2ES4WCZ.js} +2 -2
- package/dist/{chunk-5FNZ3G6X.js → chunk-VKQI47DJ.js} +4 -4
- package/dist/{chunk-JVY556FW.js → chunk-VLLD4TOB.js} +2 -2
- package/dist/{chunk-NMBX4D7P.js → chunk-VQNONC4E.js} +2 -2
- package/dist/{chunk-GZAGLCQH.js → chunk-VVDWZRZG.js} +2 -2
- package/dist/{chunk-T4BE5KJM.js → chunk-VX6GCKBS.js} +3 -3
- package/dist/{chunk-RIEX7NOZ.js → chunk-W4AOR52H.js} +2 -2
- package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-BNPLDVM6.js → chunk-XTYX6V52.js} +2 -2
- package/dist/{chunk-FZVROZKC.js → chunk-XVZA6VXC.js} +4 -4
- package/dist/{chunk-RGMVMVMF.js → chunk-YBA52OKG.js} +2 -1
- package/dist/chunk-YBA52OKG.js.map +1 -0
- package/dist/{chunk-C7JAUNFC.js → chunk-YITN5WXI.js} +2 -2
- package/dist/{chunk-HS75MCQF.js → chunk-YRULQW5J.js} +2 -2
- package/dist/{chunk-DINRT75P.js → chunk-YSBL7VPO.js} +2 -2
- package/dist/{chunk-7QK5TUTW.js → chunk-YSJTCOHR.js} +2 -2
- package/dist/{chunk-QC56VZXC.js → chunk-YTQGGO3K.js} +67 -67
- package/dist/{chunk-HKFJ3PF3.js → chunk-ZI7QA4MK.js} +2 -2
- package/dist/chunk-ZJP66Q4I.js +48 -0
- package/dist/chunk-ZJP66Q4I.js.map +1 -0
- package/dist/cli.js +86 -85
- package/dist/coding/export-okf-codegraph.js +6 -6
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +7 -7
- package/dist/config.js +13 -12
- package/dist/connectors/codex-materialize-runner.js +7 -7
- package/dist/connectors/index.js +7 -7
- package/dist/contradiction/index.js +3 -3
- package/dist/{contradiction-scan-5EJYRKD3.js → contradiction-scan-TAGMOUQ2.js} +3 -3
- package/dist/convergence-refresh.js +9 -9
- package/dist/corpus-watermark.js +9 -9
- package/dist/correction/correction-access-wiring.d.ts.map +1 -1
- package/dist/deep-recall-http-glue.js +4 -4
- package/dist/deep-recall-policy-llm.js +3 -3
- package/dist/deep-recall.js +2 -2
- package/dist/entity-retrieval.js +7 -7
- package/dist/external-wiki-access.js +46 -45
- package/dist/extraction-faithfulness.js +3 -3
- package/dist/extraction-judge.js +3 -3
- package/dist/extraction-liveness.d.ts +9 -0
- package/dist/extraction-liveness.d.ts.map +1 -1
- package/dist/extraction-liveness.js +2 -1
- package/dist/extraction-llm-health.d.ts +32 -0
- package/dist/extraction-llm-health.d.ts.map +1 -0
- package/dist/extraction-llm-health.js +17 -0
- package/dist/extraction-span-config.js +3 -2
- package/dist/extraction-span-materialize.js +2 -2
- package/dist/extraction.js +16 -15
- package/dist/fallback-llm.js +2 -2
- package/dist/{first-start-migration-GAT7G733.js → first-start-migration-7OATHJKB.js} +4 -4
- package/dist/{graph-edge-decay-PVKDKLQK.js → graph-edge-decay-KQGS3L3B.js} +6 -6
- package/dist/graph-jsonl.js +4 -4
- package/dist/graph-snapshot.js +4 -4
- package/dist/graph.js +5 -5
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.js +136 -135
- package/dist/json-extract.d.ts.map +1 -1
- package/dist/json-extract.js +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +5 -5
- package/dist/local-llm.d.ts.map +1 -1
- package/dist/local-llm.js +1 -1
- package/dist/location/index.js +9 -9
- package/dist/maintenance/memory-governance.js +7 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.js +8 -8
- package/dist/memory-browse-operations.js +46 -45
- package/dist/memory-browse.js +3 -3
- package/dist/namespaces/migrate.js +8 -8
- package/dist/namespaces/storage.js +7 -7
- package/dist/operator-doctor-corpus.js +10 -10
- package/dist/operator-toolkit.js +25 -24
- package/dist/orchestration/extraction-run.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +17 -17
- package/dist/orchestrator.js +111 -110
- package/dist/recall-navigation-operations.js +46 -45
- package/dist/recall-navigation.js +45 -44
- package/dist/recall-planner-llm.js +2 -2
- package/dist/recall-state-view-admission.js +3 -3
- package/dist/recall-why-service.js +6 -6
- package/dist/recall-why.js +3 -3
- package/dist/{resolution-UYJU5JUA.js → resolution-DNX7FO7A.js} +4 -4
- package/dist/resolution-DNX7FO7A.js.map +1 -0
- package/dist/resume-bundles.js +14 -13
- package/dist/semantic-consolidation.js +8 -8
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.js +7 -7
- package/dist/source-agent-qualifier.js +11 -10
- package/dist/standup/build.js +7 -7
- package/dist/storage.js +6 -6
- package/dist/summarizer.js +5 -5
- package/dist/support-passport/index.js +48 -47
- 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 +7 -7
- package/dist/verified-recall.js +7 -7
- package/dist/who-knows-cli.js +8 -8
- package/dist/who-knows.js +7 -7
- package/package.json +2 -2
- package/src/access-extraction-force-flush.ts +15 -7
- package/src/access-mcp.ts +90 -28
- package/src/access-service-health.test.ts +41 -5
- package/src/access-service-helpers.ts +1 -0
- package/src/access-service-observe-lcm-parity.test.ts +56 -24
- package/src/correction/correction-access-wiring.ts +36 -8
- package/src/correction/correction-classify-parse.test.ts +178 -0
- package/src/extraction-liveness.ts +13 -1
- package/src/extraction-llm-health.test.ts +103 -0
- package/src/extraction-llm-health.ts +71 -0
- package/src/json-extract.ts +6 -0
- package/src/local-llm-thinking.test.ts +12 -0
- package/src/local-llm.ts +1 -0
- package/src/orchestration/extraction-run.test.ts +55 -1
- package/src/orchestration/extraction-run.ts +13 -0
- package/dist/chunk-3SG4FGDV.js.map +0 -1
- package/dist/chunk-6DTJBTHL.js.map +0 -1
- package/dist/chunk-C2WWQ4OR.js.map +0 -1
- package/dist/chunk-I53NAHZ4.js.map +0 -1
- package/dist/chunk-QB75LKQQ.js.map +0 -1
- package/dist/chunk-RGMVMVMF.js.map +0 -1
- package/dist/chunk-SVZWVZ7E.js.map +0 -1
- /package/dist/{auto-sync-WEUDMXUW.js.map → auto-sync-6IPX2BAC.js.map} +0 -0
- /package/dist/{capsule-crypto-GWVG7LGC.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
- /package/dist/{chunk-J3IWHCMF.js.map → chunk-2DT7PIDZ.js.map} +0 -0
- /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-MFZACYZ6.js.map → chunk-37S3IMYB.js.map} +0 -0
- /package/dist/{chunk-IBGEHKQ6.js.map → chunk-3C3J243X.js.map} +0 -0
- /package/dist/{chunk-55FCXCXJ.js.map → chunk-3GQC3NMB.js.map} +0 -0
- /package/dist/{chunk-QOPB6KE3.js.map → chunk-3TB2XQ3C.js.map} +0 -0
- /package/dist/{chunk-3XQYM4RE.js.map → chunk-4BZP2B4U.js.map} +0 -0
- /package/dist/{chunk-FYAU4S6E.js.map → chunk-4CDYONWO.js.map} +0 -0
- /package/dist/{chunk-CRKWSSS4.js.map → chunk-4IEOGIPB.js.map} +0 -0
- /package/dist/{chunk-W75BBBSY.js.map → chunk-4IIMMZZK.js.map} +0 -0
- /package/dist/{chunk-Q3THVX75.js.map → chunk-4RL3JOB3.js.map} +0 -0
- /package/dist/{chunk-7G6WUOLB.js.map → chunk-66DKTOBY.js.map} +0 -0
- /package/dist/{chunk-IP5VLSLO.js.map → chunk-6L3PBSAR.js.map} +0 -0
- /package/dist/{chunk-AZ2X5LNA.js.map → chunk-6LYAEM54.js.map} +0 -0
- /package/dist/{chunk-RCQKAOOB.js.map → chunk-6NQ4LSMS.js.map} +0 -0
- /package/dist/{chunk-WB2I2EQK.js.map → chunk-6TT6HRE4.js.map} +0 -0
- /package/dist/{chunk-JX6USXZR.js.map → chunk-72JNNJJJ.js.map} +0 -0
- /package/dist/{chunk-BUKK5SWA.js.map → chunk-7LCGFUMB.js.map} +0 -0
- /package/dist/{chunk-OEBLZZKK.js.map → chunk-7XG7KMIW.js.map} +0 -0
- /package/dist/{chunk-3GN2PEOT.js.map → chunk-AQK54Q7O.js.map} +0 -0
- /package/dist/{chunk-BOUFVFV2.js.map → chunk-AZSGSRIM.js.map} +0 -0
- /package/dist/{chunk-N3MNDVJO.js.map → chunk-BFO3IR56.js.map} +0 -0
- /package/dist/{chunk-2UPTRDGM.js.map → chunk-BJRAELDH.js.map} +0 -0
- /package/dist/{chunk-IMAP236M.js.map → chunk-CS52MFDV.js.map} +0 -0
- /package/dist/{chunk-UNJOB4DY.js.map → chunk-CUCWX3MO.js.map} +0 -0
- /package/dist/{chunk-DQISIT63.js.map → chunk-DZ42MDZE.js.map} +0 -0
- /package/dist/{chunk-ML2MVAEZ.js.map → chunk-DZ525SNQ.js.map} +0 -0
- /package/dist/{chunk-5ELENZTA.js.map → chunk-EDIH6X2P.js.map} +0 -0
- /package/dist/{chunk-S42HZILI.js.map → chunk-EHS5MJEF.js.map} +0 -0
- /package/dist/{chunk-IA3X2Y4K.js.map → chunk-EQ6YOIKO.js.map} +0 -0
- /package/dist/{chunk-4V44AA7O.js.map → chunk-FAN73COB.js.map} +0 -0
- /package/dist/{chunk-KERNN6BC.js.map → chunk-FB64XE3R.js.map} +0 -0
- /package/dist/{chunk-S55WTPFJ.js.map → chunk-FRCESVV6.js.map} +0 -0
- /package/dist/{chunk-GEX5NI36.js.map → chunk-HBBHL4YF.js.map} +0 -0
- /package/dist/{chunk-2I6VHX6Q.js.map → chunk-HESDNYRB.js.map} +0 -0
- /package/dist/{chunk-2U3R3DDH.js.map → chunk-ICZQG4XE.js.map} +0 -0
- /package/dist/{chunk-DGAWEJVI.js.map → chunk-JGSQ6P4D.js.map} +0 -0
- /package/dist/{chunk-TIQZNDZ5.js.map → chunk-JKMHZF3Q.js.map} +0 -0
- /package/dist/{chunk-NAFPHFDM.js.map → chunk-JNLEJE4A.js.map} +0 -0
- /package/dist/{chunk-IFCKHPY4.js.map → chunk-K4J4E7WV.js.map} +0 -0
- /package/dist/{chunk-2QG4SLZI.js.map → chunk-KF7PQ7G2.js.map} +0 -0
- /package/dist/{chunk-JXMECXMF.js.map → chunk-KFCMMDHH.js.map} +0 -0
- /package/dist/{chunk-SQDKX4RW.js.map → chunk-KP7SANCF.js.map} +0 -0
- /package/dist/{chunk-OM5BIPBR.js.map → chunk-L6UZEAID.js.map} +0 -0
- /package/dist/{chunk-TEV2JGU5.js.map → chunk-LF5IGOOD.js.map} +0 -0
- /package/dist/{chunk-QWUJWBZ5.js.map → chunk-LQG5O5EP.js.map} +0 -0
- /package/dist/{chunk-NKFLHW7J.js.map → chunk-LS3XH3UE.js.map} +0 -0
- /package/dist/{chunk-BJX3VQWZ.js.map → chunk-M536WNTQ.js.map} +0 -0
- /package/dist/{chunk-EU3N5MK3.js.map → chunk-MQJL6CDZ.js.map} +0 -0
- /package/dist/{chunk-VZBW6CDG.js.map → chunk-N4S4NUX5.js.map} +0 -0
- /package/dist/{chunk-L2LBMKSI.js.map → chunk-NE6PHPLG.js.map} +0 -0
- /package/dist/{chunk-VTPASHTO.js.map → chunk-NE7I2RCT.js.map} +0 -0
- /package/dist/{chunk-OAOBL6OY.js.map → chunk-NR2ASHBL.js.map} +0 -0
- /package/dist/{chunk-7UUKRS3J.js.map → chunk-P2WZHLUF.js.map} +0 -0
- /package/dist/{chunk-IVH7INFM.js.map → chunk-P6WFK4ZJ.js.map} +0 -0
- /package/dist/{chunk-CHDGJJV6.js.map → chunk-PUKTBULQ.js.map} +0 -0
- /package/dist/{chunk-NAYBHNGN.js.map → chunk-QEV26L2F.js.map} +0 -0
- /package/dist/{chunk-I4G4UWQA.js.map → chunk-QI2E6ISQ.js.map} +0 -0
- /package/dist/{chunk-CK5U7JGH.js.map → chunk-QVS22WTO.js.map} +0 -0
- /package/dist/{chunk-B47OHVFT.js.map → chunk-QWW2JDDX.js.map} +0 -0
- /package/dist/{chunk-EJPF6XDQ.js.map → chunk-RDJ6C7NZ.js.map} +0 -0
- /package/dist/{chunk-RDEUG2M2.js.map → chunk-SOZUJCME.js.map} +0 -0
- /package/dist/{chunk-T4QGUXCV.js.map → chunk-SPSRGEDQ.js.map} +0 -0
- /package/dist/{chunk-TY446V34.js.map → chunk-SRZW7FNU.js.map} +0 -0
- /package/dist/{chunk-SV72F4DS.js.map → chunk-SY5KDWGU.js.map} +0 -0
- /package/dist/{chunk-H36X4NRH.js.map → chunk-TDYAIXHK.js.map} +0 -0
- /package/dist/{chunk-ZTHGFTMW.js.map → chunk-TKC5LZ43.js.map} +0 -0
- /package/dist/{chunk-AXDOF4VI.js.map → chunk-UA5DX5C3.js.map} +0 -0
- /package/dist/{chunk-JS3S2HVG.js.map → chunk-V2ES4WCZ.js.map} +0 -0
- /package/dist/{chunk-5FNZ3G6X.js.map → chunk-VKQI47DJ.js.map} +0 -0
- /package/dist/{chunk-JVY556FW.js.map → chunk-VLLD4TOB.js.map} +0 -0
- /package/dist/{chunk-NMBX4D7P.js.map → chunk-VQNONC4E.js.map} +0 -0
- /package/dist/{chunk-GZAGLCQH.js.map → chunk-VVDWZRZG.js.map} +0 -0
- /package/dist/{chunk-T4BE5KJM.js.map → chunk-VX6GCKBS.js.map} +0 -0
- /package/dist/{chunk-RIEX7NOZ.js.map → chunk-W4AOR52H.js.map} +0 -0
- /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-BNPLDVM6.js.map → chunk-XTYX6V52.js.map} +0 -0
- /package/dist/{chunk-FZVROZKC.js.map → chunk-XVZA6VXC.js.map} +0 -0
- /package/dist/{chunk-C7JAUNFC.js.map → chunk-YITN5WXI.js.map} +0 -0
- /package/dist/{chunk-HS75MCQF.js.map → chunk-YRULQW5J.js.map} +0 -0
- /package/dist/{chunk-DINRT75P.js.map → chunk-YSBL7VPO.js.map} +0 -0
- /package/dist/{chunk-7QK5TUTW.js.map → chunk-YSJTCOHR.js.map} +0 -0
- /package/dist/{chunk-QC56VZXC.js.map → chunk-YTQGGO3K.js.map} +0 -0
- /package/dist/{chunk-HKFJ3PF3.js.map → chunk-ZI7QA4MK.js.map} +0 -0
- /package/dist/{contradiction-scan-5EJYRKD3.js.map → contradiction-scan-TAGMOUQ2.js.map} +0 -0
- /package/dist/{graph-edge-decay-PVKDKLQK.js.map → extraction-llm-health.js.map} +0 -0
- /package/dist/{first-start-migration-GAT7G733.js.map → first-start-migration-7OATHJKB.js.map} +0 -0
- /package/dist/{resolution-UYJU5JUA.js.map → graph-edge-decay-KQGS3L3B.js.map} +0 -0
|
@@ -433,14 +433,13 @@ test("#2128: successful LCM flush does not bind per-call project context", async
|
|
|
433
433
|
test("#2128: extraction force-flush uses observe's scoped target even when LCM is disabled", async () => {
|
|
434
434
|
const probe = makeParityProbe(withSelfPolicyPrefix("pi-geek"));
|
|
435
435
|
const service = new EngramAccessService(probe.orch);
|
|
436
|
-
const deadlineMs = Date.now() + 10_000;
|
|
437
436
|
const abortController = new AbortController();
|
|
438
437
|
probe.lcmEngine.enabled = false;
|
|
439
438
|
|
|
440
439
|
const response = await service.extractionForceFlush({
|
|
441
440
|
sessionKey: "pi-geek:force-flush",
|
|
442
441
|
projectTag: "Acme/Webshop",
|
|
443
|
-
deadlineMs,
|
|
442
|
+
deadlineMs: 10_000,
|
|
444
443
|
abortSignal: abortController.signal,
|
|
445
444
|
authenticatedPrincipal: "pi-geek",
|
|
446
445
|
});
|
|
@@ -460,7 +459,11 @@ test("#2128: extraction force-flush uses observe's scoped target even when LCM i
|
|
|
460
459
|
false,
|
|
461
460
|
"force-flush must let the orchestrator discover all session buffer keys",
|
|
462
461
|
);
|
|
463
|
-
|
|
462
|
+
// #3140: the duration budget arrives at the orchestrator as an absolute deadline.
|
|
463
|
+
assert.ok(
|
|
464
|
+
(call.options.extractionDeadlineMs ?? 0) >= Date.now() + 9_000,
|
|
465
|
+
"the orchestrator deadline is the duration converted at force-flush entry",
|
|
466
|
+
);
|
|
464
467
|
assert.equal(call.options.failOnExtractionFailure, true);
|
|
465
468
|
assert.equal(call.options.abortSignal, abortController.signal);
|
|
466
469
|
assert.equal(call.options.writeNamespaceOverride, expectedNamespace);
|
|
@@ -573,7 +576,7 @@ test("#2128: pending extraction barriers stay isolated by authenticated principa
|
|
|
573
576
|
sessionKey,
|
|
574
577
|
namespace: "team",
|
|
575
578
|
authenticatedPrincipal: "alice",
|
|
576
|
-
deadlineMs:
|
|
579
|
+
deadlineMs: 500,
|
|
577
580
|
});
|
|
578
581
|
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
579
582
|
pending.get("alice")?.();
|
|
@@ -583,7 +586,7 @@ test("#2128: pending extraction barriers stay isolated by authenticated principa
|
|
|
583
586
|
sessionKey,
|
|
584
587
|
namespace: "team",
|
|
585
588
|
authenticatedPrincipal: "bob",
|
|
586
|
-
deadlineMs:
|
|
589
|
+
deadlineMs: 500,
|
|
587
590
|
});
|
|
588
591
|
pending.get("bob")?.();
|
|
589
592
|
assert.equal((await bobFlush).flushed, true);
|
|
@@ -600,7 +603,7 @@ test("#2128: chained observes remain behind one force-flush barrier", async () =
|
|
|
600
603
|
const sessionKey = "chained-observes";
|
|
601
604
|
|
|
602
605
|
await service.observe(observeRequest({ sessionKey, skipExtraction: false }));
|
|
603
|
-
const flushPromise = service.extractionForceFlush({ sessionKey, deadlineMs:
|
|
606
|
+
const flushPromise = service.extractionForceFlush({ sessionKey, deadlineMs: 500 });
|
|
604
607
|
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
605
608
|
await service.observe(observeRequest({ sessionKey, skipExtraction: false }));
|
|
606
609
|
assert.equal(resolvers.length, 2);
|
|
@@ -669,7 +672,7 @@ test("#2128: force-flush deadline cancels the pending observe extraction", async
|
|
|
669
672
|
await assert.rejects(
|
|
670
673
|
service.extractionForceFlush({
|
|
671
674
|
sessionKey,
|
|
672
|
-
deadlineMs:
|
|
675
|
+
deadlineMs: 25,
|
|
673
676
|
}),
|
|
674
677
|
(error: unknown) =>
|
|
675
678
|
error instanceof EngramAccessInputError &&
|
|
@@ -893,7 +896,7 @@ test("#2128: post-flush retained cleanup is best effort and receives lifecycle g
|
|
|
893
896
|
},
|
|
894
897
|
};
|
|
895
898
|
const service = new EngramAccessService(probe.orch);
|
|
896
|
-
const deadlineMs =
|
|
899
|
+
const deadlineMs = 10_000;
|
|
897
900
|
|
|
898
901
|
const response = await service.extractionForceFlush({
|
|
899
902
|
sessionKey: "pi-geek:cleanup-best-effort",
|
|
@@ -903,11 +906,15 @@ test("#2128: post-flush retained cleanup is best effort and receives lifecycle g
|
|
|
903
906
|
});
|
|
904
907
|
|
|
905
908
|
assert.equal(response.flushed, true);
|
|
906
|
-
assert.
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
909
|
+
assert.equal(cleanupCalls.length, 1);
|
|
910
|
+
// #3140: cleanup receives the duration converted to an absolute deadline.
|
|
911
|
+
assert.equal(cleanupCalls[0]?.sessionKey, "pi-geek:cleanup-best-effort");
|
|
912
|
+
assert.equal(cleanupCalls[0]?.ownerPrincipal, "pi-geek");
|
|
913
|
+
assert.equal(cleanupCalls[0]?.options?.abortSignal, abortController.signal);
|
|
914
|
+
assert.ok(
|
|
915
|
+
(cleanupCalls[0]?.options?.deadlineMs ?? 0) >= Date.now() + 9_000,
|
|
916
|
+
"cleanup deadline is the duration converted at force-flush entry",
|
|
917
|
+
);
|
|
911
918
|
});
|
|
912
919
|
|
|
913
920
|
test("#2128: post-flush retained cleanup stops on abort or deadline", async (t) => {
|
|
@@ -982,7 +989,11 @@ test("#2128: post-flush retained cleanup stops on abort or deadline", async (t)
|
|
|
982
989
|
/replay extraction deadline exceeded \(retained_turn_cleanup\)/,
|
|
983
990
|
);
|
|
984
991
|
});
|
|
985
|
-
test("#
|
|
992
|
+
test("#3140: a duration deadline reaches scope resolution and aborts it when it elapses", async () => {
|
|
993
|
+
// Regression for issue #3140: `deadlineMs` is a BUDGET from now. The old
|
|
994
|
+
// absolute-epoch reading compared a relative value (e.g. 30000) against
|
|
995
|
+
// Date.now(), so a live-session force flush was rejected INSTANTLY with
|
|
996
|
+
// `replay extraction deadline exceeded (scope_resolution)` before any work.
|
|
986
997
|
const probe = makeParityProbe({ namespacesEnabled: false } as Partial<PluginConfig>);
|
|
987
998
|
const service = new EngramAccessService(probe.orch);
|
|
988
999
|
let scopeResolutionStarted = false;
|
|
@@ -991,17 +1002,23 @@ test("#2206: an expired force-flush deadline stops before scope resolution", asy
|
|
|
991
1002
|
};
|
|
992
1003
|
internals.resolveMemoryScopePlan = async () => {
|
|
993
1004
|
scopeResolutionStarted = true;
|
|
994
|
-
|
|
1005
|
+
return new Promise<never>(() => {});
|
|
995
1006
|
};
|
|
996
1007
|
|
|
1008
|
+
const started = Date.now();
|
|
997
1009
|
await assert.rejects(
|
|
998
1010
|
service.extractionForceFlush({
|
|
999
|
-
sessionKey: "
|
|
1000
|
-
deadlineMs:
|
|
1011
|
+
sessionKey: "duration-deadline-scope-resolution",
|
|
1012
|
+
deadlineMs: 30,
|
|
1001
1013
|
}),
|
|
1002
1014
|
/scope_resolution/,
|
|
1003
1015
|
);
|
|
1004
|
-
assert.equal(scopeResolutionStarted,
|
|
1016
|
+
assert.equal(scopeResolutionStarted, true, "the budget lets scope resolution start");
|
|
1017
|
+
assert.ok(
|
|
1018
|
+
Date.now() - started >= 25,
|
|
1019
|
+
"the flush waits out the budget instead of failing instantly",
|
|
1020
|
+
);
|
|
1021
|
+
assert.equal(probe.extractionForceFlushCalls.length, 0);
|
|
1005
1022
|
});
|
|
1006
1023
|
|
|
1007
1024
|
test("#2206: abort during scope resolution cancels pre-resolution observe preparations", async () => {
|
|
@@ -1089,8 +1106,10 @@ test("#2206: scope-resolution deadline cancels preparations by raw projectTag an
|
|
|
1089
1106
|
await observeScopeGate;
|
|
1090
1107
|
return resolveMemoryScopePlan(request);
|
|
1091
1108
|
}
|
|
1109
|
+
// Blocked scope work, as a live session's pending observe preparations
|
|
1110
|
+
// would hold it (#3140): the duration budget elapses while it hangs.
|
|
1092
1111
|
flushScopeStarted = true;
|
|
1093
|
-
|
|
1112
|
+
return new Promise<never>(() => {});
|
|
1094
1113
|
};
|
|
1095
1114
|
|
|
1096
1115
|
const observe = service.observe(observeRequest({
|
|
@@ -1104,12 +1123,12 @@ test("#2206: scope-resolution deadline cancels preparations by raw projectTag an
|
|
|
1104
1123
|
service.extractionForceFlush({
|
|
1105
1124
|
sessionKey: "pi-geek:raw-hint-deadline",
|
|
1106
1125
|
authenticatedPrincipal: "pi-geek",
|
|
1107
|
-
deadlineMs:
|
|
1126
|
+
deadlineMs: 1,
|
|
1108
1127
|
...rawHintCase,
|
|
1109
1128
|
}),
|
|
1110
1129
|
/scope_resolution/,
|
|
1111
1130
|
);
|
|
1112
|
-
assert.equal(flushScopeStarted,
|
|
1131
|
+
assert.equal(flushScopeStarted, true);
|
|
1113
1132
|
releaseObserveScope();
|
|
1114
1133
|
const response = await observe;
|
|
1115
1134
|
assert.equal(response.extractionQueued, false);
|
|
@@ -1132,12 +1151,19 @@ test("#2128: aborted or expired extraction force-flush never touches a buffer",
|
|
|
1132
1151
|
}),
|
|
1133
1152
|
/extraction force-flush aborted/,
|
|
1134
1153
|
);
|
|
1154
|
+
// #3140: the expired-at-entry scenario is gone (durations always start in
|
|
1155
|
+
// the future); a TINY budget elapsing while scope work is blocked still
|
|
1156
|
+
// rejects with the same error and never reaches the buffer.
|
|
1157
|
+
const internals = service as unknown as {
|
|
1158
|
+
resolveMemoryScopePlan: (request: unknown) => Promise<never>;
|
|
1159
|
+
};
|
|
1160
|
+
internals.resolveMemoryScopePlan = async () => new Promise<never>(() => {});
|
|
1135
1161
|
await assert.rejects(
|
|
1136
1162
|
() =>
|
|
1137
1163
|
service.extractionForceFlush({
|
|
1138
1164
|
sessionKey: "pi-geek:expired-flush",
|
|
1139
1165
|
authenticatedPrincipal: "pi-geek",
|
|
1140
|
-
deadlineMs:
|
|
1166
|
+
deadlineMs: 1,
|
|
1141
1167
|
}),
|
|
1142
1168
|
(error: unknown) =>
|
|
1143
1169
|
error instanceof EngramAccessInputError &&
|
|
@@ -1167,13 +1193,19 @@ test("#2128: self-deps wires force-flush cancellation into the observe tracker",
|
|
|
1167
1193
|
cancellations.push({ sessionKey, principal, namespace });
|
|
1168
1194
|
};
|
|
1169
1195
|
|
|
1196
|
+
// #3140: a tiny budget elapsing while scope work is blocked still cancels
|
|
1197
|
+
// the pending observe preparations via the same deadline hook.
|
|
1198
|
+
const internals = service as unknown as {
|
|
1199
|
+
resolveMemoryScopePlan: (request: unknown) => Promise<never>;
|
|
1200
|
+
};
|
|
1201
|
+
internals.resolveMemoryScopePlan = async () => new Promise<never>(() => {});
|
|
1170
1202
|
await assert.rejects(
|
|
1171
1203
|
() =>
|
|
1172
1204
|
service.extractionForceFlush({
|
|
1173
1205
|
sessionKey: "pi-geek:cancel-hook",
|
|
1174
1206
|
namespace: "pi-geek",
|
|
1175
1207
|
authenticatedPrincipal: "pi-geek",
|
|
1176
|
-
deadlineMs:
|
|
1208
|
+
deadlineMs: 1,
|
|
1177
1209
|
}),
|
|
1178
1210
|
(error: unknown) =>
|
|
1179
1211
|
error instanceof EngramAccessInputError &&
|
|
@@ -1198,7 +1230,7 @@ test("#2128: late extraction deadline is surfaced as an access input error", asy
|
|
|
1198
1230
|
service.extractionForceFlush({
|
|
1199
1231
|
sessionKey: "pi-geek:late-deadline",
|
|
1200
1232
|
authenticatedPrincipal: "pi-geek",
|
|
1201
|
-
deadlineMs:
|
|
1233
|
+
deadlineMs: 10_000,
|
|
1202
1234
|
}),
|
|
1203
1235
|
(error: unknown) =>
|
|
1204
1236
|
error instanceof EngramAccessInputError &&
|
|
@@ -30,6 +30,7 @@ import { writeFileAtomically } from "../maintenance/atomic-file.js";
|
|
|
30
30
|
import type { Orchestrator } from "../orchestrator.js";
|
|
31
31
|
import type { MemoryFile, MemoryStatus, PluginConfig } from "../types.js";
|
|
32
32
|
import { stripAttributesSuffix } from "../structured-attributes.js";
|
|
33
|
+
import { extractJsonCandidates } from "../json-extract.js";
|
|
33
34
|
import { supersessionKeysForFact } from "../temporal-supersession.js";
|
|
34
35
|
import { computeContentHash } from "../content-hash.js";
|
|
35
36
|
import { sanitizeMemoryContent } from "../sanitize.js";
|
|
@@ -1010,16 +1011,43 @@ function parseClassifyResponse(
|
|
|
1010
1011
|
raw: string,
|
|
1011
1012
|
candidates: PlannerCandidate[],
|
|
1012
1013
|
): LlmClassificationResult {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1014
|
+
// Correction plans can mutate memory: accept one schema-valid, verbatim
|
|
1015
|
+
// response, never an arbitrary example or a fence-rewritten patch payload.
|
|
1016
|
+
// The response envelope gates candidacy; actions need only ≥1 valid entry
|
|
1017
|
+
// (an empty list is fine, an all-malformed list is unusable). Malformed
|
|
1018
|
+
// siblings are dropped after selection, so one bad action cannot discard
|
|
1019
|
+
// the whole plan; distinct eligible responses stay ambiguous (fail closed).
|
|
1020
|
+
const responses = new Map<string, Record<string, unknown>>();
|
|
1021
|
+
let sawJson = false;
|
|
1022
|
+
for (const candidate of new Set([raw.trim(), ...extractJsonCandidates(raw)])) {
|
|
1023
|
+
if (!raw.includes(candidate)) continue;
|
|
1024
|
+
try {
|
|
1025
|
+
const parsed: unknown = JSON.parse(candidate);
|
|
1026
|
+
sawJson = true;
|
|
1027
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
|
|
1028
|
+
const value = parsed as Record<string, unknown>;
|
|
1029
|
+
if (!isClassification(value.classification) ||
|
|
1030
|
+
typeof value.confidence !== "number" || !Number.isFinite(value.confidence) ||
|
|
1031
|
+
value.confidence < 0 || value.confidence > 1 || !Array.isArray(value.actions)) continue;
|
|
1032
|
+
const hasUsable = value.actions.some((action) => {
|
|
1033
|
+
try {
|
|
1034
|
+
validateCorrectionAction(action);
|
|
1035
|
+
return true;
|
|
1036
|
+
} catch {
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
if (hasUsable || value.actions.length === 0) responses.set(JSON.stringify(value), value);
|
|
1041
|
+
} catch {
|
|
1042
|
+
// Keep scanning after malformed JSON, not after mere syntax success.
|
|
1043
|
+
}
|
|
1018
1044
|
}
|
|
1019
|
-
if (
|
|
1020
|
-
return fallbackClassification(candidates,
|
|
1045
|
+
if (responses.size !== 1) {
|
|
1046
|
+
return fallbackClassification(candidates, responses.size > 1
|
|
1047
|
+
? "LLM returned ambiguous correction responses"
|
|
1048
|
+
: sawJson ? "LLM returned invalid correction response" : "LLM returned non-JSON response");
|
|
1021
1049
|
}
|
|
1022
|
-
const obj =
|
|
1050
|
+
const obj = responses.values().next().value as Record<string, unknown>;
|
|
1023
1051
|
const classification = isClassification(obj.classification) ? obj.classification : "outdated";
|
|
1024
1052
|
const confidence = typeof obj.confidence === "number" ? Math.min(1, Math.max(0, obj.confidence)) : 0.5;
|
|
1025
1053
|
const rawActions = Array.isArray(obj.actions) ? obj.actions : [];
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* parseClassifyResponse regression tests (issue #3134).
|
|
3
|
+
*
|
|
4
|
+
* Proves the correction planner's single LLM-response parse chokepoint:
|
|
5
|
+
* - fenced JSON responses (```json … ```) parse into real actions instead of
|
|
6
|
+
* degrading to the deterministic fallback (#3134, same class as #1514);
|
|
7
|
+
* - genuinely non-JSON responses still return the byte-identical
|
|
8
|
+
* deterministic fallback (rule 13);
|
|
9
|
+
* - bare JSON keeps parsing through the same candidate chain.
|
|
10
|
+
*/
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { test } from "node:test";
|
|
13
|
+
|
|
14
|
+
import { parseClassifyResponse } from "./correction-access-wiring.js";
|
|
15
|
+
import type { PlannerCandidate } from "./correction-planner.js";
|
|
16
|
+
|
|
17
|
+
const CANDIDATES: PlannerCandidate[] = [
|
|
18
|
+
{ memoryId: "m1", path: "facts/m1.md", content: "m1 content", excerpt: "m1 excerpt", score: 1 },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const CLASSIFY_JSON = JSON.stringify({
|
|
22
|
+
classification: "wrong",
|
|
23
|
+
confidence: 0.9,
|
|
24
|
+
actions: [{ kind: "edit", memoryId: "m1", patch: "m1 corrected content" }],
|
|
25
|
+
relevance: [{ memoryId: "m1", why: "names the corrected fact" }],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("#3134 fenced json classify response parses into actions instead of fallback", () => {
|
|
29
|
+
const result = parseClassifyResponse(["```json", CLASSIFY_JSON, "```"].join("\n"), CANDIDATES);
|
|
30
|
+
assert.equal(result.fallback, undefined, "a fenced response must not degrade to the fallback");
|
|
31
|
+
assert.equal(result.classification, "wrong");
|
|
32
|
+
assert.equal(result.confidence, 0.9);
|
|
33
|
+
assert.deepEqual(result.actions, [{ kind: "edit", memoryId: "m1", patch: "m1 corrected content" }]);
|
|
34
|
+
assert.deepEqual(result.relevance, [{ memoryId: "m1", why: "names the corrected fact" }]);
|
|
35
|
+
assert.deepEqual(result.warnings, []);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("#3134 fenced response without a language tag parses", () => {
|
|
39
|
+
const result = parseClassifyResponse(["```", CLASSIFY_JSON, "```"].join("\n"), CANDIDATES);
|
|
40
|
+
assert.equal(result.fallback, undefined);
|
|
41
|
+
assert.equal(result.actions.length, 1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("#3134 prose-wrapped fenced response with trailing prose parses", () => {
|
|
45
|
+
const result = parseClassifyResponse(
|
|
46
|
+
["Here is the classification:", "```json", CLASSIFY_JSON, "```", "Done."].join("\n"),
|
|
47
|
+
CANDIDATES
|
|
48
|
+
);
|
|
49
|
+
assert.equal(result.fallback, undefined);
|
|
50
|
+
assert.equal(result.actions.length, 1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("#3134 bare JSON with an inner markdown code block keeps its patch content verbatim", () => {
|
|
54
|
+
const patch = "```\nconst fixed = true;\n```";
|
|
55
|
+
const result = parseClassifyResponse(
|
|
56
|
+
JSON.stringify({
|
|
57
|
+
classification: "wrong",
|
|
58
|
+
confidence: 0.8,
|
|
59
|
+
actions: [{ kind: "edit", memoryId: "m1", patch }],
|
|
60
|
+
}),
|
|
61
|
+
CANDIDATES
|
|
62
|
+
);
|
|
63
|
+
assert.equal(result.fallback, undefined, "valid bare JSON must not degrade to the fallback");
|
|
64
|
+
assert.deepEqual(result.actions, [{ kind: "edit", memoryId: "m1", patch }], "inner code-block delimiters must survive verbatim");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("#3134 outer-fenced JSON with an inner code block parses with the patch verbatim", () => {
|
|
68
|
+
const patch = "```py\nprint(\"fixed\")\n```";
|
|
69
|
+
const inner = JSON.stringify({
|
|
70
|
+
classification: "wrong",
|
|
71
|
+
confidence: 0.8,
|
|
72
|
+
actions: [{ kind: "edit", memoryId: "m1", patch }],
|
|
73
|
+
});
|
|
74
|
+
const result = parseClassifyResponse(["```json", inner, "```"].join("\n"), CANDIDATES);
|
|
75
|
+
assert.equal(result.fallback, undefined, "an outer fence plus an inner code block must not degrade to the fallback");
|
|
76
|
+
assert.deepEqual(result.actions, [{ kind: "edit", memoryId: "m1", patch }], "inner code-block delimiters must survive verbatim");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("#3134 genuinely non-JSON response still returns the deterministic fallback", () => {
|
|
80
|
+
const result = parseClassifyResponse("I'm sorry, I cannot classify that request.", CANDIDATES);
|
|
81
|
+
assert.deepEqual(result, {
|
|
82
|
+
classification: "outdated",
|
|
83
|
+
confidence: 0,
|
|
84
|
+
actions: [],
|
|
85
|
+
relevance: [{ memoryId: "m1", why: "located for review" }],
|
|
86
|
+
warnings: ["LLM returned non-JSON response"],
|
|
87
|
+
fallback: true,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("#3134 empty response still returns the deterministic fallback", () => {
|
|
92
|
+
const result = parseClassifyResponse("", CANDIDATES);
|
|
93
|
+
assert.equal(result.fallback, true);
|
|
94
|
+
assert.deepEqual(result.warnings, ["LLM returned non-JSON response"]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("#3134 bare JSON keeps parsing through the same candidate chain", () => {
|
|
98
|
+
const result = parseClassifyResponse(CLASSIFY_JSON, CANDIDATES);
|
|
99
|
+
assert.equal(result.fallback, undefined);
|
|
100
|
+
assert.equal(result.classification, "wrong");
|
|
101
|
+
assert.equal(result.confidence, 0.9);
|
|
102
|
+
assert.equal(result.actions.length, 1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("#3134 bare JSON object with no actions still parses without fallback", () => {
|
|
106
|
+
const result = parseClassifyResponse(
|
|
107
|
+
JSON.stringify({ classification: "incomplete", confidence: 0.4, actions: [] }),
|
|
108
|
+
CANDIDATES
|
|
109
|
+
);
|
|
110
|
+
assert.equal(result.fallback, undefined);
|
|
111
|
+
assert.equal(result.classification, "incomplete");
|
|
112
|
+
assert.equal(result.actions.length, 0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("#3145 skips unrelated JSON objects before the correction response", () => {
|
|
116
|
+
const result = parseClassifyResponse("Example: {}\nAnswer: " + CLASSIFY_JSON, CANDIDATES);
|
|
117
|
+
assert.equal(result.fallback, undefined);
|
|
118
|
+
assert.equal(result.classification, "wrong");
|
|
119
|
+
assert.equal(result.actions.length, 1);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("#3145 rejects ambiguous valid correction drafts instead of choosing an example", () => {
|
|
123
|
+
const other = JSON.stringify({ classification: "outdated", confidence: 0.99,
|
|
124
|
+
actions: [{ kind: "edit", memoryId: "m1", patch: "wrong example" }] });
|
|
125
|
+
const result = parseClassifyResponse(other + "\nActual answer: " + CLASSIFY_JSON, CANDIDATES);
|
|
126
|
+
assert.equal(result.fallback, true);
|
|
127
|
+
assert.equal(result.confidence, 0);
|
|
128
|
+
assert.deepEqual(result.actions, []);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("#3145 skips malformed correction actions before a valid response", () => {
|
|
132
|
+
const malformed = JSON.stringify({ classification: "wrong", confidence: 0.9,
|
|
133
|
+
actions: [{ kind: "invented-action" }] });
|
|
134
|
+
const result = parseClassifyResponse(malformed + "\n" + CLASSIFY_JSON, CANDIDATES);
|
|
135
|
+
assert.equal(result.fallback, undefined);
|
|
136
|
+
assert.deepEqual(result.actions, [{ kind: "edit", memoryId: "m1", patch: "m1 corrected content" }]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("#3145 keeps the usable action when a sibling action is malformed", () => {
|
|
140
|
+
const mixed = JSON.stringify({
|
|
141
|
+
classification: "outdated",
|
|
142
|
+
confidence: 0.8,
|
|
143
|
+
actions: [
|
|
144
|
+
{ kind: "edit", memoryId: "m1", patch: "m1 corrected content" },
|
|
145
|
+
{ kind: "invented-action" },
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
const result = parseClassifyResponse(mixed, CANDIDATES);
|
|
149
|
+
assert.equal(result.fallback, undefined, "one malformed sibling must not discard the whole plan");
|
|
150
|
+
assert.deepEqual(result.actions, [{ kind: "edit", memoryId: "m1", patch: "m1 corrected content" }]);
|
|
151
|
+
assert.equal(
|
|
152
|
+
result.warnings.filter((w) => w.startsWith("dropped malformed action")).length,
|
|
153
|
+
1,
|
|
154
|
+
"exactly the malformed sibling is dropped with a warning",
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("#3145 a partially valid draft is ambiguous against a distinct valid draft", () => {
|
|
159
|
+
const partialDraft = JSON.stringify({
|
|
160
|
+
classification: "outdated",
|
|
161
|
+
confidence: 0.8,
|
|
162
|
+
actions: [
|
|
163
|
+
{ kind: "edit", memoryId: "m1", patch: "partial example" },
|
|
164
|
+
{ kind: "invented-action" },
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
const result = parseClassifyResponse(partialDraft + "\n" + CLASSIFY_JSON, CANDIDATES);
|
|
168
|
+
assert.equal(result.fallback, true);
|
|
169
|
+
assert.equal(result.confidence, 0);
|
|
170
|
+
assert.deepEqual(result.actions, []);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("#3145 an unrelated standalone object never becomes a plausible correction", () => {
|
|
174
|
+
const result = parseClassifyResponse("{}", CANDIDATES);
|
|
175
|
+
assert.equal(result.fallback, true);
|
|
176
|
+
assert.equal(result.confidence, 0);
|
|
177
|
+
assert.deepEqual(result.actions, []);
|
|
178
|
+
});
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* the verdict never diverges across code paths.
|
|
20
20
|
*/
|
|
21
21
|
import { coerceBool, coerceNumber } from "./connectors/coerce.js";
|
|
22
|
+
import { getExtractionLlmHealth } from "./extraction-llm-health.js";
|
|
22
23
|
import { log } from "./logger.js";
|
|
23
24
|
import type { OperatorDoctorCheck } from "./operator-doctor-types.js";
|
|
24
25
|
import type { PluginConfig } from "./types.js";
|
|
@@ -121,6 +122,15 @@ export interface ExtractionLivenessStatus {
|
|
|
121
122
|
degradedReason: string | null;
|
|
122
123
|
watermarkPending?: boolean;
|
|
123
124
|
watermarkScope: "aggregate";
|
|
125
|
+
/**
|
|
126
|
+
* Extraction-LLM availability (issue #3140): outcome of the last extraction
|
|
127
|
+
* attempt. `lastFailureReason` is non-null exactly while `llmReachable` is
|
|
128
|
+
* false. Populated by the daemon-wide status readers (`/health`, doctor,
|
|
129
|
+
* stats), not by the pure evaluators below.
|
|
130
|
+
*/
|
|
131
|
+
llmReachable?: boolean;
|
|
132
|
+
lastFailureReason?: string | null;
|
|
133
|
+
lastFailureAt?: string | null;
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
interface ExtractionLivenessOrchestratorLike {
|
|
@@ -380,7 +390,9 @@ export async function computeExtractionLivenessStatus(
|
|
|
380
390
|
nowMs,
|
|
381
391
|
});
|
|
382
392
|
throttle?.maybeWarn(status, config.staleWindowMs, nowMs);
|
|
383
|
-
|
|
393
|
+
// Extraction-LLM availability (#3140): a silent no_models / LLM-unreachable
|
|
394
|
+
// outage must be visible on /health (and doctor/stats) without debug logs.
|
|
395
|
+
return { ...status, ...getExtractionLlmHealth() };
|
|
384
396
|
}
|
|
385
397
|
|
|
386
398
|
/**
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extraction-LLM health surfacing (issue #3140).
|
|
3
|
+
*
|
|
4
|
+
* Covers the three #3140 surfaces:
|
|
5
|
+
* 1. the singleton tracker that `/health.extraction` reads for LLM
|
|
6
|
+
* availability + last failure reason,
|
|
7
|
+
* 2. the ERROR-level, per-reason-deduplicated failure event emitted by the
|
|
8
|
+
* extraction run instead of debug-only lines,
|
|
9
|
+
* 3. the runExtraction integration recording failures/successes.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import test from "node:test";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
emitExtractionLlmFailureError,
|
|
17
|
+
getExtractionLlmHealth,
|
|
18
|
+
recordExtractionLlmFailure,
|
|
19
|
+
recordExtractionLlmSuccess,
|
|
20
|
+
resetExtractionLlmHealthForTests,
|
|
21
|
+
} from "./extraction-llm-health.js";
|
|
22
|
+
import { log } from "./logger.js";
|
|
23
|
+
|
|
24
|
+
test("tracker: starts reachable with no failure recorded", () => {
|
|
25
|
+
resetExtractionLlmHealthForTests();
|
|
26
|
+
try {
|
|
27
|
+
assert.deepEqual(getExtractionLlmHealth(), {
|
|
28
|
+
llmReachable: true,
|
|
29
|
+
lastFailureReason: null,
|
|
30
|
+
lastFailureAt: null,
|
|
31
|
+
});
|
|
32
|
+
} finally {
|
|
33
|
+
resetExtractionLlmHealthForTests();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("tracker: a failure marks the extractor unavailable with the last reason (health payload, non-debug)", () => {
|
|
38
|
+
resetExtractionLlmHealthForTests();
|
|
39
|
+
try {
|
|
40
|
+
recordExtractionLlmFailure("no_models", 1_000);
|
|
41
|
+
const health = getExtractionLlmHealth();
|
|
42
|
+
assert.equal(health.llmReachable, false);
|
|
43
|
+
assert.equal(health.lastFailureReason, "no_models");
|
|
44
|
+
assert.equal(health.lastFailureAt, new Date(1_000).toISOString());
|
|
45
|
+
|
|
46
|
+
recordExtractionLlmFailure("local_llm_unavailable", 2_000);
|
|
47
|
+
assert.equal(getExtractionLlmHealth().lastFailureReason, "local_llm_unavailable");
|
|
48
|
+
} finally {
|
|
49
|
+
resetExtractionLlmHealthForTests();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("tracker: a later success clears the failure so llmReachable and the reason agree", () => {
|
|
54
|
+
resetExtractionLlmHealthForTests();
|
|
55
|
+
try {
|
|
56
|
+
recordExtractionLlmFailure("no_models");
|
|
57
|
+
recordExtractionLlmSuccess();
|
|
58
|
+
assert.deepEqual(getExtractionLlmHealth(), {
|
|
59
|
+
llmReachable: true,
|
|
60
|
+
lastFailureReason: null,
|
|
61
|
+
lastFailureAt: null,
|
|
62
|
+
});
|
|
63
|
+
} finally {
|
|
64
|
+
resetExtractionLlmHealthForTests();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("dedup: the first failure of a reason logs at ERROR level, repeats are suppressed", () => {
|
|
69
|
+
resetExtractionLlmHealthForTests();
|
|
70
|
+
const errors: string[] = [];
|
|
71
|
+
const debugs: string[] = [];
|
|
72
|
+
const originalError = log.error;
|
|
73
|
+
const originalDebug = log.debug;
|
|
74
|
+
log.error = (message: unknown) => {
|
|
75
|
+
errors.push(String(message));
|
|
76
|
+
};
|
|
77
|
+
log.debug = (message: unknown) => {
|
|
78
|
+
debugs.push(String(message));
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
assert.equal(emitExtractionLlmFailureError("no_models", "auth_config", 0), true);
|
|
82
|
+
assert.equal(errors.length, 1);
|
|
83
|
+
assert.match(errors[0] ?? "", /no_models/);
|
|
84
|
+
|
|
85
|
+
// Same reason inside the window: suppressed to debug, no second ERROR.
|
|
86
|
+
assert.equal(emitExtractionLlmFailureError("no_models", "auth_config", 60_000), false);
|
|
87
|
+
assert.equal(errors.length, 1);
|
|
88
|
+
assert.equal(debugs.length, 1);
|
|
89
|
+
|
|
90
|
+
// A different reason emits its own first ERROR.
|
|
91
|
+
assert.equal(emitExtractionLlmFailureError("local_llm_unavailable", undefined, 60_000), true);
|
|
92
|
+
assert.equal(errors.length, 2);
|
|
93
|
+
assert.match(errors[1] ?? "", /local_llm_unavailable/);
|
|
94
|
+
|
|
95
|
+
// Outside the window the same reason emits again.
|
|
96
|
+
assert.equal(emitExtractionLlmFailureError("no_models", undefined, 3_600_000), true);
|
|
97
|
+
assert.equal(errors.length, 3);
|
|
98
|
+
} finally {
|
|
99
|
+
log.error = originalError;
|
|
100
|
+
log.debug = originalDebug;
|
|
101
|
+
resetExtractionLlmHealthForTests();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extraction-LLM availability tracking surfaced on `/health` (issue #3140).
|
|
3
|
+
*
|
|
4
|
+
* A failing extraction LLM (unreachable endpoint, gateway with no models)
|
|
5
|
+
* used to surface only as debug-level lines while extraction silently skipped
|
|
6
|
+
* persistence and the buffer grew for days. The extraction run records every
|
|
7
|
+
* attempt outcome here; the access `/health` payload reads the last outcome so
|
|
8
|
+
* an unavailable extractor is visible without debug logs, and each distinct
|
|
9
|
+
* failure reason logs at ERROR level at most once per window instead of once
|
|
10
|
+
* per failed flush.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { log } from "./logger.js";
|
|
14
|
+
|
|
15
|
+
export interface ExtractionLlmHealth {
|
|
16
|
+
/** Whether the LAST extraction attempt completed without a failure marker. */
|
|
17
|
+
llmReachable: boolean;
|
|
18
|
+
/** `failureReason` of the last failed attempt; null while reachable. */
|
|
19
|
+
lastFailureReason: string | null;
|
|
20
|
+
/** ISO timestamp of the last failed attempt; null while reachable. */
|
|
21
|
+
lastFailureAt: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** One ERROR line per distinct reason per window; repeats stay at debug. */
|
|
25
|
+
const DEDUP_WINDOW_MS = 3_600_000;
|
|
26
|
+
|
|
27
|
+
let lastFailureReason: string | null = null;
|
|
28
|
+
let lastFailureAtMs: number | null = null;
|
|
29
|
+
const lastErrorEmitByReason = new Map<string, number>();
|
|
30
|
+
|
|
31
|
+
export function recordExtractionLlmSuccess(): void {
|
|
32
|
+
lastFailureReason = null;
|
|
33
|
+
lastFailureAtMs = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function recordExtractionLlmFailure(reason: string, atMs: number = Date.now()): void {
|
|
37
|
+
lastFailureReason = reason;
|
|
38
|
+
lastFailureAtMs = atMs;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getExtractionLlmHealth(): ExtractionLlmHealth {
|
|
42
|
+
return {
|
|
43
|
+
llmReachable: lastFailureReason === null,
|
|
44
|
+
lastFailureReason,
|
|
45
|
+
lastFailureAt: lastFailureAtMs === null ? null : new Date(lastFailureAtMs).toISOString(),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* ERROR-level, deduplicated failure event (issue #3140): the first failure of
|
|
51
|
+
* a given reason inside the window logs at error level; repeats inside the
|
|
52
|
+
* window log at debug so the backoff retry loop cannot flood the transcript
|
|
53
|
+
* while the outage stays visible. Returns whether the error line was emitted.
|
|
54
|
+
*/
|
|
55
|
+
export function emitExtractionLlmFailureError(reason: string, detail?: string, nowMs: number = Date.now()): boolean {
|
|
56
|
+
const suffix = detail ? `: ${detail}` : "";
|
|
57
|
+
const last = lastErrorEmitByReason.get(reason);
|
|
58
|
+
if (last !== undefined && nowMs - last < DEDUP_WINDOW_MS) {
|
|
59
|
+
log.debug(`extraction failure repeat suppressed for ${DEDUP_WINDOW_MS / 60_000}m: ${reason}${suffix}`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
lastErrorEmitByReason.set(reason, nowMs);
|
|
63
|
+
log.error(`extraction failed with no durable outputs (buffer retained for retry): ${reason}${suffix}`);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Test seam: restore pristine singleton state. */
|
|
68
|
+
export function resetExtractionLlmHealthForTests(): void {
|
|
69
|
+
recordExtractionLlmSuccess();
|
|
70
|
+
lastErrorEmitByReason.clear();
|
|
71
|
+
}
|
package/src/json-extract.ts
CHANGED
|
@@ -21,6 +21,12 @@ export function extractJsonCandidates(text: string): string[] {
|
|
|
21
21
|
const cleaned = stripCodeFences(trimmed);
|
|
22
22
|
const candidates: string[] = [];
|
|
23
23
|
|
|
24
|
+
// Verbatim balanced blocks FIRST: stripCodeFences rewrites every ``` pair, so
|
|
25
|
+
// a stripped candidate can parse while carrying altered content (e.g. a
|
|
26
|
+
// patch value that embeds an inner code block). A string-aware balanced scan
|
|
27
|
+
// of the unstripped text yields the payload unmodified; stripped candidates
|
|
28
|
+
// remain as later fallbacks for shapes the raw scan cannot cut.
|
|
29
|
+
candidates.push(...scanBalancedJsonBlocks(trimmed));
|
|
24
30
|
if (cleaned.length > 0) candidates.push(cleaned);
|
|
25
31
|
candidates.push(...scanBalancedJsonBlocks(cleaned));
|
|
26
32
|
|