@fased/fased 0.1.36 → 0.1.37

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 (244) hide show
  1. package/config/capability-catalog.json +241 -0
  2. package/config/dependency-ownership.json +130 -0
  3. package/config/official-channel-catalog.json +39 -0
  4. package/dist/agents/tools/web-fetch-utils.js +2 -1
  5. package/dist/agents/tools/web-fetch-visibility.js +3 -1
  6. package/dist/brand.js +1 -1
  7. package/dist/browser/pw-session.js +2 -1
  8. package/dist/browser/pw-tools-core.state.js +3 -2
  9. package/dist/browser/runtime-dependency.js +27 -0
  10. package/dist/build-info.json +3 -3
  11. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  12. package/dist/capabilities/catalog.js +228 -0
  13. package/dist/cli/components-cli.js +105 -0
  14. package/dist/cli/hooks-cli.js +1 -1
  15. package/dist/cli/plugins-cli.js +3 -3
  16. package/dist/cli/program/register.subclis.js +9 -0
  17. package/dist/cli/update-cli/update-command.js +1 -1
  18. package/dist/commands/doctor-capabilities.js +26 -0
  19. package/dist/commands/doctor.js +2 -0
  20. package/dist/control-ui/assets/{app-DXYMrRJ9.js → app-JBrTSsNu.js} +1480 -1465
  21. package/dist/control-ui/assets/{index-iGQkJE0l.js → index-H-2DWAnH.js} +3 -3
  22. package/dist/control-ui/index.html +1 -1
  23. package/dist/gateway/method-scopes.js +1 -0
  24. package/dist/gateway/server-methods/plugins-marketplace.js +1 -1
  25. package/dist/gateway/server-methods/services.js +8 -0
  26. package/dist/gateway/server-methods-list.js +1 -0
  27. package/dist/infra/optional-runtime-dependency.js +46 -0
  28. package/dist/media/image-ops.js +6 -1
  29. package/dist/media/input-files.js +11 -2
  30. package/dist/media/mime.js +6 -1
  31. package/dist/memory/sqlite-vec.js +7 -1
  32. package/dist/plugin-sdk/{accounts-VqAX3HJ0.js → accounts-BwkH5bTD.js} +1 -1
  33. package/dist/plugin-sdk/{accounts-COY-15zJ.js → accounts-C5BjfxoO.js} +1 -1
  34. package/dist/plugin-sdk/{accounts-Zgt0NP54.js → accounts-RT6y7n3H.js} +1 -1
  35. package/dist/plugin-sdk/{actions-lB3PCwsz.js → actions-BrU_JMqM.js} +2 -2
  36. package/dist/plugin-sdk/{actions-BI5QViLk.js → actions-CUdVHzeJ.js} +2 -2
  37. package/dist/plugin-sdk/{active-listener-DKZiSaRA.js → active-listener-CE8t5g6l.js} +1 -1
  38. package/dist/plugin-sdk/{active-listener-D6_pdTMC.js → active-listener-Dx1SEGDn.js} +1 -1
  39. package/dist/plugin-sdk/{audio-preflight-Dx8J7XhV.js → audio-preflight-C8SbY4fV.js} +22 -22
  40. package/dist/plugin-sdk/{audio-preflight-CeRstduc.js → audio-preflight-CuABlLDL.js} +22 -22
  41. package/dist/plugin-sdk/{audit-BImNOQvk.js → audit-DxB2Kml5.js} +11 -11
  42. package/dist/plugin-sdk/{audit-CKWkq0Un.js → audit-sSeRDwJb.js} +11 -11
  43. package/dist/plugin-sdk/channel-plugin-common.js +3 -2
  44. package/dist/plugin-sdk/{chunk-skAJZWCT.js → chunk-KCxbJLsV.js} +1 -1
  45. package/dist/plugin-sdk/{chunk-Cjw4712g.js → chunk-M2d7r-Zt.js} +1 -1
  46. package/dist/plugin-sdk/{chunk-items-Df5811D0.js → chunk-items-BGbYyNvO.js} +1 -1
  47. package/dist/plugin-sdk/command-status.js +22 -22
  48. package/dist/plugin-sdk/{commands-registry-5J619bQ2.js → commands-registry-CdT-nl7v.js} +2 -2
  49. package/dist/plugin-sdk/{commands-registry-CoLAT03J.js → commands-registry-DoWdYAJd.js} +2 -2
  50. package/dist/plugin-sdk/{common-CSTxewqY.js → common-Cpw-vuuM.js} +1 -1
  51. package/dist/plugin-sdk/{common-BiF0sTnY.js → common-DJWrErdE.js} +1 -1
  52. package/dist/plugin-sdk/{config-BxVjLTIv.js → config-C0qggNKQ.js} +7 -7
  53. package/dist/plugin-sdk/{config-TFokWwvO.js → config-CgwTaper.js} +45 -2
  54. package/dist/plugin-sdk/{config-DXlISPlx.js → config-DtqAN2L3.js} +45 -2
  55. package/dist/plugin-sdk/{config-DgD5T-Ui.js → config-g1MG9Bet.js} +7 -7
  56. package/dist/plugin-sdk/discord.js +2 -1
  57. package/dist/plugin-sdk/{fetch-guard-DPMynms8.js → fetch-guard-BLdujDnt.js} +1 -1
  58. package/dist/plugin-sdk/{fetch-guard-COLmW2TM.js → fetch-guard-DTKgMN9f.js} +1 -1
  59. package/dist/plugin-sdk/{handle-action-C-UndB8E.js → handle-action-B61D85yo.js} +12 -12
  60. package/dist/plugin-sdk/{handle-action-hhufKxmU.js → handle-action-CjcxGzQD.js} +12 -12
  61. package/dist/plugin-sdk/index.js +22 -22
  62. package/dist/plugin-sdk/{login-DL1oBBfL.js → login-CB_nCjX3.js} +3 -3
  63. package/dist/plugin-sdk/{login-YveoqEvH.js → login-CfRfRNc7.js} +2 -2
  64. package/dist/plugin-sdk/{login-Dnxas1dI.js → login-CzUuDd1v.js} +3 -3
  65. package/dist/plugin-sdk/{login-qr-CVQsGWKq.js → login-qr-D7MEpFza.js} +3 -3
  66. package/dist/plugin-sdk/{login-qr-B343JNXJ.js → login-qr-DgxfBFVN.js} +3 -3
  67. package/dist/plugin-sdk/{manager-BytwRIfK.js → manager-9see0IX5.js} +8 -3
  68. package/dist/plugin-sdk/{manager-BZjd_PCt.js → manager-D2kZ0Cps.js} +8 -3
  69. package/dist/plugin-sdk/{markdown-tables-krKiU4cT.js → markdown-tables-CCSD52rA.js} +1 -1
  70. package/dist/plugin-sdk/{markdown-tables-9rJDQ1X-.js → markdown-tables-DWUNvLrc.js} +1 -1
  71. package/dist/plugin-sdk/{media-D7ZkWU-A.js → media-C7YT4DeU.js} +13 -4
  72. package/dist/plugin-sdk/{media-C6C2c9a4.js → media-Cxw-xLGU.js} +13 -4
  73. package/dist/plugin-sdk/{monitor-CVQNDfhs.js → monitor-9X7wapUN.js} +27 -27
  74. package/dist/plugin-sdk/{monitor-BCxkQBoh.js → monitor-B3IfJzKX.js} +26 -26
  75. package/dist/plugin-sdk/{monitor-BzDW50H6.js → monitor-CTjAST_W.js} +27 -27
  76. package/dist/plugin-sdk/{monitor-BsLMfn3U.js → monitor-tYFfy2u-.js} +26 -26
  77. package/dist/plugin-sdk/{openresponses-http-BB67Z4QM.js → openresponses-http-DEB2fEya.js} +22 -22
  78. package/dist/plugin-sdk/{openresponses-http-NE76GGhj.js → openresponses-http-YgMg1jnp.js} +22 -22
  79. package/dist/plugin-sdk/{ssrf-u0Ul7okY.js → optional-runtime-dependency-szS49voX.js} +46 -2
  80. package/dist/plugin-sdk/{outbound-PGNWU6T-.js → outbound-BCxOxdi-.js} +6 -6
  81. package/dist/plugin-sdk/{outbound-BHWJD9q0.js → outbound-JIwQbpRY.js} +6 -6
  82. package/dist/plugin-sdk/{outbound-attachment-BVzfhqnk.js → outbound-attachment-C-jSFaEB.js} +1 -1
  83. package/dist/plugin-sdk/{outbound-attachment-ClMvNW17.js → outbound-attachment-D6tj5Y5H.js} +1 -1
  84. package/dist/plugin-sdk/{pi-embedded-runner-DQ6UPx6-.js → pi-embedded-runner-CUVk8CIj.js} +72 -60
  85. package/dist/plugin-sdk/{pi-model-discovery-D51L5E9I.js → pi-model-discovery-DadlvsZW.js} +1 -1
  86. package/dist/plugin-sdk/{pi-model-discovery-runtime-DxoFm4Vd.js → pi-model-discovery-runtime-C5ssE6EW.js} +1 -1
  87. package/dist/plugin-sdk/{pi-model-discovery-runtime-DRuh4PNV.js → pi-model-discovery-runtime-DAaOPt64.js} +2 -2
  88. package/dist/plugin-sdk/{pi-model-discovery-runtime-CQOauapl.js → pi-model-discovery-runtime-LB-5lX5L.js} +1 -1
  89. package/dist/plugin-sdk/{pi-model-discovery-runtime-BTWgkx4x.js → pi-model-discovery-runtime-gd77b0UF.js} +2 -2
  90. package/dist/plugin-sdk/{pi-model-discovery-CkH5l9X5.js → pi-model-discovery-yifTIDIn.js} +1 -1
  91. package/dist/plugin-sdk/{pw-ai-DZs6uYRd.js → pw-ai-SNCI9UXT.js} +6 -5
  92. package/dist/plugin-sdk/{pw-ai-Cn-EUPZK.js → pw-ai-sH1Pi-Fh.js} +6 -5
  93. package/dist/plugin-sdk/{registry-B4HvWzob.js → registry-DNkJsNsz.js} +4 -3
  94. package/dist/plugin-sdk/{replies-jDMUBsNO.js → replies-fvzN5wnU.js} +2 -2
  95. package/dist/plugin-sdk/{replies-B_LW9c9f.js → replies-zebgZCHX.js} +2 -2
  96. package/dist/plugin-sdk/{resolve-route-_YZHbS-z.js → resolve-route-CNhTJG_2.js} +1 -1
  97. package/dist/plugin-sdk/{resolve-route-DlHzggnG.js → resolve-route-DO0WR4nB.js} +1 -1
  98. package/dist/plugin-sdk/{resolve-utils-zBhVX3Vx.js → resolve-utils-EFP1DYqG.js} +1 -1
  99. package/dist/plugin-sdk/{retry-policy-DGLs5ebP.js → retry-policy-BLYEgIwP.js} +1 -1
  100. package/dist/plugin-sdk/{retry-policy-DaodShGa.js → retry-policy-sM01QZT2.js} +1 -1
  101. package/dist/plugin-sdk/{runtime-Dd0sswuW.js → runtime-BPyuAmka.js} +4 -3
  102. package/dist/plugin-sdk/{paths-DW-U2r7Y.js → runtime-dependency-B-1u41Ph.js} +27 -3
  103. package/dist/plugin-sdk/{paths-CBCpJ5bu.js → runtime-dependency-Csswyv5r.js} +27 -3
  104. package/dist/plugin-sdk/sat-runtime.js +1 -0
  105. package/dist/plugin-sdk/{send-8remZhsH.js → send-B09HRfHN.js} +6 -6
  106. package/dist/plugin-sdk/{send-DaaAscWj.js → send-BBYcgXA4.js} +5 -5
  107. package/dist/plugin-sdk/{send-DFTB0cnx.js → send-BQXQA81e.js} +6 -6
  108. package/dist/plugin-sdk/{send-CtMVvMJs.js → send-BzFdvD_i.js} +6 -6
  109. package/dist/plugin-sdk/{send-Di9irjS-.js → send-C3u-F_8s.js} +6 -6
  110. package/dist/plugin-sdk/{send-Dl88ZKq2.js → send-D3836_UN.js} +6 -6
  111. package/dist/plugin-sdk/{send-ifgZCHVB.js → send-D7tr-EsH.js} +5 -5
  112. package/dist/plugin-sdk/{send-DvUpYh0d.js → send-DRGzRdI3.js} +6 -6
  113. package/dist/plugin-sdk/{send-D2jlPeKH.js → send-DfkPKz_0.js} +4 -4
  114. package/dist/plugin-sdk/{send-QSt6nbny.js → send-DsxFXrCO.js} +4 -4
  115. package/dist/plugin-sdk/{session-CknrceVU.js → session-DG_bYisw.js} +2 -2
  116. package/dist/plugin-sdk/{session-CyRTcJ8N.js → session-EIStzK0E.js} +2 -2
  117. package/dist/plugin-sdk/{skill-commands-DHxkTA0s.js → skill-commands-CkgGmWBS.js} +2 -2
  118. package/dist/plugin-sdk/{skill-commands-CLXAG5sU.js → skill-commands-hyZA0LE3.js} +2 -2
  119. package/dist/plugin-sdk/{slack-BpJXkwxF.js → slack-C0WZVJPZ.js} +34 -34
  120. package/dist/plugin-sdk/{slack-Br9c6Yzs.js → slack-DtlPw_PN.js} +34 -34
  121. package/dist/plugin-sdk/{slack-actions-8tLGt1Ht.js → slack-actions-BQawWGTJ.js} +11 -11
  122. package/dist/plugin-sdk/{slack-actions-CDSSRo8Y.js → slack-actions-DDMszx56.js} +11 -11
  123. package/dist/plugin-sdk/slack.js +2 -1
  124. package/dist/plugin-sdk/src/brand.d.ts +2 -2
  125. package/dist/plugin-sdk/src/browser/runtime-dependency.d.ts +3 -0
  126. package/dist/plugin-sdk/src/infra/optional-runtime-dependency.d.ts +6 -0
  127. package/dist/plugin-sdk/{ssrf-fG4UHKBw.js → ssrf-Cce6LubA.js} +41 -7
  128. package/dist/plugin-sdk/{status-DoxnzyMC.js → status-CIiBeb9B.js} +72 -60
  129. package/dist/plugin-sdk/{sticker-cache-zmJ39ByC.js → sticker-cache-9houHChV.js} +3 -3
  130. package/dist/plugin-sdk/{sticker-cache-D7WcMYsF.js → sticker-cache-Dwi-XsgB.js} +3 -3
  131. package/dist/plugin-sdk/{tables-CkxblF0u.js → tables-BD7H5ug-.js} +1 -1
  132. package/dist/plugin-sdk/{tables-RBtf8G4P.js → tables-CJE8kB35.js} +1 -1
  133. package/dist/plugin-sdk/{telegram-actions-DAeYXSLk.js → telegram-actions-Bu1edXF2.js} +25 -25
  134. package/dist/plugin-sdk/{telegram-actions-BDeS3ocd.js → telegram-actions-CRXVO8lG.js} +25 -25
  135. package/dist/plugin-sdk/telegram.js +2 -1
  136. package/dist/plugin-sdk/{thread-bindings.lifecycle-DsB94WbE.js → thread-bindings.lifecycle-Djem8FWF.js} +2 -2
  137. package/dist/plugin-sdk/{thread-bindings.lifecycle-DMhV3TsR.js → thread-bindings.lifecycle-gmHv3hc7.js} +2 -2
  138. package/dist/plugin-sdk/{thread-bindings.state-DYWpy7hJ.js → thread-bindings.state-CNT2vxm3.js} +2 -2
  139. package/dist/plugin-sdk/{thread-bindings.state-DG5cBaRn.js → thread-bindings.state-D8kN2X6H.js} +2 -2
  140. package/dist/plugin-sdk/{web-IJ4NoGPJ.js → web-DCrVQr6y.js} +26 -26
  141. package/dist/plugin-sdk/{web-DJwacWQo.js → web-Ds6OWw3C.js} +26 -26
  142. package/dist/plugin-sdk/{whatsapp-actions-DAY8hWpD.js → whatsapp-actions-68Mfs2dJ.js} +12 -12
  143. package/dist/plugin-sdk/{whatsapp-actions-6opfo-oB.js → whatsapp-actions-DRqtzvTO.js} +12 -12
  144. package/dist/plugin-sdk/whatsapp.js +6 -5
  145. package/dist/plugin-sdk/{wsl-B2euLEc2.js → wsl-DcM2PgSS.js} +3 -3
  146. package/dist/plugin-sdk/{wsl-B8eyviuv.js → wsl-PXrdWitE.js} +3 -3
  147. package/dist/plugins/update.js +1 -1
  148. package/dist/tts/tts-core.js +6 -1
  149. package/dist/wizard/onboarding.finalize.js +9 -0
  150. package/extensions/acpx/package.json +1 -1
  151. package/extensions/bluebubbles/package.json +1 -1
  152. package/extensions/copilot-proxy/package.json +1 -1
  153. package/extensions/diagnostics-otel/package.json +1 -1
  154. package/extensions/fased-federation/package.json +1 -1
  155. package/extensions/google-gemini-cli-auth/package.json +1 -1
  156. package/extensions/imessage/package.json +1 -1
  157. package/extensions/irc/package.json +1 -1
  158. package/extensions/line/package.json +1 -1
  159. package/extensions/llm-task/package.json +1 -1
  160. package/extensions/lobster/package.json +1 -1
  161. package/extensions/matrix/CHANGELOG.md +6 -0
  162. package/extensions/matrix/package.json +1 -1
  163. package/extensions/mattermost/package.json +1 -1
  164. package/extensions/memory-core/package.json +2 -2
  165. package/extensions/memory-lancedb/package.json +1 -1
  166. package/extensions/minimax-portal-auth/package.json +1 -1
  167. package/extensions/msteams/CHANGELOG.md +6 -0
  168. package/extensions/msteams/package.json +1 -1
  169. package/extensions/nextcloud-talk/package.json +1 -1
  170. package/extensions/nostr/CHANGELOG.md +6 -0
  171. package/extensions/nostr/package.json +1 -1
  172. package/extensions/open-prose/package.json +1 -1
  173. package/extensions/sat-mining/package.json +1 -1
  174. package/extensions/signal/package.json +1 -1
  175. package/extensions/synology-chat/package.json +1 -1
  176. package/extensions/tlon/package.json +1 -1
  177. package/extensions/twitch/CHANGELOG.md +6 -0
  178. package/extensions/twitch/package.json +1 -1
  179. package/extensions/voice-call/CHANGELOG.md +6 -0
  180. package/extensions/voice-call/package.json +1 -1
  181. package/extensions/zalo/CHANGELOG.md +6 -0
  182. package/extensions/zalo/package.json +1 -1
  183. package/extensions/zalouser/CHANGELOG.md +6 -0
  184. package/extensions/zalouser/package.json +1 -1
  185. package/package.json +22 -15
  186. package/extensions/feishu/fased.plugin.json +0 -10
  187. package/extensions/feishu/index.ts +0 -63
  188. package/extensions/feishu/package.json +0 -33
  189. package/extensions/feishu/skills/feishu-doc/SKILL.md +0 -211
  190. package/extensions/feishu/skills/feishu-doc/references/block-types.md +0 -103
  191. package/extensions/feishu/skills/feishu-drive/SKILL.md +0 -97
  192. package/extensions/feishu/skills/feishu-perm/SKILL.md +0 -119
  193. package/extensions/feishu/skills/feishu-wiki/SKILL.md +0 -111
  194. package/extensions/feishu/src/accounts.ts +0 -159
  195. package/extensions/feishu/src/bitable.ts +0 -713
  196. package/extensions/feishu/src/bot.ts +0 -979
  197. package/extensions/feishu/src/channel.ts +0 -359
  198. package/extensions/feishu/src/client.ts +0 -118
  199. package/extensions/feishu/src/config-schema.ts +0 -226
  200. package/extensions/feishu/src/dedup.ts +0 -54
  201. package/extensions/feishu/src/directory.ts +0 -177
  202. package/extensions/feishu/src/doc-schema.ts +0 -122
  203. package/extensions/feishu/src/docx.ts +0 -998
  204. package/extensions/feishu/src/drive-schema.ts +0 -46
  205. package/extensions/feishu/src/drive.ts +0 -232
  206. package/extensions/feishu/src/dynamic-agent.ts +0 -131
  207. package/extensions/feishu/src/external-keys.ts +0 -19
  208. package/extensions/feishu/src/media.ts +0 -447
  209. package/extensions/feishu/src/mention.ts +0 -133
  210. package/extensions/feishu/src/monitor.ts +0 -434
  211. package/extensions/feishu/src/onboarding.ts +0 -430
  212. package/extensions/feishu/src/outbound.ts +0 -56
  213. package/extensions/feishu/src/perm-schema.ts +0 -52
  214. package/extensions/feishu/src/perm.ts +0 -180
  215. package/extensions/feishu/src/policy.ts +0 -116
  216. package/extensions/feishu/src/probe.ts +0 -44
  217. package/extensions/feishu/src/reactions.ts +0 -160
  218. package/extensions/feishu/src/reply-dispatcher.ts +0 -239
  219. package/extensions/feishu/src/runtime.ts +0 -14
  220. package/extensions/feishu/src/send-result.ts +0 -29
  221. package/extensions/feishu/src/send-target.ts +0 -25
  222. package/extensions/feishu/src/send.ts +0 -313
  223. package/extensions/feishu/src/streaming-card.ts +0 -218
  224. package/extensions/feishu/src/targets.ts +0 -78
  225. package/extensions/feishu/src/tool-account.ts +0 -58
  226. package/extensions/feishu/src/tools-config.ts +0 -21
  227. package/extensions/feishu/src/types.ts +0 -81
  228. package/extensions/feishu/src/typing.ts +0 -80
  229. package/extensions/feishu/src/wiki-schema.ts +0 -55
  230. package/extensions/feishu/src/wiki.ts +0 -237
  231. package/extensions/googlechat/fased.plugin.json +0 -9
  232. package/extensions/googlechat/index.ts +0 -19
  233. package/extensions/googlechat/package.json +0 -37
  234. package/extensions/googlechat/src/accounts.ts +0 -147
  235. package/extensions/googlechat/src/actions.ts +0 -176
  236. package/extensions/googlechat/src/api.ts +0 -282
  237. package/extensions/googlechat/src/auth.ts +0 -137
  238. package/extensions/googlechat/src/channel.ts +0 -591
  239. package/extensions/googlechat/src/monitor.ts +0 -996
  240. package/extensions/googlechat/src/onboarding.ts +0 -344
  241. package/extensions/googlechat/src/runtime.ts +0 -14
  242. package/extensions/googlechat/src/targets.ts +0 -65
  243. package/extensions/googlechat/src/types.config.ts +0 -3
  244. package/extensions/googlechat/src/types.ts +0 -73
