@cc-monitoring/proxy-core 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/dist/cli/__tests__/commands.test.js +1 -1
  2. package/dist/cli/commands/exec.js +31 -42
  3. package/dist/docs/404.html +2 -2
  4. package/dist/docs/assets/{app.B9A39uFv.js → app.DtYNT5zS.js} +1 -1
  5. package/dist/docs/assets/chunks/@localSearchIndexroot.C4sjikMO.js +1 -0
  6. package/dist/docs/assets/chunks/{VPLocalSearchBox.DdhC696L.js → VPLocalSearchBox.DpXF9ZAI.js} +1 -1
  7. package/dist/docs/assets/chunks/{theme.CfB2tOFi.js → theme.TS_gUyc7.js} +2 -2
  8. package/dist/docs/assets/{proxy-cli_data-hooks.md.w9Oe1OD3.js → proxy-cli_data-hooks.md.Bz8CVbxO.js} +1 -1
  9. package/dist/docs/assets/{proxy-core_ui-routes.md.Dyqkeoiv.js → proxy-core_ui-routes.md.B21t1p7R.js} +1 -1
  10. package/dist/docs/hashmap.json +1 -1
  11. package/dist/docs/index.html +3 -3
  12. package/dist/docs/overview.html +3 -3
  13. package/dist/docs/packages-utils.html +3 -3
  14. package/dist/docs/proxy-admin/env-and-build.html +3 -3
  15. package/dist/docs/proxy-admin/index.html +3 -3
  16. package/dist/docs/proxy-admin/layout-and-menu.html +3 -3
  17. package/dist/docs/proxy-admin/pages.html +3 -3
  18. package/dist/docs/proxy-admin/routing-and-auth.html +3 -3
  19. package/dist/docs/proxy-admin/services-and-mock.html +3 -3
  20. package/dist/docs/proxy-cli/api-layer.html +3 -3
  21. package/dist/docs/proxy-cli/app-shell.html +3 -3
  22. package/dist/docs/proxy-cli/components-common.html +3 -3
  23. package/dist/docs/proxy-cli/components-config.html +3 -3
  24. package/dist/docs/proxy-cli/components-requests.html +3 -3
  25. package/dist/docs/proxy-cli/components-stats.html +3 -3
  26. package/dist/docs/proxy-cli/components-status.html +3 -3
  27. package/dist/docs/proxy-cli/data-hooks.html +5 -5
  28. package/dist/docs/proxy-cli/index.html +3 -3
  29. package/dist/docs/proxy-cli/utils-and-i18n.html +3 -3
  30. package/dist/docs/proxy-core/chat-api.html +3 -3
  31. package/dist/docs/proxy-core/cli.html +3 -3
  32. package/dist/docs/proxy-core/config.html +3 -3
  33. package/dist/docs/proxy-core/data-layer.html +3 -3
  34. package/dist/docs/proxy-core/entry.html +3 -3
  35. package/dist/docs/proxy-core/index.html +3 -3
  36. package/dist/docs/proxy-core/pm2.html +3 -3
  37. package/dist/docs/proxy-core/providers.html +3 -3
  38. package/dist/docs/proxy-core/proxy.html +3 -3
  39. package/dist/docs/proxy-core/stats.html +3 -3
  40. package/dist/docs/proxy-core/tool.html +3 -3
  41. package/dist/docs/proxy-core/ui-routes.html +5 -5
  42. package/dist/docs/proxy-core/utilities.html +3 -3
  43. package/dist/docs/quickstart.html +3 -3
  44. package/dist/docs/request-flow.html +3 -3
  45. package/dist/logger/index.d.ts +0 -3
  46. package/dist/logger/index.js +0 -1
  47. package/dist/mcp-server/tools.js +8 -9
  48. package/dist/server/__tests__/providers-route.test.js +1 -0
  49. package/dist/server/auth.d.ts +0 -1
  50. package/dist/server/auth.js +1 -1
  51. package/dist/server/chat-route.js +48 -162
  52. package/dist/server/data-push.d.ts +0 -1
  53. package/dist/server/data-push.js +0 -7
  54. package/dist/server/proxy.js +128 -224
  55. package/dist/server/ui-route.js +76 -59
  56. package/dist/tools/index.d.ts +1 -1
  57. package/dist/tools/index.js +1 -1
  58. package/dist/tools/registry.d.ts +0 -4
  59. package/dist/tools/registry.js +0 -8
  60. package/dist/types/config.d.ts +0 -13
  61. package/dist/types/pm2.d.ts +1 -2
  62. package/dist/ui/assets/{1c-DAu68mP4.js → 1c-CCx-FOtz.js} +1 -1
  63. package/dist/ui/assets/{abnf-Cu8RHCHK.js → abnf-Bwbi42Z8.js} +1 -1
  64. package/dist/ui/assets/{abnfDiagram-VRR7QNED-BUBToPXF.js → abnfDiagram-VRR7QNED-Dm1-8q0E.js} +1 -1
  65. package/dist/ui/assets/{accesslog-BG-34XOy.js → accesslog-DCytz_-m.js} +1 -1
  66. package/dist/ui/assets/{actionscript-iw0HHMGM.js → actionscript-3udf15oh.js} +1 -1
  67. package/dist/ui/assets/{ada-qRqj6VGQ.js → ada-M5Xa74xG.js} +1 -1
  68. package/dist/ui/assets/{angelscript-BeaXyx_P.js → angelscript-qlrFxfW-.js} +1 -1
  69. package/dist/ui/assets/{apache-CU8d-z9X.js → apache-B-sF-DQH.js} +1 -1
  70. package/dist/ui/assets/{applescript-d1Wxkqz1.js → applescript-4FdcBnbI.js} +1 -1
  71. package/dist/ui/assets/{arc-BMsPIm3j.js → arc-BB3K1enp.js} +1 -1
  72. package/dist/ui/assets/{arcade-CtbgrWTM.js → arcade-B_yqWaaG.js} +1 -1
  73. package/dist/ui/assets/{architectureDiagram-ZJ3FMSHR-CVjC5TrS.js → architectureDiagram-ZJ3FMSHR-B39vkY8V.js} +1 -1
  74. package/dist/ui/assets/{arduino-Bc9L39Hr.js → arduino-yAcBoTWU.js} +1 -1
  75. package/dist/ui/assets/{armasm-DuWmpjWR.js → armasm-wIcY84bI.js} +1 -1
  76. package/dist/ui/assets/{asciidoc-CDIhFTnx.js → asciidoc-Bxnu28-Z.js} +1 -1
  77. package/dist/ui/assets/{aspectj-SYj-57Es.js → aspectj-B2cKJA_O.js} +1 -1
  78. package/dist/ui/assets/{autohotkey-hBi5dq37.js → autohotkey-B_rMWTtE.js} +1 -1
  79. package/dist/ui/assets/{autoit-BSawXzhg.js → autoit-DwcuvMwv.js} +1 -1
  80. package/dist/ui/assets/{avrasm-CwYom8Np.js → avrasm-heGX1PXK.js} +1 -1
  81. package/dist/ui/assets/{awk-BvBRfGo2.js → awk-BiqCQAEf.js} +1 -1
  82. package/dist/ui/assets/{axapta-CqUavA8k.js → axapta-Cv8M-Ih6.js} +1 -1
  83. package/dist/ui/assets/{bash-BS_ZnmEu.js → bash-Divgn3JV.js} +1 -1
  84. package/dist/ui/assets/{basic-xbP4IXrN.js → basic-B9dnAP55.js} +1 -1
  85. package/dist/ui/assets/{blockDiagram-677ZJIJ3-CmpI3f5p.js → blockDiagram-677ZJIJ3-DQ1mf7A_.js} +1 -1
  86. package/dist/ui/assets/{bnf-BECwu6uW.js → bnf-kIL5mSsP.js} +1 -1
  87. package/dist/ui/assets/{brainfuck-QQgu4j4N.js → brainfuck-DqB7xDjH.js} +1 -1
  88. package/dist/ui/assets/{coq-IDMIFfem.js → c-HmsCM6sx.js} +1 -1
  89. package/dist/ui/assets/{c-like-DYrFOtXy.js → c-like-De5MhE7G.js} +1 -1
  90. package/dist/ui/assets/{c4Diagram-LMCZKHZV-B6Hgngz9.js → c4Diagram-LMCZKHZV-DQxtYsAx.js} +1 -1
  91. package/dist/ui/assets/{cal-CNMrJ6v5.js → cal-mYdfz9en.js} +1 -1
  92. package/dist/ui/assets/{capnproto-DYCKl6oY.js → capnproto-D04CRlOt.js} +1 -1
  93. package/dist/ui/assets/{ceylon-mb8tRPl0.js → ceylon-pi9lFCja.js} +1 -1
  94. package/dist/ui/assets/channel-ChTtxQDg.js +1 -0
  95. package/dist/ui/assets/{chunk-2Q5K7J3B-LxvQ-G67.js → chunk-2Q5K7J3B-BDD1nPt8.js} +1 -1
  96. package/dist/ui/assets/{chunk-32BRIVSS-DxzKK6Aw.js → chunk-32BRIVSS-DgeQFBb5.js} +1 -1
  97. package/dist/ui/assets/{chunk-5VM5RSS4-CZEoYJ2W.js → chunk-5VM5RSS4-C7Xf2SnI.js} +1 -1
  98. package/dist/ui/assets/{chunk-EX3LRPZG-DS2PytrN.js → chunk-EX3LRPZG-B_3p1HEv.js} +1 -1
  99. package/dist/ui/assets/{chunk-JWPE2WC7-UX0Sr1Ie.js → chunk-JWPE2WC7-Dvs7u8eY.js} +1 -1
  100. package/dist/ui/assets/{chunk-MOJQB5TN-YbJgT9t4.js → chunk-MOJQB5TN-By21wDCN.js} +1 -1
  101. package/dist/ui/assets/{chunk-RYQCIY6F-DS2VH_U9.js → chunk-RYQCIY6F-6NC700r1.js} +1 -1
  102. package/dist/ui/assets/{chunk-V7JOEXUC-CZr0pkI1.js → chunk-V7JOEXUC-vEUqZ3rh.js} +1 -1
  103. package/dist/ui/assets/{chunk-VR4S4FIN-50opPAD3.js → chunk-VR4S4FIN-6tX2dNN9.js} +1 -1
  104. package/dist/ui/assets/{chunk-XXDRQBXY-DAAX9l7U.js → chunk-XXDRQBXY-qDb2jxAR.js} +1 -1
  105. package/dist/ui/assets/classDiagram-OUVF2IWQ-Ccbvamlu.js +1 -0
  106. package/dist/ui/assets/classDiagram-v2-EOCWNBFH-Ccbvamlu.js +1 -0
  107. package/dist/ui/assets/{clean-C4X7dSkh.js → clean-CLCsDUl1.js} +1 -1
  108. package/dist/ui/assets/{clojure-DSg9gUa7.js → clojure-dVi5Xifj.js} +1 -1
  109. package/dist/ui/assets/{clojure-repl-CagRYx7h.js → clojure-repl-BO_FpFmF.js} +1 -1
  110. package/dist/ui/assets/{cmake-CFdBn53c.js → cmake-CP5aJbyK.js} +1 -1
  111. package/dist/ui/assets/{coffeescript-CJZtMOPx.js → coffeescript-ZEfu_9pF.js} +1 -1
  112. package/dist/ui/assets/{c-BRJEPYra.js → coq-6aKUn3zn.js} +1 -1
  113. package/dist/ui/assets/{cos-Bx2VJuzG.js → cos-BohCnRN2.js} +1 -1
  114. package/dist/ui/assets/{cose-bilkent-JH36ORCC-zWRLsZpZ.js → cose-bilkent-JH36ORCC-DyXCbbro.js} +1 -1
  115. package/dist/ui/assets/{cpp-CsG6DlC4.js → cpp-FOgPWPEt.js} +1 -1
  116. package/dist/ui/assets/{crmsh-DTy9MnmJ.js → crmsh-B0OYb-Oe.js} +1 -1
  117. package/dist/ui/assets/{crystal-BwgrLrZ1.js → crystal-CuWb_dXm.js} +1 -1
  118. package/dist/ui/assets/{csharp-B2VeTPAF.js → csharp-BbcqM09j.js} +1 -1
  119. package/dist/ui/assets/{csp-CTpydNhH.js → csp-rgHnTaDq.js} +1 -1
  120. package/dist/ui/assets/{css-B0mMUbpY.js → css-C-ibh7ZG.js} +1 -1
  121. package/dist/ui/assets/{cynefin-VYW2F7L2-BmjQEj6q.js → cynefin-VYW2F7L2-CkuBhgC4.js} +1 -1
  122. package/dist/ui/assets/{cynefinDiagram-TSTJHNR4-Dr0Zrke0.js → cynefinDiagram-TSTJHNR4-eycFyDao.js} +1 -1
  123. package/dist/ui/assets/{d-DjuDZnbI.js → d-C0gXxJ0O.js} +1 -1
  124. package/dist/ui/assets/{dagre-VKFMJZFB-xoTPT2Uu.js → dagre-VKFMJZFB-DzqX9e49.js} +1 -1
  125. package/dist/ui/assets/{dart-xR3GU9TJ.js → dart-C-fTpuUV.js} +1 -1
  126. package/dist/ui/assets/{delphi-bJas5QnG.js → delphi-Chm0B9xv.js} +1 -1
  127. package/dist/ui/assets/{diagram-FQU43EPY-rL_IU7gU.js → diagram-FQU43EPY-BEgCqfg1.js} +1 -1
  128. package/dist/ui/assets/{diagram-G47NLZAW-EcDiCVGj.js → diagram-G47NLZAW-Cw11Vs2v.js} +1 -1
  129. package/dist/ui/assets/{diagram-NH7WQ7WH-CGGXOjev.js → diagram-NH7WQ7WH-DSG_UWpP.js} +1 -1
  130. package/dist/ui/assets/{diagram-OA4YK3LP-DnE7hDjD.js → diagram-OA4YK3LP-CJz-WOaJ.js} +1 -1
  131. package/dist/ui/assets/{diagram-WEI45ONY-CTJH5eD8.js → diagram-WEI45ONY-CBXjBCvc.js} +1 -1
  132. package/dist/ui/assets/{diff-doJFF3fv.js → diff-C-vyuMSL.js} +1 -1
  133. package/dist/ui/assets/{django-D3b-owa_.js → django-CJK7lReT.js} +1 -1
  134. package/dist/ui/assets/{dns-C42KYV-Z.js → dns-D02SHjSy.js} +1 -1
  135. package/dist/ui/assets/{dockerfile-9KUINc2j.js → dockerfile-trKL1Bqu.js} +1 -1
  136. package/dist/ui/assets/{dos-CxnlJ6qp.js → dos-C52dy-sK.js} +1 -1
  137. package/dist/ui/assets/{dsconfig-6CMMCwmY.js → dsconfig-MzHgzVrt.js} +1 -1
  138. package/dist/ui/assets/{dts-D78rdUIB.js → dts-B137dHyX.js} +1 -1
  139. package/dist/ui/assets/{dust-Dv1uTfJU.js → dust-BcbM0r5C.js} +1 -1
  140. package/dist/ui/assets/{ebnf-BGbG1cFK.js → ebnf-DkbpI7bK.js} +1 -1
  141. package/dist/ui/assets/{ebnfDiagram-CCIWWBDH-z2VgRSUV.js → ebnfDiagram-CCIWWBDH-aI5uQ7w1.js} +1 -1
  142. package/dist/ui/assets/{elixir-ZgAQVY1J.js → elixir-BOtDuqQy.js} +1 -1
  143. package/dist/ui/assets/{elm-CudiFuNu.js → elm-DS8Tyyu-.js} +1 -1
  144. package/dist/ui/assets/{erDiagram-Q63AITRT-X1TODuRv.js → erDiagram-Q63AITRT-2vCbd4Mm.js} +1 -1
  145. package/dist/ui/assets/{erb-Bz_NZh6W.js → erb-CSXECRbn.js} +1 -1
  146. package/dist/ui/assets/{erlang-DNuj1nb2.js → erlang-BMxlgKPJ.js} +1 -1
  147. package/dist/ui/assets/{erlang-repl-SP1Gan9W.js → erlang-repl-DPzlolPg.js} +1 -1
  148. package/dist/ui/assets/{excel-0K77uECF.js → excel-CW2Uxed3.js} +1 -1
  149. package/dist/ui/assets/{fix-CalJq7JD.js → fix-UW84SlCs.js} +1 -1
  150. package/dist/ui/assets/{flix-BlIs11_w.js → flix-4UGiPULd.js} +1 -1
  151. package/dist/ui/assets/{flowDiagram-23GEKE2U-DA2dnmc0.js → flowDiagram-23GEKE2U-R5PbCbZI.js} +1 -1
  152. package/dist/ui/assets/{fortran-ZCobXY2R.js → fortran-nhHsngdu.js} +1 -1
  153. package/dist/ui/assets/{fsharp-Be8rwUtc.js → fsharp-CDyZ1ST5.js} +1 -1
  154. package/dist/ui/assets/{gams-DUat8frQ.js → gams-DHzkkUie.js} +1 -1
  155. package/dist/ui/assets/{ganttDiagram-NO4QXBWP-DdFwaIGZ.js → ganttDiagram-NO4QXBWP-CGcq4Ie_.js} +1 -1
  156. package/dist/ui/assets/{gauss-BB4U88dy.js → gauss-DdhFGyBv.js} +1 -1
  157. package/dist/ui/assets/{gcode-DzeUZuZg.js → gcode-ImHh8OLA.js} +1 -1
  158. package/dist/ui/assets/{gherkin-B_8vW_BO.js → gherkin-CTWAW-XR.js} +1 -1
  159. package/dist/ui/assets/{gitGraphDiagram-IHSO6WYX-B-Dn8x6I.js → gitGraphDiagram-IHSO6WYX-8nM2Z9lD.js} +1 -1
  160. package/dist/ui/assets/{glsl-DGHGjPC1.js → glsl-0_W_27G2.js} +1 -1
  161. package/dist/ui/assets/{gml-BYEku8Yh.js → gml-BoDggu2z.js} +1 -1
  162. package/dist/ui/assets/{go-D8aSgQMc.js → go-DYfFygwl.js} +1 -1
  163. package/dist/ui/assets/{golo-CDjCU_jh.js → golo-Mlq-TCg9.js} +1 -1
  164. package/dist/ui/assets/{gradle-BeUw7tl_.js → gradle-BALAinEB.js} +1 -1
  165. package/dist/ui/assets/{groovy-5iCQOtR3.js → groovy-Dht--dWU.js} +1 -1
  166. package/dist/ui/assets/{haml-BEdL8FJi.js → haml-DpMrxg0d.js} +1 -1
  167. package/dist/ui/assets/{handlebars-BuYQNzoK.js → handlebars-DqHzujNj.js} +1 -1
  168. package/dist/ui/assets/{haskell-I4FCBcW0.js → haskell-DZSchhkY.js} +1 -1
  169. package/dist/ui/assets/{haxe-yigyKfFr.js → haxe-C0zBRjdy.js} +1 -1
  170. package/dist/ui/assets/{hsp-ZUk4j7d1.js → hsp-D_PwbSv2.js} +1 -1
  171. package/dist/ui/assets/{htmlbars-QYP3lT3u.js → htmlbars-DxAFMG8u.js} +1 -1
  172. package/dist/ui/assets/{http-BJpD1O71.js → http-DvTRF0Do.js} +1 -1
  173. package/dist/ui/assets/{hy-CIJ1bP6S.js → hy-lVO-kNyp.js} +1 -1
  174. package/dist/ui/assets/{index-BDXWttnT.js → index-BbMRVVGt.js} +3 -3
  175. package/dist/ui/assets/{index-COAgrx1J.js → index-Ci23SMK2.js} +242 -242
  176. package/dist/ui/assets/index-DlsVXaHT.css +10 -0
  177. package/dist/ui/assets/{infoDiagram-FWYZ7A6U-fTg9eLNt.js → infoDiagram-FWYZ7A6U-C7WYDlvc.js} +1 -1
  178. package/dist/ui/assets/{inform7-DAsu-lC2.js → inform7-B8Tf2n8v.js} +1 -1
  179. package/dist/ui/assets/{ini-Bhz-zHCg.js → ini-DSoYMaLF.js} +1 -1
  180. package/dist/ui/assets/{irpf90-BunhbwBl.js → irpf90-RCPdkB14.js} +1 -1
  181. package/dist/ui/assets/{isbl-q9L6hOhP.js → isbl-C2p75PPj.js} +1 -1
  182. package/dist/ui/assets/{ishikawaDiagram-FXEZZL3T-B8G0sdgd.js → ishikawaDiagram-FXEZZL3T-Cm6jsPlc.js} +1 -1
  183. package/dist/ui/assets/{java-Iks5Pkau.js → java-DeY4Yuia.js} +1 -1
  184. package/dist/ui/assets/{javascript-C4vltXPw.js → javascript-3uX1CO-H.js} +1 -1
  185. package/dist/ui/assets/{jboss-cli-BQBvd2X2.js → jboss-cli-DgnD8mEc.js} +1 -1
  186. package/dist/ui/assets/{journeyDiagram-5HDEW3XC-Crbzgrum.js → journeyDiagram-5HDEW3XC-DEGV1uA5.js} +1 -1
  187. package/dist/ui/assets/{json-DXK0_jUK.js → json-BV2PmJJp.js} +1 -1
  188. package/dist/ui/assets/{julia-CCYoBiYK.js → julia-B-m8kQm7.js} +1 -1
  189. package/dist/ui/assets/{julia-repl-y8v2Oorl.js → julia-repl-C51atU2X.js} +1 -1
  190. package/dist/ui/assets/{kanban-definition-HUTT4EX6-C8sCOsBX.js → kanban-definition-HUTT4EX6-BJ4k5iTX.js} +1 -1
  191. package/dist/ui/assets/{kotlin-Bnjk7_fw.js → kotlin-CYBziIgl.js} +1 -1
  192. package/dist/ui/assets/{lasso-B-al6x63.js → lasso-Dw2SBMDW.js} +1 -1
  193. package/dist/ui/assets/{latex-BUmqlb9r.js → latex-Dpd35yke.js} +1 -1
  194. package/dist/ui/assets/{ldif-D42RWj6Q.js → ldif-Ce9EZU__.js} +1 -1
  195. package/dist/ui/assets/{leaf-CrH4BZ0m.js → leaf-CpkZzQtC.js} +1 -1
  196. package/dist/ui/assets/{less-upmRlQQW.js → less-Uzo7M0FD.js} +1 -1
  197. package/dist/ui/assets/{lisp-DHYS22SA.js → lisp-DWXDlJYm.js} +1 -1
  198. package/dist/ui/assets/{livecodeserver-CeQNmFP3.js → livecodeserver-uEyPEgVJ.js} +1 -1
  199. package/dist/ui/assets/{livescript-PJWpY1JI.js → livescript-BQ5f07oK.js} +1 -1
  200. package/dist/ui/assets/{llvm-BKtKWwCt.js → llvm-BAvP-skE.js} +1 -1
  201. package/dist/ui/assets/{lsl-RWRLRjk7.js → lsl-DhbiIKu6.js} +1 -1
  202. package/dist/ui/assets/{lua-BFUDiPGm.js → lua-COxeUeLx.js} +1 -1
  203. package/dist/ui/assets/{makefile-CUDyOz5D.js → makefile-DhDx-IgL.js} +1 -1
  204. package/dist/ui/assets/{markdown-zZCLypc6.js → markdown-DX5_kSxq.js} +1 -1
  205. package/dist/ui/assets/{mathematica-BHbxKxnq.js → mathematica-D351ZddB.js} +1 -1
  206. package/dist/ui/assets/{matlab-DlGtg--p.js → matlab-CELqIiwF.js} +1 -1
  207. package/dist/ui/assets/{maxima-BBBeS3Ra.js → maxima-B_YljrMm.js} +1 -1
  208. package/dist/ui/assets/{mel-nh8j7W_6.js → mel-DltsbNS8.js} +1 -1
  209. package/dist/ui/assets/{mercury-DyyD-pqr.js → mercury-axX-5Vjj.js} +1 -1
  210. package/dist/ui/assets/{mindmap-definition-LN4V7U3C-CJlhDHqM.js → mindmap-definition-LN4V7U3C-Bl1EJo6p.js} +1 -1
  211. package/dist/ui/assets/{mipsasm-hiEhRRiq.js → mipsasm-BxmerVwX.js} +1 -1
  212. package/dist/ui/assets/{mizar-BZa3iAAL.js → mizar-XNHfbpL2.js} +1 -1
  213. package/dist/ui/assets/{mojolicious-BCIm2V_y.js → mojolicious-BR0U_7zk.js} +1 -1
  214. package/dist/ui/assets/{monkey-zmK-7N8b.js → monkey-lLuu9_f-.js} +1 -1
  215. package/dist/ui/assets/{moonscript-BsS_Mn3D.js → moonscript-DoaZ3DRQ.js} +1 -1
  216. package/dist/ui/assets/{n1ql-D7xVgDP0.js → n1ql-C6TId6Iu.js} +1 -1
  217. package/dist/ui/assets/{nginx-ct2q4dtd.js → nginx-BezSG-JT.js} +1 -1
  218. package/dist/ui/assets/{nim-RO0UOmW3.js → nim-Bb0rfXie.js} +1 -1
  219. package/dist/ui/assets/{nix-BcRJxPKH.js → nix-o3sJ5-54.js} +1 -1
  220. package/dist/ui/assets/{node-repl-DgIefwxW.js → node-repl-Sv5hhcTf.js} +1 -1
  221. package/dist/ui/assets/{nsis-DqHs9QhB.js → nsis-B45BJFhF.js} +1 -1
  222. package/dist/ui/assets/{objectivec-BBcWqc7Y.js → objectivec-CzS6zVgh.js} +1 -1
  223. package/dist/ui/assets/{ocaml-BExfw7D6.js → ocaml-Bqux7PyP.js} +1 -1
  224. package/dist/ui/assets/{openscad-CHPRELQb.js → openscad-4Wf6mF84.js} +1 -1
  225. package/dist/ui/assets/{oxygene-DpS9uMJW.js → oxygene-Fvwqgkkt.js} +1 -1
  226. package/dist/ui/assets/{parser3-CjJ_QdBp.js → parser3-zEfzT5wv.js} +1 -1
  227. package/dist/ui/assets/{pegDiagram-2B236MQR-GM70woGy.js → pegDiagram-2B236MQR-Dd4rIztM.js} +1 -1
  228. package/dist/ui/assets/{perl-BSNM2Kt8.js → perl-CsS1Gs6o.js} +1 -1
  229. package/dist/ui/assets/{pf-Z-hKf_88.js → pf-BNH1jO0R.js} +1 -1
  230. package/dist/ui/assets/{pgsql-DDgznxBg.js → pgsql-DqPigHrq.js} +1 -1
  231. package/dist/ui/assets/{php-BzAhxF-l.js → php-CdOl9bRs.js} +1 -1
  232. package/dist/ui/assets/{php-template-DkgcKHRc.js → php-template-DPfirJg2.js} +1 -1
  233. package/dist/ui/assets/{pieDiagram-ENE6RG2P-Bst7mPyz.js → pieDiagram-ENE6RG2P-DGaJNf4S.js} +1 -1
  234. package/dist/ui/assets/{plaintext-BYEppD0R.js → plaintext-EHbgBKzw.js} +1 -1
  235. package/dist/ui/assets/{pony-BT_kQd1K.js → pony-DmJajtp2.js} +1 -1
  236. package/dist/ui/assets/{powershell-BfRsuSsf.js → powershell-DfGzdWR6.js} +1 -1
  237. package/dist/ui/assets/{processing-DpzWe5Iu.js → processing-BT0ETgPG.js} +1 -1
  238. package/dist/ui/assets/{profile-BCOhS5cM.js → profile-B3I-x5Lt.js} +1 -1
  239. package/dist/ui/assets/{prolog-OxaCCaW8.js → prolog-B_4QJIvI.js} +1 -1
  240. package/dist/ui/assets/{properties-DMFlA2Ca.js → properties-BFVI8weS.js} +1 -1
  241. package/dist/ui/assets/{protobuf-CRDFkXvZ.js → protobuf-PaF39oZZ.js} +1 -1
  242. package/dist/ui/assets/{puppet-DnrVyLzx.js → puppet-BDC6Q8VF.js} +1 -1
  243. package/dist/ui/assets/{purebasic-Dht4Hniv.js → purebasic-B2ejrOZK.js} +1 -1
  244. package/dist/ui/assets/{python-B0aMirWu.js → python-BOxtIk9H.js} +1 -1
  245. package/dist/ui/assets/{python-repl-BlYDBGa7.js → python-repl-BslBh_Mx.js} +1 -1
  246. package/dist/ui/assets/{q-DzrtW9kT.js → q-BPe8WLGW.js} +1 -1
  247. package/dist/ui/assets/{qml-DnfQsR3i.js → qml-BazRR6fz.js} +1 -1
  248. package/dist/ui/assets/{quadrantDiagram-ABIIQ3AL-DNRYurBT.js → quadrantDiagram-ABIIQ3AL-BQ7aPkHk.js} +1 -1
  249. package/dist/ui/assets/{r-DGI_ZuTD.js → r-C09pGNJC.js} +1 -1
  250. package/dist/ui/assets/{railroadDiagram-RFXS5EU6-CmbC3Vz4.js → railroadDiagram-RFXS5EU6-sCO9VGvE.js} +1 -1
  251. package/dist/ui/assets/{reasonml-3SohMgh2.js → reasonml-S7dzwVL9.js} +1 -1
  252. package/dist/ui/assets/{requirementDiagram-TGXJPOKE-8CxrXHco.js → requirementDiagram-TGXJPOKE-CfT5aVZg.js} +1 -1
  253. package/dist/ui/assets/{rib-DfRSd4Qa.js → rib-fM2EwQd4.js} +1 -1
  254. package/dist/ui/assets/{roboconf-CQczGdGW.js → roboconf-D7AaeyrW.js} +1 -1
  255. package/dist/ui/assets/{routeros-DCka-T2i.js → routeros-c6Q7mKuq.js} +1 -1
  256. package/dist/ui/assets/{rsl-Dr8MOwMo.js → rsl-sCpEQBxh.js} +1 -1
  257. package/dist/ui/assets/{ruby-C2zFSkh_.js → ruby-lcKhz2w9.js} +1 -1
  258. package/dist/ui/assets/{ruleslanguage-C97YsgQq.js → ruleslanguage-BW4ATOuX.js} +1 -1
  259. package/dist/ui/assets/{rust-H3h8kd9A.js → rust-BbpU-0VY.js} +1 -1
  260. package/dist/ui/assets/{sankeyDiagram-HTMAVEWB-DBXLWjEq.js → sankeyDiagram-HTMAVEWB-CaweCpvN.js} +1 -1
  261. package/dist/ui/assets/{sas-04z0Xu8P.js → sas-BPt5R0dx.js} +1 -1
  262. package/dist/ui/assets/{scala-DyB9Aoiv.js → scala-DxW_vqXs.js} +1 -1
  263. package/dist/ui/assets/{scheme-DBj6nxUi.js → scheme-T2Q_jWzT.js} +1 -1
  264. package/dist/ui/assets/{scilab-Btj7AD9s.js → scilab-DMORg9lV.js} +1 -1
  265. package/dist/ui/assets/{scss-BXZJyXt7.js → scss-t0VsosqT.js} +1 -1
  266. package/dist/ui/assets/{sequenceDiagram-DBY2YBRQ-DXIkBFKC.js → sequenceDiagram-DBY2YBRQ-o-H_R4zb.js} +1 -1
  267. package/dist/ui/assets/{shell-BsreQR1Z.js → shell-CnCDa1s9.js} +1 -1
  268. package/dist/ui/assets/{sizeCapture-X5ZJPWSS-ocS9jxAQ.js → sizeCapture-X5ZJPWSS-BNLkHGDS.js} +1 -1
  269. package/dist/ui/assets/{smali-D9wQw9zm.js → smali-_n-BxJyG.js} +1 -1
  270. package/dist/ui/assets/{smalltalk-Dku6G_H3.js → smalltalk-B90PXJwK.js} +1 -1
  271. package/dist/ui/assets/{sml-BknyU6ya.js → sml-fuY7bj-n.js} +1 -1
  272. package/dist/ui/assets/{sqf-DRYMaOg8.js → sqf-COpwkyuo.js} +1 -1
  273. package/dist/ui/assets/{sql-Cw3Ax4KA.js → sql-CXIT0j-x.js} +1 -1
  274. package/dist/ui/assets/{sql_more-vWdslLE0.js → sql_more-C1Oxq4rx.js} +1 -1
  275. package/dist/ui/assets/{stan-8XH5bDS6.js → stan-BZEpaHeC.js} +1 -1
  276. package/dist/ui/assets/{stata-4e77wgfe.js → stata-CkBwaiZM.js} +1 -1
  277. package/dist/ui/assets/{stateDiagram-2N3HPSRC-RMmL9jMG.js → stateDiagram-2N3HPSRC-BkdnNF7I.js} +1 -1
  278. package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-0JnZ1VSQ.js +1 -0
  279. package/dist/ui/assets/{step21-ByIeDMY3.js → step21-fs9N_U3O.js} +1 -1
  280. package/dist/ui/assets/{stylus-ZAhJyWsw.js → stylus-Bs_9cvKZ.js} +1 -1
  281. package/dist/ui/assets/{subunit-pPW2UGkj.js → subunit-DvgcOCqJ.js} +1 -1
  282. package/dist/ui/assets/{swift-CX3BB5az.js → swift-BE4ONsur.js} +1 -1
  283. package/dist/ui/assets/{swimlanes-5IMT3BWC-C2zAuJ56.js → swimlanes-5IMT3BWC-uqTAc7o7.js} +2 -2
  284. package/dist/ui/assets/swimlanesDiagram-G3AALYLV-DQRIIEmi.js +8 -0
  285. package/dist/ui/assets/{taggerscript-BHcjbWIC.js → taggerscript-BhmPJ64C.js} +1 -1
  286. package/dist/ui/assets/{tap-BOl1lBlS.js → tap-DkueIVKV.js} +1 -1
  287. package/dist/ui/assets/{tcl-DZjeOBDh.js → tcl-DyXOOkFi.js} +1 -1
  288. package/dist/ui/assets/{thrift-BxboWg4x.js → thrift-Dhgbk56T.js} +1 -1
  289. package/dist/ui/assets/{timeline-definition-FHXFAJF6-BOxsxyq_.js → timeline-definition-FHXFAJF6-DBAk9Ouc.js} +1 -1
  290. package/dist/ui/assets/{tp-BNM6ql3J.js → tp-BsXyrRGH.js} +1 -1
  291. package/dist/ui/assets/{twig-Dj9ZiXD4.js → twig-J0HqIuHw.js} +1 -1
  292. package/dist/ui/assets/{typescript-7MEJlu_6.js → typescript-Dkwvul39.js} +1 -1
  293. package/dist/ui/assets/{vala-DNqOAgHJ.js → vala-CRGnf8aY.js} +1 -1
  294. package/dist/ui/assets/{vbnet-T0mNKVc_.js → vbnet-BtJAUe3z.js} +1 -1
  295. package/dist/ui/assets/{vbscript-CeuwVMAI.js → vbscript-RgFFNuM3.js} +1 -1
  296. package/dist/ui/assets/{vbscript-html-BbJSt2w6.js → vbscript-html-DO32Io9Y.js} +1 -1
  297. package/dist/ui/assets/{vennDiagram-L72KCM5P-BX9mWeiT.js → vennDiagram-L72KCM5P-BJi6y0hh.js} +1 -1
  298. package/dist/ui/assets/{verilog-DYuhYfkX.js → verilog-C4h6MLcI.js} +1 -1
  299. package/dist/ui/assets/{vhdl-Cf9aT-t7.js → vhdl-DOafePyU.js} +1 -1
  300. package/dist/ui/assets/{vim-DciP44K1.js → vim-BAdTA9TY.js} +1 -1
  301. package/dist/ui/assets/{wardleyDiagram-EHGQE667-CDj2rOzu.js → wardleyDiagram-EHGQE667-C7c_4IJ6.js} +1 -1
  302. package/dist/ui/assets/{x86asm-E-AN_LXO.js → x86asm-BCL6__QH.js} +1 -1
  303. package/dist/ui/assets/{xl-CxYLJelN.js → xl-qIzE4HOe.js} +1 -1
  304. package/dist/ui/assets/{xml-yz1SRLuU.js → xml-DE2sJXYU.js} +1 -1
  305. package/dist/ui/assets/{xquery-BLzBF3tV.js → xquery-CQ35t92y.js} +1 -1
  306. package/dist/ui/assets/{xychartDiagram-FW5EYKEG-Czapk9-Z.js → xychartDiagram-FW5EYKEG-D2ILn0jL.js} +1 -1
  307. package/dist/ui/assets/{yaml-Cv4Y7iN_.js → yaml-5KaAvG5C.js} +1 -1
  308. package/dist/ui/assets/{zephir-KRcdQfEX.js → zephir-CbR3Ryfq.js} +1 -1
  309. package/dist/ui/index.html +2 -2
  310. package/dist/utils/__tests__/abort-signals.test.js +67 -0
  311. package/dist/utils/__tests__/inject-loop.test.js +1 -53
  312. package/dist/utils/__tests__/learn-tasks-db.test.js +82 -0
  313. package/dist/utils/__tests__/openai-converter.test.js +25 -0
  314. package/dist/utils/__tests__/pi-crawl-tools.test.js +15 -4
  315. package/dist/utils/__tests__/pi-dedup-reconcile.test.js +11 -9
  316. package/dist/utils/__tests__/pi-events.test.js +172 -0
  317. package/dist/utils/__tests__/pi-learn-orchestrator.test.js +216 -6
  318. package/dist/utils/__tests__/pi-runtime-json.test.js +38 -3
  319. package/dist/utils/__tests__/providers-db.test.js +40 -2
  320. package/dist/utils/__tests__/request-writer.test.js +271 -39
  321. package/dist/utils/__tests__/sse-protocol.test.d.ts +1 -0
  322. package/dist/utils/__tests__/sse-protocol.test.js +115 -0
  323. package/dist/utils/__tests__/vault.test.d.ts +1 -0
  324. package/dist/utils/__tests__/{knowledge-store.test.js → vault.test.js} +16 -16
  325. package/dist/utils/abort-signals.d.ts +13 -0
  326. package/dist/utils/abort-signals.js +33 -0
  327. package/dist/utils/db.d.ts +1 -0
  328. package/dist/utils/db.js +24 -0
  329. package/dist/utils/esm-dirname.d.ts +0 -15
  330. package/dist/utils/esm-dirname.js +0 -4
  331. package/dist/utils/inject-loop.d.ts +0 -27
  332. package/dist/utils/inject-loop.js +2 -56
  333. package/dist/utils/learn-tasks-db.d.ts +15 -0
  334. package/dist/utils/learn-tasks-db.js +35 -0
  335. package/dist/utils/openai-converter.js +88 -78
  336. package/dist/utils/pi-browser-tools.d.ts +0 -2
  337. package/dist/utils/pi-browser-tools.js +0 -174
  338. package/dist/utils/pi-crawl-tools.d.ts +2 -1
  339. package/dist/utils/pi-crawl-tools.js +16 -11
  340. package/dist/utils/pi-dedup.d.ts +8 -30
  341. package/dist/utils/pi-dedup.js +32 -67
  342. package/dist/utils/pi-events.d.ts +65 -0
  343. package/dist/utils/pi-events.js +98 -0
  344. package/dist/utils/pi-learn-orchestrator.d.ts +6 -5
  345. package/dist/utils/pi-learn-orchestrator.js +71 -106
  346. package/dist/utils/pi-runtime.d.ts +5 -6
  347. package/dist/utils/pi-runtime.js +38 -42
  348. package/dist/utils/pi-sandbox-ops.d.ts +0 -36
  349. package/dist/utils/pi-sandbox-ops.js +5 -190
  350. package/dist/utils/providers-db.d.ts +3 -0
  351. package/dist/utils/providers-db.js +21 -7
  352. package/dist/utils/request-writer.d.ts +59 -30
  353. package/dist/utils/request-writer.js +178 -58
  354. package/dist/utils/sse-protocol.d.ts +34 -0
  355. package/dist/utils/sse-protocol.js +121 -0
  356. package/dist/utils/sse-stream-processor.d.ts +4 -9
  357. package/dist/utils/sse-stream-processor.js +13 -104
  358. package/dist/utils/sse-writer.d.ts +1 -1
  359. package/dist/utils/sse-writer.js +3 -2
  360. package/dist/utils/stats-aggregator.js +3 -0
  361. package/dist/utils/tool-injector.js +7 -0
  362. package/dist/utils/vault.d.ts +111 -0
  363. package/dist/utils/vault.js +434 -0
  364. package/package.json +1 -1
  365. package/dist/docs/assets/chunks/@localSearchIndexroot.B6Qfxe6W.js +0 -1
  366. package/dist/types/api.d.ts +0 -20
  367. package/dist/types/index.d.ts +0 -4
  368. package/dist/types/index.js +0 -4
  369. package/dist/types/log.d.ts +0 -13
  370. package/dist/ui/assets/channel-Bc4Td8eZ.js +0 -1
  371. package/dist/ui/assets/classDiagram-OUVF2IWQ-BicxhZFq.js +0 -1
  372. package/dist/ui/assets/classDiagram-v2-EOCWNBFH-BicxhZFq.js +0 -1
  373. package/dist/ui/assets/index-C0fxlU9d.css +0 -10
  374. package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-QvaEO5U6.js +0 -1
  375. package/dist/ui/assets/swimlanesDiagram-G3AALYLV-DPgCOY0D.js +0 -8
  376. package/dist/utils/__tests__/sse-accumulator.test.js +0 -83
  377. package/dist/utils/knowledge-store.d.ts +0 -51
  378. package/dist/utils/knowledge-store.js +0 -241
  379. package/dist/utils/migrate-files-to-db.d.ts +0 -1
  380. package/dist/utils/migrate-files-to-db.js +0 -138
  381. package/dist/utils/sse-accumulator.d.ts +0 -9
  382. package/dist/utils/sse-accumulator.js +0 -59
  383. /package/dist/docs/assets/{proxy-cli_data-hooks.md.w9Oe1OD3.lean.js → proxy-cli_data-hooks.md.Bz8CVbxO.lean.js} +0 -0
  384. /package/dist/docs/assets/{proxy-core_ui-routes.md.Dyqkeoiv.lean.js → proxy-core_ui-routes.md.B21t1p7R.lean.js} +0 -0
  385. /package/dist/{types/api.js → utils/__tests__/abort-signals.test.d.ts} +0 -0
  386. /package/dist/{types/log.js → utils/__tests__/learn-tasks-db.test.d.ts} +0 -0
  387. /package/dist/utils/__tests__/{knowledge-store.test.d.ts → openai-converter.test.d.ts} +0 -0
  388. /package/dist/utils/__tests__/{sse-accumulator.test.d.ts → pi-events.test.d.ts} +0 -0
