@cc-monitoring/proxy-core 0.4.0 → 0.4.2

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.
Files changed (326) hide show
  1. package/dist/cli/__tests__/commands.test.js +19 -0
  2. package/dist/cli/commands/repair.d.ts +1 -0
  3. package/dist/cli/commands/repair.js +15 -0
  4. package/dist/cli/index.js +30 -0
  5. package/dist/docs/404.html +1 -1
  6. package/dist/docs/assets/{app.DtYNT5zS.js → app.vxoC6jUT.js} +1 -1
  7. package/dist/docs/assets/chunks/@localSearchIndexroot.lKm6N3Ik.js +1 -0
  8. package/dist/docs/assets/chunks/{VPLocalSearchBox.DpXF9ZAI.js → VPLocalSearchBox.DrfGpz86.js} +1 -1
  9. package/dist/docs/assets/chunks/{theme.TS_gUyc7.js → theme.CCmzbSn9.js} +2 -2
  10. package/dist/docs/index.html +2 -2
  11. package/dist/docs/overview.html +2 -2
  12. package/dist/docs/packages-utils.html +2 -2
  13. package/dist/docs/proxy-admin/env-and-build.html +2 -2
  14. package/dist/docs/proxy-admin/index.html +2 -2
  15. package/dist/docs/proxy-admin/layout-and-menu.html +2 -2
  16. package/dist/docs/proxy-admin/pages.html +2 -2
  17. package/dist/docs/proxy-admin/routing-and-auth.html +2 -2
  18. package/dist/docs/proxy-admin/services-and-mock.html +2 -2
  19. package/dist/docs/proxy-cli/api-layer.html +2 -2
  20. package/dist/docs/proxy-cli/app-shell.html +2 -2
  21. package/dist/docs/proxy-cli/components-common.html +2 -2
  22. package/dist/docs/proxy-cli/components-config.html +2 -2
  23. package/dist/docs/proxy-cli/components-requests.html +2 -2
  24. package/dist/docs/proxy-cli/components-stats.html +2 -2
  25. package/dist/docs/proxy-cli/components-status.html +2 -2
  26. package/dist/docs/proxy-cli/data-hooks.html +2 -2
  27. package/dist/docs/proxy-cli/index.html +2 -2
  28. package/dist/docs/proxy-cli/utils-and-i18n.html +2 -2
  29. package/dist/docs/proxy-core/chat-api.html +2 -2
  30. package/dist/docs/proxy-core/cli.html +2 -2
  31. package/dist/docs/proxy-core/config.html +2 -2
  32. package/dist/docs/proxy-core/data-layer.html +2 -2
  33. package/dist/docs/proxy-core/entry.html +2 -2
  34. package/dist/docs/proxy-core/index.html +2 -2
  35. package/dist/docs/proxy-core/pm2.html +2 -2
  36. package/dist/docs/proxy-core/providers.html +2 -2
  37. package/dist/docs/proxy-core/proxy.html +2 -2
  38. package/dist/docs/proxy-core/stats.html +2 -2
  39. package/dist/docs/proxy-core/tool.html +2 -2
  40. package/dist/docs/proxy-core/ui-routes.html +2 -2
  41. package/dist/docs/proxy-core/utilities.html +2 -2
  42. package/dist/docs/quickstart.html +2 -2
  43. package/dist/docs/request-flow.html +2 -2
  44. package/dist/server/__tests__/data-push.test.js +44 -0
  45. package/dist/server/__tests__/proxy.test.js +365 -2
  46. package/dist/server/__tests__/server.test.js +15 -10
  47. package/dist/server/__tests__/ui-route.test.js +43 -0
  48. package/dist/server/chat-route.js +3 -3
  49. package/dist/server/data-push.d.ts +2 -0
  50. package/dist/server/data-push.js +22 -0
  51. package/dist/server/index.js +4 -3
  52. package/dist/server/proxy.js +127 -23
  53. package/dist/server/ui-route.js +8 -1
  54. package/dist/ui/assets/{1c-CCx-FOtz.js → 1c-BdWVxVKv.js} +1 -1
  55. package/dist/ui/assets/{abnf-Bwbi42Z8.js → abnf-hHBtR6kN.js} +1 -1
  56. package/dist/ui/assets/{abnfDiagram-VRR7QNED-Dm1-8q0E.js → abnfDiagram-VRR7QNED-B0E8zzLB.js} +1 -1
  57. package/dist/ui/assets/{accesslog-DCytz_-m.js → accesslog-BZmvPz-q.js} +1 -1
  58. package/dist/ui/assets/{actionscript-3udf15oh.js → actionscript-XtUFCVQF.js} +1 -1
  59. package/dist/ui/assets/{ada-M5Xa74xG.js → ada-DDsWH3P3.js} +1 -1
  60. package/dist/ui/assets/{angelscript-qlrFxfW-.js → angelscript-CGLPsuae.js} +1 -1
  61. package/dist/ui/assets/{apache-B-sF-DQH.js → apache-eGe6GEfx.js} +1 -1
  62. package/dist/ui/assets/{applescript-4FdcBnbI.js → applescript-Ch2Hdkz_.js} +1 -1
  63. package/dist/ui/assets/{arc-BB3K1enp.js → arc-Bi_rJJly.js} +1 -1
  64. package/dist/ui/assets/{arcade-B_yqWaaG.js → arcade-BAeq_5jU.js} +1 -1
  65. package/dist/ui/assets/{architectureDiagram-ZJ3FMSHR-B39vkY8V.js → architectureDiagram-ZJ3FMSHR-D3rUGwDS.js} +1 -1
  66. package/dist/ui/assets/{arduino-yAcBoTWU.js → arduino-DJR8nayp.js} +1 -1
  67. package/dist/ui/assets/{armasm-wIcY84bI.js → armasm-DqDD_ZDN.js} +1 -1
  68. package/dist/ui/assets/{asciidoc-Bxnu28-Z.js → asciidoc-CgBdhmR5.js} +1 -1
  69. package/dist/ui/assets/{aspectj-B2cKJA_O.js → aspectj-Dbm_6mY0.js} +1 -1
  70. package/dist/ui/assets/{autohotkey-B_rMWTtE.js → autohotkey-BkbluSS8.js} +1 -1
  71. package/dist/ui/assets/{autoit-DwcuvMwv.js → autoit-qUepyZBT.js} +1 -1
  72. package/dist/ui/assets/{avrasm-heGX1PXK.js → avrasm-2snPxE5L.js} +1 -1
  73. package/dist/ui/assets/{awk-BiqCQAEf.js → awk-oPcQTdJK.js} +1 -1
  74. package/dist/ui/assets/{axapta-Cv8M-Ih6.js → axapta-CCu7hfQ-.js} +1 -1
  75. package/dist/ui/assets/{bash-Divgn3JV.js → bash-CmWN3rPR.js} +1 -1
  76. package/dist/ui/assets/{basic-B9dnAP55.js → basic-Dg8KcxuC.js} +1 -1
  77. package/dist/ui/assets/{blockDiagram-677ZJIJ3-DQ1mf7A_.js → blockDiagram-677ZJIJ3-Dusa5zr8.js} +1 -1
  78. package/dist/ui/assets/{bnf-kIL5mSsP.js → bnf-Zmfygl9A.js} +1 -1
  79. package/dist/ui/assets/{brainfuck-DqB7xDjH.js → brainfuck-K4-sBD6i.js} +1 -1
  80. package/dist/ui/assets/{coq-6aKUn3zn.js → c-BGtacyfP.js} +1 -1
  81. package/dist/ui/assets/{c-like-De5MhE7G.js → c-like-FRDCqB2X.js} +1 -1
  82. package/dist/ui/assets/{c4Diagram-LMCZKHZV-DQxtYsAx.js → c4Diagram-LMCZKHZV-C4Vo5RAI.js} +1 -1
  83. package/dist/ui/assets/{cal-mYdfz9en.js → cal-BibfnL8Q.js} +1 -1
  84. package/dist/ui/assets/{capnproto-D04CRlOt.js → capnproto-D4sJA0ax.js} +1 -1
  85. package/dist/ui/assets/{ceylon-pi9lFCja.js → ceylon-DuI5ot57.js} +1 -1
  86. package/dist/ui/assets/channel-CviGbONW.js +1 -0
  87. package/dist/ui/assets/{chunk-2Q5K7J3B-BDD1nPt8.js → chunk-2Q5K7J3B-C0UTs_LW.js} +1 -1
  88. package/dist/ui/assets/{chunk-32BRIVSS-DgeQFBb5.js → chunk-32BRIVSS-BMqPhehD.js} +1 -1
  89. package/dist/ui/assets/{chunk-5VM5RSS4-C7Xf2SnI.js → chunk-5VM5RSS4-CKN4Vw1w.js} +1 -1
  90. package/dist/ui/assets/{chunk-EX3LRPZG-B_3p1HEv.js → chunk-EX3LRPZG-BYHH22fs.js} +1 -1
  91. package/dist/ui/assets/{chunk-JWPE2WC7-Dvs7u8eY.js → chunk-JWPE2WC7-b55Vo5mo.js} +1 -1
  92. package/dist/ui/assets/{chunk-MOJQB5TN-By21wDCN.js → chunk-MOJQB5TN-CvtnHxi4.js} +1 -1
  93. package/dist/ui/assets/{chunk-RYQCIY6F-6NC700r1.js → chunk-RYQCIY6F-Bi9_eYfb.js} +1 -1
  94. package/dist/ui/assets/{chunk-V7JOEXUC-vEUqZ3rh.js → chunk-V7JOEXUC-C3ujZT8O.js} +1 -1
  95. package/dist/ui/assets/{chunk-VR4S4FIN-6tX2dNN9.js → chunk-VR4S4FIN-D4pfDh-j.js} +1 -1
  96. package/dist/ui/assets/{chunk-XXDRQBXY-qDb2jxAR.js → chunk-XXDRQBXY-DQAjg3aO.js} +1 -1
  97. package/dist/ui/assets/classDiagram-OUVF2IWQ-BlFcpmEd.js +1 -0
  98. package/dist/ui/assets/classDiagram-v2-EOCWNBFH-BlFcpmEd.js +1 -0
  99. package/dist/ui/assets/{clean-CLCsDUl1.js → clean-BVmfQBBV.js} +1 -1
  100. package/dist/ui/assets/{clojure-dVi5Xifj.js → clojure-BN3C1fnU.js} +1 -1
  101. package/dist/ui/assets/{clojure-repl-BO_FpFmF.js → clojure-repl-C7Gg0UPF.js} +1 -1
  102. package/dist/ui/assets/{cmake-CP5aJbyK.js → cmake-CTQFRyul.js} +1 -1
  103. package/dist/ui/assets/{coffeescript-ZEfu_9pF.js → coffeescript-DGWU3k3W.js} +1 -1
  104. package/dist/ui/assets/{c-HmsCM6sx.js → coq-Bv9CXGly.js} +1 -1
  105. package/dist/ui/assets/{cos-BohCnRN2.js → cos-JXNeNyLn.js} +1 -1
  106. package/dist/ui/assets/{cose-bilkent-JH36ORCC-DyXCbbro.js → cose-bilkent-JH36ORCC-B2e-rPVN.js} +1 -1
  107. package/dist/ui/assets/{cpp-FOgPWPEt.js → cpp-DVS4QZP-.js} +1 -1
  108. package/dist/ui/assets/{crmsh-B0OYb-Oe.js → crmsh-CFD7lrxk.js} +1 -1
  109. package/dist/ui/assets/{crystal-CuWb_dXm.js → crystal-D7vS7dho.js} +1 -1
  110. package/dist/ui/assets/{csharp-BbcqM09j.js → csharp-BgeVxKaT.js} +1 -1
  111. package/dist/ui/assets/{csp-rgHnTaDq.js → csp-CmAkjXvi.js} +1 -1
  112. package/dist/ui/assets/{css-C-ibh7ZG.js → css-DtEkZRCq.js} +1 -1
  113. package/dist/ui/assets/{cynefin-VYW2F7L2-CkuBhgC4.js → cynefin-VYW2F7L2-bhneX2QQ.js} +1 -1
  114. package/dist/ui/assets/{cynefinDiagram-TSTJHNR4-eycFyDao.js → cynefinDiagram-TSTJHNR4-BvtRhsVM.js} +1 -1
  115. package/dist/ui/assets/{d-C0gXxJ0O.js → d-ov1MlWJx.js} +1 -1
  116. package/dist/ui/assets/{dagre-VKFMJZFB-DzqX9e49.js → dagre-VKFMJZFB-nxN1qAKi.js} +1 -1
  117. package/dist/ui/assets/{dart-C-fTpuUV.js → dart-DX6s604e.js} +1 -1
  118. package/dist/ui/assets/{delphi-Chm0B9xv.js → delphi-D0oU57Jt.js} +1 -1
  119. package/dist/ui/assets/{diagram-FQU43EPY-BEgCqfg1.js → diagram-FQU43EPY-CWkdysy2.js} +1 -1
  120. package/dist/ui/assets/{diagram-G47NLZAW-Cw11Vs2v.js → diagram-G47NLZAW-L7jPJ-r1.js} +1 -1
  121. package/dist/ui/assets/{diagram-NH7WQ7WH-DSG_UWpP.js → diagram-NH7WQ7WH-fhOp1mr9.js} +1 -1
  122. package/dist/ui/assets/{diagram-OA4YK3LP-CJz-WOaJ.js → diagram-OA4YK3LP-B7UMZ-5d.js} +1 -1
  123. package/dist/ui/assets/{diagram-WEI45ONY-CBXjBCvc.js → diagram-WEI45ONY-B-toJrZu.js} +1 -1
  124. package/dist/ui/assets/{diff-C-vyuMSL.js → diff-CAzYRr8i.js} +1 -1
  125. package/dist/ui/assets/{django-CJK7lReT.js → django-DPFPMDLP.js} +1 -1
  126. package/dist/ui/assets/{dns-D02SHjSy.js → dns-kf25JjIl.js} +1 -1
  127. package/dist/ui/assets/{dockerfile-trKL1Bqu.js → dockerfile-GP7x-Ptc.js} +1 -1
  128. package/dist/ui/assets/{dos-C52dy-sK.js → dos-DlXSYH18.js} +1 -1
  129. package/dist/ui/assets/{dsconfig-MzHgzVrt.js → dsconfig-DrXWPwZN.js} +1 -1
  130. package/dist/ui/assets/{dts-B137dHyX.js → dts-B9QdwGCS.js} +1 -1
  131. package/dist/ui/assets/{dust-BcbM0r5C.js → dust-CdplAbVJ.js} +1 -1
  132. package/dist/ui/assets/{ebnf-DkbpI7bK.js → ebnf-BdCAaKiH.js} +1 -1
  133. package/dist/ui/assets/{ebnfDiagram-CCIWWBDH-aI5uQ7w1.js → ebnfDiagram-CCIWWBDH-l1kXQDgJ.js} +1 -1
  134. package/dist/ui/assets/{elixir-BOtDuqQy.js → elixir-WekcTu8c.js} +1 -1
  135. package/dist/ui/assets/{elm-DS8Tyyu-.js → elm-DKbQ6Ryz.js} +1 -1
  136. package/dist/ui/assets/{erDiagram-Q63AITRT-2vCbd4Mm.js → erDiagram-Q63AITRT-CCeU8uXM.js} +1 -1
  137. package/dist/ui/assets/{erb-CSXECRbn.js → erb-DvQ2S9Q6.js} +1 -1
  138. package/dist/ui/assets/{erlang-BMxlgKPJ.js → erlang-C4cAvWW8.js} +1 -1
  139. package/dist/ui/assets/{erlang-repl-DPzlolPg.js → erlang-repl-BMKmccqv.js} +1 -1
  140. package/dist/ui/assets/{excel-CW2Uxed3.js → excel-Cz9AeP71.js} +1 -1
  141. package/dist/ui/assets/{fix-UW84SlCs.js → fix-BFQxvtLz.js} +1 -1
  142. package/dist/ui/assets/{flix-4UGiPULd.js → flix-D3TgVgHZ.js} +1 -1
  143. package/dist/ui/assets/{flowDiagram-23GEKE2U-R5PbCbZI.js → flowDiagram-23GEKE2U-CWYmXYgW.js} +1 -1
  144. package/dist/ui/assets/{fortran-nhHsngdu.js → fortran-CWmklLY8.js} +1 -1
  145. package/dist/ui/assets/{fsharp-CDyZ1ST5.js → fsharp-V2Eau__H.js} +1 -1
  146. package/dist/ui/assets/{gams-DHzkkUie.js → gams-KwiGZby9.js} +1 -1
  147. package/dist/ui/assets/{ganttDiagram-NO4QXBWP-CGcq4Ie_.js → ganttDiagram-NO4QXBWP-Bw1obkG5.js} +1 -1
  148. package/dist/ui/assets/{gauss-DdhFGyBv.js → gauss-CRI2FQhz.js} +1 -1
  149. package/dist/ui/assets/{gcode-ImHh8OLA.js → gcode-Bh0f80Zp.js} +1 -1
  150. package/dist/ui/assets/{gherkin-CTWAW-XR.js → gherkin-DEjcfzYc.js} +1 -1
  151. package/dist/ui/assets/{gitGraphDiagram-IHSO6WYX-8nM2Z9lD.js → gitGraphDiagram-IHSO6WYX-OfPpsImd.js} +1 -1
  152. package/dist/ui/assets/{glsl-0_W_27G2.js → glsl-BrYpeP6h.js} +1 -1
  153. package/dist/ui/assets/{gml-BoDggu2z.js → gml-m818HPGO.js} +1 -1
  154. package/dist/ui/assets/{go-DYfFygwl.js → go-BX431CyL.js} +1 -1
  155. package/dist/ui/assets/{golo-Mlq-TCg9.js → golo-D4m-zAId.js} +1 -1
  156. package/dist/ui/assets/{gradle-BALAinEB.js → gradle-Cq6wabEA.js} +1 -1
  157. package/dist/ui/assets/{groovy-Dht--dWU.js → groovy-TjcpUbQS.js} +1 -1
  158. package/dist/ui/assets/{haml-DpMrxg0d.js → haml-DrCR34E5.js} +1 -1
  159. package/dist/ui/assets/{handlebars-DqHzujNj.js → handlebars-CVS3O2t9.js} +1 -1
  160. package/dist/ui/assets/{haskell-DZSchhkY.js → haskell-B8q0nv1_.js} +1 -1
  161. package/dist/ui/assets/{haxe-C0zBRjdy.js → haxe-DXPsSA4C.js} +1 -1
  162. package/dist/ui/assets/{hsp-D_PwbSv2.js → hsp-CzpLpdV9.js} +1 -1
  163. package/dist/ui/assets/{htmlbars-DxAFMG8u.js → htmlbars-Cs_-HclQ.js} +1 -1
  164. package/dist/ui/assets/{http-DvTRF0Do.js → http-CHD9pG0v.js} +1 -1
  165. package/dist/ui/assets/{hy-lVO-kNyp.js → hy-B-a7tYC4.js} +1 -1
  166. package/dist/ui/assets/{index-Ci23SMK2.js → index-CJEzcYeX.js} +21 -21
  167. package/dist/ui/assets/{index-BbMRVVGt.js → index-DBPwq3d-.js} +3 -3
  168. package/dist/ui/assets/{infoDiagram-FWYZ7A6U-C7WYDlvc.js → infoDiagram-FWYZ7A6U-DNrZutb1.js} +1 -1
  169. package/dist/ui/assets/{inform7-B8Tf2n8v.js → inform7-47bZAprM.js} +1 -1
  170. package/dist/ui/assets/{ini-DSoYMaLF.js → ini-D00zSJOc.js} +1 -1
  171. package/dist/ui/assets/{irpf90-RCPdkB14.js → irpf90-Cb5adfDu.js} +1 -1
  172. package/dist/ui/assets/{isbl-C2p75PPj.js → isbl-NcFXKDCH.js} +1 -1
  173. package/dist/ui/assets/{ishikawaDiagram-FXEZZL3T-Cm6jsPlc.js → ishikawaDiagram-FXEZZL3T-DodxL_A3.js} +1 -1
  174. package/dist/ui/assets/{java-DeY4Yuia.js → java-DdNdnIDI.js} +1 -1
  175. package/dist/ui/assets/{javascript-3uX1CO-H.js → javascript-CauBsBfA.js} +1 -1
  176. package/dist/ui/assets/{jboss-cli-DgnD8mEc.js → jboss-cli-Ce_9aKRs.js} +1 -1
  177. package/dist/ui/assets/{journeyDiagram-5HDEW3XC-DEGV1uA5.js → journeyDiagram-5HDEW3XC-CaegxpYw.js} +1 -1
  178. package/dist/ui/assets/{json-BV2PmJJp.js → json-BKT_pfo1.js} +1 -1
  179. package/dist/ui/assets/{julia-B-m8kQm7.js → julia-bWGt_b_o.js} +1 -1
  180. package/dist/ui/assets/{julia-repl-C51atU2X.js → julia-repl-D-rjMsRC.js} +1 -1
  181. package/dist/ui/assets/{kanban-definition-HUTT4EX6-BJ4k5iTX.js → kanban-definition-HUTT4EX6-DocIVGMy.js} +1 -1
  182. package/dist/ui/assets/{kotlin-CYBziIgl.js → kotlin-CW5ChHJw.js} +1 -1
  183. package/dist/ui/assets/{lasso-Dw2SBMDW.js → lasso-1z4Xsh4l.js} +1 -1
  184. package/dist/ui/assets/{latex-Dpd35yke.js → latex-DRw0gvLT.js} +1 -1
  185. package/dist/ui/assets/{ldif-Ce9EZU__.js → ldif-CC6n1ZWZ.js} +1 -1
  186. package/dist/ui/assets/{leaf-CpkZzQtC.js → leaf-DtycGYP2.js} +1 -1
  187. package/dist/ui/assets/{less-Uzo7M0FD.js → less-DHfDdpLT.js} +1 -1
  188. package/dist/ui/assets/{lisp-DWXDlJYm.js → lisp-F7y5QBiG.js} +1 -1
  189. package/dist/ui/assets/{livecodeserver-uEyPEgVJ.js → livecodeserver-B4rL97ZE.js} +1 -1
  190. package/dist/ui/assets/{livescript-BQ5f07oK.js → livescript-C1A66vR3.js} +1 -1
  191. package/dist/ui/assets/{llvm-BAvP-skE.js → llvm-DrJc4jDQ.js} +1 -1
  192. package/dist/ui/assets/{lsl-DhbiIKu6.js → lsl-ByygfQlb.js} +1 -1
  193. package/dist/ui/assets/{lua-COxeUeLx.js → lua-C93sHZoB.js} +1 -1
  194. package/dist/ui/assets/{makefile-DhDx-IgL.js → makefile-CymUCipv.js} +1 -1
  195. package/dist/ui/assets/{markdown-DX5_kSxq.js → markdown-BLwVli7a.js} +1 -1
  196. package/dist/ui/assets/{mathematica-D351ZddB.js → mathematica-DMmVw4oA.js} +1 -1
  197. package/dist/ui/assets/{matlab-CELqIiwF.js → matlab-DJPyR0-0.js} +1 -1
  198. package/dist/ui/assets/{maxima-B_YljrMm.js → maxima-B_5tEBD-.js} +1 -1
  199. package/dist/ui/assets/{mel-DltsbNS8.js → mel-B5AJmhET.js} +1 -1
  200. package/dist/ui/assets/{mercury-axX-5Vjj.js → mercury-DDm_HlP5.js} +1 -1
  201. package/dist/ui/assets/{mindmap-definition-LN4V7U3C-Bl1EJo6p.js → mindmap-definition-LN4V7U3C-Ck5xDFhg.js} +1 -1
  202. package/dist/ui/assets/{mipsasm-BxmerVwX.js → mipsasm-BBf0KZxo.js} +1 -1
  203. package/dist/ui/assets/{mizar-XNHfbpL2.js → mizar-BvkTEip4.js} +1 -1
  204. package/dist/ui/assets/{mojolicious-BR0U_7zk.js → mojolicious-t81pYOLs.js} +1 -1
  205. package/dist/ui/assets/{monkey-lLuu9_f-.js → monkey-B5URMcLy.js} +1 -1
  206. package/dist/ui/assets/{moonscript-DoaZ3DRQ.js → moonscript-Bbi3L3V5.js} +1 -1
  207. package/dist/ui/assets/{n1ql-C6TId6Iu.js → n1ql-DE8gguiN.js} +1 -1
  208. package/dist/ui/assets/{nginx-BezSG-JT.js → nginx-ytmzvrnA.js} +1 -1
  209. package/dist/ui/assets/{nim-Bb0rfXie.js → nim-28PFAP2a.js} +1 -1
  210. package/dist/ui/assets/{nix-o3sJ5-54.js → nix-DPGZqLl2.js} +1 -1
  211. package/dist/ui/assets/{node-repl-Sv5hhcTf.js → node-repl-77Ag73Mp.js} +1 -1
  212. package/dist/ui/assets/{nsis-B45BJFhF.js → nsis-Bd-Jh2mn.js} +1 -1
  213. package/dist/ui/assets/{objectivec-CzS6zVgh.js → objectivec-Dut0JGtJ.js} +1 -1
  214. package/dist/ui/assets/{ocaml-Bqux7PyP.js → ocaml-B8NbbL7d.js} +1 -1
  215. package/dist/ui/assets/{openscad-4Wf6mF84.js → openscad-3haKn8_6.js} +1 -1
  216. package/dist/ui/assets/{oxygene-Fvwqgkkt.js → oxygene-CzZcXgOW.js} +1 -1
  217. package/dist/ui/assets/{parser3-zEfzT5wv.js → parser3-B9_LTx13.js} +1 -1
  218. package/dist/ui/assets/{pegDiagram-2B236MQR-Dd4rIztM.js → pegDiagram-2B236MQR-CnkbIPdt.js} +1 -1
  219. package/dist/ui/assets/{perl-CsS1Gs6o.js → perl-CJHWWgIf.js} +1 -1
  220. package/dist/ui/assets/{pf-BNH1jO0R.js → pf-BpbQ2FKx.js} +1 -1
  221. package/dist/ui/assets/{pgsql-DqPigHrq.js → pgsql-DNFfp1qF.js} +1 -1
  222. package/dist/ui/assets/{php-CdOl9bRs.js → php-BHGIfWOP.js} +1 -1
  223. package/dist/ui/assets/{php-template-DPfirJg2.js → php-template-CajBP9Ht.js} +1 -1
  224. package/dist/ui/assets/{pieDiagram-ENE6RG2P-DGaJNf4S.js → pieDiagram-ENE6RG2P-BOBkdC1f.js} +1 -1
  225. package/dist/ui/assets/{plaintext-EHbgBKzw.js → plaintext-CdRaKh_-.js} +1 -1
  226. package/dist/ui/assets/{pony-DmJajtp2.js → pony-BTFZduq1.js} +1 -1
  227. package/dist/ui/assets/{powershell-DfGzdWR6.js → powershell-gWcxiMpD.js} +1 -1
  228. package/dist/ui/assets/{processing-BT0ETgPG.js → processing-DbryYP4B.js} +1 -1
  229. package/dist/ui/assets/{profile-B3I-x5Lt.js → profile-CHL6RTDR.js} +1 -1
  230. package/dist/ui/assets/{prolog-B_4QJIvI.js → prolog-CAz3V1M6.js} +1 -1
  231. package/dist/ui/assets/{properties-BFVI8weS.js → properties-BLvgyNqZ.js} +1 -1
  232. package/dist/ui/assets/{protobuf-PaF39oZZ.js → protobuf-C-bEtiAJ.js} +1 -1
  233. package/dist/ui/assets/{puppet-BDC6Q8VF.js → puppet-67xqXQgg.js} +1 -1
  234. package/dist/ui/assets/{purebasic-B2ejrOZK.js → purebasic-DAwxmhyv.js} +1 -1
  235. package/dist/ui/assets/{python-repl-BslBh_Mx.js → python-repl-BJmGMIGi.js} +1 -1
  236. package/dist/ui/assets/{python-BOxtIk9H.js → python-z-wrDm9i.js} +1 -1
  237. package/dist/ui/assets/{q-BPe8WLGW.js → q-CDMPAvF9.js} +1 -1
  238. package/dist/ui/assets/{qml-BazRR6fz.js → qml-CbE9R2YT.js} +1 -1
  239. package/dist/ui/assets/{quadrantDiagram-ABIIQ3AL-BQ7aPkHk.js → quadrantDiagram-ABIIQ3AL-BHAhzSkD.js} +1 -1
  240. package/dist/ui/assets/{r-C09pGNJC.js → r-B1k7RrWh.js} +1 -1
  241. package/dist/ui/assets/{railroadDiagram-RFXS5EU6-sCO9VGvE.js → railroadDiagram-RFXS5EU6-CsQep5fB.js} +1 -1
  242. package/dist/ui/assets/{reasonml-S7dzwVL9.js → reasonml-CghNJ9wO.js} +1 -1
  243. package/dist/ui/assets/{requirementDiagram-TGXJPOKE-CfT5aVZg.js → requirementDiagram-TGXJPOKE-BTTRG_C-.js} +1 -1
  244. package/dist/ui/assets/{rib-fM2EwQd4.js → rib-BzZqpngB.js} +1 -1
  245. package/dist/ui/assets/{roboconf-D7AaeyrW.js → roboconf-Bn6vqIcm.js} +1 -1
  246. package/dist/ui/assets/{routeros-c6Q7mKuq.js → routeros-CWiIB6Sc.js} +1 -1
  247. package/dist/ui/assets/{rsl-sCpEQBxh.js → rsl-8XzLd-ex.js} +1 -1
  248. package/dist/ui/assets/{ruby-lcKhz2w9.js → ruby-CMsox328.js} +1 -1
  249. package/dist/ui/assets/{ruleslanguage-BW4ATOuX.js → ruleslanguage-sJ93c6T4.js} +1 -1
  250. package/dist/ui/assets/{rust-BbpU-0VY.js → rust-q4W6ksGp.js} +1 -1
  251. package/dist/ui/assets/{sankeyDiagram-HTMAVEWB-CaweCpvN.js → sankeyDiagram-HTMAVEWB-_xUP3uiO.js} +1 -1
  252. package/dist/ui/assets/{sas-BPt5R0dx.js → sas-JzGZkjB8.js} +1 -1
  253. package/dist/ui/assets/{scala-DxW_vqXs.js → scala-BBRUB1bT.js} +1 -1
  254. package/dist/ui/assets/{scheme-T2Q_jWzT.js → scheme-gFzNu8q5.js} +1 -1
  255. package/dist/ui/assets/{scilab-DMORg9lV.js → scilab-0XjfEPk4.js} +1 -1
  256. package/dist/ui/assets/{scss-t0VsosqT.js → scss-CuET4vU_.js} +1 -1
  257. package/dist/ui/assets/{sequenceDiagram-DBY2YBRQ-o-H_R4zb.js → sequenceDiagram-DBY2YBRQ-DYM-Lf_t.js} +1 -1
  258. package/dist/ui/assets/{shell-CnCDa1s9.js → shell-DozhgmQz.js} +1 -1
  259. package/dist/ui/assets/{sizeCapture-X5ZJPWSS-BNLkHGDS.js → sizeCapture-X5ZJPWSS-enn2ctEl.js} +1 -1
  260. package/dist/ui/assets/{smali-_n-BxJyG.js → smali-DCS5Fxk1.js} +1 -1
  261. package/dist/ui/assets/{smalltalk-B90PXJwK.js → smalltalk-BHQ0G1rO.js} +1 -1
  262. package/dist/ui/assets/{sml-fuY7bj-n.js → sml-Bj9p_O7l.js} +1 -1
  263. package/dist/ui/assets/{sqf-COpwkyuo.js → sqf-VjHINPVc.js} +1 -1
  264. package/dist/ui/assets/{sql-CXIT0j-x.js → sql-DNYdjhOa.js} +1 -1
  265. package/dist/ui/assets/{sql_more-C1Oxq4rx.js → sql_more-DF9pwFii.js} +1 -1
  266. package/dist/ui/assets/{stan-BZEpaHeC.js → stan-DQ7LiXhT.js} +1 -1
  267. package/dist/ui/assets/{stata-CkBwaiZM.js → stata-4eQacDMD.js} +1 -1
  268. package/dist/ui/assets/{stateDiagram-2N3HPSRC-BkdnNF7I.js → stateDiagram-2N3HPSRC-CX-Qz4n7.js} +1 -1
  269. package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-Ch54U9zs.js +1 -0
  270. package/dist/ui/assets/{step21-fs9N_U3O.js → step21-BgYnjmFD.js} +1 -1
  271. package/dist/ui/assets/{stylus-Bs_9cvKZ.js → stylus-ZZ5CxZ05.js} +1 -1
  272. package/dist/ui/assets/{subunit-DvgcOCqJ.js → subunit-Ci5aUEE2.js} +1 -1
  273. package/dist/ui/assets/{swift-BE4ONsur.js → swift-DQJ4QPRG.js} +1 -1
  274. package/dist/ui/assets/{swimlanes-5IMT3BWC-uqTAc7o7.js → swimlanes-5IMT3BWC-vO-7dG2V.js} +2 -2
  275. package/dist/ui/assets/swimlanesDiagram-G3AALYLV-DxwJcNf4.js +8 -0
  276. package/dist/ui/assets/{taggerscript-BhmPJ64C.js → taggerscript-DM07ScL_.js} +1 -1
  277. package/dist/ui/assets/{tap-DkueIVKV.js → tap-C6aPKgww.js} +1 -1
  278. package/dist/ui/assets/{tcl-DyXOOkFi.js → tcl-DjtvddtN.js} +1 -1
  279. package/dist/ui/assets/{thrift-Dhgbk56T.js → thrift-BoJuHfsC.js} +1 -1
  280. package/dist/ui/assets/{timeline-definition-FHXFAJF6-DBAk9Ouc.js → timeline-definition-FHXFAJF6-Cn7z-Bqh.js} +1 -1
  281. package/dist/ui/assets/{tp-BsXyrRGH.js → tp-7mrfAfpj.js} +1 -1
  282. package/dist/ui/assets/{twig-J0HqIuHw.js → twig-DOOUfGqf.js} +1 -1
  283. package/dist/ui/assets/{typescript-Dkwvul39.js → typescript-BbC9hUFK.js} +1 -1
  284. package/dist/ui/assets/{vala-CRGnf8aY.js → vala-XjKwsdD0.js} +1 -1
  285. package/dist/ui/assets/{vbnet-BtJAUe3z.js → vbnet-Da3MfWx9.js} +1 -1
  286. package/dist/ui/assets/{vbscript-RgFFNuM3.js → vbscript-DT_81k7v.js} +1 -1
  287. package/dist/ui/assets/{vbscript-html-DO32Io9Y.js → vbscript-html-DUFh3J5N.js} +1 -1
  288. package/dist/ui/assets/{vennDiagram-L72KCM5P-BJi6y0hh.js → vennDiagram-L72KCM5P-BnjZCady.js} +1 -1
  289. package/dist/ui/assets/{verilog-C4h6MLcI.js → verilog-D5Okzav9.js} +1 -1
  290. package/dist/ui/assets/{vhdl-DOafePyU.js → vhdl-_hcd5jXC.js} +1 -1
  291. package/dist/ui/assets/{vim-BAdTA9TY.js → vim-BuKTsAic.js} +1 -1
  292. package/dist/ui/assets/{wardleyDiagram-EHGQE667-C7c_4IJ6.js → wardleyDiagram-EHGQE667-Bgp49v0-.js} +1 -1
  293. package/dist/ui/assets/{x86asm-BCL6__QH.js → x86asm-DWOIgEnP.js} +1 -1
  294. package/dist/ui/assets/{xl-qIzE4HOe.js → xl-D6TFf1q5.js} +1 -1
  295. package/dist/ui/assets/{xml-DE2sJXYU.js → xml-_5JZFtHD.js} +1 -1
  296. package/dist/ui/assets/{xquery-CQ35t92y.js → xquery-BC0pvaic.js} +1 -1
  297. package/dist/ui/assets/{xychartDiagram-FW5EYKEG-D2ILn0jL.js → xychartDiagram-FW5EYKEG-DmyzmXyI.js} +1 -1
  298. package/dist/ui/assets/{yaml-5KaAvG5C.js → yaml-CLzRjl8_.js} +1 -1
  299. package/dist/ui/assets/{zephir-CbR3Ryfq.js → zephir-nZ6mLYuU.js} +1 -1
  300. package/dist/ui/index.html +1 -1
  301. package/dist/utils/__tests__/db-maintenance.test.d.ts +1 -0
  302. package/dist/utils/__tests__/db-maintenance.test.js +55 -0
  303. package/dist/utils/__tests__/db-migration.test.js +79 -1
  304. package/dist/utils/__tests__/request-writer.test.js +264 -3
  305. package/dist/utils/__tests__/stats-aggregator.test.d.ts +1 -0
  306. package/dist/utils/__tests__/stats-aggregator.test.js +114 -0
  307. package/dist/utils/db-maintenance.d.ts +8 -0
  308. package/dist/utils/db-maintenance.js +18 -0
  309. package/dist/utils/db.d.ts +1 -0
  310. package/dist/utils/db.js +62 -0
  311. package/dist/utils/request-writer.d.ts +23 -1
  312. package/dist/utils/request-writer.js +200 -14
  313. package/dist/utils/stats-aggregator.d.ts +1 -1
  314. package/dist/utils/stats-aggregator.js +44 -27
  315. package/ecosystem.config.cjs +2 -1
  316. package/package.json +1 -1
  317. package/dist/docs/assets/chunks/@localSearchIndexroot.C4sjikMO.js +0 -1
  318. package/dist/server/middleware/__tests__/request-id.test.js +0 -28
  319. package/dist/server/middleware/request-id.d.ts +0 -2
  320. package/dist/server/middleware/request-id.js +0 -12
  321. package/dist/ui/assets/channel-ChTtxQDg.js +0 -1
  322. package/dist/ui/assets/classDiagram-OUVF2IWQ-Ccbvamlu.js +0 -1
  323. package/dist/ui/assets/classDiagram-v2-EOCWNBFH-Ccbvamlu.js +0 -1
  324. package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-0JnZ1VSQ.js +0 -1
  325. package/dist/ui/assets/swimlanesDiagram-G3AALYLV-DQRIIEmi.js +0 -8
  326. /package/dist/server/{middleware/__tests__/request-id.test.d.ts → __tests__/data-push.test.d.ts} +0 -0
