agim-cli 1.2.143 → 1.2.147

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 (279) hide show
  1. package/CHANGELOG.md +186 -0
  2. package/dist/cli-ui/setup-llm.d.ts.map +1 -1
  3. package/dist/cli-ui/setup-llm.js +3 -1
  4. package/dist/cli-ui/setup-llm.js.map +1 -1
  5. package/dist/cli-ui/tui/app.d.ts +1 -0
  6. package/dist/cli-ui/tui/app.d.ts.map +1 -1
  7. package/dist/cli-ui/tui/app.js +24 -9
  8. package/dist/cli-ui/tui/app.js.map +1 -1
  9. package/dist/cli-ui/tui/markdown.d.ts.map +1 -1
  10. package/dist/cli-ui/tui/markdown.js +12 -3
  11. package/dist/cli-ui/tui/markdown.js.map +1 -1
  12. package/dist/cli.js +23 -3
  13. package/dist/cli.js.map +1 -1
  14. package/dist/core/access-token.d.ts.map +1 -1
  15. package/dist/core/access-token.js +4 -2
  16. package/dist/core/access-token.js.map +1 -1
  17. package/dist/core/circuit-breaker.d.ts +28 -0
  18. package/dist/core/circuit-breaker.d.ts.map +1 -1
  19. package/dist/core/circuit-breaker.js +45 -0
  20. package/dist/core/circuit-breaker.js.map +1 -1
  21. package/dist/core/intent.d.ts.map +1 -1
  22. package/dist/core/intent.js +3 -1
  23. package/dist/core/intent.js.map +1 -1
  24. package/dist/core/llm/agent-loop.d.ts +9 -1
  25. package/dist/core/llm/agent-loop.d.ts.map +1 -1
  26. package/dist/core/llm/agent-loop.js +80 -1
  27. package/dist/core/llm/agent-loop.js.map +1 -1
  28. package/dist/core/llm/anthropic-provider.d.ts.map +1 -1
  29. package/dist/core/llm/anthropic-provider.js +18 -4
  30. package/dist/core/llm/anthropic-provider.js.map +1 -1
  31. package/dist/core/llm/hallucination-detector.d.ts +33 -0
  32. package/dist/core/llm/hallucination-detector.d.ts.map +1 -0
  33. package/dist/core/llm/hallucination-detector.js +103 -0
  34. package/dist/core/llm/hallucination-detector.js.map +1 -0
  35. package/dist/core/llm/imhub-dispatcher.d.ts.map +1 -1
  36. package/dist/core/llm/imhub-dispatcher.js +7 -0
  37. package/dist/core/llm/imhub-dispatcher.js.map +1 -1
  38. package/dist/core/llm/provider-base.d.ts +9 -0
  39. package/dist/core/llm/provider-base.d.ts.map +1 -1
  40. package/dist/core/llm/provider-base.js.map +1 -1
  41. package/dist/core/memory-distill.d.ts.map +1 -1
  42. package/dist/core/memory-distill.js +18 -3
  43. package/dist/core/memory-distill.js.map +1 -1
  44. package/dist/core/memory.d.ts +14 -0
  45. package/dist/core/memory.d.ts.map +1 -1
  46. package/dist/core/memory.js +39 -0
  47. package/dist/core/memory.js.map +1 -1
  48. package/dist/core/message-sink.d.ts +6 -0
  49. package/dist/core/message-sink.d.ts.map +1 -1
  50. package/dist/core/message-sink.js +18 -3
  51. package/dist/core/message-sink.js.map +1 -1
  52. package/dist/core/outbox.d.ts +30 -2
  53. package/dist/core/outbox.d.ts.map +1 -1
  54. package/dist/core/outbox.js +102 -10
  55. package/dist/core/outbox.js.map +1 -1
  56. package/dist/core/reminders.d.ts.map +1 -1
  57. package/dist/core/reminders.js +11 -1
  58. package/dist/core/reminders.js.map +1 -1
  59. package/dist/core/router.d.ts.map +1 -1
  60. package/dist/core/router.js +16 -4
  61. package/dist/core/router.js.map +1 -1
  62. package/dist/core/schedule.d.ts +18 -0
  63. package/dist/core/schedule.d.ts.map +1 -1
  64. package/dist/core/schedule.js +80 -17
  65. package/dist/core/schedule.js.map +1 -1
  66. package/dist/core/sensitive-paths.d.ts.map +1 -1
  67. package/dist/core/sensitive-paths.js +53 -9
  68. package/dist/core/sensitive-paths.js.map +1 -1
  69. package/dist/core/types.d.ts +6 -0
  70. package/dist/core/types.d.ts.map +1 -1
  71. package/dist/plugins/agents/native/index.d.ts +47 -8
  72. package/dist/plugins/agents/native/index.d.ts.map +1 -1
  73. package/dist/plugins/agents/native/index.js +253 -102
  74. package/dist/plugins/agents/native/index.js.map +1 -1
  75. package/dist/plugins/agents/native/tool-registry.d.ts +33 -0
  76. package/dist/plugins/agents/native/tool-registry.d.ts.map +1 -0
  77. package/dist/plugins/agents/native/tool-registry.js +82 -0
  78. package/dist/plugins/agents/native/tool-registry.js.map +1 -0
  79. package/dist/plugins/messengers/dingtalk/dingtalk-client.d.ts.map +1 -1
  80. package/dist/plugins/messengers/dingtalk/dingtalk-client.js +11 -11
  81. package/dist/plugins/messengers/dingtalk/dingtalk-client.js.map +1 -1
  82. package/dist/plugins/messengers/feishu/feishu-adapter.d.ts.map +1 -1
  83. package/dist/plugins/messengers/feishu/feishu-adapter.js +9 -5
  84. package/dist/plugins/messengers/feishu/feishu-adapter.js.map +1 -1
  85. package/dist/plugins/messengers/wechat/ilink-adapter.d.ts.map +1 -1
  86. package/dist/plugins/messengers/wechat/ilink-adapter.js +11 -1
  87. package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -1
  88. package/dist/web/public/assets/{a2a-DczMMkbl.js → a2a-Cll3P4QN.js} +2 -2
  89. package/dist/web/public/assets/{a2a-DczMMkbl.js.map → a2a-Cll3P4QN.js.map} +1 -1
  90. package/dist/web/public/assets/{activity-cbLHkzca.js → activity-B7T7YFlD.js} +2 -2
  91. package/dist/web/public/assets/{activity-cbLHkzca.js.map → activity-B7T7YFlD.js.map} +1 -1
  92. package/dist/web/public/assets/{admins-C-YsGMj7.js → admins-CN7P018S.js} +2 -2
  93. package/dist/web/public/assets/{admins-C-YsGMj7.js.map → admins-CN7P018S.js.map} +1 -1
  94. package/dist/web/public/assets/{agents-BWfov_1-.js → agents-Bqgq7GBF.js} +2 -2
  95. package/dist/web/public/assets/{agents-BWfov_1-.js.map → agents-Bqgq7GBF.js.map} +1 -1
  96. package/dist/web/public/assets/{approvals-HSssmXKS.js → approvals-C8IUJQ_A.js} +2 -2
  97. package/dist/web/public/assets/{approvals-HSssmXKS.js.map → approvals-C8IUJQ_A.js.map} +1 -1
  98. package/dist/web/public/assets/{arrow-down-BXvC8Al2.js → arrow-down-SLWKqtDc.js} +2 -2
  99. package/dist/web/public/assets/{arrow-down-BXvC8Al2.js.map → arrow-down-SLWKqtDc.js.map} +1 -1
  100. package/dist/web/public/assets/{arrow-up-63xELY5Q.js → arrow-up-BOADc9ce.js} +2 -2
  101. package/dist/web/public/assets/{arrow-up-63xELY5Q.js.map → arrow-up-BOADc9ce.js.map} +1 -1
  102. package/dist/web/public/assets/{asks-COLEFOvK.js → asks-C-j-DypC.js} +2 -2
  103. package/dist/web/public/assets/{asks-COLEFOvK.js.map → asks-C-j-DypC.js.map} +1 -1
  104. package/dist/web/public/assets/{audit-D4ZEiZub.js → audit-DQb-RuXh.js} +2 -2
  105. package/dist/web/public/assets/{audit-D4ZEiZub.js.map → audit-DQb-RuXh.js.map} +1 -1
  106. package/dist/web/public/assets/{bell-Cg2Bvv06.js → bell-CV88-ul6.js} +2 -2
  107. package/dist/web/public/assets/{bell-Cg2Bvv06.js.map → bell-CV88-ul6.js.map} +1 -1
  108. package/dist/web/public/assets/{bgjobs-CEjCzwtd.js → bgjobs-CDrK0d-W.js} +2 -2
  109. package/dist/web/public/assets/{bgjobs-CEjCzwtd.js.map → bgjobs-CDrK0d-W.js.map} +1 -1
  110. package/dist/web/public/assets/{brain-euvl6F6C.js → brain-B7HtSOQU.js} +2 -2
  111. package/dist/web/public/assets/{brain-euvl6F6C.js.map → brain-B7HtSOQU.js.map} +1 -1
  112. package/dist/web/public/assets/{briefcase-DPWLbCnA.js → briefcase-mdzuIa__.js} +2 -2
  113. package/dist/web/public/assets/{briefcase-DPWLbCnA.js.map → briefcase-mdzuIa__.js.map} +1 -1
  114. package/dist/web/public/assets/{browser-ponyfill-BUutOaRz.js → browser-ponyfill-DBWdeCTC.js} +2 -2
  115. package/dist/web/public/assets/{browser-ponyfill-BUutOaRz.js.map → browser-ponyfill-DBWdeCTC.js.map} +1 -1
  116. package/dist/web/public/assets/{chat-Dz9kfaxH.js → chat-CSjtY2rN.js} +3 -3
  117. package/dist/web/public/assets/{chat-Dz9kfaxH.js.map → chat-CSjtY2rN.js.map} +1 -1
  118. package/dist/web/public/assets/{chevron-left-BeIh5thq.js → chevron-left-uSfPn636.js} +2 -2
  119. package/dist/web/public/assets/{chevron-left-BeIh5thq.js.map → chevron-left-uSfPn636.js.map} +1 -1
  120. package/dist/web/public/assets/{chevron-right-uP_l9MMb.js → chevron-right-CtelqacW.js} +2 -2
  121. package/dist/web/public/assets/{chevron-right-uP_l9MMb.js.map → chevron-right-CtelqacW.js.map} +1 -1
  122. package/dist/web/public/assets/{circle-check-CewnjFgv.js → circle-check-8dbL-u7O.js} +2 -2
  123. package/dist/web/public/assets/{circle-check-CewnjFgv.js.map → circle-check-8dbL-u7O.js.map} +1 -1
  124. package/dist/web/public/assets/{circle-check-big-C2RTc48c.js → circle-check-big-D8-svk9a.js} +2 -2
  125. package/dist/web/public/assets/{circle-check-big-C2RTc48c.js.map → circle-check-big-D8-svk9a.js.map} +1 -1
  126. package/dist/web/public/assets/{circle-x-Ccg1HyV-.js → circle-x-rUxzIz5P.js} +2 -2
  127. package/dist/web/public/assets/{circle-x-Ccg1HyV-.js.map → circle-x-rUxzIz5P.js.map} +1 -1
  128. package/dist/web/public/assets/{clock-qxbYSynv.js → clock-CG5dlBGB.js} +2 -2
  129. package/dist/web/public/assets/{clock-qxbYSynv.js.map → clock-CG5dlBGB.js.map} +1 -1
  130. package/dist/web/public/assets/{confirm-dialog-DmJq4Td9.js → confirm-dialog-DlUsSur3.js} +2 -2
  131. package/dist/web/public/assets/{confirm-dialog-DmJq4Td9.js.map → confirm-dialog-DlUsSur3.js.map} +1 -1
  132. package/dist/web/public/assets/{copy-DxSHRdbc.js → copy-DnC76wFT.js} +2 -2
  133. package/dist/web/public/assets/{copy-DxSHRdbc.js.map → copy-DnC76wFT.js.map} +1 -1
  134. package/dist/web/public/assets/{data-table-S7rIjwdO.js → data-table-DswkWUfG.js} +2 -2
  135. package/dist/web/public/assets/{data-table-S7rIjwdO.js.map → data-table-DswkWUfG.js.map} +1 -1
  136. package/dist/web/public/assets/dialog-Ceo4YuXy.js +6 -0
  137. package/dist/web/public/assets/dialog-Ceo4YuXy.js.map +1 -0
  138. package/dist/web/public/assets/{download-OhsGtnO-.js → download-DF-46tS4.js} +2 -2
  139. package/dist/web/public/assets/{download-OhsGtnO-.js.map → download-DF-46tS4.js.map} +1 -1
  140. package/dist/web/public/assets/{email-C1-HxWLF.js → email-CZee26-_.js} +3 -3
  141. package/dist/web/public/assets/{email-C1-HxWLF.js.map → email-CZee26-_.js.map} +1 -1
  142. package/dist/web/public/assets/{empty-state-C-qjOHyu.js → empty-state-D9Hi0Atm.js} +2 -2
  143. package/dist/web/public/assets/{empty-state-C-qjOHyu.js.map → empty-state-D9Hi0Atm.js.map} +1 -1
  144. package/dist/web/public/assets/{external-link-DRVp9-lb.js → external-link-D64iZa9P.js} +2 -2
  145. package/dist/web/public/assets/{external-link-DRVp9-lb.js.map → external-link-D64iZa9P.js.map} +1 -1
  146. package/dist/web/public/assets/{eye-CFhg5BTa.js → eye-sY6WZb7D.js} +2 -2
  147. package/dist/web/public/assets/{eye-CFhg5BTa.js.map → eye-sY6WZb7D.js.map} +1 -1
  148. package/dist/web/public/assets/{facts-CGaLWhzi.js → facts-B7bGGwvi.js} +2 -2
  149. package/dist/web/public/assets/{facts-CGaLWhzi.js.map → facts-B7bGGwvi.js.map} +1 -1
  150. package/dist/web/public/assets/{goals-C-dJANmn.js → goals-BfQbsvZv.js} +2 -2
  151. package/dist/web/public/assets/{goals-C-dJANmn.js.map → goals-BfQbsvZv.js.map} +1 -1
  152. package/dist/web/public/assets/{health-CWcti5h3.js → health-Ba_mY0Ts.js} +2 -2
  153. package/dist/web/public/assets/{health-CWcti5h3.js.map → health-Ba_mY0Ts.js.map} +1 -1
  154. package/dist/web/public/assets/{heart-pulse-DmGhKR2W.js → heart-pulse-BjikOVwU.js} +2 -2
  155. package/dist/web/public/assets/{heart-pulse-DmGhKR2W.js.map → heart-pulse-BjikOVwU.js.map} +1 -1
  156. package/dist/web/public/assets/{heartbeat-kLoGBNCo.js → heartbeat-BM8LlPes.js} +2 -2
  157. package/dist/web/public/assets/{heartbeat-kLoGBNCo.js.map → heartbeat-BM8LlPes.js.map} +1 -1
  158. package/dist/web/public/assets/{hot-BITDoax1.js → hot-BtuLL6n8.js} +2 -2
  159. package/dist/web/public/assets/{hot-BITDoax1.js.map → hot-BtuLL6n8.js.map} +1 -1
  160. package/dist/web/public/assets/index-DEWFfW_Z.js +199 -0
  161. package/dist/web/public/assets/index-DEWFfW_Z.js.map +1 -0
  162. package/dist/web/public/assets/{installed-Co9WrtQ7.js → installed-Xr8p31ij.js} +2 -2
  163. package/dist/web/public/assets/{installed-Co9WrtQ7.js.map → installed-Xr8p31ij.js.map} +1 -1
  164. package/dist/web/public/assets/{jobs-hdHhBEvi.js → jobs-Ddy81Udm.js} +2 -2
  165. package/dist/web/public/assets/{jobs-hdHhBEvi.js.map → jobs-Ddy81Udm.js.map} +1 -1
  166. package/dist/web/public/assets/{layout-CQtbOBag.js → layout-BL74fT-L.js} +2 -2
  167. package/dist/web/public/assets/{layout-CQtbOBag.js.map → layout-BL74fT-L.js.map} +1 -1
  168. package/dist/web/public/assets/{layout-bDMXIKIR.js → layout-Bn2qUxcK.js} +2 -2
  169. package/dist/web/public/assets/{layout-bDMXIKIR.js.map → layout-Bn2qUxcK.js.map} +1 -1
  170. package/dist/web/public/assets/{layout-BMXC1Uh1.js → layout-Bp4SAA8_.js} +2 -2
  171. package/dist/web/public/assets/{layout-BMXC1Uh1.js.map → layout-Bp4SAA8_.js.map} +1 -1
  172. package/dist/web/public/assets/{layout-CysVsySh.js → layout-CZ9pGnW8.js} +2 -2
  173. package/dist/web/public/assets/{layout-CysVsySh.js.map → layout-CZ9pGnW8.js.map} +1 -1
  174. package/dist/web/public/assets/{layout-CyBGneZ9.js → layout-pasFRkKV.js} +2 -2
  175. package/dist/web/public/assets/{layout-CyBGneZ9.js.map → layout-pasFRkKV.js.map} +1 -1
  176. package/dist/web/public/assets/llm-yp7b5xxL.js +7 -0
  177. package/dist/web/public/assets/llm-yp7b5xxL.js.map +1 -0
  178. package/dist/web/public/assets/{loader-circle-9VUMGitw.js → loader-circle-Bbw4pEyE.js} +2 -2
  179. package/dist/web/public/assets/{loader-circle-9VUMGitw.js.map → loader-circle-Bbw4pEyE.js.map} +1 -1
  180. package/dist/web/public/assets/{map-pin-BXYvvHry.js → map-pin-DIXHUQgM.js} +2 -2
  181. package/dist/web/public/assets/{map-pin-BXYvvHry.js.map → map-pin-DIXHUQgM.js.map} +1 -1
  182. package/dist/web/public/assets/{mcp-BgLdlwSn.js → mcp-DyaljIM_.js} +2 -2
  183. package/dist/web/public/assets/{mcp-BgLdlwSn.js.map → mcp-DyaljIM_.js.map} +1 -1
  184. package/dist/web/public/assets/memos-Dkoc157i.js +12 -0
  185. package/dist/web/public/assets/memos-Dkoc157i.js.map +1 -0
  186. package/dist/web/public/assets/{messengers-7Phqea62.js → messengers-CcyGDeUI.js} +2 -2
  187. package/dist/web/public/assets/{messengers-7Phqea62.js.map → messengers-CcyGDeUI.js.map} +1 -1
  188. package/dist/web/public/assets/{mobile-CV5b6D2W.js → mobile-DqzIv4Xb.js} +2 -2
  189. package/dist/web/public/assets/{mobile-CV5b6D2W.js.map → mobile-DqzIv4Xb.js.map} +1 -1
  190. package/dist/web/public/assets/{native-agent-QvIa6LjE.js → native-agent-BQ7WaRGK.js} +2 -2
  191. package/dist/web/public/assets/{native-agent-QvIa6LjE.js.map → native-agent-BQ7WaRGK.js.map} +1 -1
  192. package/dist/web/public/assets/{network-BXhEjGhE.js → network-B_yUFAqC.js} +2 -2
  193. package/dist/web/public/assets/{network-BXhEjGhE.js.map → network-B_yUFAqC.js.map} +1 -1
  194. package/dist/web/public/assets/{outbox-DHQL7TQb.js → outbox-l8aVOZqO.js} +2 -2
  195. package/dist/web/public/assets/{outbox-DHQL7TQb.js.map → outbox-l8aVOZqO.js.map} +1 -1
  196. package/dist/web/public/assets/{pagination-VKuPb1Ot.js → pagination-BAKRGKa9.js} +2 -2
  197. package/dist/web/public/assets/{pagination-VKuPb1Ot.js.map → pagination-BAKRGKa9.js.map} +1 -1
  198. package/dist/web/public/assets/{persona-CWug2GLR.js → persona-D3VL9Rg1.js} +2 -2
  199. package/dist/web/public/assets/{persona-CWug2GLR.js.map → persona-D3VL9Rg1.js.map} +1 -1
  200. package/dist/web/public/assets/{plans-CZoEs5SY.js → plans-BBB5e9my.js} +2 -2
  201. package/dist/web/public/assets/{plans-CZoEs5SY.js.map → plans-BBB5e9my.js.map} +1 -1
  202. package/dist/web/public/assets/{play-CfSn5Vdl.js → play-7-Wd369f.js} +2 -2
  203. package/dist/web/public/assets/{play-CfSn5Vdl.js.map → play-7-Wd369f.js.map} +1 -1
  204. package/dist/web/public/assets/{plus-Z8l4CiqJ.js → plus-B0sfZy-j.js} +2 -2
  205. package/dist/web/public/assets/{plus-Z8l4CiqJ.js.map → plus-B0sfZy-j.js.map} +1 -1
  206. package/dist/web/public/assets/{policy-CutDSEPW.js → policy-BM1WRXH0.js} +2 -2
  207. package/dist/web/public/assets/{policy-CutDSEPW.js.map → policy-BM1WRXH0.js.map} +1 -1
  208. package/dist/web/public/assets/{qr-code-DgU5aiM6.js → qr-code-DcKs5fi3.js} +2 -2
  209. package/dist/web/public/assets/{qr-code-DgU5aiM6.js.map → qr-code-DcKs5fi3.js.map} +1 -1
  210. package/dist/web/public/assets/{react-Cb2sDjhD.js → react-DlP5eolq.js} +2 -2
  211. package/dist/web/public/assets/{react-Cb2sDjhD.js.map → react-DlP5eolq.js.map} +1 -1
  212. package/dist/web/public/assets/{refresh-ccw-D2CWiyU_.js → refresh-ccw-uNKeBeRl.js} +2 -2
  213. package/dist/web/public/assets/{refresh-ccw-D2CWiyU_.js.map → refresh-ccw-uNKeBeRl.js.map} +1 -1
  214. package/dist/web/public/assets/{reminders-Cb6Izedg.js → reminders-DHM8K0_O.js} +2 -2
  215. package/dist/web/public/assets/{reminders-Cb6Izedg.js.map → reminders-DHM8K0_O.js.map} +1 -1
  216. package/dist/web/public/assets/{save-DB0BDYTs.js → save-qwJa5_SA.js} +2 -2
  217. package/dist/web/public/assets/{save-DB0BDYTs.js.map → save-qwJa5_SA.js.map} +1 -1
  218. package/dist/web/public/assets/{schedules-8mSjE14D.js → schedules-Bcd0wbT4.js} +2 -2
  219. package/dist/web/public/assets/{schedules-8mSjE14D.js.map → schedules-Bcd0wbT4.js.map} +1 -1
  220. package/dist/web/public/assets/{search-Con69NhG.js → search-BUlzNWrj.js} +2 -2
  221. package/dist/web/public/assets/{search-Con69NhG.js.map → search-BUlzNWrj.js.map} +1 -1
  222. package/dist/web/public/assets/{search-B4fHilZ0.js → search-i1tP2maJ.js} +2 -2
  223. package/dist/web/public/assets/{search-B4fHilZ0.js.map → search-i1tP2maJ.js.map} +1 -1
  224. package/dist/web/public/assets/{security-BTe3zUg8.js → security-DgJyTT4g.js} +2 -2
  225. package/dist/web/public/assets/{security-BTe3zUg8.js.map → security-DgJyTT4g.js.map} +1 -1
  226. package/dist/web/public/assets/{service-C7SqcwfL.js → service-A0Hzear0.js} +2 -2
  227. package/dist/web/public/assets/{service-C7SqcwfL.js.map → service-A0Hzear0.js.map} +1 -1
  228. package/dist/web/public/assets/{shield-alert-CKFVsGgI.js → shield-alert-DrnN6fz_.js} +2 -2
  229. package/dist/web/public/assets/{shield-alert-CKFVsGgI.js.map → shield-alert-DrnN6fz_.js.map} +1 -1
  230. package/dist/web/public/assets/{status-badge-BSkpyN4D.js → status-badge-Ryzf96Pl.js} +2 -2
  231. package/dist/web/public/assets/{status-badge-BSkpyN4D.js.map → status-badge-Ryzf96Pl.js.map} +1 -1
  232. package/dist/web/public/assets/{subtasks-Bel-I1Sk.js → subtasks-Bzh3o3EF.js} +2 -2
  233. package/dist/web/public/assets/{subtasks-Bel-I1Sk.js.map → subtasks-Bzh3o3EF.js.map} +1 -1
  234. package/dist/web/public/assets/{table-CPn1MRcy.js → table-BbAOSyc8.js} +2 -2
  235. package/dist/web/public/assets/{table-CPn1MRcy.js.map → table-BbAOSyc8.js.map} +1 -1
  236. package/dist/web/public/assets/{topn-Ba3RjcK1.js → topn-DkhYw-Gp.js} +2 -2
  237. package/dist/web/public/assets/{topn-Ba3RjcK1.js.map → topn-DkhYw-Gp.js.map} +1 -1
  238. package/dist/web/public/assets/{trash-2-Dfov8aHD.js → trash-2-CA0cLpnU.js} +2 -2
  239. package/dist/web/public/assets/{trash-2-Dfov8aHD.js.map → trash-2-CA0cLpnU.js.map} +1 -1
  240. package/dist/web/public/assets/{use-background-tasks-BQrEeUwY.js → use-background-tasks-B64YjlA8.js} +2 -2
  241. package/dist/web/public/assets/{use-background-tasks-BQrEeUwY.js.map → use-background-tasks-B64YjlA8.js.map} +1 -1
  242. package/dist/web/public/assets/{use-event-stream-DgGpGKop.js → use-event-stream-I1lMFEfh.js} +2 -2
  243. package/dist/web/public/assets/{use-event-stream-DgGpGKop.js.map → use-event-stream-I1lMFEfh.js.map} +1 -1
  244. package/dist/web/public/assets/{use-llm-admin-DYekqogG.js → use-llm-admin-DY2axI4D.js} +2 -2
  245. package/dist/web/public/assets/{use-llm-admin-DYekqogG.js.map → use-llm-admin-DY2axI4D.js.map} +1 -1
  246. package/dist/web/public/assets/{use-memory-DbJ4pP2Z.js → use-memory-BYEjVWbU.js} +2 -2
  247. package/dist/web/public/assets/{use-memory-DbJ4pP2Z.js.map → use-memory-BYEjVWbU.js.map} +1 -1
  248. package/dist/web/public/assets/{use-observability-C2M6WZ9W.js → use-observability-Coj02yDo.js} +2 -2
  249. package/dist/web/public/assets/{use-observability-C2M6WZ9W.js.map → use-observability-Coj02yDo.js.map} +1 -1
  250. package/dist/web/public/assets/{use-settings-DMdaoWsB.js → use-settings-i1MhlkyC.js} +2 -2
  251. package/dist/web/public/assets/{use-settings-DMdaoWsB.js.map → use-settings-i1MhlkyC.js.map} +1 -1
  252. package/dist/web/public/assets/{use-workspace-BHG7h3jQ.js → use-workspace-DgEM35PY.js} +2 -2
  253. package/dist/web/public/assets/{use-workspace-BHG7h3jQ.js.map → use-workspace-DgEM35PY.js.map} +1 -1
  254. package/dist/web/public/assets/{useQuery-PdiC7-sY.js → useQuery-CY2iazjN.js} +2 -2
  255. package/dist/web/public/assets/{useQuery-PdiC7-sY.js.map → useQuery-CY2iazjN.js.map} +1 -1
  256. package/dist/web/public/assets/{vector-DnZM3OXU.js → vector-Ic76u2hY.js} +2 -2
  257. package/dist/web/public/assets/{vector-DnZM3OXU.js.map → vector-Ic76u2hY.js.map} +1 -1
  258. package/dist/web/public/assets/{viewer-Dz6k0YKp.js → viewer-BXbUN1Rl.js} +2 -2
  259. package/dist/web/public/assets/{viewer-Dz6k0YKp.js.map → viewer-BXbUN1Rl.js.map} +1 -1
  260. package/dist/web/public/assets/{workspace-BnXrWS3j.js → workspace-CUg0JPn6.js} +3 -3
  261. package/dist/web/public/assets/{workspace-BnXrWS3j.js.map → workspace-CUg0JPn6.js.map} +1 -1
  262. package/dist/web/public/assets/{workspaces-CSS_UBEi.js → workspaces-C-wb5FQj.js} +2 -2
  263. package/dist/web/public/assets/{workspaces-CSS_UBEi.js.map → workspaces-C-wb5FQj.js.map} +1 -1
  264. package/dist/web/public/assets/{x-DG-JKVw_.js → x-D1iSuoqg.js} +2 -2
  265. package/dist/web/public/assets/{x-DG-JKVw_.js.map → x-D1iSuoqg.js.map} +1 -1
  266. package/dist/web/public/index.html +2 -2
  267. package/dist/web/server.d.ts +23 -0
  268. package/dist/web/server.d.ts.map +1 -1
  269. package/dist/web/server.js +109 -20
  270. package/dist/web/server.js.map +1 -1
  271. package/package.json +3 -2
  272. package/dist/web/public/assets/dialog-bAIDaO-6.js +0 -6
  273. package/dist/web/public/assets/dialog-bAIDaO-6.js.map +0 -1
  274. package/dist/web/public/assets/index-O0BQoyzo.js +0 -199
  275. package/dist/web/public/assets/index-O0BQoyzo.js.map +0 -1
  276. package/dist/web/public/assets/llm-CPIRNQU2.js +0 -7
  277. package/dist/web/public/assets/llm-CPIRNQU2.js.map +0 -1
  278. package/dist/web/public/assets/memos-CfneX9DH.js +0 -12
  279. package/dist/web/public/assets/memos-CfneX9DH.js.map +0 -1
