@cc-monitoring/proxy-core 0.3.0 → 0.4.0
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/cli/__tests__/commands.test.js +1 -1
- package/dist/cli/commands/exec.js +31 -42
- package/dist/docs/404.html +2 -2
- package/dist/docs/assets/{app.Dc46Hlqp.js → app.DtYNT5zS.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.C4sjikMO.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.Snq5jR6J.js → VPLocalSearchBox.DpXF9ZAI.js} +1 -1
- package/dist/docs/assets/chunks/{theme.XLtxGM-l.js → theme.TS_gUyc7.js} +2 -2
- package/dist/docs/assets/{proxy-cli_data-hooks.md.w9Oe1OD3.js → proxy-cli_data-hooks.md.Bz8CVbxO.js} +1 -1
- package/dist/docs/assets/{proxy-core_ui-routes.md.Dyqkeoiv.js → proxy-core_ui-routes.md.B21t1p7R.js} +1 -1
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/index.html +3 -3
- package/dist/docs/overview.html +3 -3
- package/dist/docs/packages-utils.html +3 -3
- package/dist/docs/proxy-admin/env-and-build.html +3 -3
- package/dist/docs/proxy-admin/index.html +3 -3
- package/dist/docs/proxy-admin/layout-and-menu.html +3 -3
- package/dist/docs/proxy-admin/pages.html +3 -3
- package/dist/docs/proxy-admin/routing-and-auth.html +3 -3
- package/dist/docs/proxy-admin/services-and-mock.html +3 -3
- package/dist/docs/proxy-cli/api-layer.html +3 -3
- package/dist/docs/proxy-cli/app-shell.html +3 -3
- package/dist/docs/proxy-cli/components-common.html +3 -3
- package/dist/docs/proxy-cli/components-config.html +3 -3
- package/dist/docs/proxy-cli/components-requests.html +3 -3
- package/dist/docs/proxy-cli/components-stats.html +3 -3
- package/dist/docs/proxy-cli/components-status.html +3 -3
- package/dist/docs/proxy-cli/data-hooks.html +5 -5
- package/dist/docs/proxy-cli/index.html +3 -3
- package/dist/docs/proxy-cli/utils-and-i18n.html +3 -3
- package/dist/docs/proxy-core/chat-api.html +3 -3
- package/dist/docs/proxy-core/cli.html +3 -3
- package/dist/docs/proxy-core/config.html +3 -3
- package/dist/docs/proxy-core/data-layer.html +3 -3
- package/dist/docs/proxy-core/entry.html +3 -3
- package/dist/docs/proxy-core/index.html +3 -3
- package/dist/docs/proxy-core/pm2.html +3 -3
- package/dist/docs/proxy-core/providers.html +3 -3
- package/dist/docs/proxy-core/proxy.html +3 -3
- package/dist/docs/proxy-core/stats.html +3 -3
- package/dist/docs/proxy-core/tool.html +3 -3
- package/dist/docs/proxy-core/ui-routes.html +5 -5
- package/dist/docs/proxy-core/utilities.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/request-flow.html +3 -3
- package/dist/logger/index.d.ts +0 -3
- package/dist/logger/index.js +0 -1
- package/dist/mcp-server/index.js +5 -3
- package/dist/mcp-server/tools.js +31 -10
- package/dist/server/__tests__/providers-route.test.js +1 -0
- package/dist/server/auth.d.ts +0 -1
- package/dist/server/auth.js +1 -1
- package/dist/server/chat-route.js +48 -156
- package/dist/server/data-push.d.ts +0 -1
- package/dist/server/data-push.js +0 -7
- package/dist/server/proxy.js +128 -219
- package/dist/server/ui-route.js +76 -59
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/registry.d.ts +0 -4
- package/dist/tools/registry.js +0 -8
- package/dist/types/config.d.ts +0 -13
- package/dist/types/pm2.d.ts +1 -2
- package/dist/ui/assets/{1c-Bx3CHH-I.js → 1c-CCx-FOtz.js} +1 -1
- package/dist/ui/assets/{abnf-NN4-iXDM.js → abnf-Bwbi42Z8.js} +1 -1
- package/dist/ui/assets/{abnfDiagram-VRR7QNED-CXFZgDk7.js → abnfDiagram-VRR7QNED-Dm1-8q0E.js} +1 -1
- package/dist/ui/assets/{accesslog-Bq9x4vSZ.js → accesslog-DCytz_-m.js} +1 -1
- package/dist/ui/assets/{actionscript-CeaB6gjT.js → actionscript-3udf15oh.js} +1 -1
- package/dist/ui/assets/{ada-DObFDSC6.js → ada-M5Xa74xG.js} +1 -1
- package/dist/ui/assets/{angelscript-4pIbuWPH.js → angelscript-qlrFxfW-.js} +1 -1
- package/dist/ui/assets/{apache-w0LzP4me.js → apache-B-sF-DQH.js} +1 -1
- package/dist/ui/assets/{applescript-DXpD6Cc9.js → applescript-4FdcBnbI.js} +1 -1
- package/dist/ui/assets/{arc-Dtt1H4m8.js → arc-BB3K1enp.js} +1 -1
- package/dist/ui/assets/{arcade-D_zU9Djr.js → arcade-B_yqWaaG.js} +1 -1
- package/dist/ui/assets/{architectureDiagram-ZJ3FMSHR-C0E_eQDM.js → architectureDiagram-ZJ3FMSHR-B39vkY8V.js} +1 -1
- package/dist/ui/assets/{arduino-BnmN_xp_.js → arduino-yAcBoTWU.js} +1 -1
- package/dist/ui/assets/{armasm-CH_xJaHf.js → armasm-wIcY84bI.js} +1 -1
- package/dist/ui/assets/{asciidoc-BlvZZ-hO.js → asciidoc-Bxnu28-Z.js} +1 -1
- package/dist/ui/assets/{aspectj-DP5DdFKQ.js → aspectj-B2cKJA_O.js} +1 -1
- package/dist/ui/assets/{autohotkey-Cy7dy_B1.js → autohotkey-B_rMWTtE.js} +1 -1
- package/dist/ui/assets/{autoit-BmQwjQdM.js → autoit-DwcuvMwv.js} +1 -1
- package/dist/ui/assets/{avrasm-fcPy4w1X.js → avrasm-heGX1PXK.js} +1 -1
- package/dist/ui/assets/{awk-AlcwGSdk.js → awk-BiqCQAEf.js} +1 -1
- package/dist/ui/assets/{axapta-NyMN6Mvz.js → axapta-Cv8M-Ih6.js} +1 -1
- package/dist/ui/assets/{bash-Bn3yyTUs.js → bash-Divgn3JV.js} +1 -1
- package/dist/ui/assets/{basic-gp9eOjjv.js → basic-B9dnAP55.js} +1 -1
- package/dist/ui/assets/{blockDiagram-677ZJIJ3-BHAadGGo.js → blockDiagram-677ZJIJ3-DQ1mf7A_.js} +1 -1
- package/dist/ui/assets/{bnf-DRAiK-_J.js → bnf-kIL5mSsP.js} +1 -1
- package/dist/ui/assets/{brainfuck-IYgPavhh.js → brainfuck-DqB7xDjH.js} +1 -1
- package/dist/ui/assets/{coq-BYVsAo31.js → c-HmsCM6sx.js} +1 -1
- package/dist/ui/assets/{c-like-B7T39dtK.js → c-like-De5MhE7G.js} +1 -1
- package/dist/ui/assets/{c4Diagram-LMCZKHZV-4cXT8R9f.js → c4Diagram-LMCZKHZV-DQxtYsAx.js} +1 -1
- package/dist/ui/assets/{cal-DmXm_WLX.js → cal-mYdfz9en.js} +1 -1
- package/dist/ui/assets/{capnproto-Cb-rKd-Z.js → capnproto-D04CRlOt.js} +1 -1
- package/dist/ui/assets/{ceylon-Bn2aNwEq.js → ceylon-pi9lFCja.js} +1 -1
- package/dist/ui/assets/channel-ChTtxQDg.js +1 -0
- package/dist/ui/assets/{chunk-2Q5K7J3B-B3N_AddF.js → chunk-2Q5K7J3B-BDD1nPt8.js} +1 -1
- package/dist/ui/assets/{chunk-32BRIVSS-CW7iU3gE.js → chunk-32BRIVSS-DgeQFBb5.js} +1 -1
- package/dist/ui/assets/{chunk-5VM5RSS4-M8Ep3dJD.js → chunk-5VM5RSS4-C7Xf2SnI.js} +1 -1
- package/dist/ui/assets/{chunk-EX3LRPZG-t2hCXH-v.js → chunk-EX3LRPZG-B_3p1HEv.js} +1 -1
- package/dist/ui/assets/{chunk-JWPE2WC7-rQK8maXo.js → chunk-JWPE2WC7-Dvs7u8eY.js} +1 -1
- package/dist/ui/assets/{chunk-MOJQB5TN-BpU7vp3L.js → chunk-MOJQB5TN-By21wDCN.js} +1 -1
- package/dist/ui/assets/{chunk-RYQCIY6F-CJVNOulR.js → chunk-RYQCIY6F-6NC700r1.js} +1 -1
- package/dist/ui/assets/{chunk-V7JOEXUC-B--maww3.js → chunk-V7JOEXUC-vEUqZ3rh.js} +1 -1
- package/dist/ui/assets/{chunk-VR4S4FIN-QJ5u9nSh.js → chunk-VR4S4FIN-6tX2dNN9.js} +1 -1
- package/dist/ui/assets/{chunk-XXDRQBXY-BcM9JOXa.js → chunk-XXDRQBXY-qDb2jxAR.js} +1 -1
- package/dist/ui/assets/classDiagram-OUVF2IWQ-Ccbvamlu.js +1 -0
- package/dist/ui/assets/classDiagram-v2-EOCWNBFH-Ccbvamlu.js +1 -0
- package/dist/ui/assets/{clean-BCfhFoON.js → clean-CLCsDUl1.js} +1 -1
- package/dist/ui/assets/{clojure-DtnBj8eB.js → clojure-dVi5Xifj.js} +1 -1
- package/dist/ui/assets/{clojure-repl-D8X-urCW.js → clojure-repl-BO_FpFmF.js} +1 -1
- package/dist/ui/assets/{cmake-BH2RXBgt.js → cmake-CP5aJbyK.js} +1 -1
- package/dist/ui/assets/{coffeescript-CuoLiQ_d.js → coffeescript-ZEfu_9pF.js} +1 -1
- package/dist/ui/assets/{c-Dht0EgwX.js → coq-6aKUn3zn.js} +1 -1
- package/dist/ui/assets/{cos-t-cDfrf6.js → cos-BohCnRN2.js} +1 -1
- package/dist/ui/assets/{cose-bilkent-JH36ORCC-CYip_9u7.js → cose-bilkent-JH36ORCC-DyXCbbro.js} +1 -1
- package/dist/ui/assets/{cpp-Cgv0dQ7g.js → cpp-FOgPWPEt.js} +1 -1
- package/dist/ui/assets/{crmsh-7pr1Pq_e.js → crmsh-B0OYb-Oe.js} +1 -1
- package/dist/ui/assets/{crystal-BHZFifeV.js → crystal-CuWb_dXm.js} +1 -1
- package/dist/ui/assets/{csharp-CVMfBRGF.js → csharp-BbcqM09j.js} +1 -1
- package/dist/ui/assets/{csp-NEBRDuZY.js → csp-rgHnTaDq.js} +1 -1
- package/dist/ui/assets/{css-B0fbMkoQ.js → css-C-ibh7ZG.js} +1 -1
- package/dist/ui/assets/{cynefin-VYW2F7L2-CycR2u2l.js → cynefin-VYW2F7L2-CkuBhgC4.js} +1 -1
- package/dist/ui/assets/{cynefinDiagram-TSTJHNR4-CvXYb5em.js → cynefinDiagram-TSTJHNR4-eycFyDao.js} +1 -1
- package/dist/ui/assets/{d-AzA6F7Ab.js → d-C0gXxJ0O.js} +1 -1
- package/dist/ui/assets/{dagre-VKFMJZFB-6NuSYMRG.js → dagre-VKFMJZFB-DzqX9e49.js} +1 -1
- package/dist/ui/assets/{dart-D0VXSwKi.js → dart-C-fTpuUV.js} +1 -1
- package/dist/ui/assets/{delphi-B-bkLmCx.js → delphi-Chm0B9xv.js} +1 -1
- package/dist/ui/assets/{diagram-FQU43EPY-CNNW_5O7.js → diagram-FQU43EPY-BEgCqfg1.js} +1 -1
- package/dist/ui/assets/{diagram-G47NLZAW-C60bRph-.js → diagram-G47NLZAW-Cw11Vs2v.js} +1 -1
- package/dist/ui/assets/{diagram-NH7WQ7WH-CwERNWac.js → diagram-NH7WQ7WH-DSG_UWpP.js} +1 -1
- package/dist/ui/assets/{diagram-OA4YK3LP-9XSTmf1Z.js → diagram-OA4YK3LP-CJz-WOaJ.js} +1 -1
- package/dist/ui/assets/{diagram-WEI45ONY-h8K41_YA.js → diagram-WEI45ONY-CBXjBCvc.js} +1 -1
- package/dist/ui/assets/{diff-Ch9RlEn8.js → diff-C-vyuMSL.js} +1 -1
- package/dist/ui/assets/{django-C00rhzcB.js → django-CJK7lReT.js} +1 -1
- package/dist/ui/assets/{dns-C5MKVde0.js → dns-D02SHjSy.js} +1 -1
- package/dist/ui/assets/{dockerfile-BpRaVDMB.js → dockerfile-trKL1Bqu.js} +1 -1
- package/dist/ui/assets/{dos-CjQOJ_vn.js → dos-C52dy-sK.js} +1 -1
- package/dist/ui/assets/{dsconfig-3zd3UkyN.js → dsconfig-MzHgzVrt.js} +1 -1
- package/dist/ui/assets/{dts-Bb6PtK5T.js → dts-B137dHyX.js} +1 -1
- package/dist/ui/assets/{dust-BEJKezIa.js → dust-BcbM0r5C.js} +1 -1
- package/dist/ui/assets/{ebnf-5-TvfsoC.js → ebnf-DkbpI7bK.js} +1 -1
- package/dist/ui/assets/{ebnfDiagram-CCIWWBDH-CRmIjeas.js → ebnfDiagram-CCIWWBDH-aI5uQ7w1.js} +1 -1
- package/dist/ui/assets/{elixir-B6gSEyen.js → elixir-BOtDuqQy.js} +1 -1
- package/dist/ui/assets/{elm-rR0aqyyf.js → elm-DS8Tyyu-.js} +1 -1
- package/dist/ui/assets/{erDiagram-Q63AITRT-DS3jYOq8.js → erDiagram-Q63AITRT-2vCbd4Mm.js} +1 -1
- package/dist/ui/assets/{erb-DUu22Jxg.js → erb-CSXECRbn.js} +1 -1
- package/dist/ui/assets/{erlang-C0m-R-6W.js → erlang-BMxlgKPJ.js} +1 -1
- package/dist/ui/assets/{erlang-repl-B7O2MhlI.js → erlang-repl-DPzlolPg.js} +1 -1
- package/dist/ui/assets/{excel-CIqnqFs1.js → excel-CW2Uxed3.js} +1 -1
- package/dist/ui/assets/{fix-Ds9tcTzQ.js → fix-UW84SlCs.js} +1 -1
- package/dist/ui/assets/{flix-ByzuevTB.js → flix-4UGiPULd.js} +1 -1
- package/dist/ui/assets/{flowDiagram-23GEKE2U-B9F3S_Hl.js → flowDiagram-23GEKE2U-R5PbCbZI.js} +1 -1
- package/dist/ui/assets/{fortran-DieSVGx5.js → fortran-nhHsngdu.js} +1 -1
- package/dist/ui/assets/{fsharp-DGP8m6H3.js → fsharp-CDyZ1ST5.js} +1 -1
- package/dist/ui/assets/{gams-BiLGBohb.js → gams-DHzkkUie.js} +1 -1
- package/dist/ui/assets/{ganttDiagram-NO4QXBWP-CHiCWnJt.js → ganttDiagram-NO4QXBWP-CGcq4Ie_.js} +1 -1
- package/dist/ui/assets/{gauss-Btl1fjJ0.js → gauss-DdhFGyBv.js} +1 -1
- package/dist/ui/assets/{gcode-DWl2YoL_.js → gcode-ImHh8OLA.js} +1 -1
- package/dist/ui/assets/{gherkin-D42ESIJ2.js → gherkin-CTWAW-XR.js} +1 -1
- package/dist/ui/assets/{gitGraphDiagram-IHSO6WYX-0pEKZnJg.js → gitGraphDiagram-IHSO6WYX-8nM2Z9lD.js} +1 -1
- package/dist/ui/assets/{glsl-Dx1QhGIM.js → glsl-0_W_27G2.js} +1 -1
- package/dist/ui/assets/{gml-f0noEVTB.js → gml-BoDggu2z.js} +1 -1
- package/dist/ui/assets/{go-DXT6c-0g.js → go-DYfFygwl.js} +1 -1
- package/dist/ui/assets/{golo-D3NuJxTg.js → golo-Mlq-TCg9.js} +1 -1
- package/dist/ui/assets/{gradle-CUsTLc_e.js → gradle-BALAinEB.js} +1 -1
- package/dist/ui/assets/{groovy-BIkOcIpQ.js → groovy-Dht--dWU.js} +1 -1
- package/dist/ui/assets/{haml-C5jI7YVN.js → haml-DpMrxg0d.js} +1 -1
- package/dist/ui/assets/{handlebars-UOvbWwDx.js → handlebars-DqHzujNj.js} +1 -1
- package/dist/ui/assets/{haskell-DuO_Av2n.js → haskell-DZSchhkY.js} +1 -1
- package/dist/ui/assets/{haxe-C0Cg5Hjh.js → haxe-C0zBRjdy.js} +1 -1
- package/dist/ui/assets/{hsp-CTDajZig.js → hsp-D_PwbSv2.js} +1 -1
- package/dist/ui/assets/{htmlbars-_ITuCkOs.js → htmlbars-DxAFMG8u.js} +1 -1
- package/dist/ui/assets/{http-Dr26PHfb.js → http-DvTRF0Do.js} +1 -1
- package/dist/ui/assets/{hy-B-ThgV4T.js → hy-lVO-kNyp.js} +1 -1
- package/dist/ui/assets/{index-CpQr158p.js → index-BbMRVVGt.js} +3 -3
- package/dist/ui/assets/{index-CwcnuNUs.js → index-Ci23SMK2.js} +242 -242
- package/dist/ui/assets/index-DlsVXaHT.css +10 -0
- package/dist/ui/assets/{infoDiagram-FWYZ7A6U-ClKPOdlx.js → infoDiagram-FWYZ7A6U-C7WYDlvc.js} +1 -1
- package/dist/ui/assets/{inform7-D7JYYkX9.js → inform7-B8Tf2n8v.js} +1 -1
- package/dist/ui/assets/{ini-BZwCkFAf.js → ini-DSoYMaLF.js} +1 -1
- package/dist/ui/assets/{irpf90-omMve0rd.js → irpf90-RCPdkB14.js} +1 -1
- package/dist/ui/assets/{isbl-C4xosfum.js → isbl-C2p75PPj.js} +1 -1
- package/dist/ui/assets/{ishikawaDiagram-FXEZZL3T-PtbNBMgh.js → ishikawaDiagram-FXEZZL3T-Cm6jsPlc.js} +1 -1
- package/dist/ui/assets/{java-CWjWDe7Q.js → java-DeY4Yuia.js} +1 -1
- package/dist/ui/assets/{javascript-C3dxrGuA.js → javascript-3uX1CO-H.js} +1 -1
- package/dist/ui/assets/{jboss-cli-CJE7m0Y-.js → jboss-cli-DgnD8mEc.js} +1 -1
- package/dist/ui/assets/{journeyDiagram-5HDEW3XC-DgK9V4yg.js → journeyDiagram-5HDEW3XC-DEGV1uA5.js} +1 -1
- package/dist/ui/assets/{json-DFUiNV7v.js → json-BV2PmJJp.js} +1 -1
- package/dist/ui/assets/{julia-CUTHpx8X.js → julia-B-m8kQm7.js} +1 -1
- package/dist/ui/assets/{julia-repl-_ZaQqdrz.js → julia-repl-C51atU2X.js} +1 -1
- package/dist/ui/assets/{kanban-definition-HUTT4EX6-BFrDfKdv.js → kanban-definition-HUTT4EX6-BJ4k5iTX.js} +1 -1
- package/dist/ui/assets/{kotlin-CrYjQYp8.js → kotlin-CYBziIgl.js} +1 -1
- package/dist/ui/assets/{lasso-CAiVg9m2.js → lasso-Dw2SBMDW.js} +1 -1
- package/dist/ui/assets/{latex-BkxnXIrT.js → latex-Dpd35yke.js} +1 -1
- package/dist/ui/assets/{ldif-CBOqxiN7.js → ldif-Ce9EZU__.js} +1 -1
- package/dist/ui/assets/{leaf-CbBZyOGH.js → leaf-CpkZzQtC.js} +1 -1
- package/dist/ui/assets/{less-Ca6uLPP0.js → less-Uzo7M0FD.js} +1 -1
- package/dist/ui/assets/{lisp-0Cm7_Bzv.js → lisp-DWXDlJYm.js} +1 -1
- package/dist/ui/assets/{livecodeserver-B7ka0vFT.js → livecodeserver-uEyPEgVJ.js} +1 -1
- package/dist/ui/assets/{livescript-CGmWmcLd.js → livescript-BQ5f07oK.js} +1 -1
- package/dist/ui/assets/{llvm-DCVMvkrH.js → llvm-BAvP-skE.js} +1 -1
- package/dist/ui/assets/{lsl-PVrTScpn.js → lsl-DhbiIKu6.js} +1 -1
- package/dist/ui/assets/{lua-BTo3z2Ux.js → lua-COxeUeLx.js} +1 -1
- package/dist/ui/assets/{makefile-DJGh9r9C.js → makefile-DhDx-IgL.js} +1 -1
- package/dist/ui/assets/{markdown-BOloL-q7.js → markdown-DX5_kSxq.js} +1 -1
- package/dist/ui/assets/{mathematica-CtMMQM2V.js → mathematica-D351ZddB.js} +1 -1
- package/dist/ui/assets/{matlab-Bfg_AWdR.js → matlab-CELqIiwF.js} +1 -1
- package/dist/ui/assets/{maxima-B2cdS7BY.js → maxima-B_YljrMm.js} +1 -1
- package/dist/ui/assets/{mel-BvfPvpYI.js → mel-DltsbNS8.js} +1 -1
- package/dist/ui/assets/{mercury-B6KgNIai.js → mercury-axX-5Vjj.js} +1 -1
- package/dist/ui/assets/{mindmap-definition-LN4V7U3C-BkeXo5If.js → mindmap-definition-LN4V7U3C-Bl1EJo6p.js} +1 -1
- package/dist/ui/assets/{mipsasm-Csras-cD.js → mipsasm-BxmerVwX.js} +1 -1
- package/dist/ui/assets/{mizar-C3WDI1-X.js → mizar-XNHfbpL2.js} +1 -1
- package/dist/ui/assets/{mojolicious-MnD2Whic.js → mojolicious-BR0U_7zk.js} +1 -1
- package/dist/ui/assets/{monkey-DLvUPYTI.js → monkey-lLuu9_f-.js} +1 -1
- package/dist/ui/assets/{moonscript-B0XEb3R3.js → moonscript-DoaZ3DRQ.js} +1 -1
- package/dist/ui/assets/{n1ql-BMRF54sY.js → n1ql-C6TId6Iu.js} +1 -1
- package/dist/ui/assets/{nginx-DqUyeHb8.js → nginx-BezSG-JT.js} +1 -1
- package/dist/ui/assets/{nim-3xwg8mOL.js → nim-Bb0rfXie.js} +1 -1
- package/dist/ui/assets/{nix-ByTkLdcH.js → nix-o3sJ5-54.js} +1 -1
- package/dist/ui/assets/{node-repl-sGSuIUFG.js → node-repl-Sv5hhcTf.js} +1 -1
- package/dist/ui/assets/{nsis-Dng5mFSM.js → nsis-B45BJFhF.js} +1 -1
- package/dist/ui/assets/{objectivec-DSQ8vb7n.js → objectivec-CzS6zVgh.js} +1 -1
- package/dist/ui/assets/{ocaml-BHOhEgRu.js → ocaml-Bqux7PyP.js} +1 -1
- package/dist/ui/assets/{openscad-DcUxQ-FQ.js → openscad-4Wf6mF84.js} +1 -1
- package/dist/ui/assets/{oxygene-DNlmqhsR.js → oxygene-Fvwqgkkt.js} +1 -1
- package/dist/ui/assets/{parser3-DIR98Q6n.js → parser3-zEfzT5wv.js} +1 -1
- package/dist/ui/assets/{pegDiagram-2B236MQR-BrXp_1_9.js → pegDiagram-2B236MQR-Dd4rIztM.js} +1 -1
- package/dist/ui/assets/{perl-16cY7fyu.js → perl-CsS1Gs6o.js} +1 -1
- package/dist/ui/assets/{pf-KrAq2BW7.js → pf-BNH1jO0R.js} +1 -1
- package/dist/ui/assets/{pgsql-BV1Z2bjp.js → pgsql-DqPigHrq.js} +1 -1
- package/dist/ui/assets/{php-FGjYSZqs.js → php-CdOl9bRs.js} +1 -1
- package/dist/ui/assets/{php-template-B1OW86WT.js → php-template-DPfirJg2.js} +1 -1
- package/dist/ui/assets/{pieDiagram-ENE6RG2P-Bh0bhWSa.js → pieDiagram-ENE6RG2P-DGaJNf4S.js} +1 -1
- package/dist/ui/assets/{plaintext-Cvn_UgSv.js → plaintext-EHbgBKzw.js} +1 -1
- package/dist/ui/assets/{pony-CRWnA3MR.js → pony-DmJajtp2.js} +1 -1
- package/dist/ui/assets/{powershell-Cwn155MZ.js → powershell-DfGzdWR6.js} +1 -1
- package/dist/ui/assets/{processing-BS6-1lrQ.js → processing-BT0ETgPG.js} +1 -1
- package/dist/ui/assets/{profile-C45OLQrv.js → profile-B3I-x5Lt.js} +1 -1
- package/dist/ui/assets/{prolog-C5YafSzY.js → prolog-B_4QJIvI.js} +1 -1
- package/dist/ui/assets/{properties-BULd-Zhg.js → properties-BFVI8weS.js} +1 -1
- package/dist/ui/assets/{protobuf-Qf1IuG5l.js → protobuf-PaF39oZZ.js} +1 -1
- package/dist/ui/assets/{puppet-93XdQvnn.js → puppet-BDC6Q8VF.js} +1 -1
- package/dist/ui/assets/{purebasic-DlYNScQP.js → purebasic-B2ejrOZK.js} +1 -1
- package/dist/ui/assets/{python-D-Do6-F0.js → python-BOxtIk9H.js} +1 -1
- package/dist/ui/assets/{python-repl-DGutP04w.js → python-repl-BslBh_Mx.js} +1 -1
- package/dist/ui/assets/{q-lmgfrySy.js → q-BPe8WLGW.js} +1 -1
- package/dist/ui/assets/{qml-BbjX_mSU.js → qml-BazRR6fz.js} +1 -1
- package/dist/ui/assets/{quadrantDiagram-ABIIQ3AL-B891FOO8.js → quadrantDiagram-ABIIQ3AL-BQ7aPkHk.js} +1 -1
- package/dist/ui/assets/{r--x8NzO9N.js → r-C09pGNJC.js} +1 -1
- package/dist/ui/assets/{railroadDiagram-RFXS5EU6-R3O-GKNe.js → railroadDiagram-RFXS5EU6-sCO9VGvE.js} +1 -1
- package/dist/ui/assets/{reasonml-DwHsAflh.js → reasonml-S7dzwVL9.js} +1 -1
- package/dist/ui/assets/{requirementDiagram-TGXJPOKE-D4l_xEXt.js → requirementDiagram-TGXJPOKE-CfT5aVZg.js} +1 -1
- package/dist/ui/assets/{rib-DMJNtDwV.js → rib-fM2EwQd4.js} +1 -1
- package/dist/ui/assets/{roboconf-BNNGTvHr.js → roboconf-D7AaeyrW.js} +1 -1
- package/dist/ui/assets/{routeros-BF7SVruD.js → routeros-c6Q7mKuq.js} +1 -1
- package/dist/ui/assets/{rsl-1EHeIZqp.js → rsl-sCpEQBxh.js} +1 -1
- package/dist/ui/assets/{ruby-B-v_BYYp.js → ruby-lcKhz2w9.js} +1 -1
- package/dist/ui/assets/{ruleslanguage-GFeEMSdK.js → ruleslanguage-BW4ATOuX.js} +1 -1
- package/dist/ui/assets/{rust-ApqGtMhh.js → rust-BbpU-0VY.js} +1 -1
- package/dist/ui/assets/{sankeyDiagram-HTMAVEWB-Dc-2lhBq.js → sankeyDiagram-HTMAVEWB-CaweCpvN.js} +1 -1
- package/dist/ui/assets/{sas-YkxX6N9_.js → sas-BPt5R0dx.js} +1 -1
- package/dist/ui/assets/{scala-BpHi86GZ.js → scala-DxW_vqXs.js} +1 -1
- package/dist/ui/assets/{scheme-D889qY8Y.js → scheme-T2Q_jWzT.js} +1 -1
- package/dist/ui/assets/{scilab-C3l-bcRx.js → scilab-DMORg9lV.js} +1 -1
- package/dist/ui/assets/{scss-D0iGASxm.js → scss-t0VsosqT.js} +1 -1
- package/dist/ui/assets/{sequenceDiagram-DBY2YBRQ-DvppgciC.js → sequenceDiagram-DBY2YBRQ-o-H_R4zb.js} +1 -1
- package/dist/ui/assets/{shell-PUnaEPCO.js → shell-CnCDa1s9.js} +1 -1
- package/dist/ui/assets/{sizeCapture-X5ZJPWSS-DhvIbxXs.js → sizeCapture-X5ZJPWSS-BNLkHGDS.js} +1 -1
- package/dist/ui/assets/{smali-Du_dF9hl.js → smali-_n-BxJyG.js} +1 -1
- package/dist/ui/assets/{smalltalk-CSr2Gc1v.js → smalltalk-B90PXJwK.js} +1 -1
- package/dist/ui/assets/{sml-D3zkvzP2.js → sml-fuY7bj-n.js} +1 -1
- package/dist/ui/assets/{sqf-BZQ34qEZ.js → sqf-COpwkyuo.js} +1 -1
- package/dist/ui/assets/{sql-BFKScMsf.js → sql-CXIT0j-x.js} +1 -1
- package/dist/ui/assets/{sql_more-C2Lc_Qqq.js → sql_more-C1Oxq4rx.js} +1 -1
- package/dist/ui/assets/{stan-CzY8VdxU.js → stan-BZEpaHeC.js} +1 -1
- package/dist/ui/assets/{stata-9GxyhPpV.js → stata-CkBwaiZM.js} +1 -1
- package/dist/ui/assets/{stateDiagram-2N3HPSRC-CM63y6f0.js → stateDiagram-2N3HPSRC-BkdnNF7I.js} +1 -1
- package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-0JnZ1VSQ.js +1 -0
- package/dist/ui/assets/{step21-BpK_Zw3v.js → step21-fs9N_U3O.js} +1 -1
- package/dist/ui/assets/{stylus-DQ4k28M4.js → stylus-Bs_9cvKZ.js} +1 -1
- package/dist/ui/assets/{subunit-C6ciOPNF.js → subunit-DvgcOCqJ.js} +1 -1
- package/dist/ui/assets/{swift-CJfqqSbT.js → swift-BE4ONsur.js} +1 -1
- package/dist/ui/assets/{swimlanes-5IMT3BWC-DS6_HNF7.js → swimlanes-5IMT3BWC-uqTAc7o7.js} +2 -2
- package/dist/ui/assets/swimlanesDiagram-G3AALYLV-DQRIIEmi.js +8 -0
- package/dist/ui/assets/{taggerscript-VouMmJ_v.js → taggerscript-BhmPJ64C.js} +1 -1
- package/dist/ui/assets/{tap-wXCy18ot.js → tap-DkueIVKV.js} +1 -1
- package/dist/ui/assets/{tcl-ewDOmCFX.js → tcl-DyXOOkFi.js} +1 -1
- package/dist/ui/assets/{thrift-BfFA03rE.js → thrift-Dhgbk56T.js} +1 -1
- package/dist/ui/assets/{timeline-definition-FHXFAJF6-Dz2m6S6x.js → timeline-definition-FHXFAJF6-DBAk9Ouc.js} +1 -1
- package/dist/ui/assets/{tp-HGBcpNf9.js → tp-BsXyrRGH.js} +1 -1
- package/dist/ui/assets/{twig-nrQZ0pw8.js → twig-J0HqIuHw.js} +1 -1
- package/dist/ui/assets/{typescript-B7tNYFEQ.js → typescript-Dkwvul39.js} +1 -1
- package/dist/ui/assets/{vala-BIjQtoo6.js → vala-CRGnf8aY.js} +1 -1
- package/dist/ui/assets/{vbnet-Bbspccm6.js → vbnet-BtJAUe3z.js} +1 -1
- package/dist/ui/assets/{vbscript-C45_QgOy.js → vbscript-RgFFNuM3.js} +1 -1
- package/dist/ui/assets/{vbscript-html-DlF2QBc7.js → vbscript-html-DO32Io9Y.js} +1 -1
- package/dist/ui/assets/{vennDiagram-L72KCM5P-BPEphYtX.js → vennDiagram-L72KCM5P-BJi6y0hh.js} +1 -1
- package/dist/ui/assets/{verilog-BSitd8W0.js → verilog-C4h6MLcI.js} +1 -1
- package/dist/ui/assets/{vhdl-C1bbeiC4.js → vhdl-DOafePyU.js} +1 -1
- package/dist/ui/assets/{vim-DchwbT8X.js → vim-BAdTA9TY.js} +1 -1
- package/dist/ui/assets/{wardleyDiagram-EHGQE667-BttTCoBT.js → wardleyDiagram-EHGQE667-C7c_4IJ6.js} +1 -1
- package/dist/ui/assets/{x86asm-vARxMUvn.js → x86asm-BCL6__QH.js} +1 -1
- package/dist/ui/assets/{xl-dx63vvXo.js → xl-qIzE4HOe.js} +1 -1
- package/dist/ui/assets/{xml-BUnj27wL.js → xml-DE2sJXYU.js} +1 -1
- package/dist/ui/assets/{xquery-T7gpnf0e.js → xquery-CQ35t92y.js} +1 -1
- package/dist/ui/assets/{xychartDiagram-FW5EYKEG-BfrNhdrG.js → xychartDiagram-FW5EYKEG-D2ILn0jL.js} +1 -1
- package/dist/ui/assets/{yaml-xRgQKzeL.js → yaml-5KaAvG5C.js} +1 -1
- package/dist/ui/assets/{zephir-Yu6l5qJj.js → zephir-CbR3Ryfq.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/dist/utils/__tests__/abort-signals.test.js +67 -0
- package/dist/utils/__tests__/inject-loop.test.js +1 -53
- package/dist/utils/__tests__/learn-tasks-db.test.js +82 -0
- package/dist/utils/__tests__/openai-converter.test.js +25 -0
- package/dist/utils/__tests__/pi-crawl-tools.test.js +15 -4
- package/dist/utils/__tests__/pi-dedup-reconcile.test.js +11 -9
- package/dist/utils/__tests__/pi-events.test.js +172 -0
- package/dist/utils/__tests__/pi-learn-orchestrator.test.js +216 -6
- package/dist/utils/__tests__/pi-runtime-json.test.js +38 -3
- package/dist/utils/__tests__/providers-db.test.js +40 -2
- package/dist/utils/__tests__/request-writer.test.js +271 -39
- package/dist/utils/__tests__/sse-protocol.test.d.ts +1 -0
- package/dist/utils/__tests__/sse-protocol.test.js +115 -0
- package/dist/utils/__tests__/vault.test.d.ts +1 -0
- package/dist/utils/__tests__/{knowledge-store.test.js → vault.test.js} +16 -16
- package/dist/utils/abort-signals.d.ts +13 -0
- package/dist/utils/abort-signals.js +33 -0
- package/dist/utils/db.d.ts +2 -0
- package/dist/utils/db.js +42 -0
- package/dist/utils/esm-dirname.d.ts +0 -15
- package/dist/utils/esm-dirname.js +0 -4
- package/dist/utils/inject-loop.d.ts +0 -27
- package/dist/utils/inject-loop.js +2 -56
- package/dist/utils/learn-tasks-db.d.ts +15 -0
- package/dist/utils/learn-tasks-db.js +35 -0
- package/dist/utils/openai-converter.js +88 -78
- package/dist/utils/pi-browser-tools.d.ts +0 -2
- package/dist/utils/pi-browser-tools.js +0 -174
- package/dist/utils/pi-crawl-tools.d.ts +2 -1
- package/dist/utils/pi-crawl-tools.js +16 -11
- package/dist/utils/pi-dedup.d.ts +8 -30
- package/dist/utils/pi-dedup.js +32 -67
- package/dist/utils/pi-events.d.ts +65 -0
- package/dist/utils/pi-events.js +98 -0
- package/dist/utils/pi-learn-orchestrator.d.ts +6 -5
- package/dist/utils/pi-learn-orchestrator.js +71 -106
- package/dist/utils/pi-runtime.d.ts +5 -6
- package/dist/utils/pi-runtime.js +38 -42
- package/dist/utils/pi-sandbox-ops.d.ts +0 -36
- package/dist/utils/pi-sandbox-ops.js +5 -190
- package/dist/utils/providers-db.d.ts +3 -0
- package/dist/utils/providers-db.js +21 -7
- package/dist/utils/request-writer.d.ts +60 -30
- package/dist/utils/request-writer.js +182 -61
- package/dist/utils/sse-protocol.d.ts +34 -0
- package/dist/utils/sse-protocol.js +121 -0
- package/dist/utils/sse-stream-processor.d.ts +4 -9
- package/dist/utils/sse-stream-processor.js +13 -104
- package/dist/utils/sse-writer.d.ts +1 -1
- package/dist/utils/sse-writer.js +3 -2
- package/dist/utils/stats-aggregator.js +3 -0
- package/dist/utils/tool-injector.js +7 -0
- package/dist/utils/vault.d.ts +111 -0
- package/dist/utils/vault.js +434 -0
- package/package.json +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BN6hB8Jr.js +0 -1
- package/dist/types/api.d.ts +0 -20
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -4
- package/dist/types/log.d.ts +0 -13
- package/dist/ui/assets/channel-BgAIihW-.js +0 -1
- package/dist/ui/assets/classDiagram-OUVF2IWQ-0fsnUoNI.js +0 -1
- package/dist/ui/assets/classDiagram-v2-EOCWNBFH-0fsnUoNI.js +0 -1
- package/dist/ui/assets/index-C0fxlU9d.css +0 -10
- package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-OA9dcuA9.js +0 -1
- package/dist/ui/assets/swimlanesDiagram-G3AALYLV-BLEnJU_R.js +0 -8
- package/dist/utils/__tests__/sse-accumulator.test.js +0 -83
- package/dist/utils/knowledge-store.d.ts +0 -44
- package/dist/utils/knowledge-store.js +0 -193
- package/dist/utils/migrate-files-to-db.d.ts +0 -1
- package/dist/utils/migrate-files-to-db.js +0 -138
- package/dist/utils/sse-accumulator.d.ts +0 -9
- package/dist/utils/sse-accumulator.js +0 -59
- /package/dist/docs/assets/{proxy-cli_data-hooks.md.w9Oe1OD3.lean.js → proxy-cli_data-hooks.md.Bz8CVbxO.lean.js} +0 -0
- /package/dist/docs/assets/{proxy-core_ui-routes.md.Dyqkeoiv.lean.js → proxy-core_ui-routes.md.B21t1p7R.lean.js} +0 -0
- /package/dist/{types/api.js → utils/__tests__/abort-signals.test.d.ts} +0 -0
- /package/dist/{types/log.js → utils/__tests__/learn-tasks-db.test.d.ts} +0 -0
- /package/dist/utils/__tests__/{knowledge-store.test.d.ts → openai-converter.test.d.ts} +0 -0
- /package/dist/utils/__tests__/{sse-accumulator.test.d.ts → pi-events.test.d.ts} +0 -0
|
@@ -1,48 +1,280 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { mkdtempSync, rmSync } from 'fs';
|
|
2
|
+
import { tmpdir } from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { configureDbPath, initDatabase, closeDatabase, getDatabase } from '../db.js';
|
|
5
|
+
import { createRequestRecorder, recordIteration, listRequestDB, interruptStaleRequestsOnStartup } from '../request-writer.js';
|
|
6
|
+
import { pushNewRequest } from '../../server/data-push.js';
|
|
7
|
+
jest.mock('../../server/data-push.js', () => ({ pushNewRequest: jest.fn() }));
|
|
8
|
+
const pushMock = pushNewRequest;
|
|
9
|
+
let counter = 0;
|
|
10
|
+
function makeMeta(tag) {
|
|
11
|
+
counter += 1;
|
|
12
|
+
const unique = `${tag}_${counter}`;
|
|
13
|
+
return {
|
|
14
|
+
sessionId: `sess_${unique}`,
|
|
15
|
+
localRequestId: `local_${unique}`,
|
|
16
|
+
timestamp: new Date().toISOString(),
|
|
17
|
+
apiKey: 'ak',
|
|
18
|
+
proxyApiKey: 'pak',
|
|
19
|
+
baseUrl: 'https://api.example.com',
|
|
20
|
+
model: 'claude-test',
|
|
21
|
+
machineId: 'mid',
|
|
22
|
+
request: {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
path: '/v1/messages',
|
|
25
|
+
headers: { 'user-agent': 'ua-test' },
|
|
26
|
+
body: { hello: 'world' }
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function getRequestRow(requestId, sessionId) {
|
|
31
|
+
return getDatabase()
|
|
32
|
+
.prepare('SELECT * FROM requests WHERE request_id = ? AND session_id = ?')
|
|
33
|
+
.get(requestId, sessionId);
|
|
34
|
+
}
|
|
35
|
+
function getEventRows(requestId, sessionId) {
|
|
36
|
+
return getDatabase()
|
|
37
|
+
.prepare('SELECT * FROM response_events WHERE request_id = ? AND session_id = ? ORDER BY event_index ASC')
|
|
38
|
+
.all(requestId, sessionId);
|
|
39
|
+
}
|
|
40
|
+
describe('request-writer', () => {
|
|
41
|
+
let tempDir;
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
tempDir = mkdtempSync(path.join(tmpdir(), 'ccm-rw-'));
|
|
44
|
+
configureDbPath(path.join(tempDir, 'test.db'));
|
|
5
45
|
initDatabase();
|
|
46
|
+
pushMock.mockClear();
|
|
6
47
|
});
|
|
7
|
-
|
|
48
|
+
afterEach(() => {
|
|
8
49
|
closeDatabase();
|
|
50
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
9
51
|
});
|
|
10
|
-
it('
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
52
|
+
it('pending() inserts a status_code=0 row and pushes pending', async () => {
|
|
53
|
+
const meta = makeMeta('pending');
|
|
54
|
+
const rec = createRequestRecorder(meta);
|
|
55
|
+
await rec.pending();
|
|
56
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
57
|
+
expect(row).toBeTruthy();
|
|
58
|
+
expect(row.status_code).toBe(0);
|
|
59
|
+
expect(pushMock).toHaveBeenCalledTimes(1);
|
|
60
|
+
expect(pushMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
61
|
+
requestId: meta.localRequestId,
|
|
62
|
+
compositeId: `${meta.sessionId}:${meta.localRequestId}`,
|
|
63
|
+
statusCode: 0,
|
|
64
|
+
responseTime: 0
|
|
65
|
+
}), 'pending');
|
|
66
|
+
});
|
|
67
|
+
it('appendEvent flushes full batches of 10; complete flushes residual events with continuous indexes', async () => {
|
|
68
|
+
const meta = makeMeta('append');
|
|
69
|
+
const rec = createRequestRecorder(meta);
|
|
70
|
+
for (let i = 0; i < 12; i++) {
|
|
71
|
+
rec.appendEvent({ type: 'chunk', seq: i });
|
|
72
|
+
}
|
|
73
|
+
// Without complete: only the full first batch is persisted, indexes 0..9
|
|
74
|
+
let rows = getEventRows(meta.localRequestId, meta.sessionId);
|
|
75
|
+
expect(rows).toHaveLength(10);
|
|
76
|
+
expect(rows.map(r => r.event_index)).toEqual(Array.from({ length: 10 }, (_, i) => i));
|
|
77
|
+
// Residual 2 events land at complete, full sequence 0..11 continuous
|
|
78
|
+
await rec.complete({ statusCode: 200 });
|
|
79
|
+
rows = getEventRows(meta.localRequestId, meta.sessionId);
|
|
80
|
+
expect(rows).toHaveLength(12);
|
|
81
|
+
expect(rows.map(r => r.event_index)).toEqual(Array.from({ length: 12 }, (_, i) => i));
|
|
82
|
+
});
|
|
83
|
+
it('complete() with same id writes completed row, token columns and pushes completed without pendingId', async () => {
|
|
84
|
+
const meta = makeMeta('same');
|
|
85
|
+
const rec = createRequestRecorder(meta);
|
|
86
|
+
await rec.pending();
|
|
87
|
+
rec.appendEvent({ type: 'message_start' });
|
|
88
|
+
rec.appendEvent({
|
|
89
|
+
type: 'message_delta',
|
|
90
|
+
usage: {
|
|
91
|
+
input_tokens: 10,
|
|
92
|
+
output_tokens: 5,
|
|
93
|
+
cache_read_input_tokens: 2,
|
|
94
|
+
cache_creation_input_tokens: 1
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
await rec.complete({ statusCode: 200, headers: { 'x-test': '1' } });
|
|
98
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
99
|
+
expect(row.status_code).toBe(200);
|
|
100
|
+
expect(row.completed_at).toBeTruthy();
|
|
101
|
+
expect(row.input_tokens).toBe(10);
|
|
102
|
+
expect(row.output_tokens).toBe(5);
|
|
103
|
+
expect(row.cache_read_tokens).toBe(2);
|
|
104
|
+
expect(row.cache_creation_tokens).toBe(1);
|
|
105
|
+
const completedCalls = pushMock.mock.calls.filter(c => c[1] === 'completed');
|
|
106
|
+
expect(completedCalls).toHaveLength(1);
|
|
107
|
+
const summary = completedCalls[0][0];
|
|
108
|
+
expect(summary.statusCode).toBe(200);
|
|
109
|
+
expect(summary.usage.output_tokens).toBe(5);
|
|
110
|
+
expect(summary.pendingId).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
it('complete() with a different cloud id moves requests row and events to cloud id and pushes pendingId', async () => {
|
|
113
|
+
const meta = makeMeta('cloud');
|
|
114
|
+
counter += 1;
|
|
115
|
+
const cloudId = `cloud_${counter}`;
|
|
116
|
+
const rec = createRequestRecorder(meta);
|
|
117
|
+
await rec.pending();
|
|
118
|
+
for (let i = 0; i < 12; i++) {
|
|
119
|
+
rec.appendEvent({ type: 'chunk', seq: i });
|
|
120
|
+
}
|
|
121
|
+
await rec.complete({ cloudRequestId: cloudId, statusCode: 200 });
|
|
122
|
+
// Local row and local events are gone
|
|
123
|
+
expect(getRequestRow(meta.localRequestId, meta.sessionId)).toBeFalsy();
|
|
124
|
+
expect(getEventRows(meta.localRequestId, meta.sessionId)).toHaveLength(0);
|
|
125
|
+
// Cloud row exists and is completed
|
|
126
|
+
const cloudRow = getRequestRow(cloudId, meta.sessionId);
|
|
127
|
+
expect(cloudRow).toBeTruthy();
|
|
128
|
+
expect(cloudRow.status_code).toBe(200);
|
|
129
|
+
// Events moved with continuous indexes
|
|
130
|
+
const cloudEvents = getEventRows(cloudId, meta.sessionId);
|
|
131
|
+
expect(cloudEvents).toHaveLength(12);
|
|
132
|
+
expect(cloudEvents.map(r => r.event_index)).toEqual(Array.from({ length: 12 }, (_, i) => i));
|
|
133
|
+
const completedCalls = pushMock.mock.calls.filter(c => c[1] === 'completed');
|
|
134
|
+
expect(completedCalls).toHaveLength(1);
|
|
135
|
+
const summary = completedCalls[0][0];
|
|
136
|
+
expect(summary.requestId).toBe(cloudId);
|
|
137
|
+
expect(summary.pendingId).toBe(`${meta.sessionId}:${meta.localRequestId}`);
|
|
138
|
+
});
|
|
139
|
+
it('fail() updates the pending row status/error/closed_at and pushes completed', async () => {
|
|
140
|
+
const meta = makeMeta('fail');
|
|
141
|
+
const rec = createRequestRecorder(meta);
|
|
142
|
+
await rec.pending();
|
|
143
|
+
rec.fail({ statusCode: 502, error: 'Target server unreachable' });
|
|
144
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
145
|
+
expect(row).toBeTruthy();
|
|
146
|
+
expect(row.status_code).toBe(502);
|
|
147
|
+
expect(row.error).toBe('Target server unreachable');
|
|
148
|
+
expect(row.closed_at).toBeTruthy();
|
|
149
|
+
expect(row.status_code).not.toBe(0);
|
|
150
|
+
const completedCalls = pushMock.mock.calls.filter(c => c[1] === 'completed');
|
|
151
|
+
expect(completedCalls).toHaveLength(1);
|
|
152
|
+
});
|
|
153
|
+
it('recordIteration() writes a completed row and events with no pending row', async () => {
|
|
154
|
+
const meta = makeMeta('iter');
|
|
155
|
+
await recordIteration({
|
|
156
|
+
...meta,
|
|
25
157
|
response: {
|
|
26
158
|
statusCode: 200,
|
|
27
|
-
events: [
|
|
159
|
+
events: [
|
|
160
|
+
{ type: 'message', content: [] },
|
|
161
|
+
{ type: 'message_delta', usage: { output_tokens: 7 } }
|
|
162
|
+
]
|
|
28
163
|
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
expect(
|
|
33
|
-
expect(
|
|
34
|
-
expect(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
expect(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
164
|
+
});
|
|
165
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
166
|
+
expect(row).toBeTruthy();
|
|
167
|
+
expect(row.status_code).toBe(200);
|
|
168
|
+
expect(row.completed_at).toBeTruthy();
|
|
169
|
+
expect(getEventRows(meta.localRequestId, meta.sessionId)).toHaveLength(2);
|
|
170
|
+
const pendingCount = getDatabase()
|
|
171
|
+
.prepare('SELECT COUNT(*) AS c FROM requests WHERE status_code = 0')
|
|
172
|
+
.get().c;
|
|
173
|
+
expect(pendingCount).toBe(0);
|
|
174
|
+
const completedCalls = pushMock.mock.calls.filter(c => c[1] === 'completed');
|
|
175
|
+
expect(completedCalls).toHaveLength(1);
|
|
176
|
+
expect(completedCalls[0][0].pendingId).toBeUndefined();
|
|
177
|
+
});
|
|
178
|
+
it('complete() synthesizes message_delta from OpenAI usage and persists output_tokens=42', async () => {
|
|
179
|
+
const meta = makeMeta('openai');
|
|
180
|
+
const rec = createRequestRecorder(meta);
|
|
181
|
+
await rec.pending();
|
|
182
|
+
rec.appendEvent({ type: 'chat.completion.chunk' });
|
|
183
|
+
rec.appendEvent({ type: 'chat.completion', usage: { prompt_tokens: 8, completion_tokens: 42 } });
|
|
184
|
+
await rec.complete({ statusCode: 200 });
|
|
185
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
186
|
+
expect(row.output_tokens).toBe(42);
|
|
187
|
+
const deltaRows = getEventRows(meta.localRequestId, meta.sessionId)
|
|
188
|
+
.filter(r => r.event_type === 'message_delta');
|
|
189
|
+
expect(deltaRows.length).toBeGreaterThan(0);
|
|
190
|
+
const parsed = JSON.parse(deltaRows[deltaRows.length - 1].event_data);
|
|
191
|
+
expect(parsed.type).toBe('message_delta');
|
|
192
|
+
expect(parsed.usage.output_tokens).toBe(42);
|
|
193
|
+
});
|
|
194
|
+
it('headers() writes real status_code while completed_at stays NULL and pushes pending', async () => {
|
|
195
|
+
const meta = makeMeta('headers');
|
|
196
|
+
const rec = createRequestRecorder(meta);
|
|
197
|
+
await rec.pending();
|
|
198
|
+
pushMock.mockClear();
|
|
199
|
+
rec.headers({ statusCode: 200, headers: { 'content-type': 'text/event-stream' } });
|
|
200
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
201
|
+
expect(row.status_code).toBe(200);
|
|
202
|
+
expect(row.completed_at).toBeNull();
|
|
203
|
+
expect(row.closed_at).toBeNull();
|
|
204
|
+
expect(JSON.parse(row.response_headers)['content-type']).toBe('text/event-stream');
|
|
205
|
+
expect(pushMock).toHaveBeenCalledTimes(1);
|
|
206
|
+
expect(pushMock).toHaveBeenCalledWith(expect.objectContaining({ requestId: meta.localRequestId, statusCode: 200, responseTime: 0 }), 'pending');
|
|
207
|
+
});
|
|
208
|
+
it('interrupt() flushes events and writes closed_at/error keeping passed status_code, pushes completed with closedAt', async () => {
|
|
209
|
+
const meta = makeMeta('interrupt');
|
|
210
|
+
const rec = createRequestRecorder(meta);
|
|
211
|
+
await rec.pending();
|
|
212
|
+
rec.appendEvent({ type: 'chunk', seq: 1 });
|
|
213
|
+
pushMock.mockClear();
|
|
214
|
+
rec.interrupt({ statusCode: 200, error: 'stream interrupted: socket hang up' });
|
|
215
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
216
|
+
expect(row.status_code).toBe(200);
|
|
217
|
+
expect(row.completed_at).toBeNull();
|
|
218
|
+
expect(row.closed_at).toBeTruthy();
|
|
219
|
+
expect(row.error).toBe('stream interrupted: socket hang up');
|
|
220
|
+
// 剩余事件在中断前已落库
|
|
221
|
+
expect(getEventRows(meta.localRequestId, meta.sessionId)).toHaveLength(1);
|
|
222
|
+
expect(pushMock).toHaveBeenCalledTimes(1);
|
|
223
|
+
expect(pushMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
224
|
+
requestId: meta.localRequestId,
|
|
225
|
+
statusCode: 200,
|
|
226
|
+
closedAt: expect.any(String)
|
|
227
|
+
}), 'completed');
|
|
228
|
+
});
|
|
229
|
+
it('listRequestDB maps closed_at to closedAt only on closed rows', async () => {
|
|
230
|
+
const closedMeta = makeMeta('listclosed');
|
|
231
|
+
const closedRec = createRequestRecorder(closedMeta);
|
|
232
|
+
await closedRec.pending();
|
|
233
|
+
closedRec.interrupt({ statusCode: 200, error: 'boom' });
|
|
234
|
+
const openMeta = makeMeta('listopen');
|
|
235
|
+
const openRec = createRequestRecorder(openMeta);
|
|
236
|
+
await openRec.pending();
|
|
237
|
+
await openRec.complete({ statusCode: 200 });
|
|
238
|
+
const { requests } = await listRequestDB();
|
|
239
|
+
const closed = requests.find(r => r.requestId === closedMeta.localRequestId);
|
|
240
|
+
const open = requests.find(r => r.requestId === openMeta.localRequestId);
|
|
241
|
+
expect(closed).toBeTruthy();
|
|
242
|
+
expect(closed.closedAt).toEqual(expect.any(String));
|
|
243
|
+
expect(open).toBeTruthy();
|
|
244
|
+
expect(open.closedAt).toBeUndefined();
|
|
245
|
+
});
|
|
246
|
+
it('interruptStaleRequestsOnStartup() marks aged orphan rows and leaves fresh/completed rows untouched', async () => {
|
|
247
|
+
// 超龄无主行:已收到响应头(200),时间戳回拨到 6 分钟前
|
|
248
|
+
const oldMeta = makeMeta('staleold');
|
|
249
|
+
const oldRec = createRequestRecorder(oldMeta);
|
|
250
|
+
await oldRec.pending();
|
|
251
|
+
oldRec.headers({ statusCode: 200 });
|
|
252
|
+
getDatabase()
|
|
253
|
+
.prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
|
|
254
|
+
.run(new Date(Date.now() - 6 * 60000).toISOString(), oldMeta.localRequestId, oldMeta.sessionId);
|
|
255
|
+
// 刚 pending 的新行,不超龄
|
|
256
|
+
const freshMeta = makeMeta('stalefresh');
|
|
257
|
+
const freshRec = createRequestRecorder(freshMeta);
|
|
258
|
+
await freshRec.pending();
|
|
259
|
+
// 超龄但已完成的行
|
|
260
|
+
const doneMeta = makeMeta('staledone');
|
|
261
|
+
const doneRec = createRequestRecorder(doneMeta);
|
|
262
|
+
await doneRec.pending();
|
|
263
|
+
await doneRec.complete({ statusCode: 200 });
|
|
264
|
+
getDatabase()
|
|
265
|
+
.prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
|
|
266
|
+
.run(new Date(Date.now() - 6 * 60000).toISOString(), doneMeta.localRequestId, doneMeta.sessionId);
|
|
267
|
+
const count = interruptStaleRequestsOnStartup();
|
|
268
|
+
expect(count).toBe(1);
|
|
269
|
+
const oldRow = getRequestRow(oldMeta.localRequestId, oldMeta.sessionId);
|
|
270
|
+
expect(oldRow.closed_at).toBeTruthy();
|
|
271
|
+
expect(oldRow.error).toBe('interrupted: proxy restarted');
|
|
272
|
+
expect(oldRow.status_code).toBe(200);
|
|
273
|
+
expect(oldRow.completed_at).toBeNull();
|
|
274
|
+
const freshRow = getRequestRow(freshMeta.localRequestId, freshMeta.sessionId);
|
|
275
|
+
expect(freshRow.closed_at).toBeNull();
|
|
276
|
+
expect(freshRow.error).toBeNull();
|
|
277
|
+
const doneRow = getRequestRow(doneMeta.localRequestId, doneMeta.sessionId);
|
|
278
|
+
expect(doneRow.closed_at).toBeNull();
|
|
47
279
|
});
|
|
48
280
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createSSEDecoder, createEventDecoder, encodeSSEFrame } from '../sse-protocol.js';
|
|
2
|
+
// 收集分发帧的辅助
|
|
3
|
+
function collectFrames() {
|
|
4
|
+
const frames = [];
|
|
5
|
+
const decoder = createSSEDecoder((frame) => frames.push(frame));
|
|
6
|
+
return { frames, decoder };
|
|
7
|
+
}
|
|
8
|
+
describe('sse-protocol', () => {
|
|
9
|
+
// 样例 1:单帧含 event: + data:
|
|
10
|
+
it('1. 单帧含 event 和 data', () => {
|
|
11
|
+
const { frames, decoder } = collectFrames();
|
|
12
|
+
decoder.write('event: foo\ndata: {"a":1}\n\n');
|
|
13
|
+
expect(frames).toEqual([
|
|
14
|
+
{ event: 'foo', data: ['{"a":1}'], id: undefined }
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
// 样例 2:仅 data:(event 为 undefined)
|
|
18
|
+
it('2. 仅 data,event 缺省为 undefined', () => {
|
|
19
|
+
const { frames, decoder } = collectFrames();
|
|
20
|
+
decoder.write('data: {"b":2}\n\n');
|
|
21
|
+
expect(frames).toEqual([
|
|
22
|
+
{ event: undefined, data: ['{"b":2}'], id: undefined }
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
// 样例 3:一个 data 行切成 3 个 chunk(残帧缓冲)
|
|
26
|
+
it('3. data 行跨 3 个 chunk 恰好得到一帧', () => {
|
|
27
|
+
const { frames, decoder } = collectFrames();
|
|
28
|
+
decoder.write('dat');
|
|
29
|
+
expect(frames).toHaveLength(0);
|
|
30
|
+
decoder.write('a: {"x');
|
|
31
|
+
expect(frames).toHaveLength(0);
|
|
32
|
+
decoder.write('":1}\n\n');
|
|
33
|
+
expect(frames).toEqual([
|
|
34
|
+
{ event: undefined, data: ['{"x":1}'], id: undefined }
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
// 样例 4:CRLF 行尾 + 空行分帧
|
|
38
|
+
it('4. CRLF 行尾与空行分帧', () => {
|
|
39
|
+
const { frames, decoder } = collectFrames();
|
|
40
|
+
decoder.write('event: ping\r\ndata: {"y":2}\r\n\r\n');
|
|
41
|
+
expect(frames).toEqual([
|
|
42
|
+
{ event: 'ping', data: ['{"y":2}'], id: undefined }
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
// 样例 5:同一帧内多行 data:,数组保留两行,消费 join('\n') 正确
|
|
46
|
+
it('5. 多行 data 逐行保留且 join 正确', () => {
|
|
47
|
+
const { frames, decoder } = collectFrames();
|
|
48
|
+
decoder.write('data: line1\ndata: line2\n\n');
|
|
49
|
+
expect(frames).toHaveLength(1);
|
|
50
|
+
expect(frames[0].data).toEqual(['line1', 'line2']);
|
|
51
|
+
expect(frames[0].data.join('\n')).toBe('line1\nline2');
|
|
52
|
+
});
|
|
53
|
+
// 样例 6:注释行不产生帧
|
|
54
|
+
it('6. 注释行被忽略不产生帧', () => {
|
|
55
|
+
const { frames, decoder } = collectFrames();
|
|
56
|
+
decoder.write(': ping\n\n');
|
|
57
|
+
expect(frames).toHaveLength(0);
|
|
58
|
+
// 注释与真实帧混合时仅真实帧分发
|
|
59
|
+
decoder.write(': keep-alive\ndata: {"r":1}\n\n');
|
|
60
|
+
expect(frames).toEqual([
|
|
61
|
+
{ event: undefined, data: ['{"r":1}'], id: undefined }
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
// 样例 7:data: [DONE] → onDone 触发、onEvent 不触发
|
|
65
|
+
it('7. [DONE] 仅触发 onDone', () => {
|
|
66
|
+
const events = [];
|
|
67
|
+
let doneCount = 0;
|
|
68
|
+
const decoder = createEventDecoder((e) => events.push(e), () => { doneCount++; });
|
|
69
|
+
decoder.write('data: [DONE]\n\n');
|
|
70
|
+
expect(events).toHaveLength(0);
|
|
71
|
+
expect(doneCount).toBe(1);
|
|
72
|
+
});
|
|
73
|
+
// 样例 8:非 JSON data → onEvent 不触发、不抛错
|
|
74
|
+
it('8. 非 JSON data 静默跳过', () => {
|
|
75
|
+
const events = [];
|
|
76
|
+
const decoder = createEventDecoder((e) => events.push(e));
|
|
77
|
+
expect(() => {
|
|
78
|
+
decoder.write('data: not-json\n\n');
|
|
79
|
+
}).not.toThrow();
|
|
80
|
+
expect(events).toHaveLength(0);
|
|
81
|
+
});
|
|
82
|
+
// 样例 9:末尾帧无尾空行,write 不触发,end() 后恰好一帧
|
|
83
|
+
it('9. end() 输出末尾无空行的残留帧', () => {
|
|
84
|
+
const { frames, decoder } = collectFrames();
|
|
85
|
+
decoder.write('data: {"z":9}');
|
|
86
|
+
expect(frames).toHaveLength(0);
|
|
87
|
+
decoder.end();
|
|
88
|
+
expect(frames).toEqual([
|
|
89
|
+
{ event: undefined, data: ['{"z":9}'], id: undefined }
|
|
90
|
+
]);
|
|
91
|
+
});
|
|
92
|
+
// 样例 10:id: 行保留在帧 id 字段
|
|
93
|
+
it('10. id 字段保留在帧上', () => {
|
|
94
|
+
const { frames, decoder } = collectFrames();
|
|
95
|
+
decoder.write('id: e1\ndata: {"i":1}\n\n');
|
|
96
|
+
expect(frames).toEqual([
|
|
97
|
+
{ event: undefined, data: ['{"i":1}'], id: 'e1' }
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
// 样例 11:encodeSSEFrame → createSSEDecoder 往返一致(有/无 event)
|
|
101
|
+
it('11. encode/decode 往返帧内容一致(有 event 和无 event)', () => {
|
|
102
|
+
const withEvent = encodeSSEFrame({ event: 'foo', data: '{"x":1}' });
|
|
103
|
+
const d1 = collectFrames();
|
|
104
|
+
d1.decoder.write(withEvent);
|
|
105
|
+
expect(d1.frames).toEqual([
|
|
106
|
+
{ event: 'foo', data: ['{"x":1}'], id: undefined }
|
|
107
|
+
]);
|
|
108
|
+
const withoutEvent = encodeSSEFrame({ data: '{"x":2}' });
|
|
109
|
+
const d2 = collectFrames();
|
|
110
|
+
d2.decoder.write(withoutEvent);
|
|
111
|
+
expect(d2.frames).toEqual([
|
|
112
|
+
{ event: undefined, data: ['{"x":2}'], id: undefined }
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import {
|
|
4
|
+
import { createVault } from '../vault.js';
|
|
5
5
|
async function makeTmpDocs() {
|
|
6
6
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-test-'));
|
|
7
7
|
// 根 index.json
|
|
@@ -22,10 +22,10 @@ async function makeTmpDocs() {
|
|
|
22
22
|
await fs.writeFile(path.join(dir, 'react-hooks', 'chunks', 'chunk-001.md'), '# 状态管理:useState\n\nuseState 用于组件状态。', 'utf8');
|
|
23
23
|
return dir;
|
|
24
24
|
}
|
|
25
|
-
describe('
|
|
25
|
+
describe('vault', () => {
|
|
26
26
|
it('listTopics 解析根 index.json', async () => {
|
|
27
27
|
const root = await makeTmpDocs();
|
|
28
|
-
const store =
|
|
28
|
+
const store = createVault(root);
|
|
29
29
|
const topics = await store.listTopics();
|
|
30
30
|
expect(topics).toEqual([
|
|
31
31
|
{ slug: 'react-hooks', title: 'React Hooks 参考', summary: 'React hooks 用法', tags: ['react', 'hooks'] },
|
|
@@ -33,12 +33,12 @@ describe('knowledge-store', () => {
|
|
|
33
33
|
});
|
|
34
34
|
it('listTopics 根 index.json 不存在返回空数组', async () => {
|
|
35
35
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-empty-'));
|
|
36
|
-
const store =
|
|
36
|
+
const store = createVault(dir);
|
|
37
37
|
expect(await store.listTopics()).toEqual([]);
|
|
38
38
|
});
|
|
39
39
|
it('getTopic 返回元数据 + 片段清单', async () => {
|
|
40
40
|
const root = await makeTmpDocs();
|
|
41
|
-
const store =
|
|
41
|
+
const store = createVault(root);
|
|
42
42
|
const detail = await store.getTopic('react-hooks');
|
|
43
43
|
expect(detail.title).toBe('React Hooks 参考');
|
|
44
44
|
expect(detail.tags).toEqual(['react', 'hooks']);
|
|
@@ -46,25 +46,25 @@ describe('knowledge-store', () => {
|
|
|
46
46
|
});
|
|
47
47
|
it('readChunk 返回单片正文', async () => {
|
|
48
48
|
const root = await makeTmpDocs();
|
|
49
|
-
const store =
|
|
49
|
+
const store = createVault(root);
|
|
50
50
|
const text = await store.readChunk('react-hooks', 'chunk-001');
|
|
51
51
|
expect(text).toContain('useState 用于组件状态');
|
|
52
52
|
});
|
|
53
53
|
it('readChunk 拒绝路径穿越', async () => {
|
|
54
54
|
const root = await makeTmpDocs();
|
|
55
|
-
const store =
|
|
55
|
+
const store = createVault(root);
|
|
56
56
|
await expect(store.readChunk('..', 'chunk-001')).rejects.toThrow();
|
|
57
57
|
await expect(store.readChunk('react-hooks', '../chunk-001')).rejects.toThrow();
|
|
58
58
|
});
|
|
59
59
|
it('searchChunks 关键词命中返回清单', async () => {
|
|
60
60
|
const root = await makeTmpDocs();
|
|
61
|
-
const store =
|
|
61
|
+
const store = createVault(root);
|
|
62
62
|
const hits = await store.searchChunks('useState');
|
|
63
63
|
expect(hits).toEqual([{ topic: 'react-hooks', chunkId: 'chunk-001', title: '状态管理:useState' }]);
|
|
64
64
|
});
|
|
65
65
|
it('upsertTopic 两次不同 slug → listTopics 返回 2 条,不覆盖', async () => {
|
|
66
66
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-upsert-'));
|
|
67
|
-
const store =
|
|
67
|
+
const store = createVault(dir);
|
|
68
68
|
await store.upsertTopic({ slug: 'topic-a', title: 'A', summary: 'sa', tags: [] });
|
|
69
69
|
await store.upsertTopic({ slug: 'topic-b', title: 'B', summary: 'sb', tags: ['x'] });
|
|
70
70
|
const topics = await store.listTopics();
|
|
@@ -73,7 +73,7 @@ describe('knowledge-store', () => {
|
|
|
73
73
|
});
|
|
74
74
|
it('upsertTopic 同 slug 再调更新 summary,chunks 数组不变,listTopics 仍 1 条', async () => {
|
|
75
75
|
const root = await makeTmpDocs();
|
|
76
|
-
const store =
|
|
76
|
+
const store = createVault(root);
|
|
77
77
|
// 先登记一个额外 chunk ref
|
|
78
78
|
await store.upsertChunkRef({ topic: 'react-hooks', chunkId: 'chunk-002', title: 'T2', summary: 'S2', tags: [] });
|
|
79
79
|
await store.upsertTopic({
|
|
@@ -92,7 +92,7 @@ describe('knowledge-store', () => {
|
|
|
92
92
|
});
|
|
93
93
|
it('upsertChunkRef 同 topic 两次不同 chunk_id → getTopic 返回 2 chunks', async () => {
|
|
94
94
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-chunk-'));
|
|
95
|
-
const store =
|
|
95
|
+
const store = createVault(dir);
|
|
96
96
|
await store.upsertTopic({ slug: 't1', title: 'T1', summary: 's', tags: [] });
|
|
97
97
|
await store.upsertChunkRef({ topic: 't1', chunkId: 'chunk-001', title: 'c1', summary: 's1', tags: [] });
|
|
98
98
|
await store.upsertChunkRef({ topic: 't1', chunkId: 'chunk-002', title: 'c2', summary: 's2', tags: [] });
|
|
@@ -102,12 +102,12 @@ describe('knowledge-store', () => {
|
|
|
102
102
|
});
|
|
103
103
|
it('upsertChunkRef topic 不存在 → rejects', async () => {
|
|
104
104
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-no-topic-'));
|
|
105
|
-
const store =
|
|
105
|
+
const store = createVault(dir);
|
|
106
106
|
await expect(store.upsertChunkRef({ topic: 'ghost', chunkId: 'chunk-001', title: 'c', summary: 's', tags: [] })).rejects.toThrow(/topic not found: ghost/);
|
|
107
107
|
});
|
|
108
108
|
it('tags: upsertChunkRef 落盘 tags,重复调用覆写 tags', async () => {
|
|
109
109
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-tags-'));
|
|
110
|
-
const store =
|
|
110
|
+
const store = createVault(dir);
|
|
111
111
|
await store.upsertTopic({ slug: 't1', title: 'T1', summary: 's', tags: [] });
|
|
112
112
|
await store.upsertChunkRef({ topic: 't1', chunkId: 'chunk-001', title: 'c1', summary: 's1', tags: ['redis', '缓存'] });
|
|
113
113
|
// 落盘验证
|
|
@@ -128,7 +128,7 @@ describe('knowledge-store', () => {
|
|
|
128
128
|
slug: 'old', title: 'Old', summary: 'so', tags: [],
|
|
129
129
|
chunks: [{ id: 'chunk-001', title: 'oc', summary: 'os' }],
|
|
130
130
|
}), 'utf8');
|
|
131
|
-
const store =
|
|
131
|
+
const store = createVault(dir);
|
|
132
132
|
const detail = await store.getTopic('old');
|
|
133
133
|
expect(detail.chunks[0].tags).toEqual([]);
|
|
134
134
|
});
|
|
@@ -144,7 +144,7 @@ describe('knowledge-store', () => {
|
|
|
144
144
|
}), 'utf8');
|
|
145
145
|
await fs.writeFile(path.join(dir, 'redis', 'chunks', 'chunk-001.md'), '# 缓存详解\n\n正文讲缓存。', 'utf8');
|
|
146
146
|
await fs.writeFile(path.join(dir, 'redis', 'chunks', 'chunk-002.md'), '# 故障转移\n\n正文讲哨兵。', 'utf8');
|
|
147
|
-
const store =
|
|
147
|
+
const store = createVault(dir);
|
|
148
148
|
// body
|
|
149
149
|
expect(await store.searchChunks('缓存')).toEqual([
|
|
150
150
|
{ topic: 'redis', chunkId: 'chunk-001', title: '缓存详解' },
|
|
@@ -177,7 +177,7 @@ describe('knowledge-store', () => {
|
|
|
177
177
|
chunks: [{ id: 'chunk-001', title: '缓存详解', summary: '缓存摘要' }],
|
|
178
178
|
}), 'utf8');
|
|
179
179
|
await fs.writeFile(path.join(dir, 'redis', 'chunks', 'chunk-001.md'), '# 缓存详解\n\n正文讲缓存。', 'utf8');
|
|
180
|
-
const store =
|
|
180
|
+
const store = createVault(dir);
|
|
181
181
|
expect(await store.searchChunks('缓存')).toEqual([
|
|
182
182
|
{ topic: 'redis', chunkId: 'chunk-001', title: '缓存详解' },
|
|
183
183
|
]);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* 监听 Node IncomingMessage,客户端在响应结束前中止连接时 abort 返回的 signal。
|
|
4
|
+
* close 时以 req.aborted 区分:true=客户端侧中止;正常响应结束后的 close 为 false。
|
|
5
|
+
*/
|
|
6
|
+
export declare function createClientAbortSignal(req: IncomingMessage): AbortSignal;
|
|
7
|
+
/** 合成:固定超时 + 外部取消,任意触发即 abort */
|
|
8
|
+
export declare function createTimeoutSignal(ms: number, external?: AbortSignal): AbortSignal;
|
|
9
|
+
/**
|
|
10
|
+
* 把 source signal 接到 abortFn:source 已 abort 立即调,否则将来 abort 时调一次。
|
|
11
|
+
* abortFn 异步拒绝不外泄(由调用方保证幂等)。
|
|
12
|
+
*/
|
|
13
|
+
export declare function bindAbort(source: AbortSignal, abortFn: () => Promise<void>): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 监听 Node IncomingMessage,客户端在响应结束前中止连接时 abort 返回的 signal。
|
|
3
|
+
* close 时以 req.aborted 区分:true=客户端侧中止;正常响应结束后的 close 为 false。
|
|
4
|
+
*/
|
|
5
|
+
export function createClientAbortSignal(req) {
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
// 传入时已 aborted(或已 close 过):立即 abort,不再依赖 close 事件
|
|
8
|
+
if (req.aborted) {
|
|
9
|
+
controller.abort();
|
|
10
|
+
return controller.signal;
|
|
11
|
+
}
|
|
12
|
+
req.on('close', () => {
|
|
13
|
+
if (req.aborted)
|
|
14
|
+
controller.abort();
|
|
15
|
+
});
|
|
16
|
+
return controller.signal;
|
|
17
|
+
}
|
|
18
|
+
/** 合成:固定超时 + 外部取消,任意触发即 abort */
|
|
19
|
+
export function createTimeoutSignal(ms, external) {
|
|
20
|
+
return AbortSignal.any([AbortSignal.timeout(ms), ...(external ? [external] : [])]);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 把 source signal 接到 abortFn:source 已 abort 立即调,否则将来 abort 时调一次。
|
|
24
|
+
* abortFn 异步拒绝不外泄(由调用方保证幂等)。
|
|
25
|
+
*/
|
|
26
|
+
export function bindAbort(source, abortFn) {
|
|
27
|
+
if (source.aborted) {
|
|
28
|
+
abortFn().catch(() => { });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
source.addEventListener('abort', () => abortFn().catch(() => { }), { once: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
package/dist/utils/db.d.ts
CHANGED
|
@@ -9,5 +9,7 @@ export declare function applySchemaV6(db: Database.Database): void;
|
|
|
9
9
|
export declare function applySchemaV7(db: Database.Database): void;
|
|
10
10
|
export declare function applySchemaV8(db: Database.Database): void;
|
|
11
11
|
export declare function applySchemaV9(db: Database.Database): void;
|
|
12
|
+
export declare function applySchemaV10(db: Database.Database): void;
|
|
13
|
+
export declare function applySchemaV11(db: Database.Database): void;
|
|
12
14
|
export declare function closeDatabase(): void;
|
|
13
15
|
export declare function getDatabase(): Database.Database;
|