@@ -1,12 +1,33 @@
1
- import { getErrorStatusCode, getErrorMessage, resolveProxyTarget, filterForwardHeaders } from '../proxy';
1
+ import { getErrorStatusCode, getErrorMessage, resolveProxyTarget, filterForwardHeaders, setupProxy } from '../proxy';
2
+ import Fastify from 'fastify';
3
+ import http from 'node:http';
2
4
  import Database from 'better-sqlite3';
5
+ import { WebSocket } from 'ws';
6
+ import { addDataClient, removeDataClient } from '../data-push';
3
7
  jest.mock('@utils/db', () => ({
4
8
  __esModule: true,
5
9
  getDatabase: jest.fn(),
6
10
  initDatabase: jest.fn(),
7
11
  closeDatabase: jest.fn()
8
12
  }));
9
- const { getDatabase } = jest.requireMock('@utils/db');
13
+ jest.mock('@config/index', () => ({
14
+ loadConfig: jest.fn().mockResolvedValue({ storage: { maxDays: 7 } })
15
+ }));
16
+ jest.mock('../../utils/mcp-manager', () => ({
17
+ mcpManager: {
18
+ init: jest.fn().mockResolvedValue(undefined),
19
+ close: jest.fn().mockResolvedValue(undefined)
20
+ }
21
+ }));
22
+ jest.mock('../../utils/machine-id', () => ({
23
+ getMachineId: jest.fn().mockResolvedValue('test-machine-id'),
24
+ getMachineIdSync: jest.fn().mockReturnValue('test-machine-id'),
25
+ preloadMachineId: jest.fn().mockResolvedValue(undefined)
26
+ }));
27
+ jest.mock('../../utils/stats-aggregator', () => ({
28
+ aggregateStats: jest.fn()
29
+ }));
30
+ const { getDatabase, initDatabase } = jest.requireMock('@utils/db');
10
31
  function makeError(code, message) {
11
32
  const error = new Error(message);
12
33
  error.code = code;
@@ -135,3 +156,345 @@ describe('filterForwardHeaders with authStyle', () => {
135
156
  expect(out['x-api-key']).toBe('inbound-key');
136
157
  });
137
158
  });