@@ -1,3 +1,25 @@
1
+ import { type IncomingMessage } from 'node:http';
2
+ declare function isTrustedLoopbackPeer(req: IncomingMessage): boolean;
3
+ /** Resolve whether the request's actor has admin role. Used to gate
4
+ * mutation + privileged-read endpoints so a stolen viewer-role token
5
+ * can't elevate to control plane (R13 A1).
6
+ *
7
+ * Trust order:
8
+ * 1. IMHUB_WEB_AUTH=off → admin (operator explicitly disabled auth)
9
+ * 2. Trusted loopback → admin (operator on the host)
10
+ * 3. Bearer token → token.role === 'admin'
11
+ * 4. Otherwise → not admin
12
+ *
13
+ * Note: when no token has been created yet (pre-bootstrap), the
14
+ * trusted-loopback branch still grants admin so the CLI bootstrap flow
15
+ * works. Disable it with IMHUB_TRUST_LOOPBACK=off. Reverse-proxied
16
+ * requests with Forwarded / X-Forwarded-* peer headers never qualify. */
17
+ declare function isRequestAdmin(req: IncomingMessage): boolean;
18
+ export declare const __webAuthForTesting: {
19
+ isTrustedLoopbackPeer: typeof isTrustedLoopbackPeer;
20
+ isRequestAdmin: typeof isRequestAdmin;
21
+ setTokenModule(mod: typeof import("../core/access-token.js") | null): void;
22
+ };
1
23
  export declare function createSerialQueue(): (fn: () => Promise<void>) => void;
