@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
@@ -2,7 +2,7 @@ import { mkdtempSync, rmSync } from 'fs';
2
2
  import { tmpdir } from 'os';
3
3
  import path from 'path';
4
4
  import { configureDbPath, initDatabase, closeDatabase, getDatabase } from '../db.js';
5
- import { createRequestRecorder, recordIteration, listRequestDB, interruptStaleRequestsOnStartup } from '../request-writer.js';
5
+ import { createRequestRecorder, recordIteration, listRequestDB, interruptStaleRequestsOnStartup, repairClosedRequests, isStreamCompleteEvents, reconstructUsageFromEvents } from '../request-writer.js';
6
6
  import { pushNewRequest } from '../../server/data-push.js';
7
7
  jest.mock('../../server/data-push.js', () => ({ pushNewRequest: jest.fn() }));
8
8
  const pushMock = pushNewRequest;
@@ -136,6 +136,62 @@ describe('request-writer', () => {
136
136
  expect(summary.requestId).toBe(cloudId);
137
137
  expect(summary.pendingId).toBe(`${meta.sessionId}:${meta.localRequestId}`);
138
138
  });
139
+ it('complete() migration leaves pre-existing events of another session with the same localRequestId untouched', async () => {
140
+ const meta = makeMeta('residue');
141
+ counter += 1;
142
+ const cloudId = `cloud_${counter}`;
143
+ const oldSession = 'sess_old_residue';
144
+ // recorder 创建之前:相同 localRequestId、不同 session_id 的历史残留事件
145
+ const preIns = getDatabase().prepare('INSERT INTO response_events (request_id, session_id, event_index, event_data) VALUES (?, ?, ?, ?)');
146
+ preIns.run(meta.localRequestId, oldSession, 0, '"old0"');
147
+ preIns.run(meta.localRequestId, oldSession, 1, '"old1"');
148
+ const rec = createRequestRecorder(meta);
149
+ await rec.pending();
150
+ for (let i = 0; i < 3; i++) {
151
+ rec.appendEvent({ type: 'chunk', seq: i });
152
+ }
153
+ await rec.complete({ cloudRequestId: cloudId, statusCode: 200 });
154
+ // 预写事件 request_id 不变(未被迁走)
155
+ const staleRows = getEventRows(meta.localRequestId, oldSession);
156
+ expect(staleRows).toHaveLength(2);
157
+ expect(staleRows.map(r => [r.request_id, r.event_index])).toEqual([
158
+ [meta.localRequestId, 0],
159
+ [meta.localRequestId, 1]
160
+ ]);
161
+ // 本 recorder 的事件迁到 cloudId,localId 下无事件残留
162
+ expect(getEventRows(meta.localRequestId, meta.sessionId)).toHaveLength(0);
163
+ const moved = getEventRows(cloudId, meta.sessionId);
164
+ expect(moved).toHaveLength(3);
165
+ expect(moved.map(r => r.event_index)).toEqual([0, 1, 2]);
166
+ });
167
+ it('complete() migration leaves same-session historical events with the same localRequestId untouched', async () => {
168
+ const meta = makeMeta('sameresidue');
169
+ counter += 1;
170
+ const cloudId = `cloud_${counter}`;
171
+ // recorder 创建之前:相同 localRequestId、相同 session_id 的历史残留
172
+ // (模拟 req-N 计数器复用后同 session 残留)。用不与新请求重叠的 event_index,
173
+ // 避免唯一索引 + INSERT OR IGNORE 干扰本用例对迁移行为的验证。
174
+ const preIns = getDatabase().prepare('INSERT INTO response_events (request_id, session_id, event_index, event_data) VALUES (?, ?, ?, ?)');
175
+ preIns.run(meta.localRequestId, meta.sessionId, 50, '"old0"');
176
+ preIns.run(meta.localRequestId, meta.sessionId, 51, '"old1"');
177
+ const rec = createRequestRecorder(meta);
178
+ await rec.pending();
179
+ for (let i = 0; i < 3; i++) {
180
+ rec.appendEvent({ type: 'chunk', seq: i });
181
+ }
182
+ await rec.complete({ cloudRequestId: cloudId, statusCode: 200 });
183
+ // 预写的 2 行 request_id 仍为 localRequestId(未被迁走)
184
+ const staleRows = getEventRows(meta.localRequestId, meta.sessionId);
185
+ expect(staleRows).toHaveLength(2);
186
+ expect(staleRows.map(r => [r.request_id, r.event_index])).toEqual([
187
+ [meta.localRequestId, 50],
188
+ [meta.localRequestId, 51]
189
+ ]);
190
+ // recorder 自己的事件迁到 cloudId
191
+ const moved = getEventRows(cloudId, meta.sessionId);
192
+ expect(moved).toHaveLength(3);
193
+ expect(moved.map(r => r.event_index)).toEqual([0, 1, 2]);
194
+ });
139
195
  it('fail() updates the pending row status/error/closed_at and pushes completed', async () => {
140
196
  const meta = makeMeta('fail');
141
197
  const rec = createRequestRecorder(meta);
@@ -240,8 +296,10 @@ describe('request-writer', () => {
240
296
  const open = requests.find(r => r.requestId === openMeta.localRequestId);
241
297
  expect(closed).toBeTruthy();
242
298
  expect(closed.closedAt).toEqual(expect.any(String));
299
+ expect(closed.error).toBe('boom');
243
300
  expect(open).toBeTruthy();
244
301
  expect(open.closedAt).toBeUndefined();
302
+ expect(open.error).toBeUndefined();
245
303
  });
246
304
  it('interruptStaleRequestsOnStartup() marks aged orphan rows and leaves fresh/completed rows untouched', async () => {
247
305
  // 超龄无主行:已收到响应头(200),时间戳回拨到 6 分钟前
@@ -264,8 +322,9 @@ describe('request-writer', () => {
264
322
  getDatabase()
265
323
  .prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
266
324
  .run(new Date(Date.now() - 6 * 60000).toISOString(), doneMeta.localRequestId, doneMeta.sessionId);
267
- const count = interruptStaleRequestsOnStartup();
268
- expect(count).toBe(1);
325
+ const { completed, interrupted } = interruptStaleRequestsOnStartup();
326
+ expect(completed).toBe(0);
327
+ expect(interrupted).toBe(1);
269
328
  const oldRow = getRequestRow(oldMeta.localRequestId, oldMeta.sessionId);
270
329
  expect(oldRow.closed_at).toBeTruthy();
271
330
  expect(oldRow.error).toBe('interrupted: proxy restarted');
@@ -277,4 +336,206 @@ describe('request-writer', () => {
277
336
  const doneRow = getRequestRow(doneMeta.localRequestId, doneMeta.sessionId);
278
337
  expect(doneRow.closed_at).toBeNull();
279
338
  });
339
+ it('harvests an aged pending row ending with message_stop as completed: completed_at set, closed_at/error empty, tokens backfilled', async () => {
340
+ const meta = makeMeta('stopcomplete');
341
+ const rec = createRequestRecorder(meta);
342
+ await rec.pending();
343
+ rec.headers({ statusCode: 200 });
344
+ rec.appendEvent({
345
+ type: 'message_start',
346
+ usage: { input_tokens: 100, cache_read_input_tokens: 10, cache_creation_input_tokens: 2, output_tokens: 1 }
347
+ });
348
+ // 17 内容事件:总事件数 20(整批落库),message_start 在尾部 10 条窗口之外,
349
+ // 依赖定向 SQL 取锚点事件
350
+ for (let i = 0; i < 17; i++) {
351
+ rec.appendEvent({ type: 'content_block_delta', seq: i });
352
+ }
353
+ rec.appendEvent({ type: 'message_delta', usage: { output_tokens: 50 } });
354
+ rec.appendEvent({ type: 'message_stop' });
355
+ getDatabase()
356
+ .prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
357
+ .run(new Date(Date.now() - 6 * 60000).toISOString(), meta.localRequestId, meta.sessionId);
358
+ const { completed, interrupted } = interruptStaleRequestsOnStartup();
359
+ expect(completed).toBe(1);
360
+ expect(interrupted).toBe(0);
361
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
362
+ expect(row.completed_at).toBeTruthy();
363
+ expect(row.closed_at).toBeNull();
364
+ expect(row.error).toBeNull();
365
+ expect(row.status_code).toBe(200);
366
+ expect(row.response_time).toBeGreaterThan(0);
367
+ expect(row.input_tokens).toBe(100);
368
+ expect(row.output_tokens).toBe(50);
369
+ expect(row.cache_read_tokens).toBe(10);
370
+ expect(row.cache_creation_tokens).toBe(2);
371
+ });
372
+ it('aged row without message_stop stays interrupted but tokens are backfilled when usage is recoverable', async () => {
373
+ const meta = makeMeta('stopmissing');
374
+ const rec = createRequestRecorder(meta);
375
+ await rec.pending();
376
+ rec.headers({ statusCode: 200 });
377
+ rec.appendEvent({
378
+ type: 'message_start',
379
+ usage: { input_tokens: 30, cache_read_input_tokens: 3, cache_creation_input_tokens: 1 }
380
+ });
381
+ for (let i = 0; i < 18; i++) {
382
+ rec.appendEvent({ type: 'content_block_delta', seq: i });
383
+ }
384
+ rec.appendEvent({ type: 'message_delta', usage: { output_tokens: 12 } });
385
+ getDatabase()
386
+ .prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
387
+ .run(new Date(Date.now() - 6 * 60000).toISOString(), meta.localRequestId, meta.sessionId);
388
+ const { completed, interrupted } = interruptStaleRequestsOnStartup();
389
+ expect(completed).toBe(0);
390
+ expect(interrupted).toBe(1);
391
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
392
+ expect(row.closed_at).toBeTruthy();
393
+ expect(row.error).toBe('interrupted: proxy restarted');
394
+ expect(row.completed_at).toBeNull();
395
+ expect(row.input_tokens).toBe(30);
396
+ expect(row.output_tokens).toBe(12);
397
+ expect(row.cache_read_tokens).toBe(3);
398
+ expect(row.cache_creation_tokens).toBe(1);
399
+ });
400
+ it('reconstructUsageFromEvents merges message_start base with message_delta into all four fields (field-level, not overwrite)', () => {
401
+ const usage = reconstructUsageFromEvents([
402
+ { type: 'message_start', usage: { input_tokens: 100, output_tokens: 1, cache_read_input_tokens: 80, cache_creation_input_tokens: 5 } },
403
+ { type: 'content_block_delta' },
404
+ { type: 'message_delta', usage: { output_tokens: 42 } }
405
+ ]);
406
+ expect(usage).toEqual({
407
+ input_tokens: 100,
408
+ output_tokens: 42,
409
+ cache_read_input_tokens: 80,
410
+ cache_creation_input_tokens: 5
411
+ });
412
+ });
413
+ it('message_delta cache fields are merged too, and undefined is returned when no usage exists', () => {
414
+ const usage = reconstructUsageFromEvents([
415
+ { type: 'message_start', usage: { input_tokens: 10 } },
416
+ { type: 'message_delta', usage: { output_tokens: 4, cache_read_input_tokens: 2 } }
417
+ ]);
418
+ expect(usage).toEqual({ input_tokens: 10, output_tokens: 4, cache_read_input_tokens: 2 });
419
+ expect(reconstructUsageFromEvents([{ type: 'content_block_delta' }])).toBeUndefined();
420
+ });
421
+ it('OpenAI-style: choices[0].finish_reason non-empty marks complete, and a chunk with usage backfills tokens', async () => {
422
+ expect(isStreamCompleteEvents([
423
+ { choices: [{ delta: { role: 'assistant' }, finish_reason: null }] }
424
+ ])).toBe(false);
425
+ expect(isStreamCompleteEvents([
426
+ { choices: [{ delta: {}, finish_reason: 'stop' }] }
427
+ ])).toBe(true);
428
+ const usage = reconstructUsageFromEvents([
429
+ { choices: [{ delta: { content: 'hi' }, finish_reason: null }] },
430
+ { choices: [{ delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 9, completion_tokens: 17 } }
431
+ ]);
432
+ expect(usage).toEqual({ input_tokens: 9, output_tokens: 17 });
433
+ // 端到端:OpenAI 超龄行(事件 event_type 为 NULL)走完成语义并回填
434
+ const meta = makeMeta('openaistop');
435
+ const rec = createRequestRecorder(meta);
436
+ await rec.pending();
437
+ rec.headers({ statusCode: 200 });
438
+ for (let i = 0; i < 19; i++) {
439
+ rec.appendEvent({ choices: [{ delta: { content: 'x' }, finish_reason: null }] });
440
+ }
441
+ rec.appendEvent({ choices: [{ delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 9, completion_tokens: 17 } });
442
+ getDatabase()
443
+ .prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
444
+ .run(new Date(Date.now() - 6 * 60000).toISOString(), meta.localRequestId, meta.sessionId);
445
+ const { completed, interrupted } = interruptStaleRequestsOnStartup();
446
+ expect(completed).toBe(1);
447
+ expect(interrupted).toBe(0);
448
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
449
+ expect(row.completed_at).toBeTruthy();
450
+ expect(row.closed_at).toBeNull();
451
+ expect(row.input_tokens).toBe(9);
452
+ expect(row.output_tokens).toBe(17);
453
+ });
454
+ it('interrupt() also backfills recoverable token columns', async () => {
455
+ const meta = makeMeta('interrupttokens');
456
+ const rec = createRequestRecorder(meta);
457
+ await rec.pending();
458
+ rec.appendEvent({
459
+ type: 'message_start',
460
+ usage: { input_tokens: 20, cache_read_input_tokens: 4, cache_creation_input_tokens: 1 }
461
+ });
462
+ for (let i = 0; i < 20; i++) {
463
+ rec.appendEvent({ type: 'content_block_delta', seq: i });
464
+ }
465
+ rec.appendEvent({ type: 'message_delta', usage: { output_tokens: 7 } });
466
+ pushMock.mockClear();
467
+ rec.interrupt({ statusCode: 200, error: 'stream interrupted: socket hang up' });
468
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
469
+ expect(row.closed_at).toBeTruthy();
470
+ expect(row.error).toBe('stream interrupted: socket hang up');
471
+ expect(row.input_tokens).toBe(20);
472
+ expect(row.output_tokens).toBe(7);
473
+ expect(row.cache_read_tokens).toBe(4);
474
+ expect(row.cache_creation_tokens).toBe(1);
475
+ const completedCalls = pushMock.mock.calls.filter(c => c[1] === 'completed');
476
+ expect(completedCalls[0][0].usage).toEqual(expect.objectContaining({ output_tokens: 7 }));
477
+ });
478
+ it('repairClosedRequests recovers closed complete rows, fills tokens, and is idempotent', async () => {
479
+ // 完整但被旧逻辑误标中断的行:message_stop 已入库,token 全 NULL
480
+ const completeMeta = makeMeta('repaircomplete');
481
+ const completeRec = createRequestRecorder(completeMeta);
482
+ await completeRec.pending();
483
+ completeRec.appendEvent({
484
+ type: 'message_start',
485
+ usage: { input_tokens: 15, cache_read_input_tokens: 2, cache_creation_input_tokens: 1 }
486
+ });
487
+ for (let i = 0; i < 20; i++) {
488
+ completeRec.appendEvent({ type: 'content_block_delta', seq: i });
489
+ }
490
+ completeRec.appendEvent({ type: 'message_delta', usage: { output_tokens: 6 } });
491
+ completeRec.appendEvent({ type: 'message_stop' });
492
+ completeRec.interrupt({ statusCode: 200, error: 'interrupted: proxy restarted' });
493
+ // 模拟旧版本中断行:清空 token 列(旧 interrupt 不回填 usage)
494
+ getDatabase()
495
+ .prepare(`UPDATE requests SET
496
+ input_tokens = NULL, output_tokens = NULL,
497
+ cache_read_tokens = NULL, cache_creation_tokens = NULL
498
+ WHERE request_id = ? AND session_id = ?`)
499
+ .run(completeMeta.localRequestId, completeMeta.sessionId);
500
+ // 真正中断的行:无 message_stop,但 usage 可恢复
501
+ const brokenMeta = makeMeta('repairbroken');
502
+ const brokenRec = createRequestRecorder(brokenMeta);
503
+ await brokenRec.pending();
504
+ brokenRec.appendEvent({
505
+ type: 'message_start',
506
+ usage: { input_tokens: 8, cache_read_input_tokens: 1 }
507
+ });
508
+ for (let i = 0; i < 20; i++) {
509
+ brokenRec.appendEvent({ type: 'content_block_delta', seq: i });
510
+ }
511
+ brokenRec.appendEvent({ type: 'message_delta', usage: { output_tokens: 3 } });
512
+ brokenRec.interrupt({ statusCode: 200, error: 'interrupted: proxy restarted' });
513
+ // 模拟旧版本中断行:清空 token 列
514
+ getDatabase()
515
+ .prepare(`UPDATE requests SET
516
+ input_tokens = NULL, output_tokens = NULL,
517
+ cache_read_tokens = NULL, cache_creation_tokens = NULL
518
+ WHERE request_id = ? AND session_id = ?`)
519
+ .run(brokenMeta.localRequestId, brokenMeta.sessionId);
520
+ const first = repairClosedRequests();
521
+ expect(first.recovered).toBe(1);
522
+ expect(first.tokensFilled).toBe(2);
523
+ const recoveredRow = getRequestRow(completeMeta.localRequestId, completeMeta.sessionId);
524
+ expect(recoveredRow.closed_at).toBeNull();
525
+ expect(recoveredRow.error).toBeNull();
526
+ expect(recoveredRow.completed_at).toBeTruthy();
527
+ expect(recoveredRow.input_tokens).toBe(15);
528
+ expect(recoveredRow.output_tokens).toBe(6);
529
+ const brokenRow = getRequestRow(brokenMeta.localRequestId, brokenMeta.sessionId);
530
+ expect(brokenRow.closed_at).toBeTruthy();
531
+ expect(brokenRow.input_tokens).toBe(8);
532
+ expect(brokenRow.output_tokens).toBe(3);
533
+ // 幂等:第二次不产生任何变更,行状态保持
534
+ const second = repairClosedRequests();
535
+ expect(second).toEqual({ recovered: 0, tokensFilled: 0 });
536
+ const rerunRecovered = getRequestRow(completeMeta.localRequestId, completeMeta.sessionId);
537
+ expect(rerunRecovered.completed_at).toBe(recoveredRow.completed_at);
538
+ const rerunBroken = getRequestRow(brokenMeta.localRequestId, brokenMeta.sessionId);
539
+ expect(rerunBroken.closed_at).toBe(brokenRow.closed_at);
540
+ });
280
541
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,114 @@
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 { aggregateStats, getStatsData, getTodayStats } from '../stats-aggregator.js';
6
+ let counter = 0;
7
+ // 直接往 requests 表插行,绕过 request-writer,聚焦聚合口径
8
+ function insertRequest(opts) {
9
+ counter += 1;
10
+ getDatabase()
11
+ .prepare(`
12
+ INSERT INTO requests (
13
+ request_id, session_id, timestamp, method, path, status_code, response_time,
14
+ model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, closed_at
15
+ ) VALUES (?, ?, ?, 'POST', '/v1/messages', 200, 100, ?, ?, ?, ?, ?, ?)
16
+ `)
17
+ .run(`req_${counter}`, `sess_${counter}`, opts.timestamp, opts.model ?? null, opts.inputTokens ?? null, opts.outputTokens ?? null, opts.cacheReadTokens ?? null, opts.cacheCreationTokens ?? null, opts.closedAt ?? null);
18
+ }
19
+ function hourKeyOf(iso) {
20
+ const d = new Date(iso);
21
+ return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}-${String(d.getUTCHours()).padStart(2, '0')}`;
22
+ }
23
+ describe('stats-aggregator', () => {
24
+ let tempDir;
25
+ beforeEach(() => {
26
+ tempDir = mkdtempSync(path.join(tmpdir(), 'ccm-stats-'));
27
+ configureDbPath(path.join(tempDir, 'test.db'));
28
+ initDatabase();
29
+ });
30
+ afterEach(() => {
31
+ closeDatabase();
32
+ rmSync(tempDir, { recursive: true, force: true });
33
+ });
34
+ it('P0: requests 源行删除后重算,冻结的 hourly 快照同步消失', () => {
35
+ const ts = new Date(Date.now() - 2 * 3600000).toISOString();
36
+ const key = hourKeyOf(ts);
37
+ insertRequest({ timestamp: ts, inputTokens: 100, outputTokens: 20 });
38
+ aggregateStats();
39
+ let stats = getStatsData();
40
+ expect(stats.hourly[key]).toBeTruthy();
41
+ expect(stats.hourly[key].input_tokens).toBe(100);
42
+ expect(stats.hourly[key].request_count).toBe(1);
43
+ // 模拟 cleanOldRequestsDB 把源行删掉
44
+ getDatabase().prepare('DELETE FROM requests').run();
45
+ aggregateStats();
46
+ stats = getStatsData();
47
+ expect(stats.hourly[key]).toBeUndefined();
48
+ });
49
+ it('P2: closed_at 非空的中断行仍计入 aggregateStats 与 getTodayStats', () => {
50
+ const ts = new Date().toISOString();
51
+ const key = hourKeyOf(ts);
52
+ insertRequest({
53
+ timestamp: ts,
54
+ inputTokens: 100,
55
+ outputTokens: 50,
56
+ cacheReadTokens: 10,
57
+ cacheCreationTokens: 3,
58
+ closedAt: new Date().toISOString(),
59
+ model: 'claude-interrupted'
60
+ });
61
+ aggregateStats();
62
+ const entry = getStatsData().hourly[key];
63
+ expect(entry).toBeTruthy();
64
+ expect(entry.input_tokens).toBe(100);
65
+ expect(entry.output_tokens).toBe(50);
66
+ expect(entry.cache_read_tokens).toBe(10);
67
+ expect(entry.cache_creation_tokens).toBe(3);
68
+ expect(entry.request_count).toBe(1);
69
+ const since = new Date(Date.now() - 3600000).toISOString();
70
+ const today = getTodayStats(since);
71
+ expect(today.input_tokens).toBe(100);
72
+ expect(today.output_tokens).toBe(50);
73
+ expect(today.cache_read_tokens).toBe(10);
74
+ expect(today.request_count).toBe(1);
75
+ expect(today.models['claude-interrupted']).toBe(1);
76
+ });
77
+ it('P1: aggregateStats(1) 只保留 1 天窗口,无参仍为 30 天窗口', () => {
78
+ const nowIso = new Date().toISOString();
79
+ const oldIso = new Date(Date.now() - 2 * 86400000).toISOString();
80
+ const nowKey = hourKeyOf(nowIso);
81
+ const oldKey = hourKeyOf(oldIso);
82
+ insertRequest({ timestamp: nowIso, inputTokens: 1 });
83
+ insertRequest({ timestamp: oldIso, inputTokens: 2 });
84
+ aggregateStats(1);
85
+ let stats = getStatsData();
86
+ expect(stats.hourly[nowKey]).toBeTruthy();
87
+ expect(stats.hourly[oldKey]).toBeUndefined();
88
+ aggregateStats();
89
+ stats = getStatsData();
90
+ expect(stats.hourly[nowKey]).toBeTruthy();
91
+ expect(stats.hourly[oldKey]).toBeTruthy();
92
+ });
93
+ describe('P1b: aggregateStats 的 windowDays 分支(截断与回退)', () => {
94
+ // 31 天前落在 30 天窗口外、40 天窗口内:保留它意味着窗口被放宽到 40,
95
+ // 删除它才是「截断/回退为 30」;2 天前始终在窗口内。
96
+ it.each([
97
+ [40, '超过 30 截断为 30'],
98
+ [0, '0 回退默认 30'],
99
+ [-3, '负数回退默认 30'],
100
+ [1.5, '非整数回退默认 30']
101
+ ])('maxDays = %s(%s):2 天前保留,31 天前删除', (maxDays, _desc) => {
102
+ const recentIso = new Date(Date.now() - 2 * 86400000).toISOString();
103
+ const oldIso = new Date(Date.now() - 31 * 86400000).toISOString();
104
+ const recentKey = hourKeyOf(recentIso);
105
+ const oldKey = hourKeyOf(oldIso);
106
+ insertRequest({ timestamp: recentIso, inputTokens: 2 });
107
+ insertRequest({ timestamp: oldIso, inputTokens: 31 });
108
+ aggregateStats(maxDays);
109
+ const stats = getStatsData();
110
+ expect(stats.hourly[recentKey]).toBeTruthy();
111
+ expect(stats.hourly[oldKey]).toBeUndefined();
112
+ });
113
+ });
114
+ });
@@ -0,0 +1,8 @@
1
+ import Database from 'better-sqlite3';
2
+ export interface VacuumResult {
3
+ autoVacuumBefore: number;
4
+ pagesBefore: number;
5
+ pagesAfter: number;
6
+ pageSize: number;
7
+ }
8
+ export declare function vacuumDatabase(db: Database.Database): VacuumResult;
@@ -0,0 +1,18 @@
1
+ // 回收空闲页。auto_vacuum 不是 INCREMENTAL 时,pragma 修改只对新建库生效,
2
+ // 已有库必须先 VACUUM 全库重写才能让 pragma 生效;之后例行调用走
3
+ // incremental_vacuum,只还页不重写,代价低。
4
+ export function vacuumDatabase(db) {
5
+ const autoVacuumBefore = db.pragma('auto_vacuum', { simple: true }) ?? 0;
6
+ const pagesBefore = db.pragma('page_count', { simple: true });
7
+ const pageSize = db.pragma('page_size', { simple: true });
8
+ if (autoVacuumBefore !== 2) {
9
+ db.pragma('auto_vacuum = INCREMENTAL');
10
+ // 首次:全库重写,pragma 在重写后生效
11
+ db.exec('VACUUM');
12
+ }
13
+ else {
14
+ db.pragma('incremental_vacuum');
15
+ }
16
+ const pagesAfter = db.pragma('page_count', { simple: true });
17
+ return { autoVacuumBefore, pagesBefore, pagesAfter, pageSize };
18
+ }
@@ -11,5 +11,6 @@ export declare function applySchemaV8(db: Database.Database): void;
11
11
  export declare function applySchemaV9(db: Database.Database): void;
12
12
  export declare function applySchemaV10(db: Database.Database): void;
13
13
  export declare function applySchemaV11(db: Database.Database): void;
14
+ export declare function applySchemaV12(db: Database.Database): void;
14
15
  export declare function closeDatabase(): void;
15
16
  export declare function getDatabase(): Database.Database;
package/dist/utils/db.js CHANGED
@@ -76,6 +76,10 @@ export function initDatabase() {
76
76
  applySchemaV11(db);
77
77
  db.prepare('INSERT OR REPLACE INTO schema_version (version) VALUES (11)').run();
78
78
  }
79
+ if (currentVersion < 12) {
80
+ applySchemaV12(db);
81
+ db.prepare('INSERT OR REPLACE INTO schema_version (version) VALUES (12)').run();
82
+ }
79
83
  return db;
80
84
  }
81
85
  export function applySchemaV2(db) {
@@ -304,6 +308,64 @@ export function applySchemaV11(db) {
304
308
  ON learn_tasks(slug) WHERE status = 'running'
305
309
  `);
306
310
  }
311
+ export function applySchemaV12(db) {
312
+ // response_events 历史上存在 (request_id, session_id, event_index) 三元组重复行
313
+ // (request_id 计数器重启复用所致)和 requests 表无对应行的孤儿事件;清理后加
314
+ // 唯一索引硬守。生产库约 800 万行:先一次性把待删行号收集进临时表,再按主键
315
+ // 分批删除(每批 5000 行、独立事务),避免单个巨型事务长时间锁库。
316
+ //
317
+ // 注意:不能用 `GROUP BY ... HAVING COUNT(*) > 1 AND id != MIN(id)` 直接选行——
318
+ // SQLite 裸列 id 在该计划下取 MIN(id) 所在行,HAVING 恒假,实测删除 0 行。
319
+ const batchSize = 5000;
320
+ db.exec('DROP TABLE IF EXISTS temp.ids_to_purge');
321
+ db.exec('CREATE TEMP TABLE ids_to_purge (id INTEGER PRIMARY KEY)');
322
+ // a) 去重:每组 (request_id, session_id, event_index) 保留 MIN(id),其余入临时表
323
+ db.prepare(`
324
+ INSERT INTO ids_to_purge
325
+ SELECT id FROM response_events
326
+ WHERE id NOT IN (
327
+ SELECT MIN(id) FROM response_events
328
+ GROUP BY request_id, session_id, event_index
329
+ )
330
+ `).run();
331
+ purgeResponseEventsBatched(db, batchSize);
332
+ // b) 孤儿事件:requests 表中无对应 request_id
333
+ db.prepare(`
334
+ INSERT INTO ids_to_purge
335
+ SELECT re.id FROM response_events re
336
+ WHERE NOT EXISTS (
337
+ SELECT 1 FROM requests r WHERE r.request_id = re.request_id
338
+ )
339
+ `).run();
340
+ purgeResponseEventsBatched(db, batchSize);
341
+ db.exec('DROP TABLE IF EXISTS temp.ids_to_purge');
342
+ // c) 唯一索引(IF NOT EXISTS,幂等)
343
+ db.exec(`
344
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_response_events
345
+ ON response_events(request_id, session_id, event_index)
346
+ `);
347
+ }
348
+ // 按临时表 ids_to_purge 收集的行号分批删除 response_events;每批同时剪除临时表中
349
+ // 已处理的行号,整批包在一个事务里,循环直到没有可删行。
350
+ function purgeResponseEventsBatched(db, batchSize) {
351
+ const deleteEvents = db.prepare(`
352
+ DELETE FROM response_events
353
+ WHERE id IN (SELECT id FROM ids_to_purge ORDER BY id LIMIT ?)
354
+ `);
355
+ const pruneTemp = db.prepare(`
356
+ DELETE FROM ids_to_purge
357
+ WHERE id IN (SELECT id FROM ids_to_purge ORDER BY id LIMIT ?)
358
+ `);
359
+ const runBatch = db.transaction(() => {
360
+ const info = deleteEvents.run(batchSize);
361
+ pruneTemp.run(batchSize);
362
+ return info.changes;
363
+ });
364
+ while (true) {
365
+ if (runBatch() === 0)
366
+ break;
367
+ }
368
+ }
307
369
  export function closeDatabase() {
308
370
  if (db) {
309
371
  db.close();
@@ -37,6 +37,7 @@ export interface RequestSummary {
37
37
  userAgent?: string;
38
38
  usage?: TokenUsage;
39
39
  closedAt?: string;
40
+ error?: string;
40
41
  }
41
42
  export interface RequestRecorder {
42
43
  pending(): Promise<void>;
@@ -83,6 +84,20 @@ interface LoadedRequest {
83
84
  error?: string;
84
85
  };
85
86
  }
87
+ /**
88
+ * 判断事件集合是否构成一条完整返回的流。
89
+ * Claude:出现 type === 'message_stop'。
90
+ * OpenAI 兼容:chunk 内 choices[0].finish_reason 非空
91
+ * ([DONE] 不入库,不能作为 DB 判定依据)。
92
+ */
93
+ export declare function isStreamCompleteEvents(events: any[] | undefined): boolean;
94
+ /**
95
+ * 从事件集合按字段合并重建 usage,不做整体覆盖。
96
+ * Claude:message_start.usage(input_tokens / cache_*)为底;
97
+ * message_delta.usage 的字段优先(output_tokens 为主,cache 字段也合入)。
98
+ * OpenAI:取带 .usage 的 chunk(prompt_tokens/completion_tokens)。
99
+ */
100
+ export declare function reconstructUsageFromEvents(events: any[] | undefined): TokenUsage | undefined;
86
101
  export declare function createRequestRecorder(meta: RecordedRequest): RequestRecorder;
87
102
  export declare function recordIteration(meta: RecordedRequest & {
88
103
  response: {
@@ -97,7 +112,14 @@ export declare function listRequestDB(limit?: number, before?: string): Promise<
97
112
  hasMore: boolean;
98
113
  }>;
99
114
  export declare function cleanOldRequestsDB(daysToKeep?: number): Promise<void>;
100
- export declare function interruptStaleRequestsOnStartup(maxAgeMs?: number): number;
115
+ export declare function interruptStaleRequestsOnStartup(maxAgeMs?: number): {
116
+ completed: number;
117
+ interrupted: number;
118
+ };
119
+ export declare function repairClosedRequests(): {
120
+ recovered: number;
121
+ tokensFilled: number;
122
+ };
101
123
  export declare function loadSessionHistory(sessionId: string): {
102
124
  messages: any[];
103
125
  system?: any;