@@ -1,48 +1,280 @@
1
- import { saveRequestDB, loadRequestDB, listRequestDB, cleanOldRequestsDB } from '../request-writer.js';
2
- import { initDatabase, closeDatabase } from '../db.js';
3
- describe('request-writer-db', () => {
4
- beforeAll(() => {
1
+ import { mkdtempSync, rmSync } from 'fs';
2
+ import { tmpdir } from 'os';
3
+ import path from 'path';
4
+ import { configureDbPath, initDatabase, closeDatabase, getDatabase } from '../db.js';
5
+ import { createRequestRecorder, recordIteration, listRequestDB, interruptStaleRequestsOnStartup } from '../request-writer.js';
6
+ import { pushNewRequest } from '../../server/data-push.js';
7
+ jest.mock('../../server/data-push.js', () => ({ pushNewRequest: jest.fn() }));
8
+ const pushMock = pushNewRequest;
9
+ let counter = 0;
10
+ function makeMeta(tag) {
11
+ counter += 1;
12
+ const unique = `${tag}_${counter}`;
13
+ return {
14
+ sessionId: `sess_${unique}`,
15
+ localRequestId: `local_${unique}`,
16
+ timestamp: new Date().toISOString(),
17
+ apiKey: 'ak',
18
+ proxyApiKey: 'pak',
19
+ baseUrl: 'https://api.example.com',
20
+ model: 'claude-test',
21
+ machineId: 'mid',
22
+ request: {
23
+ method: 'POST',
24
+ path: '/v1/messages',
25
+ headers: { 'user-agent': 'ua-test' },
26
+ body: { hello: 'world' }
27
+ }
28
+ };
29
+ }
30
+ function getRequestRow(requestId, sessionId) {
31
+ return getDatabase()
32
+ .prepare('SELECT * FROM requests WHERE request_id = ? AND session_id = ?')
33
+ .get(requestId, sessionId);
34
+ }
35
+ function getEventRows(requestId, sessionId) {
36
+ return getDatabase()
37
+ .prepare('SELECT * FROM response_events WHERE request_id = ? AND session_id = ? ORDER BY event_index ASC')
38
+ .all(requestId, sessionId);
39
+ }
40
+ describe('request-writer', () => {
41
+ let tempDir;
42
+ beforeEach(() => {
43
+ tempDir = mkdtempSync(path.join(tmpdir(), 'ccm-rw-'));
44
+ configureDbPath(path.join(tempDir, 'test.db'));
5
45
  initDatabase();
46
+ pushMock.mockClear();
6
47
  });
7
- afterAll(() => {
48
+ afterEach(() => {
8
49
  closeDatabase();
50
+ rmSync(tempDir, { recursive: true, force: true });
9
51
  });
10
- it('should save request data to SQLite', async () => {
11
- const data = {
12
- requestId: 'test-123',
13
- sessionId: 'session-1',
14
- timestamp: '2026-06-22T10:00:00Z',
15
- apiKey: 'original-key',
16
- proxyApiKey: 'proxy-key',
17
- baseUrl: 'https://example.com',
18
- model: 'claude-3-5-sonnet',
19
- request: {
20
- method: 'POST',
21
- path: '/v1/messages',
22
- headers: { 'content-type': 'application/json' },
23
- body: { test: true }
24
- },
52
+ it('pending() inserts a status_code=0 row and pushes pending', async () => {
53
+ const meta = makeMeta('pending');
54
+ const rec = createRequestRecorder(meta);
55
+ await rec.pending();
56
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
57
+ expect(row).toBeTruthy();
58
+ expect(row.status_code).toBe(0);
59
+ expect(pushMock).toHaveBeenCalledTimes(1);
60
+ expect(pushMock).toHaveBeenCalledWith(expect.objectContaining({
61
+ requestId: meta.localRequestId,
62
+ compositeId: `${meta.sessionId}:${meta.localRequestId}`,
63
+ statusCode: 0,
64
+ responseTime: 0
65
+ }), 'pending');
66
+ });
67
+ it('appendEvent flushes full batches of 10; complete flushes residual events with continuous indexes', async () => {
68
+ const meta = makeMeta('append');
69
+ const rec = createRequestRecorder(meta);
70
+ for (let i = 0; i < 12; i++) {
71
+ rec.appendEvent({ type: 'chunk', seq: i });
72
+ }
73
+ // Without complete: only the full first batch is persisted, indexes 0..9
74
+ let rows = getEventRows(meta.localRequestId, meta.sessionId);
75
+ expect(rows).toHaveLength(10);
76
+ expect(rows.map(r => r.event_index)).toEqual(Array.from({ length: 10 }, (_, i) => i));
77
+ // Residual 2 events land at complete, full sequence 0..11 continuous
78
+ await rec.complete({ statusCode: 200 });
79
+ rows = getEventRows(meta.localRequestId, meta.sessionId);
80
+ expect(rows).toHaveLength(12);
81
+ expect(rows.map(r => r.event_index)).toEqual(Array.from({ length: 12 }, (_, i) => i));
82
+ });
83
+ it('complete() with same id writes completed row, token columns and pushes completed without pendingId', async () => {
84
+ const meta = makeMeta('same');
85
+ const rec = createRequestRecorder(meta);
86
+ await rec.pending();
87
+ rec.appendEvent({ type: 'message_start' });
88
+ rec.appendEvent({
89
+ type: 'message_delta',
90
+ usage: {
91
+ input_tokens: 10,
92
+ output_tokens: 5,
93
+ cache_read_input_tokens: 2,
94
+ cache_creation_input_tokens: 1
95
+ }
96
+ });
97
+ await rec.complete({ statusCode: 200, headers: { 'x-test': '1' } });
98
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
99
+ expect(row.status_code).toBe(200);
100
+ expect(row.completed_at).toBeTruthy();
101
+ expect(row.input_tokens).toBe(10);
102
+ expect(row.output_tokens).toBe(5);
103
+ expect(row.cache_read_tokens).toBe(2);
104
+ expect(row.cache_creation_tokens).toBe(1);
105
+ const completedCalls = pushMock.mock.calls.filter(c => c[1] === 'completed');
106
+ expect(completedCalls).toHaveLength(1);
107
+ const summary = completedCalls[0][0];
108
+ expect(summary.statusCode).toBe(200);
109
+ expect(summary.usage.output_tokens).toBe(5);
110
+ expect(summary.pendingId).toBeUndefined();
111
+ });
112
+ it('complete() with a different cloud id moves requests row and events to cloud id and pushes pendingId', async () => {
113
+ const meta = makeMeta('cloud');
114
+ counter += 1;
115
+ const cloudId = `cloud_${counter}`;
116
+ const rec = createRequestRecorder(meta);
117
+ await rec.pending();
118
+ for (let i = 0; i < 12; i++) {
119
+ rec.appendEvent({ type: 'chunk', seq: i });
120
+ }
121
+ await rec.complete({ cloudRequestId: cloudId, statusCode: 200 });
122
+ // Local row and local events are gone
123
+ expect(getRequestRow(meta.localRequestId, meta.sessionId)).toBeFalsy();
124
+ expect(getEventRows(meta.localRequestId, meta.sessionId)).toHaveLength(0);
125
+ // Cloud row exists and is completed
126
+ const cloudRow = getRequestRow(cloudId, meta.sessionId);
127
+ expect(cloudRow).toBeTruthy();
128
+ expect(cloudRow.status_code).toBe(200);
129
+ // Events moved with continuous indexes
130
+ const cloudEvents = getEventRows(cloudId, meta.sessionId);
131
+ expect(cloudEvents).toHaveLength(12);
132
+ expect(cloudEvents.map(r => r.event_index)).toEqual(Array.from({ length: 12 }, (_, i) => i));
133
+ const completedCalls = pushMock.mock.calls.filter(c => c[1] === 'completed');
134
+ expect(completedCalls).toHaveLength(1);
135
+ const summary = completedCalls[0][0];
136
+ expect(summary.requestId).toBe(cloudId);
137
+ expect(summary.pendingId).toBe(`${meta.sessionId}:${meta.localRequestId}`);
138
+ });
139
+ it('fail() updates the pending row status/error/closed_at and pushes completed', async () => {
140
+ const meta = makeMeta('fail');
141
+ const rec = createRequestRecorder(meta);
142
+ await rec.pending();
143
+ rec.fail({ statusCode: 502, error: 'Target server unreachable' });
144
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
145
+ expect(row).toBeTruthy();
146
+ expect(row.status_code).toBe(502);
147
+ expect(row.error).toBe('Target server unreachable');
148
+ expect(row.closed_at).toBeTruthy();
149
+ expect(row.status_code).not.toBe(0);
150
+ const completedCalls = pushMock.mock.calls.filter(c => c[1] === 'completed');
151
+ expect(completedCalls).toHaveLength(1);
152
+ });
153
+ it('recordIteration() writes a completed row and events with no pending row', async () => {
154
+ const meta = makeMeta('iter');
155
+ await recordIteration({
156
+ ...meta,
25
157
  response: {
26
158
  statusCode: 200,
27
- events: [{ type: 'test' }]
159
+ events: [
160
+ { type: 'message', content: [] },
161
+ { type: 'message_delta', usage: { output_tokens: 7 } }
162
+ ]
28
163
  }
29
- };
30
- await saveRequestDB('session-1', 'test-123', data);
31
- const loaded = await loadRequestDB('session-1:test-123');
32
- expect(loaded.requestId).toBe('test-123');
33
- expect(loaded.sessionId).toBe('session-1');
34
- expect(loaded.apiKey).toBe('original-key');
35
- expect(loaded.proxyApiKey).toBe('proxy-key');
36
- expect(loaded.baseUrl).toBe('https://example.com');
37
- expect(loaded.model).toBe('claude-3-5-sonnet');
38
- });
39
- it('should list requests summary', async () => {
40
- const result = await listRequestDB();
41
- expect(result.requests.length).toBeGreaterThan(0);
42
- expect(result.requests[0].compositeId).toBeDefined();
43
- });
44
- it('should clean old requests', async () => {
45
- await cleanOldRequestsDB(7);
46
- // Should not throw
164
+ });
165
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
166
+ expect(row).toBeTruthy();
167
+ expect(row.status_code).toBe(200);
168
+ expect(row.completed_at).toBeTruthy();
169
+ expect(getEventRows(meta.localRequestId, meta.sessionId)).toHaveLength(2);
170
+ const pendingCount = getDatabase()
171
+ .prepare('SELECT COUNT(*) AS c FROM requests WHERE status_code = 0')
172
+ .get().c;
173
+ expect(pendingCount).toBe(0);
174
+ const completedCalls = pushMock.mock.calls.filter(c => c[1] === 'completed');
175
+ expect(completedCalls).toHaveLength(1);
176
+ expect(completedCalls[0][0].pendingId).toBeUndefined();
177
+ });
178
+ it('complete() synthesizes message_delta from OpenAI usage and persists output_tokens=42', async () => {
179
+ const meta = makeMeta('openai');
180
+ const rec = createRequestRecorder(meta);
181
+ await rec.pending();
182
+ rec.appendEvent({ type: 'chat.completion.chunk' });
183
+ rec.appendEvent({ type: 'chat.completion', usage: { prompt_tokens: 8, completion_tokens: 42 } });
184
+ await rec.complete({ statusCode: 200 });
185
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
186
+ expect(row.output_tokens).toBe(42);
187
+ const deltaRows = getEventRows(meta.localRequestId, meta.sessionId)
188
+ .filter(r => r.event_type === 'message_delta');
189
+ expect(deltaRows.length).toBeGreaterThan(0);
190
+ const parsed = JSON.parse(deltaRows[deltaRows.length - 1].event_data);
191
+ expect(parsed.type).toBe('message_delta');
192
+ expect(parsed.usage.output_tokens).toBe(42);
193
+ });
194
+ it('headers() writes real status_code while completed_at stays NULL and pushes pending', async () => {
195
+ const meta = makeMeta('headers');
196
+ const rec = createRequestRecorder(meta);
197
+ await rec.pending();
198
+ pushMock.mockClear();
199
+ rec.headers({ statusCode: 200, headers: { 'content-type': 'text/event-stream' } });
200
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
201
+ expect(row.status_code).toBe(200);
202
+ expect(row.completed_at).toBeNull();
203
+ expect(row.closed_at).toBeNull();
204
+ expect(JSON.parse(row.response_headers)['content-type']).toBe('text/event-stream');
205
+ expect(pushMock).toHaveBeenCalledTimes(1);
206
+ expect(pushMock).toHaveBeenCalledWith(expect.objectContaining({ requestId: meta.localRequestId, statusCode: 200, responseTime: 0 }), 'pending');
207
+ });
208
+ it('interrupt() flushes events and writes closed_at/error keeping passed status_code, pushes completed with closedAt', async () => {
209
+ const meta = makeMeta('interrupt');
210
+ const rec = createRequestRecorder(meta);
211
+ await rec.pending();
212
+ rec.appendEvent({ type: 'chunk', seq: 1 });
213
+ pushMock.mockClear();
214
+ rec.interrupt({ statusCode: 200, error: 'stream interrupted: socket hang up' });
215
+ const row = getRequestRow(meta.localRequestId, meta.sessionId);
216
+ expect(row.status_code).toBe(200);
217
+ expect(row.completed_at).toBeNull();
218
+ expect(row.closed_at).toBeTruthy();
219
+ expect(row.error).toBe('stream interrupted: socket hang up');
220
+ // 剩余事件在中断前已落库
221
+ expect(getEventRows(meta.localRequestId, meta.sessionId)).toHaveLength(1);
222
+ expect(pushMock).toHaveBeenCalledTimes(1);
223
+ expect(pushMock).toHaveBeenCalledWith(expect.objectContaining({
224
+ requestId: meta.localRequestId,
225
+ statusCode: 200,
226
+ closedAt: expect.any(String)
227
+ }), 'completed');
228
+ });
229
+ it('listRequestDB maps closed_at to closedAt only on closed rows', async () => {
230
+ const closedMeta = makeMeta('listclosed');
231
+ const closedRec = createRequestRecorder(closedMeta);
232
+ await closedRec.pending();
233
+ closedRec.interrupt({ statusCode: 200, error: 'boom' });
234
+ const openMeta = makeMeta('listopen');
235
+ const openRec = createRequestRecorder(openMeta);
236
+ await openRec.pending();
237
+ await openRec.complete({ statusCode: 200 });
238
+ const { requests } = await listRequestDB();
239
+ const closed = requests.find(r => r.requestId === closedMeta.localRequestId);
240
+ const open = requests.find(r => r.requestId === openMeta.localRequestId);
241
+ expect(closed).toBeTruthy();
242
+ expect(closed.closedAt).toEqual(expect.any(String));
243
+ expect(open).toBeTruthy();
244
+ expect(open.closedAt).toBeUndefined();
245
+ });
246
+ it('interruptStaleRequestsOnStartup() marks aged orphan rows and leaves fresh/completed rows untouched', async () => {
247
+ // 超龄无主行:已收到响应头(200),时间戳回拨到 6 分钟前
248
+ const oldMeta = makeMeta('staleold');
249
+ const oldRec = createRequestRecorder(oldMeta);
250
+ await oldRec.pending();
251
+ oldRec.headers({ statusCode: 200 });
252
+ getDatabase()
253
+ .prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
254
+ .run(new Date(Date.now() - 6 * 60000).toISOString(), oldMeta.localRequestId, oldMeta.sessionId);
255
+ // 刚 pending 的新行,不超龄
256
+ const freshMeta = makeMeta('stalefresh');
257
+ const freshRec = createRequestRecorder(freshMeta);
258
+ await freshRec.pending();
259
+ // 超龄但已完成的行
260
+ const doneMeta = makeMeta('staledone');
261
+ const doneRec = createRequestRecorder(doneMeta);
262
+ await doneRec.pending();
263
+ await doneRec.complete({ statusCode: 200 });
264
+ getDatabase()
265
+ .prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
266
+ .run(new Date(Date.now() - 6 * 60000).toISOString(), doneMeta.localRequestId, doneMeta.sessionId);
267
+ const count = interruptStaleRequestsOnStartup();
268
+ expect(count).toBe(1);
269
+ const oldRow = getRequestRow(oldMeta.localRequestId, oldMeta.sessionId);
270
+ expect(oldRow.closed_at).toBeTruthy();
271
+ expect(oldRow.error).toBe('interrupted: proxy restarted');
272
+ expect(oldRow.status_code).toBe(200);
273
+ expect(oldRow.completed_at).toBeNull();
274
+ const freshRow = getRequestRow(freshMeta.localRequestId, freshMeta.sessionId);
275
+ expect(freshRow.closed_at).toBeNull();
276
+ expect(freshRow.error).toBeNull();
277
+ const doneRow = getRequestRow(doneMeta.localRequestId, doneMeta.sessionId);
278
+ expect(doneRow.closed_at).toBeNull();
47
279
  });
48
280
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ import { createSSEDecoder, createEventDecoder, encodeSSEFrame } from '../sse-protocol.js';
2
+ // 收集分发帧的辅助
3
+ function collectFrames() {
4
+ const frames = [];
5
+ const decoder = createSSEDecoder((frame) => frames.push(frame));
6
+ return { frames, decoder };
7
+ }
8
+ describe('sse-protocol', () => {
9
+ // 样例 1:单帧含 event: + data:
10
+ it('1. 单帧含 event 和 data', () => {
11
+ const { frames, decoder } = collectFrames();
12
+ decoder.write('event: foo\ndata: {"a":1}\n\n');
13
+ expect(frames).toEqual([
14
+ { event: 'foo', data: ['{"a":1}'], id: undefined }
15
+ ]);
16
+ });
17
+ // 样例 2:仅 data:(event 为 undefined)
18
+ it('2. 仅 data,event 缺省为 undefined', () => {
19
+ const { frames, decoder } = collectFrames();
20
+ decoder.write('data: {"b":2}\n\n');
21
+ expect(frames).toEqual([
22
+ { event: undefined, data: ['{"b":2}'], id: undefined }
23
+ ]);
24
+ });
25
+ // 样例 3:一个 data 行切成 3 个 chunk(残帧缓冲)
26
+ it('3. data 行跨 3 个 chunk 恰好得到一帧', () => {
27
+ const { frames, decoder } = collectFrames();
28
+ decoder.write('dat');
29
+ expect(frames).toHaveLength(0);
30
+ decoder.write('a: {"x');
31
+ expect(frames).toHaveLength(0);
32
+ decoder.write('":1}\n\n');
33
+ expect(frames).toEqual([
34
+ { event: undefined, data: ['{"x":1}'], id: undefined }
35
+ ]);
36
+ });
37
+ // 样例 4:CRLF 行尾 + 空行分帧
38
+ it('4. CRLF 行尾与空行分帧', () => {
39
+ const { frames, decoder } = collectFrames();
40
+ decoder.write('event: ping\r\ndata: {"y":2}\r\n\r\n');
41
+ expect(frames).toEqual([
42
+ { event: 'ping', data: ['{"y":2}'], id: undefined }
43
+ ]);
44
+ });
45
+ // 样例 5:同一帧内多行 data:,数组保留两行,消费 join('\n') 正确
46
+ it('5. 多行 data 逐行保留且 join 正确', () => {
47
+ const { frames, decoder } = collectFrames();
48
+ decoder.write('data: line1\ndata: line2\n\n');
49
+ expect(frames).toHaveLength(1);
50
+ expect(frames[0].data).toEqual(['line1', 'line2']);
51
+ expect(frames[0].data.join('\n')).toBe('line1\nline2');
52
+ });
53
+ // 样例 6:注释行不产生帧
54
+ it('6. 注释行被忽略不产生帧', () => {
55
+ const { frames, decoder } = collectFrames();
56
+ decoder.write(': ping\n\n');
57
+ expect(frames).toHaveLength(0);
58
+ // 注释与真实帧混合时仅真实帧分发
59
+ decoder.write(': keep-alive\ndata: {"r":1}\n\n');
60
+ expect(frames).toEqual([
61
+ { event: undefined, data: ['{"r":1}'], id: undefined }
62
+ ]);
63
+ });
64
+ // 样例 7:data: [DONE] → onDone 触发、onEvent 不触发
65
+ it('7. [DONE] 仅触发 onDone', () => {
66
+ const events = [];
67
+ let doneCount = 0;
68
+ const decoder = createEventDecoder((e) => events.push(e), () => { doneCount++; });
69
+ decoder.write('data: [DONE]\n\n');
70
+ expect(events).toHaveLength(0);
71
+ expect(doneCount).toBe(1);
72
+ });
73
+ // 样例 8:非 JSON data → onEvent 不触发、不抛错
74
+ it('8. 非 JSON data 静默跳过', () => {
75
+ const events = [];
76
+ const decoder = createEventDecoder((e) => events.push(e));
77
+ expect(() => {
78
+ decoder.write('data: not-json\n\n');
79
+ }).not.toThrow();
80
+ expect(events).toHaveLength(0);
81
+ });
82
+ // 样例 9:末尾帧无尾空行,write 不触发,end() 后恰好一帧
83
+ it('9. end() 输出末尾无空行的残留帧', () => {
84
+ const { frames, decoder } = collectFrames();
85
+ decoder.write('data: {"z":9}');
86
+ expect(frames).toHaveLength(0);
87
+ decoder.end();
88
+ expect(frames).toEqual([
89
+ { event: undefined, data: ['{"z":9}'], id: undefined }
90
+ ]);
91
+ });
92
+ // 样例 10:id: 行保留在帧 id 字段
93
+ it('10. id 字段保留在帧上', () => {
94
+ const { frames, decoder } = collectFrames();
95
+ decoder.write('id: e1\ndata: {"i":1}\n\n');
96
+ expect(frames).toEqual([
97
+ { event: undefined, data: ['{"i":1}'], id: 'e1' }
98
+ ]);
99
+ });
100
+ // 样例 11:encodeSSEFrame → createSSEDecoder 往返一致(有/无 event)
101
+ it('11. encode/decode 往返帧内容一致(有 event 和无 event)', () => {
102
+ const withEvent = encodeSSEFrame({ event: 'foo', data: '{"x":1}' });
103
+ const d1 = collectFrames();
104
+ d1.decoder.write(withEvent);
105
+ expect(d1.frames).toEqual([
106
+ { event: 'foo', data: ['{"x":1}'], id: undefined }
107
+ ]);
108
+ const withoutEvent = encodeSSEFrame({ data: '{"x":2}' });
109
+ const d2 = collectFrames();
110
+ d2.decoder.write(withoutEvent);
111
+ expect(d2.frames).toEqual([
112
+ { event: undefined, data: ['{"x":2}'], id: undefined }
113
+ ]);
114
+ });
115
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { promises as fs } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
- import { createKnowledgeStore } from '../knowledge-store.js';
4
+ import { createVault } from '../vault.js';
5
5
  async function makeTmpDocs() {
6
6
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-test-'));
7
7
  // 根 index.json
@@ -22,10 +22,10 @@ async function makeTmpDocs() {
22
22
  await fs.writeFile(path.join(dir, 'react-hooks', 'chunks', 'chunk-001.md'), '# 状态管理:useState\n\nuseState 用于组件状态。', 'utf8');
23
23
  return dir;
24
24
  }
25
- describe('knowledge-store', () => {
25
+ describe('vault', () => {
26
26
  it('listTopics 解析根 index.json', async () => {
27
27
  const root = await makeTmpDocs();
28
- const store = createKnowledgeStore(root);
28
+ const store = createVault(root);
29
29
  const topics = await store.listTopics();
30
30
  expect(topics).toEqual([
31
31
  { slug: 'react-hooks', title: 'React Hooks 参考', summary: 'React hooks 用法', tags: ['react', 'hooks'] },
@@ -33,12 +33,12 @@ describe('knowledge-store', () => {
33
33
  });
34
34
  it('listTopics 根 index.json 不存在返回空数组', async () => {
35
35
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-empty-'));
36
- const store = createKnowledgeStore(dir);
36
+ const store = createVault(dir);
37
37
  expect(await store.listTopics()).toEqual([]);
38
38
  });
39
39
  it('getTopic 返回元数据 + 片段清单', async () => {
40
40
  const root = await makeTmpDocs();
41
- const store = createKnowledgeStore(root);
41
+ const store = createVault(root);
42
42
  const detail = await store.getTopic('react-hooks');
43
43
  expect(detail.title).toBe('React Hooks 参考');
44
44
  expect(detail.tags).toEqual(['react', 'hooks']);
@@ -46,25 +46,25 @@ describe('knowledge-store', () => {
46
46
  });
47
47
  it('readChunk 返回单片正文', async () => {
48
48
  const root = await makeTmpDocs();
49
- const store = createKnowledgeStore(root);
49
+ const store = createVault(root);
50
50
  const text = await store.readChunk('react-hooks', 'chunk-001');
51
51
  expect(text).toContain('useState 用于组件状态');
52
52
  });
53
53
  it('readChunk 拒绝路径穿越', async () => {
54
54
  const root = await makeTmpDocs();
55
- const store = createKnowledgeStore(root);
55
+ const store = createVault(root);
56
56
  await expect(store.readChunk('..', 'chunk-001')).rejects.toThrow();
57
57
  await expect(store.readChunk('react-hooks', '../chunk-001')).rejects.toThrow();
58
58
  });
59
59
  it('searchChunks 关键词命中返回清单', async () => {
60
60
  const root = await makeTmpDocs();
61
- const store = createKnowledgeStore(root);
61
+ const store = createVault(root);
62
62
  const hits = await store.searchChunks('useState');
63
63
  expect(hits).toEqual([{ topic: 'react-hooks', chunkId: 'chunk-001', title: '状态管理:useState' }]);
64
64
  });
65
65
  it('upsertTopic 两次不同 slug → listTopics 返回 2 条,不覆盖', async () => {
66
66
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-upsert-'));
67
- const store = createKnowledgeStore(dir);
67
+ const store = createVault(dir);
68
68
  await store.upsertTopic({ slug: 'topic-a', title: 'A', summary: 'sa', tags: [] });
69
69
  await store.upsertTopic({ slug: 'topic-b', title: 'B', summary: 'sb', tags: ['x'] });
70
70
  const topics = await store.listTopics();
@@ -73,7 +73,7 @@ describe('knowledge-store', () => {
73
73
  });
74
74
  it('upsertTopic 同 slug 再调更新 summary,chunks 数组不变,listTopics 仍 1 条', async () => {
75
75
  const root = await makeTmpDocs();
76
- const store = createKnowledgeStore(root);
76
+ const store = createVault(root);
77
77
  // 先登记一个额外 chunk ref
78
78
  await store.upsertChunkRef({ topic: 'react-hooks', chunkId: 'chunk-002', title: 'T2', summary: 'S2', tags: [] });
79
79
  await store.upsertTopic({
@@ -92,7 +92,7 @@ describe('knowledge-store', () => {
92
92
  });
93
93
  it('upsertChunkRef 同 topic 两次不同 chunk_id → getTopic 返回 2 chunks', async () => {
94
94
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-chunk-'));
95
- const store = createKnowledgeStore(dir);
95
+ const store = createVault(dir);
96
96
  await store.upsertTopic({ slug: 't1', title: 'T1', summary: 's', tags: [] });
97
97
  await store.upsertChunkRef({ topic: 't1', chunkId: 'chunk-001', title: 'c1', summary: 's1', tags: [] });
98
98
  await store.upsertChunkRef({ topic: 't1', chunkId: 'chunk-002', title: 'c2', summary: 's2', tags: [] });
@@ -102,12 +102,12 @@ describe('knowledge-store', () => {
102
102
  });
103
103
  it('upsertChunkRef topic 不存在 → rejects', async () => {
104
104
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-no-topic-'));
105
- const store = createKnowledgeStore(dir);
105
+ const store = createVault(dir);
106
106
  await expect(store.upsertChunkRef({ topic: 'ghost', chunkId: 'chunk-001', title: 'c', summary: 's', tags: [] })).rejects.toThrow(/topic not found: ghost/);
107
107
  });
108
108
  it('tags: upsertChunkRef 落盘 tags,重复调用覆写 tags', async () => {
109
109
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-tags-'));
110
- const store = createKnowledgeStore(dir);
110
+ const store = createVault(dir);
111
111
  await store.upsertTopic({ slug: 't1', title: 'T1', summary: 's', tags: [] });
112
112
  await store.upsertChunkRef({ topic: 't1', chunkId: 'chunk-001', title: 'c1', summary: 's1', tags: ['redis', '缓存'] });
113
113
  // 落盘验证
@@ -128,7 +128,7 @@ describe('knowledge-store', () => {
128
128
  slug: 'old', title: 'Old', summary: 'so', tags: [],
129
129
  chunks: [{ id: 'chunk-001', title: 'oc', summary: 'os' }],
130
130
  }), 'utf8');
131
- const store = createKnowledgeStore(dir);
131
+ const store = createVault(dir);
132
132
  const detail = await store.getTopic('old');
133
133
  expect(detail.chunks[0].tags).toEqual([]);
134
134
  });
@@ -144,7 +144,7 @@ describe('knowledge-store', () => {
144
144
  }), 'utf8');
145
145
  await fs.writeFile(path.join(dir, 'redis', 'chunks', 'chunk-001.md'), '# 缓存详解\n\n正文讲缓存。', 'utf8');
146
146
  await fs.writeFile(path.join(dir, 'redis', 'chunks', 'chunk-002.md'), '# 故障转移\n\n正文讲哨兵。', 'utf8');
147
- const store = createKnowledgeStore(dir);
147
+ const store = createVault(dir);
148
148
  // body
149
149
  expect(await store.searchChunks('缓存')).toEqual([
150
150
  { topic: 'redis', chunkId: 'chunk-001', title: '缓存详解' },
@@ -177,7 +177,7 @@ describe('knowledge-store', () => {
177
177
  chunks: [{ id: 'chunk-001', title: '缓存详解', summary: '缓存摘要' }],
178
178
  }), 'utf8');
179
179
  await fs.writeFile(path.join(dir, 'redis', 'chunks', 'chunk-001.md'), '# 缓存详解\n\n正文讲缓存。', 'utf8');
180
- const store = createKnowledgeStore(dir);
180
+ const store = createVault(dir);
181
181
  expect(await store.searchChunks('缓存')).toEqual([
182
182
  { topic: 'redis', chunkId: 'chunk-001', title: '缓存详解' },
183
183
  ]);
@@ -0,0 +1,13 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ /**
3
+ * 监听 Node IncomingMessage,客户端在响应结束前中止连接时 abort 返回的 signal。
4
+ * close 时以 req.aborted 区分:true=客户端侧中止;正常响应结束后的 close 为 false。
5
+ */
6
+ export declare function createClientAbortSignal(req: IncomingMessage): AbortSignal;
7
+ /** 合成:固定超时 + 外部取消,任意触发即 abort */
8
+ export declare function createTimeoutSignal(ms: number, external?: AbortSignal): AbortSignal;
9
+ /**
10
+ * 把 source signal 接到 abortFn:source 已 abort 立即调,否则将来 abort 时调一次。
11
+ * abortFn 异步拒绝不外泄(由调用方保证幂等)。
12
+ */
13
+ export declare function bindAbort(source: AbortSignal, abortFn: () => Promise<void>): void;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 监听 Node IncomingMessage,客户端在响应结束前中止连接时 abort 返回的 signal。
3
+ * close 时以 req.aborted 区分:true=客户端侧中止;正常响应结束后的 close 为 false。
4
+ */
5
+ export function createClientAbortSignal(req) {
6
+ const controller = new AbortController();
7
+ // 传入时已 aborted(或已 close 过):立即 abort,不再依赖 close 事件
8
+ if (req.aborted) {
9
+ controller.abort();
10
+ return controller.signal;
11
+ }
12
+ req.on('close', () => {
13
+ if (req.aborted)
14
+ controller.abort();
15
+ });
16
+ return controller.signal;
17
+ }
18
+ /** 合成:固定超时 + 外部取消,任意触发即 abort */
19
+ export function createTimeoutSignal(ms, external) {
20
+ return AbortSignal.any([AbortSignal.timeout(ms), ...(external ? [external] : [])]);
21
+ }
22
+ /**
23
+ * 把 source signal 接到 abortFn:source 已 abort 立即调,否则将来 abort 时调一次。
24
+ * abortFn 异步拒绝不外泄(由调用方保证幂等)。
25
+ */
26
+ export function bindAbort(source, abortFn) {
27
+ if (source.aborted) {
28
+ abortFn().catch(() => { });
29
+ }
30
+ else {
31
+ source.addEventListener('abort', () => abortFn().catch(() => { }), { once: true });
32
+ }
33
+ }
@@ -10,5 +10,6 @@ export declare function applySchemaV7(db: Database.Database): void;
10
10
  export declare function applySchemaV8(db: Database.Database): void;
11
11
  export declare function applySchemaV9(db: Database.Database): void;
12
12
  export declare function applySchemaV10(db: Database.Database): void;
13
+ export declare function applySchemaV11(db: Database.Database): void;
13
14
  export declare function closeDatabase(): void;
14
15
  export declare function getDatabase(): Database.Database;
package/dist/utils/db.js CHANGED
@@ -72,6 +72,10 @@ export function initDatabase() {
72
72
  applySchemaV10(db);
73
73
  db.prepare('INSERT OR REPLACE INTO schema_version (version) VALUES (10)').run();
74
74
  }
75
+ if (currentVersion < 11) {
76
+ applySchemaV11(db);
77
+ db.prepare('INSERT OR REPLACE INTO schema_version (version) VALUES (11)').run();
78
+ }
75
79
  return db;
76
80
  }
77
81
  export function applySchemaV2(db) {
@@ -280,6 +284,26 @@ export function applySchemaV10(db) {
280
284
  WHERE user_agent IS NULL AND request_headers IS NOT NULL
281
285
  `).run();
282
286
  }
287
+ export function applySchemaV11(db) {
288
+ // learn 流水线状态机落库:文件操作前插 running,结束转 completed/failed/aborted;
289
+ // 服务启动时无主 running 回收为 crashed。partial unique index 硬守同 slug 并发运行。
290
+ db.exec(`
291
+ CREATE TABLE IF NOT EXISTS learn_tasks (
292
+ task_id TEXT PRIMARY KEY,
293
+ slug TEXT NOT NULL,
294
+ source_type TEXT NOT NULL,
295
+ label TEXT NOT NULL,
296
+ status TEXT NOT NULL,
297
+ error TEXT,
298
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
299
+ ended_at TEXT
300
+ )
301
+ `);
302
+ db.exec(`
303
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_learn_tasks_running_slug
304
+ ON learn_tasks(slug) WHERE status = 'running'
305
+ `);
306
+ }
283
307
  export function closeDatabase() {
284
308
  if (db) {
285
309
  db.close();