159
+ // --- G2: 逻辑终止识别 + 收割接入(setupProxy 集成测试)---
160
+ // 构造集成测试用内存库:仅建 proxy.ts 运行路径触及的表
161
+ function createIntegrationDb() {
162
+ const db = new Database(':memory:');
163
+ db.exec(`
164
+ CREATE TABLE requests (
165
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
166
+ request_id TEXT UNIQUE NOT NULL,
167
+ session_id TEXT NOT NULL,
168
+ timestamp TEXT NOT NULL,
169
+ method TEXT NOT NULL,
170
+ path TEXT NOT NULL,
171
+ status_code INTEGER NOT NULL,
172
+ response_time INTEGER NOT NULL,
173
+ api_key TEXT,
174
+ proxy_api_key TEXT,
175
+ base_url TEXT,
176
+ model TEXT,
177
+ machine_id TEXT,
178
+ user_agent TEXT,
179
+ input_tokens INTEGER,
180
+ output_tokens INTEGER,
181
+ cache_read_tokens INTEGER,
182
+ cache_creation_tokens INTEGER,
183
+ request_headers TEXT,
184
+ request_body TEXT,
185
+ response_headers TEXT,
186
+ response_events TEXT,
187
+ completed_at TEXT,
188
+ closed_at TEXT,
189
+ error TEXT,
190
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
191
+ );
192
+ CREATE TABLE response_events (
193
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
194
+ request_id TEXT NOT NULL,
195
+ session_id TEXT NOT NULL,
196
+ event_index INTEGER NOT NULL,
197
+ event_type TEXT,
198
+ event_data TEXT NOT NULL
199
+ );
200
+ CREATE TABLE learn_tasks (
201
+ task_id TEXT PRIMARY KEY,
202
+ slug TEXT NOT NULL,
203
+ source_type TEXT NOT NULL,
204
+ label TEXT NOT NULL,
205
+ status TEXT NOT NULL,
206
+ error TEXT,
207
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
208
+ ended_at TEXT
209
+ );
210
+ CREATE TABLE providers (
211
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
212
+ name TEXT UNIQUE NOT NULL,
213
+ base_url TEXT,
214
+ api_key TEXT,
215
+ model TEXT,
216
+ haiku_model TEXT,
217
+ sonnet_model TEXT,
218
+ opus_model TEXT,
219
+ subagent_model TEXT,
220
+ provider TEXT NOT NULL,
221
+ is_default INTEGER NOT NULL DEFAULT 0,
222
+ is_openai_compatible INTEGER NOT NULL DEFAULT 0,
223
+ auth_style TEXT NOT NULL DEFAULT 'bearer',
224
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
225
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
226
+ );
227
+ `);
228
+ return db;
229
+ }
230
+ function sseFrame(event, data) {
231
+ const body = typeof data === 'string' ? data : JSON.stringify(data);
232
+ return event !== null ? `event: ${event}\ndata: ${body}\n\n` : `data: ${body}\n\n`;
233
+ }
234
+ const completeClaudeFrames = [
235
+ sseFrame('message_start', { type: 'message_start', usage: { input_tokens: 10, cache_read_input_tokens: 2 } }),
236
+ sseFrame('ping', { type: 'ping' }),
237
+ sseFrame('message_delta', { type: 'message_delta', usage: { output_tokens: 4 } }),
238
+ sseFrame('message_stop', { type: 'message_stop' })
239
+ ];
240
+ const incompleteClaudeFrames = [
241
+ sseFrame('message_start', { type: 'message_start', usage: { input_tokens: 3 } }),
242
+ sseFrame('content_block_start', { type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' } }),
243
+ sseFrame('content_block_delta', { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: 'hel' } })
244
+ ];
245
+ // 上游 Web 流:逐帧异步入队;endAfter=false 时入队后保持悬挂(不 close)。
246
+ // singleChunk=true 时所有帧合并为一次 enqueue(模拟真实 TCP 合包,如
247
+ // finish_reason chunk 与紧随的 [DONE] 通常在同一缓冲到达)。
248
+ function createUpstreamWebStream(frames, endAfter = false, singleChunk = false) {
249
+ let controller;
250
+ const webStream = new ReadableStream({
251
+ start(c) { controller = c; }
252
+ });
253
+ void (async () => {
254
+ try {
255
+ if (singleChunk) {
256
+ controller.enqueue(Buffer.from(frames.join(''), 'utf-8'));
257
+ }
258
+ else {
259
+ for (const frame of frames) {
260
+ controller.enqueue(Buffer.from(frame, 'utf-8'));
261
+ await new Promise(r => setTimeout(r, 3));
262
+ }
263
+ }
264
+ if (endAfter)
265
+ controller.close();
266
+ }
267
+ catch {
268
+ // nodeStream.destroy 取消 web 流后 controller 已关闭,忽略后续入队
269
+ }
270
+ })();
271
+ return webStream;
272
+ }
273
+ async function waitFor(predicate, { timeout = 3000, interval = 20 } = {}) {
274
+ const start = Date.now();
275
+ while (Date.now() - start < timeout) {
276
+ const value = predicate();
277
+ if (value !== undefined)
278
+ return value;
279
+ await new Promise(r => setTimeout(r, interval));
280
+ }
281
+ throw new Error('waitFor timed out');
282
+ }
283
+ function mockUpstreamFetch(webStream, requestId) {
284
+ return jest.spyOn(globalThis, 'fetch').mockImplementation(async () => new Response(webStream, {
285
+ status: 200,
286
+ headers: { 'content-type': 'text/event-stream', 'x-request-id': requestId }
287
+ }));
288
+ }
289
+ // 挂一个假数据客户端捕获 WS 广播(request new/update 等)
290
+ let mockWs;
291
+ function attachMockWs() {
292
+ const sent = [];
293
+ mockWs = {
294
+ readyState: WebSocket.OPEN,
295
+ send: jest.fn((payload) => { sent.push(JSON.parse(payload)); })
296
+ };
297
+ addDataClient(mockWs);
298
+ return sent;
299
+ }
300
+ const proxyConfig = {
301
+ target: 'https://upstream.example.com/apps/anthropic',
302
+ changeOrigin: true
303
+ };
304
+ const requestPayload = JSON.stringify({
305
+ model: 'claude-sonnet-4-20250514',
306
+ max_tokens: 16,
307
+ messages: [{ role: 'user', content: 'hi' }]
308
+ });
309
+ describe('setupProxy SSE lifecycle (G2)', () => {
310
+ let db;
311
+ let fastify;
312
+ beforeEach(() => {
313
+ jest.clearAllMocks();
314
+ db = createIntegrationDb();
315
+ getDatabase.mockReturnValue(db);
316
+ initDatabase.mockReturnValue(db);
317
+ fastify = Fastify();
318
+ });
319
+ afterEach(async () => {
320
+ delete process.env.CCM_STREAM_IDLE_TIMEOUT_MS;
321
+ if (mockWs) {
322
+ removeDataClient(mockWs);
323
+ mockWs = undefined;
324
+ }
325
+ await fastify.close();
326
+ db.close();
327
+ });
328
+ test('message_stop 已发出后上游悬挂:行落 completed_at,不标中断,客户端不悬挂', async () => {
329
+ await setupProxy(fastify, proxyConfig);
330
+ mockUpstreamFetch(createUpstreamWebStream(completeClaudeFrames), 'cloud-req-1');
331
+ const response = await fastify.inject({
332
+ method: 'POST',
333
+ url: '/v1/messages',
334
+ headers: { 'content-type': 'application/json' },
335
+ payload: requestPayload
336
+ });
337
+ expect(response.statusCode).toBe(200);
338
+ expect(response.payload).toContain('message_stop');
339
+ const row = db.prepare('SELECT * FROM requests WHERE request_id = ?').get('cloud-req-1');
340
+ expect(row).toBeDefined();
341
+ expect(row.completed_at).not.toBeNull();
342
+ expect(row.closed_at).toBeNull();
343
+ expect(row.error).toBeNull();
344
+ });
345
+ test('无 message_stop 的 idle 超时仍标中断(closed_at + error)', async () => {
346
+ // 测试专用时长覆盖:真实定时器,只缩短看门狗超时
347
+ process.env.CCM_STREAM_IDLE_TIMEOUT_MS = '80';
348
+ await setupProxy(fastify, proxyConfig);
349
+ mockUpstreamFetch(createUpstreamWebStream(incompleteClaudeFrames), 'cloud-req-2');
350
+ // 走真实 TCP:中断分支 pipe 不结束客户端(保持既有语义),客户端自行判超时
351
+ await fastify.listen({ port: 0, host: '127.0.0.1' });
352
+ const port = fastify.server.address().port;
353
+ const clientReq = http.request({
354
+ port,
355
+ host: '127.0.0.1',
356
+ method: 'POST',
357
+ path: '/v1/messages',
358
+ headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(requestPayload) }
359
+ });
360
+ clientReq.on('error', () => { });
361
+ clientReq.end(requestPayload);
362
+ // 客户端收到数据后看门狗触发,不再有数据;保底 1s 后自毁连接
363
+ const clientTimer = setTimeout(() => clientReq.destroy(), 1000);
364
+ clientReq.on('close', () => clearTimeout(clientTimer));
365
+ const row = await waitFor(() => {
366
+ const r = db.prepare('SELECT * FROM requests LIMIT 1').get();
367
+ return r && r.closed_at ? r : undefined;
368
+ });
369
+ expect(row.completed_at).toBeNull();
370
+ expect(row.closed_at).not.toBeNull();
371
+ expect(row.error).toContain('idle timeout');
372
+ expect(row.status_code).toBe(200);
373
+ });
374
+ test('OpenAI 流 finish_reason + [DONE] 后上游悬挂:行落 completed_at', async () => {
375
+ db.prepare(`
376
+ INSERT INTO providers (name, base_url, api_key, model, provider, is_default, is_openai_compatible, auth_style)
377
+ VALUES ('openai-test', 'https://openai.example.com/v1', 'k', 'gpt-4o', 'openai', 1, 1, 'bearer')
378
+ `).run();
379
+ await setupProxy(fastify, proxyConfig);
380
+ const openaiFrames = [
381
+ sseFrame(null, { id: 'chat-1', choices: [{ delta: { role: 'assistant' }, finish_reason: null }] }),
382
+ sseFrame(null, { id: 'chat-1', choices: [{ delta: { content: 'hi' }, finish_reason: null }] }),
383
+ sseFrame(null, { id: 'chat-1', choices: [{ delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 5, completion_tokens: 7 } }),
384
+ sseFrame(null, '[DONE]')
385
+ ];
386
+ const fetchSpy = mockUpstreamFetch(createUpstreamWebStream(openaiFrames, false, true), 'cloud-openai-1');
387
+ const response = await fastify.inject({
388
+ method: 'POST',
389
+ url: '/v1/messages',
390
+ headers: { 'content-type': 'application/json' },
391
+ payload: requestPayload
392
+ });
393
+ expect(response.statusCode).toBe(200);
394
+ expect(response.payload).toContain('message_stop');
395
+ expect(fetchSpy.mock.calls[0][0]).toBe('https://openai.example.com/v1/chat/completions');
396
+ const row = db.prepare('SELECT * FROM requests WHERE request_id = ?').get('cloud-openai-1');
397
+ expect(row).toBeDefined();
398
+ expect(row.completed_at).not.toBeNull();
399
+ expect(row.closed_at).toBeNull();
400
+ expect(row.error).toBeNull();
401
+ });
402
+ test('message_stop 到达后客户端断开:内容已完整交付,仍走 complete', async () => {
403
+ await setupProxy(fastify, proxyConfig);
404
+ mockUpstreamFetch(createUpstreamWebStream(completeClaudeFrames), 'cloud-req-3');
405
+ await fastify.listen({ port: 0, host: '127.0.0.1' });
406
+ const port = fastify.server.address().port;
407
+ await new Promise((resolve, reject) => {
408
+ const clientReq = http.request({
409
+ port,
410
+ host: '127.0.0.1',
411
+ method: 'POST',
412
+ path: '/v1/messages',
413
+ headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(requestPayload) }
414
+ });
415
+ clientReq.on('error', () => { });
416
+ clientReq.on('response', (res) => {
417
+ res.on('data', (chunk) => {
418
+ if (chunk.toString().includes('message_stop'))
419
+ clientReq.destroy();
420
+ });
421
+ });
422
+ clientReq.on('close', () => resolve());
423
+ setTimeout(() => reject(new Error('client did not close')), 3000).unref();
424
+ clientReq.end(requestPayload);
425
+ });
426
+ const row = await waitFor(() => {
427
+ const r = db.prepare('SELECT * FROM requests WHERE request_id = ?').get('cloud-req-3');
428
+ return r && r.completed_at ? r : undefined;
429
+ });
430
+ expect(row.closed_at).toBeNull();
431
+ expect(row.error).toBeNull();
432
+ });
433
+ test('上游只返回 request-id 头(无 x-request-id):completed 行取该值,WS update 的 pendingId 指向 local 行', async () => {
434
+ await setupProxy(fastify, proxyConfig);
435
+ const upstreamId = 'req_upstream_abc';
436
+ jest.spyOn(globalThis, 'fetch').mockImplementation(async () => new Response(createUpstreamWebStream(completeClaudeFrames), { status: 200, headers: { 'content-type': 'text/event-stream', 'request-id': upstreamId } }));
437
+ const sent = attachMockWs();
438
+ const response = await fastify.inject({
439
+ method: 'POST',
440
+ url: '/v1/messages',
441
+ headers: { 'content-type': 'application/json' },
442
+ payload: requestPayload
443
+ });
444
+ expect(response.statusCode).toBe(200);
445
+ const row = db.prepare('SELECT * FROM requests WHERE request_id = ?').get(upstreamId);
446
+ expect(row).toBeDefined();
447
+ expect(row.completed_at).not.toBeNull();
448
+ const localId = sent.find(m => m.type === 'request' && m.action === 'new').data.requestId;
449
+ const update = sent.find(m => m.type === 'request' && m.action === 'update' && m.data.requestId === upstreamId);
450
+ expect(update).toBeDefined();
451
+ expect(update.data.pendingId).toBe(`unknown-session:${localId}`);
452
+ });
453
+ test('Claude 模式上游 204 且 body 为 null:行落终态、WS 收到 completed、不挂起', async () => {
454
+ await setupProxy(fastify, proxyConfig);
455
+ jest.spyOn(globalThis, 'fetch').mockImplementation(async () => new Response(null, {
456
+ status: 204,
457
+ headers: { 'x-request-id': 'cloud-204-1' }
458
+ }));
459
+ const sent = attachMockWs();
460
+ const response = await fastify.inject({
461
+ method: 'POST',
462
+ url: '/v1/messages',
463
+ headers: { 'content-type': 'application/json' },
464
+ payload: requestPayload
465
+ });
466
+ expect(response.statusCode).toBe(204);
467
+ const row = db.prepare('SELECT * FROM requests WHERE request_id = ?').get('cloud-204-1');
468
+ expect(row).toBeDefined();
469
+ expect(row.completed_at).not.toBeNull();
470
+ expect(row.closed_at).toBeNull();
471
+ expect(row.error).toBeNull();
472
+ const update = sent.find(m => m.type === 'request' && m.action === 'update' && m.data.requestId === 'cloud-204-1');
473
+ expect(update).toBeDefined();
474
+ });
475
+ test('OpenAI 模式上游 204 且 body 为 null:行落终态、WS 收到 completed、不挂起', async () => {
476
+ db.prepare(`
477
+ INSERT INTO providers (name, base_url, api_key, model, provider, is_default, is_openai_compatible, auth_style)
478
+ VALUES ('openai-test', 'https://openai.example.com/v1', 'k', 'gpt-4o', 'openai', 1, 1, 'bearer')
479
+ `).run();
480
+ await setupProxy(fastify, proxyConfig);
481
+ jest.spyOn(globalThis, 'fetch').mockImplementation(async () => new Response(null, {
482
+ status: 204,
483
+ headers: { 'x-request-id': 'cloud-204-openai' }
484
+ }));
485
+ const sent = attachMockWs();
486
+ const response = await fastify.inject({
487
+ method: 'POST',
488
+ url: '/v1/messages',
489
+ headers: { 'content-type': 'application/json' },
490
+ payload: requestPayload
491
+ });
492
+ expect(response.statusCode).toBe(204);
493
+ const row = db.prepare('SELECT * FROM requests WHERE request_id = ?').get('cloud-204-openai');
494
+ expect(row).toBeDefined();
495
+ expect(row.completed_at).not.toBeNull();
496
+ expect(row.closed_at).toBeNull();
497
+ const update = sent.find(m => m.type === 'request' && m.action === 'update' && m.data.requestId === 'cloud-204-openai');
498
+ expect(update).toBeDefined();
499
+ });
500
+ });
@@ -105,22 +105,27 @@ describe('createServer', () => {
105
105
  expect(response).toBeDefined();
106
106
  expect(typeof response.statusCode).toBe('number');
107
107
  });