2
24
  /**
3
25
  * Start the web chat server
@@ -9,4 +31,5 @@ export declare function startWebServer(options: {
9
31
  close: () => void;
10
32
  port: number;
11
33
  }>;
34
+ export {};
12
35
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AA8QA,wBAAgB,iBAAiB,IAAI,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA0tC/C"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,eAAe,EAAuB,MAAM,WAAW,CAAA;AA4GnF,iBAAS,qBAAqB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAQ5D;AAyID;;;;;;;;;;;;;0EAa0E;AAC1E,iBAAS,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAcrD;AAWD,eAAO,MAAM,mBAAmB;;;wBAGV,cAAc,yBAAyB,CAAC,GAAG,IAAI,GAAG,IAAI;CAG3E,CAAA;AAED,wBAAgB,iBAAiB,IAAI,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAuvC/C"}
@@ -75,6 +75,29 @@ function isLoopbackPeer(req) {
75
75
  const ip = (req.socket.remoteAddress || '').replace(/^::ffff:/, '');
76
76
  return ip === '127.0.0.1' || ip === '::1';
77
77
  }
78
+ function isEnvOff(name) {
79
+ const v = (process.env[name] || '').trim().toLowerCase();
80
+ return v === 'off' || v === '0' || v === 'false' || v === 'no';
81
+ }
82
+ function hasForwardedPeerHeaders(req) {
83
+ return req.headers.forwarded !== undefined
84
+ || req.headers['x-forwarded-for'] !== undefined
85
+ || req.headers['x-forwarded-host'] !== undefined
86
+ || req.headers['x-real-ip'] !== undefined
87
+ || req.headers['cf-connecting-ip'] !== undefined;
88
+ }
89
+ function isTrustedLoopbackPeer(req) {
90
+ if (!isLoopbackPeer(req))
91
+ return false;
92
+ if (isEnvOff('IMHUB_TRUST_LOOPBACK'))
93
+ return false;
94
+ // A reverse proxy on the same host makes remote users appear as
95
+ // 127.0.0.1. Treat forwarded requests as network traffic and require
96
+ // the normal token path instead of granting the local bootstrap bypass.
97
+ if (hasForwardedPeerHeaders(req))
98
+ return false;
99
+ return true;
100
+ }
78
101
  /** R13 A5 — track once-per-process whether the deprecated `?token=`
79
102
  * URL fallback has been used, so we warn at most once per service
80
103
  * lifetime instead of spamming the journal. Cleared by tests via
@@ -142,8 +165,8 @@ function checkAuth(req, res, url) {
142
165
  // 1. Disabled by env → pass through.
143
166
  if ((process.env.IMHUB_WEB_AUTH || '').toLowerCase() === 'off')
144
167
  return true;
145
- // 2. Loopback → pass through (local CLI / browser-on-same-host).
146
- if (isLoopbackPeer(req))
168
+ // 2. Trusted loopback → pass through (local CLI / browser-on-same-host).
169
+ if (isTrustedLoopbackPeer(req))
147
170
  return true;
148
171
  // 3. Public-by-design path → pass through.
149
172
  if (isPublicPath(url.pathname, req.method || 'GET'))
@@ -201,7 +224,7 @@ function verifyTokenSync(raw) {
201
224
  function getRequestActor(req) {
202
225
  if ((process.env.IMHUB_WEB_AUTH || '').toLowerCase() === 'off')
203
226
  return 'web:auth-off';
204
- if (isLoopbackPeer(req))
227
+ if (isTrustedLoopbackPeer(req))
205
228
  return 'web:loopback';
206
229
  let url;
207
230
  try {
@@ -224,17 +247,18 @@ function getRequestActor(req) {
224
247
  *
225
248
  * Trust order:
226
249
  * 1. IMHUB_WEB_AUTH=off → admin (operator explicitly disabled auth)
227
- * 2. Loopback peer → admin (operator on the host)
250
+ * 2. Trusted loopback → admin (operator on the host)
228
251
  * 3. Bearer token → token.role === 'admin'
229
252
  * 4. Otherwise → not admin
230
253
  *
231
254
  * Note: when no token has been created yet (pre-bootstrap), the
232
- * loopback-peer branch still grants admin so the CLI bootstrap flow
233
- * works. */
255
+ * trusted-loopback branch still grants admin so the CLI bootstrap flow
256
+ * works. Disable it with IMHUB_TRUST_LOOPBACK=off. Reverse-proxied
257
+ * requests with Forwarded / X-Forwarded-* peer headers never qualify. */
234
258
  function isRequestAdmin(req) {
235
259
  if ((process.env.IMHUB_WEB_AUTH || '').toLowerCase() === 'off')
236
260
  return true;
237
- if (isLoopbackPeer(req))
261
+ if (isTrustedLoopbackPeer(req))
238
262
  return true;
239
263
  let url;
240
264
  try {
@@ -265,6 +289,13 @@ function requireAdmin(req, res) {
265
289
  res.end(JSON.stringify({ error: 'forbidden', message: 'admin role required' }));
266
290
  return false;
267
291
  }
292
+ export const __webAuthForTesting = {
293
+ isTrustedLoopbackPeer,
294
+ isRequestAdmin,
295
+ setTokenModule(mod) {
296
+ _tokenModule = mod;
297
+ },
298
+ };
268
299
  export function createSerialQueue() {
269
300
  let queue = Promise.resolve();
270
301
  return (fn) => {
@@ -357,6 +388,7 @@ export async function startWebServer(options) {
357
388
  event: 'web.auth_mode',
358
389
  bind: bindHost,
359
390
  enabled: isAuthEnabled(),
391
+ trustLoopback: !isEnvOff('IMHUB_TRUST_LOOPBACK'),
360
392
  }, `Web console auth: ${isAuthEnabled() ? 'token-gated' : 'disabled (IMHUB_WEB_AUTH=off)'}`);
361
393
  // HTTP request handler — static files + REST API
362
394
  const httpServer = createServer(async (req, res) => {
@@ -643,21 +675,31 @@ export async function startWebServer(options) {
643
675
  }
644
676
  // Jobs
645
677
  if (url.pathname === '/api/jobs' && req.method === 'GET') {
678
+ if (!requireAdmin(req, res))
679
+ return;
646
680
  return handleListJobs(req, res, url);
647
681
  }
648
682
  const jobIdMatch = url.pathname.match(/^\/api\/jobs\/(\d+)$/);
649
683
  if (jobIdMatch && req.method === 'GET') {
684
+ if (!requireAdmin(req, res))
685
+ return;
650
686
  return handleGetJob(req, res, parseInt(jobIdMatch[1], 10));
651
687
  }
652
688
  const jobCancelMatch = url.pathname.match(/^\/api\/jobs\/(\d+)\/cancel$/);
653
689
  if (jobCancelMatch && req.method === 'POST') {
690
+ if (!requireAdmin(req, res))
691
+ return;
654
692
  return handleCancelJob(req, res, parseInt(jobCancelMatch[1], 10));
655
693
  }
656
694
  const jobRunMatch = url.pathname.match(/^\/api\/jobs\/(\d+)\/run$/);
657
695
  if (jobRunMatch && req.method === 'POST') {
696
+ if (!requireAdmin(req, res))
697
+ return;
658
698
  return handleRunJob(req, res, parseInt(jobRunMatch[1], 10));
659
699
  }
660
700
  if (url.pathname === '/api/jobs' && req.method === 'POST') {
701
+ if (!requireAdmin(req, res))
702
+ return;
661
703
  return handleCreateJob(req, res);
662
704
  }
663
705
  // bgjobs (read-only view of ~/.claude/bgjobs, ~/.config/opencode/bgjobs, ~/.codex/bgjobs)
@@ -677,34 +719,46 @@ export async function startWebServer(options) {
677
719
  return handleListSchedules(req, res, url);
678
720
  }
679
721
  // Reminders — list / cancel / snooze. Web-only path (the IM-side path
680
- // is /remind slash command). Auth via the same web session cookie as
681
- // every other /api/* endpoint above; no per-user filtering yet, so
682
- // single-operator deployments only.
722
+ // is /remind slash command). This exposes global reminders, so it is
723
+ // admin-only until a per-user mobile scope exists.
683
724
  if (url.pathname === '/api/reminders' && req.method === 'GET') {
725
+ if (!requireAdmin(req, res))
726
+ return;
684
727
  return handleListReminders(req, res, url);
685
728
  }
686
729
  const reminderCancelMatch = url.pathname.match(/^\/api\/reminders\/(\d+)\/cancel$/);
687
730
  if (reminderCancelMatch && req.method === 'POST') {
731
+ if (!requireAdmin(req, res))
732
+ return;
688
733
  return handleCancelReminderApi(req, res, Number.parseInt(reminderCancelMatch[1], 10));
689
734
  }
690
735
  const reminderSnoozeMatch = url.pathname.match(/^\/api\/reminders\/(\d+)\/snooze$/);
691
736
  if (reminderSnoozeMatch && req.method === 'POST') {
737
+ if (!requireAdmin(req, res))
738
+ return;
692
739
  return handleSnoozeReminderApi(req, res, Number.parseInt(reminderSnoozeMatch[1], 10));
693
740
  }
694
741
  // /api/memos — search / list / delete. List uses the same searchMemos
695
742
  // function the MCP tool exposes; query/who/what/has_location/limit
696
743
  // come through as URL params.
697
744
  if (url.pathname === '/api/memos' && req.method === 'GET') {
745
+ if (!requireAdmin(req, res))
746
+ return;
698
747
  return handleListMemos(req, res, url);
699
748
  }
700
749
  const memoIdMatch = url.pathname.match(/^\/api\/memos\/(\d+)$/);
701
750
  if (memoIdMatch && req.method === 'DELETE') {
751
+ if (!requireAdmin(req, res))
752
+ return;
702
753
  return handleDeleteMemo(req, res, Number.parseInt(memoIdMatch[1], 10));
703
754
  }
704
755
  // /api/env — read/write SMTP + Baidu AK + IMHUB_WEB_BIND. Values
705
756
  // sensitive enough that GET returns them masked (only the last 4 chars
706
- // visible) unless an explicit ?reveal=1 is passed (still auth-gated).
757
+ // visible) unless an explicit ?reveal=1 is passed. Keep the settings
758
+ // surface admin-only: even masked values disclose configured providers.
707
759
  if (url.pathname === '/api/env' && req.method === 'GET') {
760
+ if (!requireAdmin(req, res))
761
+ return;
708
762
  return handleGetEnv(req, res, url);
709
763
  }
710
764
  if (url.pathname === '/api/env' && req.method === 'PUT') {
@@ -847,6 +901,12 @@ export async function startWebServer(options) {
847
901
  return handleViewerGet(req, res, viewerIdMatch[1]);
848
902
  }
849
903
  if (viewerIdMatch && req.method === 'DELETE') {
904
+ // Deleting a viewer paste is a mutation; per the security model all
905
+ // mutation endpoints require admin. Previously this route had no guard,
906
+ // so any authenticated token (incl. role=user, e.g. a mobile-QR token)
907
+ // could delete any paste by its id (ids leak via IM `/v/<uuid>` links).
908
+ if (!requireAdmin(req, res))
909
+ return;
850
910
  return handleViewerDelete(req, res, viewerIdMatch[1]);
851
911
  }
852
912
  // PR-B: agent health snapshot (circuit breaker + rate-limiter remaining
@@ -866,9 +926,13 @@ export async function startWebServer(options) {
866
926
  // v1.5 — Memory admin: enumerate users, list / delete facts, view /
867
927
  // edit / delete persona, export. Backs the Memory tab in /tasks.
868
928
  if (url.pathname === '/api/memory/users' && req.method === 'GET') {
929
+ if (!requireAdmin(req, res))
930
+ return;
869
931
  return handleMemoryUsers(req, res);
870
932
  }
871
933
  if (url.pathname === '/api/memory/facts' && req.method === 'GET') {
934
+ if (!requireAdmin(req, res))
935
+ return;
872
936
  return handleMemoryFacts(req, res, url);
873
937
  }
874
938
  if (url.pathname === '/api/memory/facts' && req.method === 'DELETE') {
@@ -878,9 +942,13 @@ export async function startWebServer(options) {
878
942
  }
879
943
  const memFactIdMatch = url.pathname.match(/^\/api\/memory\/facts\/(\d+)$/);
880
944
  if (memFactIdMatch && req.method === 'DELETE') {
945
+ if (!requireAdmin(req, res))
946
+ return;
881
947
  return handleMemoryDeleteOne(req, res, url, parseInt(memFactIdMatch[1], 10));
882
948
  }
883
949
  if (url.pathname === '/api/memory/persona' && req.method === 'GET') {
950
+ if (!requireAdmin(req, res))
951
+ return;
884
952
  return handleMemoryPersona(req, res, url);
885
953
  }
886
954
  if (url.pathname === '/api/memory/persona' && req.method === 'PUT') {
@@ -894,6 +962,8 @@ export async function startWebServer(options) {
894
962
  return handleMemoryPersonaDelete(req, res, url);
895
963
  }
896
964
  if (url.pathname === '/api/memory/export' && req.method === 'GET') {
965
+ if (!requireAdmin(req, res))
966
+ return;
897
967
  return handleMemoryExport(req, res, url);
898
968
  }
899
969
  // v1.6 — vector backend control + index ops.
@@ -928,6 +998,8 @@ export async function startWebServer(options) {
928
998
  return handleMemoryConsolidate(req, res);
929
999
  }
930
1000
  if (url.pathname === '/api/memory/consolidate/status' && req.method === 'GET') {
1001
+ if (!requireAdmin(req, res))
1002
+ return;
931
1003
  return handleMemoryConsolidateStatus(req, res);
932
1004
  }
933
1005
  // v1.2.3 — Skills browser. Lists locally-installed claude/opencode
@@ -944,10 +1016,14 @@ export async function startWebServer(options) {
944
1016
  }
945
1017
  // PR-B: HITL approvals — global pending list + per-reqId resolve.
946
1018
  if (url.pathname === '/api/approvals' && req.method === 'GET') {
1019
+ if (!requireAdmin(req, res))
1020
+ return;
947
1021
  return handleListApprovals(req, res);
948
1022
  }
949
1023
  const approvalResolveMatch = url.pathname.match(/^\/api\/approvals\/([^/]+)\/resolve$/);
950
1024
  if (approvalResolveMatch && req.method === 'POST') {
1025
+ if (!requireAdmin(req, res))
1026
+ return;
951
1027
  return handleResolveApproval(req, res, approvalResolveMatch[1]);
952
1028
  }
953
1029
  // PR-D: Agent workspace file browser. Read-only inspection of
@@ -955,6 +1031,8 @@ export async function startWebServer(options) {
955
1031
  // text files. PUT path supports inline editing (annotate CLAUDE.md,
956
1032
  // AGENTS.md, etc.) — same traversal/size guards as GET.
957
1033
  if (url.pathname === '/api/workspace-files' && req.method === 'GET') {
1034
+ if (!requireAdmin(req, res))
1035
+ return;
958
1036
  return handleWorkspaceFiles(req, res, url);
959
1037
  }
960
1038
  if (url.pathname === '/api/workspace-files' && req.method === 'PUT') {
@@ -976,9 +1054,12 @@ export async function startWebServer(options) {
976
1054
  return handleBatchJob(req, res, 'run', getDefaultAgent(options.defaultAgent));
977
1055
  }
978
1056
  // PR-C: SSE event stream — audit / approval / job / metrics events
979
- // pushed real-time so the dashboard stops polling. Open access; same
980
- // trust model as the REST API.
1057
+ // pushed real-time so the dashboard stops polling. Global control-plane
1058
+ // telemetry is admin-only; user/mobile-scoped streams should use a
1059
+ // separate endpoint when added.
981
1060
  if (url.pathname === '/events' && req.method === 'GET') {
1061
+ if (!requireAdmin(req, res))
1062
+ return;
982
1063
  return handleEventsSSE(req, res);
983
1064
  }
984
1065
  if (url.pathname === '/api/notify' && req.method === 'POST') {
@@ -1110,11 +1191,11 @@ export async function startWebServer(options) {
1110
1191
  }, 'WS upgrade refused (per-IP rate limit)');
1111
1192
  return cb(false, 429, 'rate limited');
1112
1193
  }
1113
- // Auth-off / loopback bypass — mirror checkAuth's two short-circuits
1194
+ // Auth-off / trusted-loopback bypass — mirror checkAuth's two short-circuits
1114
1195
  // so dev / local CLI sessions still work without a token.
1115
1196
  if ((process.env.IMHUB_WEB_AUTH || '').toLowerCase() === 'off')
1116
1197
  return cb(true);
1117
- if (isLoopbackPeer(info.req))
1198
+ if (isTrustedLoopbackPeer(info.req))
1118
1199
  return cb(true);
1119
1200
  // Origin check: cookie SameSite=Lax handles most of the CSWSH
1120
1201
  // surface, but defence-in-depth — reject when Origin's host
@@ -1168,8 +1249,9 @@ export async function startWebServer(options) {
1168
1249
  // IMHUB_WS_MAX_PER_IP active connections per IP (default 20)
1169
1250
  // IMHUB_WS_MAX_NEW_PER_IP_PER_MIN new connections per IP per minute (default 30)
1170
1251
  //
1171
- // Loopback bypasses both — local dev / CLI tooling makes many
1172
- // short connections legitimately.
1252
+ // Trusted loopback bypasses both — local dev / CLI tooling makes many
1253
+ // short connections legitimately. Reverse-proxied loopback traffic is
1254
+ // still counted as network traffic.
1173
1255
  const wsMaxPerIp = (() => {
1174
1256
  const raw = process.env.IMHUB_WS_MAX_PER_IP;
1175
1257
  if (raw) {
@@ -1198,7 +1280,7 @@ export async function startWebServer(options) {
1198
1280
  }
1199
1281
  /** Returns {ok:true} when the IP may open a new WS, else {ok:false, reason}. */
1200
1282
  function checkWsIpRateLimit(req) {
1201
- if (isLoopbackPeer(req))
1283
+ if (isTrustedLoopbackPeer(req))
1202
1284
  return { ok: true };
1203
1285
  const ip = peerIp(req);
1204
1286
  if (!ip)
@@ -1216,7 +1298,7 @@ export async function startWebServer(options) {
1216
1298
  return { ok: true };
1217
1299
  }
1218
1300
  function recordWsIpOpen(req) {
1219
- if (isLoopbackPeer(req))
1301
+ if (isTrustedLoopbackPeer(req))
1220
1302
  return;
1221
1303
  const ip = peerIp(req);
1222
1304
  if (!ip)
@@ -1227,7 +1309,7 @@ export async function startWebServer(options) {
1227
1309
  wsPerIp.set(ip, slot);
1228
1310
  }
1229
1311
  function recordWsIpClose(req) {
1230
- if (isLoopbackPeer(req))
1312
+ if (isTrustedLoopbackPeer(req))
1231
1313
  return;
1232
1314
  const ip = peerIp(req);
1233
1315
  if (!ip)
@@ -4655,6 +4737,13 @@ function readBody(req, res) {
4655
4737
  aborted = true;
4656
4738
  if (res && !res.headersSent) {
4657
4739
  sendJson(res, 413, { error: 'Request body too large' });
4740
+ res.once('finish', () => {
4741
+ if (!req.destroyed)
4742
+ req.destroy();
4743
+ });
4744
+ }
4745
+ else if (!req.destroyed) {
4746
+ req.destroy();
4658
4747
  }
4659
4748
  const err = new Error('Request body too large');
4660
4749
  err.statusCode = 413;