@@ -1,434 +0,0 @@
1
- import * as http from "http";
2
- import * as Lark from "@larksuiteoapi/node-sdk";
3
- import {
4
- type FasedAgentConfig,
5
- type RuntimeEnv,
6
- type HistoryEntry,
7
- installRequestBodyLimitGuard,
8
- } from "fased/plugin-sdk";
9
- import { resolveFeishuAccount, listEnabledFeishuAccounts } from "./accounts.js";
10
- import { handleFeishuMessage, type FeishuMessageEvent, type FeishuBotAddedEvent } from "./bot.js";
11
- import { createFeishuWSClient, createEventDispatcher } from "./client.js";
12
- import { probeFeishu } from "./probe.js";
13
- import type { ResolvedFeishuAccount } from "./types.js";
14
-
15
- export type MonitorFeishuOpts = {
16
- config?: FasedAgentConfig;
17
- runtime?: RuntimeEnv;
18
- abortSignal?: AbortSignal;
19
- accountId?: string;
20
- };
21
-
22
- // Per-account WebSocket clients, HTTP servers, and bot info
23
- const wsClients = new Map<string, Lark.WSClient>();
24
- const httpServers = new Map<string, http.Server>();
25
- const botOpenIds = new Map<string, string>();
26
- const FEISHU_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
27
- const FEISHU_WEBHOOK_BODY_TIMEOUT_MS = 30_000;
28
- const FEISHU_WEBHOOK_RATE_LIMIT_WINDOW_MS = 60_000;
29
- const FEISHU_WEBHOOK_RATE_LIMIT_MAX_REQUESTS = 120;
30
- const FEISHU_WEBHOOK_RATE_LIMIT_MAX_TRACKED_KEYS = 4_096;
31
- const FEISHU_WEBHOOK_COUNTER_LOG_EVERY = 25;
32
- const feishuWebhookRateLimits = new Map<string, { count: number; windowStartMs: number }>();
33
- const feishuWebhookStatusCounters = new Map<string, number>();
34
- let lastWebhookRateLimitCleanupMs = 0;
35
-
36
- function isJsonContentType(value: string | string[] | undefined): boolean {
37
- const first = Array.isArray(value) ? value[0] : value;
38
- if (!first) {
39
- return false;
40
- }
41
- const mediaType = first.split(";", 1)[0]?.trim().toLowerCase();
42
- return mediaType === "application/json" || Boolean(mediaType?.endsWith("+json"));
43
- }
44
-
45
- function trimWebhookRateLimitState(): void {
46
- while (feishuWebhookRateLimits.size > FEISHU_WEBHOOK_RATE_LIMIT_MAX_TRACKED_KEYS) {
47
- const oldestKey = feishuWebhookRateLimits.keys().next().value;
48
- if (typeof oldestKey !== "string") {
49
- break;
50
- }
51
- feishuWebhookRateLimits.delete(oldestKey);
52
- }
53
- }
54
-
55
- function maybePruneWebhookRateLimitState(nowMs: number): void {
56
- if (
57
- feishuWebhookRateLimits.size === 0 ||
58
- nowMs - lastWebhookRateLimitCleanupMs < FEISHU_WEBHOOK_RATE_LIMIT_WINDOW_MS
59
- ) {
60
- return;
61
- }
62
- lastWebhookRateLimitCleanupMs = nowMs;
63
- for (const [key, state] of feishuWebhookRateLimits) {
64
- if (nowMs - state.windowStartMs >= FEISHU_WEBHOOK_RATE_LIMIT_WINDOW_MS) {
65
- feishuWebhookRateLimits.delete(key);
66
- }
67
- }
68
- }
69
-
70
- export function clearFeishuWebhookRateLimitStateForTest(): void {
71
- feishuWebhookRateLimits.clear();
72
- lastWebhookRateLimitCleanupMs = 0;
73
- }
74
-
75
- export function getFeishuWebhookRateLimitStateSizeForTest(): number {
76
- return feishuWebhookRateLimits.size;
77
- }
78
-
79
- export function isWebhookRateLimitedForTest(key: string, nowMs: number): boolean {
80
- maybePruneWebhookRateLimitState(nowMs);
81
-
82
- const state = feishuWebhookRateLimits.get(key);
83
- if (!state || nowMs - state.windowStartMs >= FEISHU_WEBHOOK_RATE_LIMIT_WINDOW_MS) {
84
- feishuWebhookRateLimits.set(key, { count: 1, windowStartMs: nowMs });
85
- trimWebhookRateLimitState();
86
- return false;
87
- }
88
-
89
- state.count += 1;
90
- if (state.count > FEISHU_WEBHOOK_RATE_LIMIT_MAX_REQUESTS) {
91
- return true;
92
- }
93
- return false;
94
- }
95
-
96
- function isWebhookRateLimited(key: string, nowMs: number): boolean {
97
- return isWebhookRateLimitedForTest(key, nowMs);
98
- }
99
-
100
- function recordWebhookStatus(
101
- runtime: RuntimeEnv | undefined,
102
- accountId: string,
103
- path: string,
104
- statusCode: number,
105
- ): void {
106
- if (![400, 401, 408, 413, 415, 429].includes(statusCode)) {
107
- return;
108
- }
109
- const key = `${accountId}:${path}:${statusCode}`;
110
- const next = (feishuWebhookStatusCounters.get(key) ?? 0) + 1;
111
- feishuWebhookStatusCounters.set(key, next);
112
- if (next === 1 || next % FEISHU_WEBHOOK_COUNTER_LOG_EVERY === 0) {
113
- const log = runtime?.log ?? console.log;
114
- log(`feishu[${accountId}]: webhook anomaly path=${path} status=${statusCode} count=${next}`);
115
- }
116
- }
117
-
118
- /**
119
- * Register common event handlers on an EventDispatcher.
120
- * When fireAndForget is true (webhook mode), message handling is not awaited
121
- * to avoid blocking the HTTP response (Lark requires <3s response).
122
- */
123
- function registerEventHandlers(
124
- eventDispatcher: Lark.EventDispatcher,
125
- context: {
126
- cfg: FasedAgentConfig;
127
- accountId: string;
128
- runtime?: RuntimeEnv;
129
- chatHistories: Map<string, HistoryEntry[]>;
130
- fireAndForget?: boolean;
131
- },
132
- ) {
133
- const { cfg, accountId, runtime, chatHistories, fireAndForget } = context;
134
- const log = runtime?.log ?? console.log;
135
- const error = runtime?.error ?? console.error;
136
-
137
- eventDispatcher.register({
138
- "im.message.receive_v1": async (data) => {
139
- try {
140
- const event = data as unknown as FeishuMessageEvent;
141
- const promise = handleFeishuMessage({
142
- cfg,
143
- event,
144
- botOpenId: botOpenIds.get(accountId),
145
- runtime,
146
- chatHistories,
147
- accountId,
148
- });
149
- if (fireAndForget) {
150
- promise.catch((err) => {
151
- error(`feishu[${accountId}]: error handling message: ${String(err)}`);
152
- });
153
- } else {
154
- await promise;
155
- }
156
- } catch (err) {
157
- error(`feishu[${accountId}]: error handling message: ${String(err)}`);
158
- }
159
- },
160
- "im.message.message_read_v1": async () => {
161
- // Ignore read receipts
162
- },
163
- "im.chat.member.bot.added_v1": async (data) => {
164
- try {
165
- const event = data as unknown as FeishuBotAddedEvent;
166
- log(`feishu[${accountId}]: bot added to chat ${event.chat_id}`);
167
- } catch (err) {
168
- error(`feishu[${accountId}]: error handling bot added event: ${String(err)}`);
169
- }
170
- },
171
- "im.chat.member.bot.deleted_v1": async (data) => {
172
- try {
173
- const event = data as unknown as { chat_id: string };
174
- log(`feishu[${accountId}]: bot removed from chat ${event.chat_id}`);
175
- } catch (err) {
176
- error(`feishu[${accountId}]: error handling bot removed event: ${String(err)}`);
177
- }
178
- },
179
- });
180
- }
181
-
182
- type MonitorAccountParams = {
183
- cfg: FasedAgentConfig;
184
- account: ResolvedFeishuAccount;
185
- runtime?: RuntimeEnv;
186
- abortSignal?: AbortSignal;
187
- };
188
-
189
- /**
190
- * Monitor a single Feishu account.
191
- */
192
- async function monitorSingleAccount(params: MonitorAccountParams): Promise<void> {
193
- const { cfg, account, runtime, abortSignal } = params;
194
- const { accountId } = account;
195
- const log = runtime?.log ?? console.log;
196
-
197
- const probe = await probeFeishu(account);
198
- if (!probe.ok) {
199
- log(`feishu[${accountId}]: not started; credential check failed: ${probe.error}`);
200
- return;
201
- }
202
- botOpenIds.set(accountId, probe.botOpenId ?? "");
203
- log(`feishu[${accountId}]: bot open_id resolved: ${probe.botOpenId ?? "unknown"}`);
204
-
205
- const connectionMode = account.config.connectionMode ?? "websocket";
206
- if (connectionMode === "webhook" && !account.verificationToken?.trim()) {
207
- throw new Error(`Feishu account "${accountId}" webhook mode requires verificationToken`);
208
- }
209
- const eventDispatcher = createEventDispatcher(account);
210
- const chatHistories = new Map<string, HistoryEntry[]>();
211
-
212
- registerEventHandlers(eventDispatcher, {
213
- cfg,
214
- accountId,
215
- runtime,
216
- chatHistories,
217
- fireAndForget: connectionMode === "webhook",
218
- });
219
-
220
- if (connectionMode === "webhook") {
221
- return monitorWebhook({ params, accountId, eventDispatcher });
222
- }
223
-
224
- return monitorWebSocket({ params, accountId, eventDispatcher });
225
- }
226
-
227
- type ConnectionParams = {
228
- params: MonitorAccountParams;
229
- accountId: string;
230
- eventDispatcher: Lark.EventDispatcher;
231
- };
232
-
233
- async function monitorWebSocket({
234
- params,
235
- accountId,
236
- eventDispatcher,
237
- }: ConnectionParams): Promise<void> {
238
- const { account, runtime, abortSignal } = params;
239
- const log = runtime?.log ?? console.log;
240
- const error = runtime?.error ?? console.error;
241
-
242
- log(`feishu[${accountId}]: starting WebSocket connection...`);
243
-
244
- const wsClient = createFeishuWSClient(account);
245
- wsClients.set(accountId, wsClient);
246
-
247
- return new Promise((resolve, reject) => {
248
- const cleanup = () => {
249
- wsClients.delete(accountId);
250
- botOpenIds.delete(accountId);
251
- };
252
-
253
- const handleAbort = () => {
254
- log(`feishu[${accountId}]: abort signal received, stopping`);
255
- cleanup();
256
- resolve();
257
- };
258
-
259
- if (abortSignal?.aborted) {
260
- cleanup();
261
- resolve();
262
- return;
263
- }
264
-
265
- abortSignal?.addEventListener("abort", handleAbort, { once: true });
266
-
267
- try {
268
- wsClient.start({ eventDispatcher });
269
- log(`feishu[${accountId}]: WebSocket client started`);
270
- } catch (err) {
271
- cleanup();
272
- abortSignal?.removeEventListener("abort", handleAbort);
273
- reject(err);
274
- }
275
- });
276
- }
277
-
278
- async function monitorWebhook({
279
- params,
280
- accountId,
281
- eventDispatcher,
282
- }: ConnectionParams): Promise<void> {
283
- const { account, runtime, abortSignal } = params;
284
- const log = runtime?.log ?? console.log;
285
- const error = runtime?.error ?? console.error;
286
-
287
- const port = account.config.webhookPort ?? 3000;
288
- const path = account.config.webhookPath ?? "/feishu/events";
289
- const host = account.config.webhookHost ?? "127.0.0.1";
290
-
291
- log(`feishu[${accountId}]: starting Webhook server on ${host}:${port}, path ${path}...`);
292
-
293
- const server = http.createServer();
294
- const webhookHandler = Lark.adaptDefault(path, eventDispatcher, { autoChallenge: true });
295
- server.on("request", (req, res) => {
296
- res.on("finish", () => {
297
- recordWebhookStatus(runtime, accountId, path, res.statusCode);
298
- });
299
-
300
- const rateLimitKey = `${accountId}:${path}:${req.socket.remoteAddress ?? "unknown"}`;
301
- if (isWebhookRateLimited(rateLimitKey, Date.now())) {
302
- res.statusCode = 429;
303
- res.end("Too Many Requests");
304
- return;
305
- }
306
-
307
- if (req.method === "POST" && !isJsonContentType(req.headers["content-type"])) {
308
- res.statusCode = 415;
309
- res.end("Unsupported Media Type");
310
- return;
311
- }
312
-
313
- const guard = installRequestBodyLimitGuard(req, res, {
314
- maxBytes: FEISHU_WEBHOOK_MAX_BODY_BYTES,
315
- timeoutMs: FEISHU_WEBHOOK_BODY_TIMEOUT_MS,
316
- responseFormat: "text",
317
- });
318
- if (guard.isTripped()) {
319
- return;
320
- }
321
- void Promise.resolve(webhookHandler(req, res))
322
- .catch((err) => {
323
- if (!guard.isTripped()) {
324
- error(`feishu[${accountId}]: webhook handler error: ${String(err)}`);
325
- }
326
- })
327
- .finally(() => {
328
- guard.dispose();
329
- });
330
- });
331
- httpServers.set(accountId, server);
332
-
333
- return new Promise((resolve, reject) => {
334
- const cleanup = () => {
335
- server.close();
336
- httpServers.delete(accountId);
337
- botOpenIds.delete(accountId);
338
- };
339
-
340
- const handleAbort = () => {
341
- log(`feishu[${accountId}]: abort signal received, stopping Webhook server`);
342
- cleanup();
343
- resolve();
344
- };
345
-
346
- if (abortSignal?.aborted) {
347
- cleanup();
348
- resolve();
349
- return;
350
- }
351
-
352
- abortSignal?.addEventListener("abort", handleAbort, { once: true });
353
-
354
- server.listen(port, host, () => {
355
- log(`feishu[${accountId}]: Webhook server listening on ${host}:${port}`);
356
- });
357
-
358
- server.on("error", (err) => {
359
- error(`feishu[${accountId}]: Webhook server error: ${err}`);
360
- abortSignal?.removeEventListener("abort", handleAbort);
361
- reject(err);
362
- });
363
- });
364
- }
365
-
366
- /**
367
- * Main entry: start monitoring for all enabled accounts.
368
- */
369
- export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promise<void> {
370
- const cfg = opts.config;
371
- if (!cfg) {
372
- throw new Error("Config is required for Feishu monitor");
373
- }
374
-
375
- const log = opts.runtime?.log ?? console.log;
376
-
377
- // If accountId is specified, only monitor that account
378
- if (opts.accountId) {
379
- const account = resolveFeishuAccount({ cfg, accountId: opts.accountId });
380
- if (!account.enabled || !account.configured) {
381
- throw new Error(`Feishu account "${opts.accountId}" not configured or disabled`);
382
- }
383
- return monitorSingleAccount({
384
- cfg,
385
- account,
386
- runtime: opts.runtime,
387
- abortSignal: opts.abortSignal,
388
- });
389
- }
390
-
391
- // Otherwise, start all enabled accounts
392
- const accounts = listEnabledFeishuAccounts(cfg);
393
- if (accounts.length === 0) {
394
- throw new Error("No enabled Feishu accounts configured");
395
- }
396
-
397
- log(
398
- `feishu: starting ${accounts.length} account(s): ${accounts.map((a) => a.accountId).join(", ")}`,
399
- );
400
-
401
- // Start all accounts in parallel
402
- await Promise.all(
403
- accounts.map((account) =>
404
- monitorSingleAccount({
405
- cfg,
406
- account,
407
- runtime: opts.runtime,
408
- abortSignal: opts.abortSignal,
409
- }),
410
- ),
411
- );
412
- }
413
-
414
- /**
415
- * Stop monitoring for a specific account or all accounts.
416
- */
417
- export function stopFeishuMonitor(accountId?: string): void {
418
- if (accountId) {
419
- wsClients.delete(accountId);
420
- const server = httpServers.get(accountId);
421
- if (server) {
422
- server.close();
423
- httpServers.delete(accountId);
424
- }
425
- botOpenIds.delete(accountId);
426
- } else {
427
- wsClients.clear();
428
- for (const server of httpServers.values()) {
429
- server.close();
430
- }
431
- httpServers.clear();
432
- botOpenIds.clear();
433
- }
434
- }