108
- it('propagates request ID from x-request-id header', async () => {
108
+ it('uses x-request-id header value as request id when header present', async () => {
109
109
  server = await createServer();
110
+ server.get('/request-id-echo', async (request) => ({ id: request.id }));
110
111
  const response = await server.inject({
111
112
  method: 'GET',
112
- url: '/test',
113
- headers: { 'x-request-id': 'custom-id-123' }
113
+ url: '/request-id-echo',
114
+ headers: { 'x-request-id': 'my-trace-123' }
114
115
  });
115
- expect(response).toBeDefined();
116
+ expect(JSON.parse(response.body).id).toBe('my-trace-123');
116
117
  });
117
- it('handles request without x-request-id header', async () => {
118
+ it('generates non-default nanoid request id without header, unique per request', async () => {
118
119
  server = await createServer();
119
- const response = await server.inject({
120
- method: 'GET',
121
- url: '/test'
122
- });
123
- expect(response).toBeDefined();
120
+ server.get('/request-id-echo', async (request) => ({ id: request.id }));
121
+ const response1 = await server.inject({ method: 'GET', url: '/request-id-echo' });
122
+ const response2 = await server.inject({ method: 'GET', url: '/request-id-echo' });
123
+ const id1 = JSON.parse(response1.body).id;
124
+ const id2 = JSON.parse(response2.body).id;
125
+ expect(id1).not.toMatch(/^req-\d+$/);
126
+ expect(id2).not.toMatch(/^req-\d+$/);
127
+ expect(id1).toHaveLength(16);
128
+ expect(id1).not.toBe(id2);
124
129
  });
125
130
  it('server closes cleanly', async () => {
126
131
  server = await createServer();
@@ -1,4 +1,5 @@
1
1
  import Fastify from 'fastify';
2
+ import * as statsAggregator from '@utils/stats-aggregator';
2
3
  import { setupUiRoutes } from '../ui-route';
3
4
  // Mock PM2 utils
4
5
  jest.mock('@utils/pm2', () => ({
@@ -372,4 +373,46 @@ describe('setupUiRoutes', () => {
372
373
  expect(body.error).toContain('Disk write failed');
373
374
  });
374
375
  });
376
+ describe('GET /ui/api/stats', () => {
377
+ let aggregateSpy;
378
+ let getStatsSpy;
379
+ beforeEach(() => {
380
+ aggregateSpy = jest.spyOn(statsAggregator, 'aggregateStats').mockImplementation(() => { });
381
+ getStatsSpy = jest.spyOn(statsAggregator, 'getStatsData').mockReturnValue({ hourly: {}, lastUpdated: null });
382
+ });
383
+ afterEach(() => {
384
+ aggregateSpy.mockRestore();
385
+ getStatsSpy.mockRestore();
386
+ });
387
+ it('读前触发 aggregateStats 并返回 200', async () => {
388
+ const response = await server.inject({ method: 'GET', url: '/ui/api/stats' });
389
+ expect(aggregateSpy).toHaveBeenCalledTimes(1);
390
+ expect(aggregateSpy).toHaveBeenCalledWith(7);
391
+ expect(getStatsSpy).toHaveBeenCalledTimes(1);
392
+ // 必须先聚合再读取
393
+ expect(aggregateSpy.mock.invocationCallOrder[0]).toBeLessThan(getStatsSpy.mock.invocationCallOrder[0]);
394
+ expect(response.statusCode).toBe(200);
395
+ expect(JSON.parse(response.body)).toEqual({ hourly: {}, lastUpdated: null });
396
+ });
397
+ it('聚合失败只记录不阻断读取,仍返回 200', async () => {
398
+ aggregateSpy.mockImplementation(() => { throw new Error('agg boom'); });
399
+ // inject 请求的 request.log 来自 server.log.child(),替换为可观测的 mock
400
+ const logError = jest.fn();
401
+ jest.spyOn(server.log, 'child').mockReturnValue({
402
+ level: 'info',
403
+ fatal: jest.fn(),
404
+ error: logError,
405
+ warn: jest.fn(),
406
+ info: jest.fn(),
407
+ debug: jest.fn(),
408
+ trace: jest.fn(),
409
+ child: jest.fn()
410
+ });
411
+ const response = await server.inject({ method: 'GET', url: '/ui/api/stats' });
412
+ expect(response.statusCode).toBe(200);
413
+ expect(getStatsSpy).toHaveBeenCalledTimes(1);
414
+ // 聚合异常必须记录日志
415
+ expect(logError).toHaveBeenCalledTimes(1);
416
+ });
417
+ });
375
418
  });
@@ -115,7 +115,7 @@ export async function setupChatRoute(fastify, proxyConfig) {
115
115
  });
116
116
  request.log.info(`[chat] response status: ${response.status}`);
117
117
  // Extract cloud request ID
118
- const cloudRequestId = response.headers.get('x-request-id') || request.id;
118
+ const cloudRequestId = response.headers.get('x-request-id') || response.headers.get('request-id') || request.id;
119
119
  // Check response type
120
120
  const contentType = response.headers.get('content-type') || '';
121
121
  const isSSE = contentType.includes('text/event-stream');
@@ -156,7 +156,7 @@ export async function setupChatRoute(fastify, proxyConfig) {
156
156
  // Record each loop iteration as a separate request
157
157
  await recordIteration({
158
158
  sessionId,
159
- localRequestId: r.headers.get('x-request-id') || `loop_${request.id}_${Date.now()}`,
159
+ localRequestId: r.headers.get('x-request-id') || r.headers.get('request-id') || `loop_${request.id}_${Date.now()}`,
160
160
  timestamp: new Date().toISOString(),
161
161
  apiKey,
162
162
  proxyApiKey,
@@ -241,7 +241,7 @@ export async function setupChatRoute(fastify, proxyConfig) {
241
241
  body: JSON.stringify(b),
242
242
  signal: createTimeoutSignal(300000, clientSignal)
243
243
  });
244
- const loopRequestId = r.headers.get('x-request-id') || `loop_${request.id}_${Date.now()}`;
244
+ const loopRequestId = r.headers.get('x-request-id') || r.headers.get('request-id') || `loop_${request.id}_${Date.now()}`;
245
245
  return { response: r, requestId: loopRequestId };
246
246
  },
247
247
  streamAndParse: async (loopResponse) => {
@@ -2,5 +2,7 @@ import { WebSocket } from 'ws';
2
2
  export declare function addDataClient(ws: WebSocket): void;
3
3
  export declare function removeDataClient(ws: WebSocket): void;
4
4
  export declare function getDataClientCount(): number;
5
+ export declare function scheduleStatsNotify(delayMs?: number): void;
6
+ export declare function clearStatsNotify(): void;
5
7
  export declare function pushNewRequest(summary: any, status?: 'pending' | 'completed'): void;
6
8
  export declare function broadcastToDataClients(msg: any): void;
@@ -10,6 +10,24 @@ export function removeDataClient(ws) {
10
10
  export function getDataClientCount() {
11
11
  return dataClients.size;
12
12
  }
13
+ // stats notify debounce timer(模块级单个,trailing:重复调用重置延迟)
14
+ let statsNotifyTimer = null;
15
+ // 请求终态后延迟广播一次 stats notify,并发终态合并为一次
16
+ export function scheduleStatsNotify(delayMs = 1500) {
17
+ if (statsNotifyTimer)
18
+ clearTimeout(statsNotifyTimer);
19
+ statsNotifyTimer = setTimeout(() => {
20
+ statsNotifyTimer = null;
21
+ broadcast({ type: 'stats', action: 'notify' });
22
+ }, delayMs);
23
+ }
24
+ // 服务关闭时清理 pending 的 debounce timer
25
+ export function clearStatsNotify() {
26
+ if (statsNotifyTimer) {
27
+ clearTimeout(statsNotifyTimer);
28
+ statsNotifyTimer = null;
29
+ }
30
+ }
13
31
  // Broadcast helpers
14
32
  export function pushNewRequest(summary, status = 'completed') {
15
33
  broadcast({
@@ -17,6 +35,10 @@ export function pushNewRequest(summary, status = 'completed') {
17
35
  action: status === 'pending' ? 'new' : 'update',
18
36
  data: { ...summary, status }
19
37
  });
38
+ // 终态(complete/interrupt/error)后通知前端重取 stats;pending=请求开始不触发
39
+ if (status !== 'pending') {
40
+ scheduleStatsNotify();
41
+ }
20
42
  }
21
43
  function broadcast(msg) {
22
44
  const payload = JSON.stringify(msg);
@@ -1,4 +1,5 @@
1
1
  import Fastify from 'fastify';
2
+ import { nanoid } from 'nanoid';
2
3
  import { loadConfig } from '../config/index.js';
3
4
  import { createLogger } from '../logger/index.js';
4
5
  import { configureDbPath } from '../utils/db.js';
@@ -6,7 +7,6 @@ import { setupProxy } from './proxy.js';
6
7
  import { setupUiRoutes } from './ui-route.js';
7
8
  import { setupChatRoute } from './chat-route.js';
8
9
  import { setupDocsRoutes } from './docs-route.js';
9
- import { requestIdMiddleware } from './middleware/request-id.js';
10
10
  export async function createServer() {
11
11
  const config = await loadConfig();
12
12
  if (config.storage?.path) {
@@ -16,9 +16,10 @@ export async function createServer() {
16
16
  const fastify = Fastify({
17
17
  logger: logger,
18
18
  trustProxy: true,
19
- requestTimeout: 310000
19
+ requestTimeout: 310000,
20
+ genReqId: () => nanoid(16),
21
+ requestIdHeader: 'x-request-id'
20
22
  });
21
- fastify.register(requestIdMiddleware);
22
23
  await setupUiRoutes(fastify); // UI routes registered BEFORE proxy to avoid being proxied
23
24
  await setupProxy(fastify, config.proxy);
24
25
  await setupChatRoute(fastify, config.proxy); // MCP-enhanced chat endpoint