@remnic/core 10.0.92 → 10.0.93
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 +331 -58
- package/dist/access-authorization-probe.js +2 -283
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.d.ts.map +1 -1
- package/dist/access-boundary.js +1 -282
- package/dist/access-cli.js +286 -302
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.js +3 -3
- package/dist/access-extraction-force-flush.js +331 -58
- package/dist/access-http-briefing.js +331 -59
- package/dist/access-http-query.js +331 -60
- package/dist/access-http.js +329 -111
- package/dist/access-identity-continuity-surface.js +331 -58
- package/dist/access-lcm-surface.js +331 -58
- package/dist/access-mcp-frames.d.ts +20 -6
- package/dist/access-mcp-frames.d.ts.map +1 -1
- package/dist/access-mcp-frames.js +1 -1
- package/dist/access-mcp.d.ts +5 -0
- package/dist/access-mcp.d.ts.map +1 -1
- package/dist/access-mcp.js +329 -86
- package/dist/access-namespace-preflight.js +2 -282
- package/dist/access-observe-transcript.js +3 -3
- package/dist/access-observe-write-category.js +6 -6
- package/dist/access-observe-write-surface.js +331 -58
- package/dist/access-offline-manifest.js +12 -11
- package/dist/access-operations-batch.js +329 -63
- package/dist/access-operations.js +329 -72
- package/dist/access-recall-concurrency.js +331 -58
- package/dist/access-recall-diagnostics.js +5 -4
- package/dist/access-recall-response.js +331 -58
- package/dist/access-recall-surface.js +331 -58
- package/dist/access-schema.js +2 -2
- package/dist/access-service.js +334 -61
- package/dist/access-surface-catalog.js +6 -283
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-context-transform.js +1 -1
- package/dist/active-memory-bridge.js +3 -2
- package/dist/active-recall.js +35 -36
- package/dist/active-recall.js.map +1 -1
- package/dist/activity/journal-extract.d.ts +13 -1
- package/dist/activity/journal-extract.d.ts.map +1 -1
- package/dist/{auto-sync-3AZW7MXI.js → auto-sync-BO5YZKM6.js} +13 -15
- package/dist/{auto-sync-3AZW7MXI.js.map → auto-sync-BO5YZKM6.js.map} +1 -1
- package/dist/briefing.js +19 -19
- package/dist/buffer.js +16 -16
- package/dist/calibration.js +10 -10
- package/dist/{capsule-merge-2IIXFXN5.js → capsule-merge-6IDAYTKG.js} +6 -6
- package/dist/causal-consolidation.js +22 -22
- package/dist/{chunk-ATPWZ4F7.js → chunk-2LMA22UU.js} +5 -5
- package/dist/{chunk-RGUUBAT5.js → chunk-2WCLTWV2.js} +3 -3
- package/dist/{chunk-BQZEGOZ4.js → chunk-2YODOG2O.js} +2 -2
- package/dist/{chunk-WYG5UNDY.js → chunk-3AUDELZL.js} +2 -2
- package/dist/{chunk-N4KAE2NB.js → chunk-3CYOCE6U.js} +3 -3
- package/dist/{chunk-MB6ZJDN6.js → chunk-3FBAEPCU.js} +2 -2
- package/dist/{chunk-PIITB2YZ.js → chunk-3ZS3NUEI.js} +4 -4
- package/dist/{chunk-ANZZLRUX.js → chunk-4IVRQMZW.js} +2 -2
- package/dist/{chunk-63E37JCN.js → chunk-4RAJT5L2.js} +4 -4
- package/dist/{chunk-45VUHDV2.js → chunk-5CO5G4MN.js} +2 -2
- package/dist/{chunk-BRLXOZLM.js → chunk-5YT2IIFX.js} +729 -17
- package/dist/chunk-5YT2IIFX.js.map +1 -0
- package/dist/{chunk-E4IRW2FG.js → chunk-622V2AZA.js} +233 -40
- package/dist/chunk-622V2AZA.js.map +1 -0
- package/dist/{chunk-LUJCQQ3L.js → chunk-65HWAQ4F.js} +2 -2
- package/dist/{chunk-T67LUYCR.js → chunk-77NTFICT.js} +5 -5
- package/dist/chunk-A2WFUMUN.js +86 -0
- package/dist/chunk-A2WFUMUN.js.map +1 -0
- package/dist/{chunk-UE6RRXMF.js → chunk-A4F7OIC5.js} +2 -2
- package/dist/chunk-AHA5VQGP.js +117 -0
- package/dist/chunk-AHA5VQGP.js.map +1 -0
- package/dist/{chunk-SUSW74WU.js → chunk-ATG2PQ4V.js} +56 -5
- package/dist/chunk-ATG2PQ4V.js.map +1 -0
- package/dist/{chunk-U6UNF5ZS.js → chunk-B33YWUJV.js} +3 -3
- package/dist/{chunk-RRJBDXAQ.js → chunk-B377OKUP.js} +2 -2
- package/dist/{chunk-IUKYFTGW.js → chunk-B3DR2JBZ.js} +140 -125
- package/dist/chunk-B3DR2JBZ.js.map +1 -0
- package/dist/{chunk-ETZLQOUR.js → chunk-BGHCSLE5.js} +2 -2
- package/dist/{chunk-YYOMGOHB.js → chunk-BOK6BN5M.js} +2 -2
- package/dist/{chunk-OH7QLI6W.js → chunk-BPPUH5JM.js} +5 -5
- package/dist/{chunk-4J363OAA.js → chunk-BTI4C4MA.js} +2 -2
- package/dist/{chunk-O6ZSWHVR.js → chunk-C2KE5DMD.js} +4 -4
- package/dist/{chunk-47F6WWLQ.js → chunk-C73N66PR.js} +2 -2
- package/dist/{chunk-E3A7ELSG.js → chunk-CGJYL633.js} +2 -2
- package/dist/{chunk-WRT7GJ2D.js → chunk-CIWMTNHW.js} +4 -4
- package/dist/{chunk-O55EKIEC.js → chunk-D3ZKHLN2.js} +2 -2
- package/dist/{chunk-CD4BCCNP.js → chunk-DJ7WVEWX.js} +2 -2
- package/dist/{chunk-PWXPRVLK.js → chunk-EFL24KOI.js} +2 -2
- package/dist/{chunk-4CJTQHEG.js → chunk-ESLOLCD4.js} +3 -3
- package/dist/{chunk-6F42QONS.js → chunk-ETBVCRVL.js} +4 -4
- package/dist/{chunk-VILPVT4S.js → chunk-EZ47HOJ6.js} +4 -4
- package/dist/{chunk-EFYN5I7Z.js → chunk-F4QFU5IZ.js} +2 -2
- package/dist/{chunk-T6OZW2WR.js → chunk-FA7SLQ5X.js} +7 -1
- package/dist/chunk-FA7SLQ5X.js.map +1 -0
- package/dist/{chunk-7W3RXOIG.js → chunk-FMKILENU.js} +14 -14
- package/dist/{chunk-YLXETSAH.js → chunk-GCZJ7XRI.js} +2 -2
- package/dist/{chunk-5HFXGXE5.js → chunk-GQXOBKPL.js} +7 -9
- package/dist/{chunk-5HFXGXE5.js.map → chunk-GQXOBKPL.js.map} +1 -1
- package/dist/{chunk-UDKWFDV2.js → chunk-HLUHEMN4.js} +27 -3
- package/dist/chunk-HLUHEMN4.js.map +1 -0
- package/dist/{chunk-QFEDITXM.js → chunk-JNDMIAJ4.js} +2 -2
- package/dist/chunk-JTSSSR4L.js +359 -0
- package/dist/chunk-JTSSSR4L.js.map +1 -0
- package/dist/{chunk-5KBIOI4G.js → chunk-JVBAPAWZ.js} +217 -13
- package/dist/chunk-JVBAPAWZ.js.map +1 -0
- package/dist/{chunk-E5PPE4VA.js → chunk-K2VRKCPA.js} +2 -2
- package/dist/{chunk-MKRYQROF.js → chunk-LGEHAQHK.js} +4 -4
- package/dist/{chunk-7HTUYFR3.js → chunk-LLPMMMWG.js} +27 -10
- package/dist/chunk-LLPMMMWG.js.map +1 -0
- package/dist/{chunk-XYTQJD7F.js → chunk-LMJVP5CM.js} +2 -2
- package/dist/{chunk-FQKBJJYB.js → chunk-LPV72KRQ.js} +2 -2
- package/dist/{chunk-2LCRG2YV.js → chunk-M2XR6B6V.js} +2 -2
- package/dist/{chunk-P4WNBSTW.js → chunk-MH632KPO.js} +2 -2
- package/dist/{chunk-EQ4U73UH.js → chunk-MLAMXQAW.js} +14 -2
- package/dist/chunk-MLAMXQAW.js.map +1 -0
- package/dist/{chunk-ZU5JDLOH.js → chunk-MMRTPH3J.js} +2 -2
- package/dist/{chunk-PMJFRDJI.js → chunk-MUX7IL77.js} +2 -2
- package/dist/{chunk-HF3BKUVV.js → chunk-NAQ4A5IX.js} +7 -7
- package/dist/{chunk-TVIVY7KU.js → chunk-NED7IR7B.js} +4 -4
- package/dist/{chunk-6F5MJFXY.js → chunk-NHVAX7MH.js} +2 -2
- package/dist/{chunk-TG3R2SID.js → chunk-NMGHGTVR.js} +5 -5
- package/dist/{chunk-YCNIGRRF.js → chunk-NS7U4ZYU.js} +13 -117
- package/dist/chunk-NS7U4ZYU.js.map +1 -0
- package/dist/{chunk-NGHDW6UU.js → chunk-NUQEREIP.js} +2 -2
- package/dist/{chunk-5IIQU4JZ.js → chunk-OB72KNMF.js} +4 -4
- package/dist/{chunk-JV4S6VDH.js → chunk-OJXJWNFY.js} +2 -2
- package/dist/{chunk-QJPJXCS4.js → chunk-P3WRNWWM.js} +157 -4
- package/dist/chunk-P3WRNWWM.js.map +1 -0
- package/dist/{chunk-E6M225BJ.js → chunk-PC5DRBGD.js} +18 -8
- package/dist/chunk-PC5DRBGD.js.map +1 -0
- package/dist/{chunk-Z7JIZ42P.js → chunk-PRAUN6PN.js} +3 -3
- package/dist/{chunk-RWGRSKN5.js → chunk-PT4DZDJA.js} +2 -2
- package/dist/{chunk-7EPY3IQL.js → chunk-PZJQ46DH.js} +2 -2
- package/dist/{chunk-MXHMNKWG.js → chunk-QDESY76F.js} +3 -3
- package/dist/{chunk-I6QGI65T.js → chunk-QRMG4HK4.js} +3 -3
- package/dist/{chunk-ZU3NJNO7.js → chunk-QSF5SJAM.js} +4 -4
- package/dist/{chunk-5IYOKDM4.js → chunk-R23JWJD3.js} +5 -5
- package/dist/{chunk-RHQLIHMB.js → chunk-R4BBGPI2.js} +218 -218
- package/dist/chunk-R4BBGPI2.js.map +1 -0
- package/dist/{chunk-7GLJMDEQ.js → chunk-RM2YUFH6.js} +6 -6
- package/dist/{chunk-IPT2OY4B.js → chunk-RQFXZYTF.js} +35 -33
- package/dist/chunk-RQFXZYTF.js.map +1 -0
- package/dist/{chunk-AP6NCP3V.js → chunk-S3S72HRM.js} +8 -6
- package/dist/chunk-S3S72HRM.js.map +1 -0
- package/dist/{chunk-RIMK327Y.js → chunk-SGQPR5SX.js} +7 -7
- package/dist/{chunk-ZGFLIK5H.js → chunk-SPAKBUJY.js} +2 -2
- package/dist/{chunk-ER7XANP3.js → chunk-SQ3GEIEL.js} +4 -4
- package/dist/{chunk-BLJT6JP2.js → chunk-SVT5IA2H.js} +2 -2
- package/dist/{chunk-HVX4LPNY.js → chunk-T4KY6UYF.js} +5 -3
- package/dist/{chunk-HVX4LPNY.js.map → chunk-T4KY6UYF.js.map} +1 -1
- package/dist/{chunk-GVGAO3WV.js → chunk-TJ4F6H4H.js} +3 -3
- package/dist/{chunk-R6HMAIKM.js → chunk-TOSPNM4T.js} +2 -2
- package/dist/{chunk-HH5LY6RR.js → chunk-TRQE3RYR.js} +5 -5
- package/dist/{chunk-RMXS4I66.js → chunk-TVDJEUV5.js} +2 -2
- package/dist/{chunk-PLCYKAWH.js → chunk-TYRWSE4B.js} +2 -2
- package/dist/{chunk-B4QYQFGY.js → chunk-UBEKET2V.js} +383 -36
- package/dist/chunk-UBEKET2V.js.map +1 -0
- package/dist/{chunk-IQWUACVG.js → chunk-UHMHJX5G.js} +3 -3
- package/dist/{chunk-YODUDA6R.js → chunk-UN4RVUG2.js} +22 -22
- package/dist/{chunk-E6Z4FBX6.js → chunk-V2QSNFCT.js} +2 -2
- package/dist/{chunk-NBH6K6G5.js → chunk-V65H7MI6.js} +345 -16
- package/dist/chunk-V65H7MI6.js.map +1 -0
- package/dist/chunk-W54Z2MOP.js +4500 -0
- package/dist/chunk-W54Z2MOP.js.map +1 -0
- package/dist/{chunk-E7YZXWIS.js → chunk-WKSQTMU7.js} +2 -2
- package/dist/{chunk-RIZ5NUNN.js → chunk-WQ4XKIOX.js} +4 -4
- package/dist/{chunk-FSO3G4JJ.js → chunk-WQURALGV.js} +4995 -2747
- package/dist/chunk-WQURALGV.js.map +1 -0
- package/dist/{chunk-QEYTRFR3.js → chunk-X26YOFGS.js} +2 -2
- package/dist/{chunk-ITHZFHST.js → chunk-X2BVB74H.js} +2 -2
- package/dist/{chunk-XYKBWTWI.js → chunk-XIYLWEXC.js} +2 -2
- package/dist/{chunk-IBJ6U753.js → chunk-XKZBZOKV.js} +4 -4
- package/dist/{chunk-65AUYLU3.js → chunk-XPXHQI6N.js} +2 -2
- package/dist/{chunk-EXHXJETE.js → chunk-XRDYRWZI.js} +2 -2
- package/dist/chunk-XRWWCW4H.js +102 -0
- package/dist/chunk-XRWWCW4H.js.map +1 -0
- package/dist/chunk-YKJBSMMP.js +67137 -0
- package/dist/chunk-YKJBSMMP.js.map +1 -0
- package/dist/{chunk-2JSDPUKD.js → chunk-YMFPFXEX.js} +2 -2
- package/dist/{chunk-645WOU5I.js → chunk-ZDZJNXL6.js} +4 -4
- package/dist/{chunk-6TCXIRP5.js → chunk-ZKE4CO6B.js} +4 -4
- package/dist/{chunk-FNXQZGUS.js → chunk-ZSE774RV.js} +2 -2
- package/dist/cli.js +302 -184
- package/dist/coding/export-okf-codegraph.js +28 -28
- package/dist/compounding/engine.js +22 -23
- package/dist/config.js +35 -36
- package/dist/connectors/codex-materialize-runner.js +19 -19
- package/dist/connectors/index.js +20 -20
- package/dist/connectors/live/google-drive.d.ts +2 -0
- package/dist/connectors/live/google-drive.d.ts.map +1 -1
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.js +5 -5
- package/dist/contradiction/index.js +7 -6
- package/dist/{contradiction-scan-P2MHYFVG.js → contradiction-scan-IBPYGQZF.js} +4 -3
- package/dist/convergence-refresh.js +21 -21
- package/dist/corpus-watermark.js +22 -22
- package/dist/deep-recall-http-glue.js +2 -2
- package/dist/deep-recall-policy-llm.js +4 -4
- package/dist/direct-answer-wiring.js +2 -2
- package/dist/embedding-fallback.js +1 -1
- package/dist/entity-retrieval.js +20 -20
- package/dist/eval-trajectory-store.js +2 -2
- package/dist/eval-trajectory.js +2 -2
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.d.ts +3 -2
- package/dist/explicit-capture.d.ts.map +1 -1
- package/dist/explicit-capture.js +530 -6
- package/dist/explicit-cue-recall.js +2 -2
- package/dist/external-wiki-access.js +2 -283
- package/dist/extraction-faithfulness.js +4 -4
- package/dist/extraction-judge.js +5 -5
- package/dist/extraction-normalization.js +1 -1
- package/dist/extraction-quote-grounding.d.ts.map +1 -1
- package/dist/extraction-quote-grounding.js +1 -1
- package/dist/extraction-source-grounding-rules.js +2 -2
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.js +42 -43
- package/dist/fallback-llm-codex-subscription.js +2 -2
- package/dist/fallback-llm.js +3 -3
- package/dist/{first-start-migration-QFI7B5SH.js → first-start-migration-FILJH3RO.js} +6 -5
- package/dist/{first-start-migration-QFI7B5SH.js.map → first-start-migration-FILJH3RO.js.map} +1 -1
- package/dist/focused-list-recall.js +2 -2
- package/dist/harmonic-retrieval.js +19 -19
- package/dist/index.js +772 -793
- package/dist/lcm/index.js +2 -2
- package/dist/live-connectors-runner.js +2 -3
- package/dist/local-llm-config.js +2 -2
- package/dist/local-llm-helpers.d.ts +4 -0
- package/dist/local-llm-helpers.d.ts.map +1 -1
- package/dist/local-llm-helpers.js +3 -1
- package/dist/local-llm.d.ts +4 -1
- package/dist/local-llm.d.ts.map +1 -1
- package/dist/local-llm.js +4 -4
- package/dist/location/index.js +26 -28
- package/dist/maintenance/memory-governance.js +19 -19
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +19 -19
- package/dist/maintenance/rebuild-memory-projection-runner.js +21 -21
- package/dist/maintenance/rebuild-memory-projection.js +20 -20
- package/dist/memory-browse-operations.js +2 -283
- package/dist/memory-browse.js +8 -7
- package/dist/memory-controller.js +2 -2
- package/dist/memory-subject.js +2 -2
- package/dist/namespaces/migrate.js +20 -20
- package/dist/namespaces/search.js +5 -5
- package/dist/namespaces/storage.js +19 -19
- package/dist/offline-sync-changeset-generations.js +1 -1
- package/dist/offline-sync-embedding-generation.js +1 -1
- package/dist/offline-sync-impression-drain.js +6 -6
- package/dist/offline-sync-snapshot-stream.js +1 -1
- package/dist/offline-sync.js +1 -1
- package/dist/operator-doctor-corpus.js +23 -23
- package/dist/operator-toolkit.js +57 -60
- package/dist/orchestration/backlog-scheduler.js +277 -293
- package/dist/orchestration/backlog-scheduler.js.map +1 -1
- package/dist/orchestration/buffer-drain-runner.js +277 -293
- package/dist/orchestration/extraction-run.d.ts +4 -0
- package/dist/orchestration/extraction-run.d.ts.map +1 -1
- package/dist/orchestration/fact-dedup-window.d.ts +1 -1
- package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +38 -41
- package/dist/orchestrator.d.ts +9 -11
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +289 -306
- package/dist/page-versioning.js +3 -3
- package/dist/qmd-daemon-session.js +2 -2
- package/dist/qmd.js +3 -3
- package/dist/recall-explain-renderer.js +1 -1
- package/dist/recall-navigation-operations.js +2 -283
- package/dist/recall-navigation.js +331 -58
- package/dist/recall-planner-llm.js +3 -3
- package/dist/recall-state-view-admission.js +6 -6
- package/dist/recall-why-service.js +10 -9
- package/dist/recall-why.js +4 -3
- package/dist/recall-xray-cli.js +2 -2
- package/dist/recall-xray-renderer.js +1 -1
- package/dist/reconcile/manifest.js +3 -3
- package/dist/{resolution-N7H3F2E4.js → resolution-OVVIF7ZY.js} +8 -7
- package/dist/response-guidance-recall.js +2 -2
- package/dist/resume-bundles.js +39 -40
- package/dist/salvage-envelope.js +4 -4
- package/dist/search/factory.js +4 -4
- package/dist/search/index.js +4 -4
- package/dist/semantic-consolidation.js +22 -22
- package/dist/semantic-rule-promotion.js +19 -19
- package/dist/semantic-rule-verifier.js +19 -19
- package/dist/shared-context/manager.js +2 -3
- package/dist/source-agent-qualifier.js +31 -32
- package/dist/standup/build.js +19 -19
- package/dist/storage/content-hash-index.d.ts +10 -0
- package/dist/storage/content-hash-index.d.ts.map +1 -1
- package/dist/storage.js +18 -18
- package/dist/subject-config.js +3 -3
- package/dist/summarizer.d.ts +4 -1
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +6 -6
- package/dist/support-passport/index.js +53 -297
- package/dist/targeted-fact-recall.js +2 -2
- package/dist/tier-routing.js +3 -2
- package/dist/{tier-stats-FQGQSFET.js → tier-stats-FXEUJN73.js} +6 -5
- package/dist/{tier-stats-FQGQSFET.js.map → tier-stats-FXEUJN73.js.map} +1 -1
- package/dist/transcript.js +2 -2
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/export-okf.js +19 -19
- package/dist/verified-recall.js +19 -19
- package/dist/wearables/memory-gen.d.ts +5 -1
- package/dist/wearables/memory-gen.d.ts.map +1 -1
- package/dist/wearables/service.d.ts.map +1 -1
- package/dist/who-knows-cli.js +20 -20
- package/dist/who-knows.js +19 -19
- package/dist/write-envelope.js +3 -3
- package/package.json +2 -2
- package/src/access-boundary.ts +5 -1
- package/src/access-category-alias.test.ts +5 -0
- package/src/access-mcp-frames.test.ts +60 -5
- package/src/access-mcp-frames.ts +72 -18
- package/src/access-mcp.test.ts +66 -2
- package/src/access-mcp.ts +19 -13
- package/src/access-service-coding-write.test.ts +7 -0
- package/src/activity/journal-extract-storage.test.ts +31 -0
- package/src/activity/journal-extract.test.ts +1 -0
- package/src/activity/journal-extract.ts +24 -3
- package/src/connectors/live/google-drive.test.ts +39 -0
- package/src/connectors/live/google-drive.ts +41 -12
- package/src/explicit-capture.test.ts +109 -17
- package/src/explicit-capture.ts +90 -37
- package/src/extraction-quote-grounding.test.ts +20 -0
- package/src/extraction-quote-grounding.ts +21 -11
- package/src/fact-dedup-window.test.ts +37 -0
- package/src/local-llm-helpers.ts +12 -0
- package/src/local-llm-shutdown.test.ts +194 -0
- package/src/local-llm.ts +32 -30
- package/src/orchestration/extraction-run.ts +12 -3
- package/src/orchestration/fact-dedup-window.ts +5 -1
- package/src/orchestrator-connector-dedup.test.ts +9 -5
- package/src/orchestrator.ts +47 -50
- package/src/storage/content-hash-index.ts +27 -2
- package/src/storage-fact-hash-publish-failure.test.ts +87 -0
- package/src/summarizer-shutdown.test.ts +195 -0
- package/src/summarizer.ts +15 -2
- package/src/wearables/memory-gen.test.ts +20 -0
- package/src/wearables/memory-gen.ts +14 -7
- package/src/wearables/service.ts +14 -1
- package/src/wearables/storage-io.test.ts +25 -0
- package/dist/chunk-2FGLEF7C.js +0 -61
- package/dist/chunk-2FGLEF7C.js.map +0 -1
- package/dist/chunk-3RLK65ZK.js +0 -203
- package/dist/chunk-3RLK65ZK.js.map +0 -1
- package/dist/chunk-4WR2OD7K.js +0 -43
- package/dist/chunk-4WR2OD7K.js.map +0 -1
- package/dist/chunk-5KBIOI4G.js.map +0 -1
- package/dist/chunk-5M5M22GW.js +0 -346
- package/dist/chunk-5M5M22GW.js.map +0 -1
- package/dist/chunk-7HTUYFR3.js.map +0 -1
- package/dist/chunk-AP6NCP3V.js.map +0 -1
- package/dist/chunk-B4QYQFGY.js.map +0 -1
- package/dist/chunk-BJRTMNCB.js +0 -1919
- package/dist/chunk-BJRTMNCB.js.map +0 -1
- package/dist/chunk-BRLXOZLM.js.map +0 -1
- package/dist/chunk-BSACVS6R.js +0 -621
- package/dist/chunk-BSACVS6R.js.map +0 -1
- package/dist/chunk-DKAA2IHQ.js +0 -3644
- package/dist/chunk-DKAA2IHQ.js.map +0 -1
- package/dist/chunk-E4IRW2FG.js.map +0 -1
- package/dist/chunk-E6M225BJ.js.map +0 -1
- package/dist/chunk-EQ4U73UH.js.map +0 -1
- package/dist/chunk-ETSJD5IV.js +0 -361
- package/dist/chunk-ETSJD5IV.js.map +0 -1
- package/dist/chunk-F55YQP7N.js +0 -336
- package/dist/chunk-F55YQP7N.js.map +0 -1
- package/dist/chunk-FHGWKODN.js +0 -2300
- package/dist/chunk-FHGWKODN.js.map +0 -1
- package/dist/chunk-FLRP63GU.js +0 -32219
- package/dist/chunk-FLRP63GU.js.map +0 -1
- package/dist/chunk-FSO3G4JJ.js.map +0 -1
- package/dist/chunk-ID6JGIMD.js +0 -25
- package/dist/chunk-ID6JGIMD.js.map +0 -1
- package/dist/chunk-IGLJNHOX.js +0 -62
- package/dist/chunk-IGLJNHOX.js.map +0 -1
- package/dist/chunk-IPT2OY4B.js.map +0 -1
- package/dist/chunk-IUKYFTGW.js.map +0 -1
- package/dist/chunk-NBH6K6G5.js.map +0 -1
- package/dist/chunk-NTRUKAAE.js +0 -223
- package/dist/chunk-NTRUKAAE.js.map +0 -1
- package/dist/chunk-OGXXGHW6.js +0 -223
- package/dist/chunk-OGXXGHW6.js.map +0 -1
- package/dist/chunk-PPCLZ4JU.js +0 -28
- package/dist/chunk-PPCLZ4JU.js.map +0 -1
- package/dist/chunk-QJPJXCS4.js.map +0 -1
- package/dist/chunk-R2LBF4HH.js +0 -729
- package/dist/chunk-R2LBF4HH.js.map +0 -1
- package/dist/chunk-RHQLIHMB.js.map +0 -1
- package/dist/chunk-RRTVGD3J.js +0 -792
- package/dist/chunk-RRTVGD3J.js.map +0 -1
- package/dist/chunk-SUSW74WU.js.map +0 -1
- package/dist/chunk-T6OZW2WR.js.map +0 -1
- package/dist/chunk-UDKWFDV2.js.map +0 -1
- package/dist/chunk-V7IZKJI3.js +0 -22
- package/dist/chunk-V7IZKJI3.js.map +0 -1
- package/dist/chunk-X562NQFA.js +0 -10144
- package/dist/chunk-X562NQFA.js.map +0 -1
- package/dist/chunk-XDSCJOH7.js +0 -3991
- package/dist/chunk-XDSCJOH7.js.map +0 -1
- package/dist/chunk-YCNIGRRF.js.map +0 -1
- package/dist/chunk-ZTTCDQ4I.js +0 -18796
- package/dist/chunk-ZTTCDQ4I.js.map +0 -1
- /package/dist/{capsule-merge-2IIXFXN5.js.map → capsule-merge-6IDAYTKG.js.map} +0 -0
- /package/dist/{chunk-ATPWZ4F7.js.map → chunk-2LMA22UU.js.map} +0 -0
- /package/dist/{chunk-RGUUBAT5.js.map → chunk-2WCLTWV2.js.map} +0 -0
- /package/dist/{chunk-BQZEGOZ4.js.map → chunk-2YODOG2O.js.map} +0 -0
- /package/dist/{chunk-WYG5UNDY.js.map → chunk-3AUDELZL.js.map} +0 -0
- /package/dist/{chunk-N4KAE2NB.js.map → chunk-3CYOCE6U.js.map} +0 -0
- /package/dist/{chunk-MB6ZJDN6.js.map → chunk-3FBAEPCU.js.map} +0 -0
- /package/dist/{chunk-PIITB2YZ.js.map → chunk-3ZS3NUEI.js.map} +0 -0
- /package/dist/{chunk-ANZZLRUX.js.map → chunk-4IVRQMZW.js.map} +0 -0
- /package/dist/{chunk-63E37JCN.js.map → chunk-4RAJT5L2.js.map} +0 -0
- /package/dist/{chunk-45VUHDV2.js.map → chunk-5CO5G4MN.js.map} +0 -0
- /package/dist/{chunk-LUJCQQ3L.js.map → chunk-65HWAQ4F.js.map} +0 -0
- /package/dist/{chunk-T67LUYCR.js.map → chunk-77NTFICT.js.map} +0 -0
- /package/dist/{chunk-UE6RRXMF.js.map → chunk-A4F7OIC5.js.map} +0 -0
- /package/dist/{chunk-U6UNF5ZS.js.map → chunk-B33YWUJV.js.map} +0 -0
- /package/dist/{chunk-RRJBDXAQ.js.map → chunk-B377OKUP.js.map} +0 -0
- /package/dist/{chunk-ETZLQOUR.js.map → chunk-BGHCSLE5.js.map} +0 -0
- /package/dist/{chunk-YYOMGOHB.js.map → chunk-BOK6BN5M.js.map} +0 -0
- /package/dist/{chunk-OH7QLI6W.js.map → chunk-BPPUH5JM.js.map} +0 -0
- /package/dist/{chunk-4J363OAA.js.map → chunk-BTI4C4MA.js.map} +0 -0
- /package/dist/{chunk-O6ZSWHVR.js.map → chunk-C2KE5DMD.js.map} +0 -0
- /package/dist/{chunk-47F6WWLQ.js.map → chunk-C73N66PR.js.map} +0 -0
- /package/dist/{chunk-E3A7ELSG.js.map → chunk-CGJYL633.js.map} +0 -0
- /package/dist/{chunk-WRT7GJ2D.js.map → chunk-CIWMTNHW.js.map} +0 -0
- /package/dist/{chunk-O55EKIEC.js.map → chunk-D3ZKHLN2.js.map} +0 -0
- /package/dist/{chunk-CD4BCCNP.js.map → chunk-DJ7WVEWX.js.map} +0 -0
- /package/dist/{chunk-PWXPRVLK.js.map → chunk-EFL24KOI.js.map} +0 -0
- /package/dist/{chunk-4CJTQHEG.js.map → chunk-ESLOLCD4.js.map} +0 -0
- /package/dist/{chunk-6F42QONS.js.map → chunk-ETBVCRVL.js.map} +0 -0
- /package/dist/{chunk-VILPVT4S.js.map → chunk-EZ47HOJ6.js.map} +0 -0
- /package/dist/{chunk-EFYN5I7Z.js.map → chunk-F4QFU5IZ.js.map} +0 -0
- /package/dist/{chunk-7W3RXOIG.js.map → chunk-FMKILENU.js.map} +0 -0
- /package/dist/{chunk-YLXETSAH.js.map → chunk-GCZJ7XRI.js.map} +0 -0
- /package/dist/{chunk-QFEDITXM.js.map → chunk-JNDMIAJ4.js.map} +0 -0
- /package/dist/{chunk-E5PPE4VA.js.map → chunk-K2VRKCPA.js.map} +0 -0
- /package/dist/{chunk-MKRYQROF.js.map → chunk-LGEHAQHK.js.map} +0 -0
- /package/dist/{chunk-XYTQJD7F.js.map → chunk-LMJVP5CM.js.map} +0 -0
- /package/dist/{chunk-FQKBJJYB.js.map → chunk-LPV72KRQ.js.map} +0 -0
- /package/dist/{chunk-2LCRG2YV.js.map → chunk-M2XR6B6V.js.map} +0 -0
- /package/dist/{chunk-P4WNBSTW.js.map → chunk-MH632KPO.js.map} +0 -0
- /package/dist/{chunk-ZU5JDLOH.js.map → chunk-MMRTPH3J.js.map} +0 -0
- /package/dist/{chunk-PMJFRDJI.js.map → chunk-MUX7IL77.js.map} +0 -0
- /package/dist/{chunk-HF3BKUVV.js.map → chunk-NAQ4A5IX.js.map} +0 -0
- /package/dist/{chunk-TVIVY7KU.js.map → chunk-NED7IR7B.js.map} +0 -0
- /package/dist/{chunk-6F5MJFXY.js.map → chunk-NHVAX7MH.js.map} +0 -0
- /package/dist/{chunk-TG3R2SID.js.map → chunk-NMGHGTVR.js.map} +0 -0
- /package/dist/{chunk-NGHDW6UU.js.map → chunk-NUQEREIP.js.map} +0 -0
- /package/dist/{chunk-5IIQU4JZ.js.map → chunk-OB72KNMF.js.map} +0 -0
- /package/dist/{chunk-JV4S6VDH.js.map → chunk-OJXJWNFY.js.map} +0 -0
- /package/dist/{chunk-Z7JIZ42P.js.map → chunk-PRAUN6PN.js.map} +0 -0
- /package/dist/{chunk-RWGRSKN5.js.map → chunk-PT4DZDJA.js.map} +0 -0
- /package/dist/{chunk-7EPY3IQL.js.map → chunk-PZJQ46DH.js.map} +0 -0
- /package/dist/{chunk-MXHMNKWG.js.map → chunk-QDESY76F.js.map} +0 -0
- /package/dist/{chunk-I6QGI65T.js.map → chunk-QRMG4HK4.js.map} +0 -0
- /package/dist/{chunk-ZU3NJNO7.js.map → chunk-QSF5SJAM.js.map} +0 -0
- /package/dist/{chunk-5IYOKDM4.js.map → chunk-R23JWJD3.js.map} +0 -0
- /package/dist/{chunk-7GLJMDEQ.js.map → chunk-RM2YUFH6.js.map} +0 -0
- /package/dist/{chunk-RIMK327Y.js.map → chunk-SGQPR5SX.js.map} +0 -0
- /package/dist/{chunk-ZGFLIK5H.js.map → chunk-SPAKBUJY.js.map} +0 -0
- /package/dist/{chunk-ER7XANP3.js.map → chunk-SQ3GEIEL.js.map} +0 -0
- /package/dist/{chunk-BLJT6JP2.js.map → chunk-SVT5IA2H.js.map} +0 -0
- /package/dist/{chunk-GVGAO3WV.js.map → chunk-TJ4F6H4H.js.map} +0 -0
- /package/dist/{chunk-R6HMAIKM.js.map → chunk-TOSPNM4T.js.map} +0 -0
- /package/dist/{chunk-HH5LY6RR.js.map → chunk-TRQE3RYR.js.map} +0 -0
- /package/dist/{chunk-RMXS4I66.js.map → chunk-TVDJEUV5.js.map} +0 -0
- /package/dist/{chunk-PLCYKAWH.js.map → chunk-TYRWSE4B.js.map} +0 -0
- /package/dist/{chunk-IQWUACVG.js.map → chunk-UHMHJX5G.js.map} +0 -0
- /package/dist/{chunk-YODUDA6R.js.map → chunk-UN4RVUG2.js.map} +0 -0
- /package/dist/{chunk-E6Z4FBX6.js.map → chunk-V2QSNFCT.js.map} +0 -0
- /package/dist/{chunk-E7YZXWIS.js.map → chunk-WKSQTMU7.js.map} +0 -0
- /package/dist/{chunk-RIZ5NUNN.js.map → chunk-WQ4XKIOX.js.map} +0 -0
- /package/dist/{chunk-QEYTRFR3.js.map → chunk-X26YOFGS.js.map} +0 -0
- /package/dist/{chunk-ITHZFHST.js.map → chunk-X2BVB74H.js.map} +0 -0
- /package/dist/{chunk-XYKBWTWI.js.map → chunk-XIYLWEXC.js.map} +0 -0
- /package/dist/{chunk-IBJ6U753.js.map → chunk-XKZBZOKV.js.map} +0 -0
- /package/dist/{chunk-65AUYLU3.js.map → chunk-XPXHQI6N.js.map} +0 -0
- /package/dist/{chunk-EXHXJETE.js.map → chunk-XRDYRWZI.js.map} +0 -0
- /package/dist/{chunk-2JSDPUKD.js.map → chunk-YMFPFXEX.js.map} +0 -0
- /package/dist/{chunk-645WOU5I.js.map → chunk-ZDZJNXL6.js.map} +0 -0
- /package/dist/{chunk-6TCXIRP5.js.map → chunk-ZKE4CO6B.js.map} +0 -0
- /package/dist/{chunk-FNXQZGUS.js.map → chunk-ZSE774RV.js.map} +0 -0
- /package/dist/{contradiction-scan-P2MHYFVG.js.map → contradiction-scan-IBPYGQZF.js.map} +0 -0
- /package/dist/{resolution-N7H3F2E4.js.map → resolution-OVVIF7ZY.js.map} +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// Shutdown-signal behavioral tests for LocalLlmClient: a daemon stop must
|
|
2
|
+
// abort in-flight chat requests at once (the abort reaches the transport),
|
|
3
|
+
// must not start another attempt once aborted, and must not sit out the
|
|
4
|
+
// retry backoff after abort. Uses the globalThis.fetch seam from
|
|
5
|
+
// local-llm-qos.test.ts with a hanging transport so the real retry loop is
|
|
6
|
+
// exercised, not a mock of it.
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
|
|
10
|
+
import { parseConfig } from "./config.js";
|
|
11
|
+
import { LocalLlmClient } from "./local-llm.js";
|
|
12
|
+
import { initLogger, resetLogger, type LoggerBackend } from "./logger.js";
|
|
13
|
+
|
|
14
|
+
/** Captured log lines with the logger's "remnic: " prefix stripped. */
|
|
15
|
+
interface CapturedLog {
|
|
16
|
+
lines: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createConfig(memoryDir: string) {
|
|
20
|
+
return parseConfig({
|
|
21
|
+
openaiApiKey: "sk-test",
|
|
22
|
+
memoryDir,
|
|
23
|
+
workspaceDir: `${memoryDir}/workspace`,
|
|
24
|
+
localLlmEnabled: true,
|
|
25
|
+
localLlmModel: "test-local-model",
|
|
26
|
+
localLlmUrl: "http://127.0.0.1:9",
|
|
27
|
+
// Mirror the production daemon: a 10-minute budget with 2 attempts, so
|
|
28
|
+
// an unhonored abort would outlive any sane test deadline.
|
|
29
|
+
localLlmTimeoutMs: 600_000,
|
|
30
|
+
localLlmRetry5xxCount: 1,
|
|
31
|
+
localLlmRetryBackoffMs: 30_000,
|
|
32
|
+
localLlmHeaders: {},
|
|
33
|
+
localLlm400TripThreshold: 3,
|
|
34
|
+
localLlm400CooldownMs: 60_000,
|
|
35
|
+
localLlmReasoningEffort: "none",
|
|
36
|
+
debug: false,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface HangingFetchSpec {
|
|
41
|
+
calls: { signal: AbortSignal | undefined }[];
|
|
42
|
+
sawAbort: boolean;
|
|
43
|
+
/** Reject with AbortError after delayMs (timeout simulation); default hangs forever. */
|
|
44
|
+
timeoutAfterMs?: number;
|
|
45
|
+
/** Resolves when the transport stub is entered (request is in flight). */
|
|
46
|
+
entered: Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function installHangingFetch(spec: HangingFetchSpec): () => void {
|
|
50
|
+
const { promise: entered, resolve: markEntered } = Promise.withResolvers<void>();
|
|
51
|
+
spec.entered = entered;
|
|
52
|
+
const originalFetch = globalThis.fetch;
|
|
53
|
+
// SAFETY: the transport passes a string URL and an init with a signal;
|
|
54
|
+
// this async stub matches that call shape exactly.
|
|
55
|
+
globalThis.fetch = (async (url: string, init?: { signal?: AbortSignal }) => {
|
|
56
|
+
void url;
|
|
57
|
+
const signal = init?.signal;
|
|
58
|
+
spec.calls.push({ signal });
|
|
59
|
+
markEntered();
|
|
60
|
+
const { promise, reject } = Promise.withResolvers<Response>();
|
|
61
|
+
// A real transport rejects with AbortError when its signal aborts
|
|
62
|
+
// (undici behavior); the stub must honor it the same way or the client
|
|
63
|
+
// loop cannot observe the abort.
|
|
64
|
+
const onAbort = (): void => {
|
|
65
|
+
spec.sawAbort = true;
|
|
66
|
+
reject(new DOMException("This operation was aborted", "AbortError"));
|
|
67
|
+
};
|
|
68
|
+
if (signal) {
|
|
69
|
+
if (signal.aborted) onAbort();
|
|
70
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
71
|
+
}
|
|
72
|
+
if (spec.timeoutAfterMs !== undefined) {
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
signal?.removeEventListener("abort", onAbort);
|
|
75
|
+
reject(new DOMException("This operation was aborted", "AbortError"));
|
|
76
|
+
}, spec.timeoutAfterMs);
|
|
77
|
+
timer.unref?.();
|
|
78
|
+
}
|
|
79
|
+
return await promise;
|
|
80
|
+
}) as typeof fetch;
|
|
81
|
+
return () => {
|
|
82
|
+
globalThis.fetch = originalFetch;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Polls a real runtime condition (a log line, a stub entry) instead of a
|
|
87
|
+
* guessed fixed sleep: the condition, not a duration, gates progress. */
|
|
88
|
+
async function waitFor(
|
|
89
|
+
predicate: () => boolean,
|
|
90
|
+
description: string,
|
|
91
|
+
timeoutMs = 5_000,
|
|
92
|
+
): Promise<void> {
|
|
93
|
+
const deadline = Date.now() + timeoutMs;
|
|
94
|
+
while (!predicate()) {
|
|
95
|
+
if (Date.now() > deadline) throw new Error(`condition not met: ${description}`);
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function withTestDeadline<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
|
101
|
+
const { promise: deadline, reject } = Promise.withResolvers<never>();
|
|
102
|
+
const timer = setTimeout(() => reject(new Error(`${label}: test deadline exceeded (${timeoutMs}ms)`)), timeoutMs);
|
|
103
|
+
timer.unref?.();
|
|
104
|
+
try {
|
|
105
|
+
return await Promise.race([promise, deadline]);
|
|
106
|
+
} finally {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function installRecordingLogger(): CapturedLog {
|
|
112
|
+
const lines: string[] = [];
|
|
113
|
+
const body = (msg: string) => msg.replace(/^remnic: /, "");
|
|
114
|
+
const backend: LoggerBackend = {
|
|
115
|
+
info: (msg) => lines.push(body(msg)),
|
|
116
|
+
warn: (msg) => lines.push(body(msg)),
|
|
117
|
+
error: (msg) => lines.push(body(msg)),
|
|
118
|
+
};
|
|
119
|
+
initLogger(backend, false, { timestamps: false });
|
|
120
|
+
return { lines };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function quietLogger(): void {
|
|
124
|
+
initLogger({ info() {}, warn() {}, error() {}, debug() {} }, false);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
test("aborting the client shutdown signal aborts an in-flight request and reaches the transport stub", async () => {
|
|
128
|
+
quietLogger();
|
|
129
|
+
const spec: HangingFetchSpec = { calls: [], sawAbort: false, entered: Promise.resolve() };
|
|
130
|
+
const restoreFetch = installHangingFetch(spec);
|
|
131
|
+
try {
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
const client = new LocalLlmClient(createConfig("/tmp/remnic-llm-shutdown-inflight"), undefined, controller.signal);
|
|
134
|
+
// SAFETY: availability is stubbed at the established field seam (same as
|
|
135
|
+
// local-llm-qos.test.ts) so the retry loop, not the network probe, runs.
|
|
136
|
+
const seams = client as any;
|
|
137
|
+
seams.isAvailable = true;
|
|
138
|
+
seams.lastHealthCheck = Date.now();
|
|
139
|
+
|
|
140
|
+
const pending = client.chatCompletion(
|
|
141
|
+
[{ role: "user", content: "shutdown abort in flight" }],
|
|
142
|
+
{ operation: "extraction" },
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
await spec.entered;
|
|
146
|
+
controller.abort();
|
|
147
|
+
|
|
148
|
+
const result = await withTestDeadline(pending, 2_000, "in-flight abort");
|
|
149
|
+
assert.equal(result, null);
|
|
150
|
+
assert.equal(spec.calls.length, 1, "no second attempt may start after abort");
|
|
151
|
+
assert.equal(spec.sawAbort, true, "the abort signal must reach the transport stub");
|
|
152
|
+
assert.equal(spec.calls[0]?.signal?.aborted, true);
|
|
153
|
+
} finally {
|
|
154
|
+
restoreFetch();
|
|
155
|
+
resetLogger();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("an abort during retry backoff ends the loop without sleeping it out or starting another attempt", async () => {
|
|
160
|
+
const { lines } = installRecordingLogger();
|
|
161
|
+
const spec: HangingFetchSpec = { calls: [], sawAbort: false, timeoutAfterMs: 20, entered: Promise.resolve() };
|
|
162
|
+
const restoreFetch = installHangingFetch(spec);
|
|
163
|
+
try {
|
|
164
|
+
const controller = new AbortController();
|
|
165
|
+
const client = new LocalLlmClient(createConfig("/tmp/remnic-llm-shutdown-backoff"), undefined, controller.signal);
|
|
166
|
+
// SAFETY: availability is stubbed at the established field seam (same as
|
|
167
|
+
// local-llm-qos.test.ts) so the retry loop, not the network probe, runs.
|
|
168
|
+
const seams = client as any;
|
|
169
|
+
seams.isAvailable = true;
|
|
170
|
+
seams.lastHealthCheck = Date.now();
|
|
171
|
+
|
|
172
|
+
const pending = client.chatCompletion(
|
|
173
|
+
[{ role: "user", content: "shutdown abort during backoff" }],
|
|
174
|
+
{ operation: "extraction" },
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Sync point: the retry loop is in its 30s backoff when the backoff
|
|
178
|
+
// warn line lands; abort there.
|
|
179
|
+
await waitFor(
|
|
180
|
+
() => lines.some((line) => line.includes("retrying after 30000ms")),
|
|
181
|
+
"attempt 1 to fail and backoff to start",
|
|
182
|
+
);
|
|
183
|
+
controller.abort();
|
|
184
|
+
|
|
185
|
+
const startedAtMs = Date.now();
|
|
186
|
+
const result = await withTestDeadline(pending, 2_000, "backoff abort");
|
|
187
|
+
assert.equal(result, null);
|
|
188
|
+
assert.ok(Date.now() - startedAtMs < 1_500, "backoff must not be slept out after abort");
|
|
189
|
+
assert.equal(spec.calls.length, 1, "no second attempt may start after abort");
|
|
190
|
+
} finally {
|
|
191
|
+
restoreFetch();
|
|
192
|
+
resetLogger();
|
|
193
|
+
}
|
|
194
|
+
});
|
package/src/local-llm.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type ProbeFetchResult,
|
|
21
21
|
normalizeBackendTripReason,
|
|
22
22
|
waitForRetryBackoff,
|
|
23
|
+
combineClientAbortSignals,
|
|
23
24
|
SingleFlightProbe,
|
|
24
25
|
type LocalLlmChatCompletionOptions,
|
|
25
26
|
type LocalLlmChatCompletionResult,
|
|
@@ -201,10 +202,14 @@ export class LocalLlmClient {
|
|
|
201
202
|
private static readonly HEALTH_CHECK_INTERVAL_MS = 60000; // 1 minute
|
|
202
203
|
private static readonly LMS_CACHE_INTERVAL_MS = 30000; // 30 seconds
|
|
203
204
|
private readonly chatTransport = new ChatTransport();
|
|
205
|
+
/** Orchestrator-owned stop signal: aborts every in-flight and queued
|
|
206
|
+
* request (and their retry backoffs) the moment teardown begins. */
|
|
207
|
+
private readonly shutdownSignal: AbortSignal | undefined;
|
|
204
208
|
|
|
205
|
-
constructor(config: PluginConfig, modelRegistry?: ModelRegistry) {
|
|
209
|
+
constructor(config: PluginConfig, modelRegistry?: ModelRegistry, shutdownSignal?: AbortSignal) {
|
|
206
210
|
this.config = config;
|
|
207
211
|
this.modelRegistry = modelRegistry;
|
|
212
|
+
this.shutdownSignal = shutdownSignal;
|
|
208
213
|
}
|
|
209
214
|
|
|
210
215
|
/**
|
|
@@ -829,7 +834,7 @@ export class LocalLlmClient {
|
|
|
829
834
|
|
|
830
835
|
private async runQueuedRequest(next: LocalLlmQueuedRequest): Promise<void> {
|
|
831
836
|
try {
|
|
832
|
-
if (next.options.signal?.aborted) {
|
|
837
|
+
if (combineClientAbortSignals(next.options.signal, this.shutdownSignal)?.aborted) {
|
|
833
838
|
next.resolve(null);
|
|
834
839
|
return;
|
|
835
840
|
}
|
|
@@ -879,8 +884,9 @@ export class LocalLlmClient {
|
|
|
879
884
|
}
|
|
880
885
|
|
|
881
886
|
try {
|
|
882
|
-
|
|
883
|
-
|
|
887
|
+
const signal = combineClientAbortSignals(options.signal, this.shutdownSignal);
|
|
888
|
+
if (signal?.aborted) return null;
|
|
889
|
+
const isAvailable = await this.checkAvailability(signal);
|
|
884
890
|
if (!isAvailable) {
|
|
885
891
|
log.debug(
|
|
886
892
|
`local LLM: checkAvailability returned false for ${this.config.localLlmUrl}`,
|
|
@@ -977,7 +983,7 @@ export class LocalLlmClient {
|
|
|
977
983
|
response = null;
|
|
978
984
|
responseBody = "";
|
|
979
985
|
// Wait out the shared endpoint backoff, then take a concurrency slot.
|
|
980
|
-
const gate = await rateLimit.beforeAttempt(
|
|
986
|
+
const gate = await rateLimit.beforeAttempt(signal);
|
|
981
987
|
if (gate.kind === "skip") {
|
|
982
988
|
log.warn(gate.reason === "queue"
|
|
983
989
|
? `local LLM concurrency queue exceeded the ${effectiveTimeoutMs}ms request budget; skipping request (op=${operation})`
|
|
@@ -989,24 +995,25 @@ export class LocalLlmClient {
|
|
|
989
995
|
if (gate.kind === "aborted") return null;
|
|
990
996
|
const attemptAbort = new AbortController();
|
|
991
997
|
const onCallerAbort = (): void => {
|
|
992
|
-
attemptAbort.abort(
|
|
998
|
+
attemptAbort.abort(signal?.reason);
|
|
993
999
|
};
|
|
994
|
-
if (
|
|
995
|
-
if (
|
|
1000
|
+
if (signal) {
|
|
1001
|
+
if (signal.aborted) {
|
|
996
1002
|
onCallerAbort();
|
|
997
1003
|
} else {
|
|
998
|
-
|
|
1004
|
+
signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
999
1005
|
}
|
|
1000
1006
|
}
|
|
1001
1007
|
const attemptTimeout = setTimeout(() => attemptAbort.abort(), effectiveTimeoutMs);
|
|
1008
|
+
const postAttempt = (bodyJson: string): Promise<Response> => this.chatTransport.post({
|
|
1009
|
+
url: chatUrl,
|
|
1010
|
+
headers: this.buildRequestHeaders({ "Content-Type": "application/json" }),
|
|
1011
|
+
body: bodyJson,
|
|
1012
|
+
signal: attemptAbort.signal,
|
|
1013
|
+
budgetMs: this.config.localLlmTimeoutMs,
|
|
1014
|
+
});
|
|
1002
1015
|
try {
|
|
1003
|
-
response = await
|
|
1004
|
-
url: chatUrl,
|
|
1005
|
-
headers: this.buildRequestHeaders({ "Content-Type": "application/json" }),
|
|
1006
|
-
body: requestBodyJson,
|
|
1007
|
-
signal: attemptAbort.signal,
|
|
1008
|
-
budgetMs: this.config.localLlmTimeoutMs,
|
|
1009
|
-
});
|
|
1016
|
+
response = await postAttempt(requestBodyJson);
|
|
1010
1017
|
responseBody = await response.text();
|
|
1011
1018
|
if (
|
|
1012
1019
|
response.status === 400 &&
|
|
@@ -1019,18 +1026,12 @@ export class LocalLlmClient {
|
|
|
1019
1026
|
requestBodyJson = JSON.stringify(requestBody);
|
|
1020
1027
|
log.debug("local LLM rejected native JSON schema; retrying without it");
|
|
1021
1028
|
response = null;
|
|
1022
|
-
response = await
|
|
1023
|
-
url: chatUrl,
|
|
1024
|
-
headers: this.buildRequestHeaders({ "Content-Type": "application/json" }),
|
|
1025
|
-
body: requestBodyJson,
|
|
1026
|
-
signal: attemptAbort.signal,
|
|
1027
|
-
budgetMs: this.config.localLlmTimeoutMs,
|
|
1028
|
-
});
|
|
1029
|
+
response = await postAttempt(requestBodyJson);
|
|
1029
1030
|
responseBody = await response.text();
|
|
1030
1031
|
}
|
|
1031
1032
|
} catch (err) {
|
|
1032
1033
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1033
|
-
if (
|
|
1034
|
+
if (signal?.aborted) {
|
|
1034
1035
|
response = null;
|
|
1035
1036
|
lastAbortError = error;
|
|
1036
1037
|
break;
|
|
@@ -1046,12 +1047,12 @@ export class LocalLlmClient {
|
|
|
1046
1047
|
log.warn(
|
|
1047
1048
|
`local LLM request aborted: op=${operation} attempt=${attempt}/${maxAttempts} timeoutMs=${effectiveTimeoutMs} model=${this.config.localLlmModel}; retrying after ${backoffMs}ms`,
|
|
1048
1049
|
);
|
|
1049
|
-
if (!(await waitForRetryBackoff(backoffMs,
|
|
1050
|
+
if (!(await waitForRetryBackoff(backoffMs, signal))) return null;
|
|
1050
1051
|
continue;
|
|
1051
1052
|
}
|
|
1052
1053
|
} finally {
|
|
1053
1054
|
clearTimeout(attemptTimeout);
|
|
1054
|
-
|
|
1055
|
+
signal?.removeEventListener("abort", onCallerAbort);
|
|
1055
1056
|
gate.release();
|
|
1056
1057
|
}
|
|
1057
1058
|
|
|
@@ -1085,7 +1086,7 @@ export class LocalLlmClient {
|
|
|
1085
1086
|
log.warn(
|
|
1086
1087
|
`local LLM request got ${response.status}; retrying (attempt ${attempt + 1}/${maxAttempts}) after ${backoffMs}ms`,
|
|
1087
1088
|
);
|
|
1088
|
-
if (!(await waitForRetryBackoff(backoffMs,
|
|
1089
|
+
if (!(await waitForRetryBackoff(backoffMs, signal))) return null;
|
|
1089
1090
|
}
|
|
1090
1091
|
log.debug(
|
|
1091
1092
|
`local LLM: received response, status=${response?.status}, ok=${response?.ok}`,
|
|
@@ -1401,13 +1402,14 @@ export class LocalLlmClient {
|
|
|
1401
1402
|
}
|
|
1402
1403
|
if (options.priority) {
|
|
1403
1404
|
const priority = options.priority;
|
|
1405
|
+
const signal = combineClientAbortSignals(options.signal, this.shutdownSignal);
|
|
1404
1406
|
return await new Promise<LocalLlmChatCompletionResult | null>((resolve) => {
|
|
1405
1407
|
let settled = false;
|
|
1406
1408
|
let onAbort: (() => void) | undefined;
|
|
1407
1409
|
const finish = (value: LocalLlmChatCompletionResult | null): void => {
|
|
1408
1410
|
if (settled) return;
|
|
1409
1411
|
settled = true;
|
|
1410
|
-
if (onAbort)
|
|
1412
|
+
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
1411
1413
|
resolve(value);
|
|
1412
1414
|
};
|
|
1413
1415
|
const queued: LocalLlmQueuedRequest = {
|
|
@@ -1424,8 +1426,8 @@ export class LocalLlmClient {
|
|
|
1424
1426
|
finish(null);
|
|
1425
1427
|
};
|
|
1426
1428
|
this.requestQueues[priority].push(queued);
|
|
1427
|
-
if (
|
|
1428
|
-
else if (
|
|
1429
|
+
if (signal?.aborted) onAbort();
|
|
1430
|
+
else if (signal) signal.addEventListener("abort", onAbort, { once: true });
|
|
1429
1431
|
this.scheduleQueueDrain();
|
|
1430
1432
|
});
|
|
1431
1433
|
}
|
|
@@ -113,6 +113,10 @@ export interface ExtractionRunCoordinatorDeps {
|
|
|
113
113
|
* threshold and honors a foreground-opened breaker (review round).
|
|
114
114
|
*/
|
|
115
115
|
providerBreaker?: ProviderCircuitBreaker;
|
|
116
|
+
/** Orchestrator-owned stop signal, combined into every extraction run's
|
|
117
|
+
* abort signal: queued tasks never start and in-flight runs abort the
|
|
118
|
+
* moment destroy() begins. Absent for standalone coordinators (tests). */
|
|
119
|
+
shutdownSignal?: AbortSignal;
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
// Recall/extraction abort helpers live in orchestrator-helpers.ts since
|
|
@@ -692,11 +696,16 @@ export class ExtractionRunCoordinator {
|
|
|
692
696
|
typeof deadlineMs === "number" && Number.isFinite(deadlineMs)
|
|
693
697
|
? new AbortController()
|
|
694
698
|
: undefined;
|
|
699
|
+
const shutdownSignal = this.deps.shutdownSignal;
|
|
700
|
+
const callerAbortSignal =
|
|
701
|
+
options.abortSignal && shutdownSignal
|
|
702
|
+
? AbortSignal.any([options.abortSignal, shutdownSignal])
|
|
703
|
+
: (options.abortSignal ?? shutdownSignal);
|
|
695
704
|
const extractionAbortSignal = extractionDeadlineController
|
|
696
|
-
?
|
|
697
|
-
? AbortSignal.any([
|
|
705
|
+
? callerAbortSignal
|
|
706
|
+
? AbortSignal.any([callerAbortSignal, extractionDeadlineController.signal])
|
|
698
707
|
: extractionDeadlineController.signal
|
|
699
|
-
:
|
|
708
|
+
: callerAbortSignal;
|
|
700
709
|
let extractionDeadlineTimer: NodeJS.Timeout | undefined;
|
|
701
710
|
const clearExtractionDeadlineTimer = (): void => {
|
|
702
711
|
if (extractionDeadlineTimer) {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { serializeMutations, withHeldFileLock } from "../utils/serialize-mutations.js";
|
|
3
|
-
|
|
3
|
+
// Import from the real modules, NOT the ../index.js barrel: a barrel edge
|
|
4
|
+
// from orchestration code that explicit-capture.ts imports re-orders module
|
|
5
|
+
// initialization and leaves access-boundary's registry in TDZ (#3354).
|
|
6
|
+
import { ContentHashIndex } from "../storage/content-hash-index.js";
|
|
7
|
+
import type { StorageManager } from "../storage.js";
|
|
4
8
|
import type { MemoryFile } from "../types.js";
|
|
5
9
|
import { normalizeStoredHashSource } from "./extraction-persist-citation.js";
|
|
6
10
|
import { stripAttributesSuffix } from "../structured-attributes.js";
|
|
@@ -1331,8 +1331,10 @@ test("#1909 round 11: the restart rebuild reflects the current corpus (archived
|
|
|
1331
1331
|
test("#1909 round 11 finding 2: destroy() flushes the debounced buffer BEFORE catalog touches", async () => {
|
|
1332
1332
|
// The buffer save fires a coalesced namespace-catalog touch; if catalog touches
|
|
1333
1333
|
// were flushed first, that shutdown-time touch would queue after and be lost.
|
|
1334
|
-
// destroy()
|
|
1335
|
-
// flush
|
|
1334
|
+
// destroy() flushes the buffer before the extraction drain and again after it
|
|
1335
|
+
// (post-drain removals); every buffer flush must run before the one catalog
|
|
1336
|
+
// flush so each shutdown-time touch folds into it and settles before destroy()
|
|
1337
|
+
// returns.
|
|
1336
1338
|
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-destroy-order-"));
|
|
1337
1339
|
const config = parseConfig({
|
|
1338
1340
|
openaiApiKey: "sk-test",
|
|
@@ -1363,10 +1365,12 @@ test("#1909 round 11 finding 2: destroy() flushes the debounced buffer BEFORE ca
|
|
|
1363
1365
|
|
|
1364
1366
|
await priv.destroy();
|
|
1365
1367
|
assert.deepEqual(
|
|
1366
|
-
order,
|
|
1367
|
-
["
|
|
1368
|
-
"
|
|
1368
|
+
order.filter((step) => step === "catalog"),
|
|
1369
|
+
["catalog"],
|
|
1370
|
+
"the catalog-touch flush runs exactly once",
|
|
1369
1371
|
);
|
|
1372
|
+
assert.equal(order.at(-1), "catalog", "no buffer flush may run after the catalog-touch flush");
|
|
1373
|
+
assert.ok(order.includes("buffer"), "destroy() flushes the buffer");
|
|
1370
1374
|
});
|
|
1371
1375
|
test("#1909 round 14: destroy() surfaces a buffer flush failure and still completes teardown", async () => {
|
|
1372
1376
|
// Graceful-shutdown durability contract: a failed shutdown buffer flush must
|
package/src/orchestrator.ts
CHANGED
|
@@ -739,6 +739,9 @@ export class Orchestrator {
|
|
|
739
739
|
private _dependencyPropagationRecovery: Promise<void> | null = null;
|
|
740
740
|
private destroyed = false;
|
|
741
741
|
private acceptingEntityMigrationMaintenance = true;
|
|
742
|
+
/** Aborted at the start of destroy(); stops every local-LLM client and extraction task at once. */
|
|
743
|
+
private readonly shutdownController = new AbortController();
|
|
744
|
+
readonly shutdownSignal: AbortSignal = this.shutdownController.signal;
|
|
742
745
|
|
|
743
746
|
private get dependencyPropagationDelivery(): DependencyPropagationDelivery {
|
|
744
747
|
if (this.destroyed) throw new Error("orchestrator has been destroyed");
|
|
@@ -770,9 +773,8 @@ export class Orchestrator {
|
|
|
770
773
|
return this._providerBreaker;
|
|
771
774
|
}
|
|
772
775
|
|
|
773
|
-
/** The run-coordinator deps bundle, parameterized by its buffer accessor
|
|
774
|
-
* an isolated claim-backed drain
|
|
775
|
-
* backlog scheduler) while live extraction keeps `this.buffer`. */
|
|
776
|
+
/** The run-coordinator deps bundle, parameterized by its buffer accessor:
|
|
777
|
+
* an isolated claim-backed drain binds its own SmartBuffer (backlog scheduler). */
|
|
776
778
|
private extractionRunDeps(getBuffer: () => SmartBuffer): ExtractionRunCoordinatorDeps {
|
|
777
779
|
return {
|
|
778
780
|
config: this.config,
|
|
@@ -795,9 +797,26 @@ export class Orchestrator {
|
|
|
795
797
|
runTierMigrationCycle: (storage, trigger, options) => this.runTierMigrationCycle(storage, trigger, options),
|
|
796
798
|
recordProcessedExtractionFingerprint: (storage, fingerprint, preloadedMeta) =>
|
|
797
799
|
this.recordProcessedExtractionFingerprint(storage, fingerprint, preloadedMeta),
|
|
800
|
+
shutdownSignal: this.shutdownSignal,
|
|
798
801
|
};
|
|
799
802
|
}
|
|
800
803
|
|
|
804
|
+
/** Fast-tier client: low latency at all costs, thinking always suppressed. */
|
|
805
|
+
private buildFastLlm(config: PluginConfig): LocalLlmClient {
|
|
806
|
+
const client = new LocalLlmClient(
|
|
807
|
+
{
|
|
808
|
+
...config,
|
|
809
|
+
localLlmModel: config.localLlmFastModel || config.localLlmModel,
|
|
810
|
+
localLlmUrl: config.localLlmFastUrl,
|
|
811
|
+
localLlmTimeoutMs: config.localLlmFastTimeoutMs,
|
|
812
|
+
},
|
|
813
|
+
this.modelRegistry,
|
|
814
|
+
this.shutdownSignal,
|
|
815
|
+
);
|
|
816
|
+
client.disableThinking = true;
|
|
817
|
+
return client;
|
|
818
|
+
}
|
|
819
|
+
|
|
801
820
|
private get extractionRunCoordinator(): ExtractionRunCoordinator {
|
|
802
821
|
if (!this._extractionRunCoordinator) {
|
|
803
822
|
this._extractionRunCoordinator = new ExtractionRunCoordinator(this.extractionRunDeps(() => this.buffer));
|
|
@@ -983,6 +1002,8 @@ export class Orchestrator {
|
|
|
983
1002
|
* commands should call it before returning to let Node exit naturally.
|
|
984
1003
|
*/
|
|
985
1004
|
async destroy(): Promise<void> {
|
|
1005
|
+
// First, always: every client and extraction task holds this signal.
|
|
1006
|
+
this.shutdownController.abort();
|
|
986
1007
|
this.acceptingEntityMigrationMaintenance = false;
|
|
987
1008
|
this._orchestratorInitCoordinator?.retireStartupEpoch();
|
|
988
1009
|
this.abortDeferredInit();
|
|
@@ -996,23 +1017,29 @@ export class Orchestrator {
|
|
|
996
1017
|
await this.maintenanceScheduler.dispose();
|
|
997
1018
|
await this._dependencyPropagationRecovery;
|
|
998
1019
|
this._dependencyPropagationRecovery = null;
|
|
1020
|
+
// Persist unsent turns BEFORE the extraction drain: buffered turns must
|
|
1021
|
+
// already be on disk when shutdown abandons or bounds the drain's wait.
|
|
1022
|
+
let bufferFlushError: unknown;
|
|
1023
|
+
try {
|
|
1024
|
+
await this.buffer.flushPendingSave({ throwOnFailure: true });
|
|
1025
|
+
} catch (err) {
|
|
1026
|
+
bufferFlushError = err;
|
|
1027
|
+
}
|
|
999
1028
|
const extractionDrainError = await drainExtractionAndShutdownDependencyPropagation(
|
|
1000
1029
|
this.extractionQueueCoordinator,
|
|
1001
1030
|
this._dependencyPropagationDelivery,
|
|
1002
1031
|
);
|
|
1032
|
+
// After the drain, never before: an extraction already in its persist
|
|
1033
|
+
// stage reads dependencyPropagationDelivery, and this flag gates that
|
|
1034
|
+
// getter ("orchestrator has been destroyed").
|
|
1003
1035
|
this.destroyed = true;
|
|
1004
1036
|
this._dependencyPropagationDelivery = undefined;
|
|
1037
|
+
// Persist post-drain removals; a failed write stays pending for next boot.
|
|
1038
|
+
await this.buffer.flushPendingSave().catch(() => undefined);
|
|
1005
1039
|
await drainRecallWrites(this);
|
|
1006
1040
|
await this.persistenceIndexCoordinator
|
|
1007
1041
|
.drainContentHashReconcileRetries()
|
|
1008
1042
|
.catch((err) => log.warn(`content-hash reconcile drain failed during destroy: ${err}`));
|
|
1009
|
-
// Save buffered turns before catalog flush; rethrow save failures after teardown.
|
|
1010
|
-
let bufferFlushError: unknown;
|
|
1011
|
-
try {
|
|
1012
|
-
await this.buffer.flushPendingSave({ throwOnFailure: true });
|
|
1013
|
-
} catch (err) {
|
|
1014
|
-
bufferFlushError = err;
|
|
1015
|
-
}
|
|
1016
1043
|
try {
|
|
1017
1044
|
// Flush catalog touches before backend disposal.
|
|
1018
1045
|
await this.namespaceCatalog.flushPendingTouches().catch(() => undefined);
|
|
@@ -1668,35 +1695,16 @@ export class Orchestrator {
|
|
|
1668
1695
|
config.gatewayConfig,
|
|
1669
1696
|
this.modelRegistry,
|
|
1670
1697
|
this.transcript,
|
|
1698
|
+
this.shutdownSignal,
|
|
1671
1699
|
);
|
|
1672
1700
|
this.judgeVerdictCache = createVerdictCache();
|
|
1673
1701
|
this.judgeDeferCounts = createDeferCountMap();
|
|
1674
|
-
this.localLlm = new LocalLlmClient(config, this.modelRegistry);
|
|
1675
|
-
//
|
|
1676
|
-
//
|
|
1677
|
-
// nothing from chain-of-thought reasoning. Apply the operator's
|
|
1678
|
-
// configured preference (default true) so thinking-capable models
|
|
1679
|
-
// skip reasoning tokens and avoid the common 60s extraction
|
|
1680
|
-
// timeout. Operators can set `localLlmDisableThinking: false`
|
|
1681
|
-
// when they want thinking enabled for narrative paths.
|
|
1702
|
+
this.localLlm = new LocalLlmClient(config, this.modelRegistry, this.shutdownSignal);
|
|
1703
|
+
// Operator-controlled thinking suppression for structured-output ops;
|
|
1704
|
+
// full rationale on THINKING_SUPPRESSED_OPERATIONS in local-llm.ts (issue #548).
|
|
1682
1705
|
this.localLlm.disableThinking = config.localLlmDisableThinking;
|
|
1683
1706
|
this.fastLlm = resolvePipelineProcessingCapabilities(this.config).localLlmFast
|
|
1684
|
-
? (
|
|
1685
|
-
const client = new LocalLlmClient(
|
|
1686
|
-
{
|
|
1687
|
-
...config,
|
|
1688
|
-
localLlmModel: config.localLlmFastModel || config.localLlmModel,
|
|
1689
|
-
localLlmUrl: config.localLlmFastUrl,
|
|
1690
|
-
localLlmTimeoutMs: config.localLlmFastTimeoutMs,
|
|
1691
|
-
},
|
|
1692
|
-
this.modelRegistry,
|
|
1693
|
-
);
|
|
1694
|
-
// Fast-tier always suppresses thinking — the contract of
|
|
1695
|
-
// `fastLlm` is "low latency at all costs" and that is
|
|
1696
|
-
// independent of the main-client config.
|
|
1697
|
-
client.disableThinking = true;
|
|
1698
|
-
return client;
|
|
1699
|
-
})()
|
|
1707
|
+
? this.buildFastLlm(config)
|
|
1700
1708
|
: this.localLlm;
|
|
1701
1709
|
this.semanticConsolidationCoordinator = new SemanticConsolidationCoordinator({
|
|
1702
1710
|
config,
|
|
@@ -1715,11 +1723,7 @@ export class Orchestrator {
|
|
|
1715
1723
|
)
|
|
1716
1724
|
: null;
|
|
1717
1725
|
if (config.modelSource === "gateway") {
|
|
1718
|
-
log.debug(
|
|
1719
|
-
`orchestrator: gateway model source active` +
|
|
1720
|
-
(config.gatewayAgentId ? ` (primary: ${config.gatewayAgentId})` : "") +
|
|
1721
|
-
(config.fastGatewayAgentId ? ` (fast: ${config.fastGatewayAgentId})` : ""),
|
|
1722
|
-
);
|
|
1726
|
+
log.debug(`orchestrator: gateway model source active (primary: ${config.gatewayAgentId ?? "none"}, fast: ${config.fastGatewayAgentId ?? "none"})`);
|
|
1723
1727
|
}
|
|
1724
1728
|
this.extraction = new ExtractionEngine(
|
|
1725
1729
|
config,
|
|
@@ -1973,18 +1977,11 @@ export class Orchestrator {
|
|
|
1973
1977
|
};
|
|
1974
1978
|
}
|
|
1975
1979
|
|
|
1976
|
-
/**
|
|
1977
|
-
*
|
|
1978
|
-
* (gateway model source or local LLM). Returns `undefined` when no chain
|
|
1979
|
-
* is available so `buildBriefing` can surface a clear unavailable reason
|
|
1980
|
-
* instead of failing at call time. Used by the access service and CLI as
|
|
1981
|
-
* the fallback when no direct `openaiApiKey` is configured, so briefing
|
|
1982
|
-
* follow-ups ride the same routing as every other fast-tier LLM feature.
|
|
1983
|
-
*/
|
|
1980
|
+
/** Briefing follow-up generator over the configured LLM chain, or
|
|
1981
|
+
* `undefined` when no chain is available (see buildBriefing). */
|
|
1984
1982
|
get briefingChainFollowupGenerator(): BriefingFollowupGenerator | undefined {
|
|
1985
|
-
// Plugin mode gates on `localLlmEnabled` alone:
|
|
1986
|
-
//
|
|
1987
|
-
// itself cannot serve requests (Cursor review on PR #1463).
|
|
1983
|
+
// Plugin mode gates on `localLlmEnabled` alone: chatCompletion returns
|
|
1984
|
+
// null when the master switch is off (Cursor review on PR #1463).
|
|
1988
1985
|
const chainAvailable =
|
|
1989
1986
|
this.config.modelSource === "gateway"
|
|
1990
1987
|
? this._fastGatewayLlm?.isAvailable(
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// rebuild saves plus the deferred durable-retry machinery. storage.ts imports
|
|
4
4
|
// these back and re-exports the class + companion types that were previously
|
|
5
5
|
// part of its public API.
|
|
6
|
-
import { mkdir, stat } from "node:fs/promises";
|
|
6
|
+
import { mkdir, stat, utimes } from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { computeContentHash, normalizeContent } from "../content-hash.js";
|
|
9
9
|
import { log } from "../logger.js";
|
|
@@ -291,6 +291,25 @@ export class ContentHashIndex {
|
|
|
291
291
|
);
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Change the durable file's mtime without touching its content. A publish
|
|
296
|
+
* that throws or stays deferred leaves the file as it was, so a peer that
|
|
297
|
+
* already synced it keeps answering authoritative MISSes for deltas this
|
|
298
|
+
* instance holds only in memory (#3355). A new stamp makes every holder of the
|
|
299
|
+
* old fingerprint, this instance included, fail the freshness check and
|
|
300
|
+
* confirm against the corpus. Best effort: a file that cannot be stamped
|
|
301
|
+
* leaves the callers' existing retry and rebuild paths as the only repair.
|
|
302
|
+
*/
|
|
303
|
+
private async invalidatePeerFreshness(): Promise<void> {
|
|
304
|
+
try {
|
|
305
|
+
const { mtimeMs } = await stat(this.filePath);
|
|
306
|
+
const bumped = new Date(Math.max(Date.now(), mtimeMs + 1));
|
|
307
|
+
await utimes(this.filePath, bumped, bumped);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
log.debug(`content-hash index: could not invalidate peer freshness of ${this.filePath}: ${err}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
294
313
|
/** Check if content already exists in the index. */
|
|
295
314
|
has(content: string): boolean {
|
|
296
315
|
return this.hashes.has(ContentHashIndex.computeHash(content));
|
|
@@ -473,9 +492,15 @@ export class ContentHashIndex {
|
|
|
473
492
|
`content-hash index: lock not acquired for ${this.filePath}; deferring reconcile save (dirty retained, scheduling durable retry)`
|
|
474
493
|
);
|
|
475
494
|
this.scheduleReconcileRetry();
|
|
495
|
+
await this.invalidatePeerFreshness();
|
|
476
496
|
return;
|
|
477
497
|
}
|
|
478
|
-
|
|
498
|
+
try {
|
|
499
|
+
await publish();
|
|
500
|
+
} catch (err) {
|
|
501
|
+
await this.invalidatePeerFreshness();
|
|
502
|
+
throw err;
|
|
503
|
+
}
|
|
479
504
|
if (this.dirty) {
|
|
480
505
|
// A concurrent add()/remove() landed during publish's disk awaits, so
|
|
481
506
|
// deltas remain unpersisted (PR #2016 thread SD7Tj). Arm a bounded
|