@fased/fased 0.1.36 → 0.1.38

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 (240) 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-Zgt0NP54.js → accounts-RT6y7n3H.js} +1 -1
  34. package/dist/plugin-sdk/{actions-lB3PCwsz.js → actions-BrU_JMqM.js} +2 -2
  35. package/dist/plugin-sdk/{actions-BI5QViLk.js → actions-CUdVHzeJ.js} +2 -2
  36. package/dist/plugin-sdk/{active-listener-DKZiSaRA.js → active-listener-CE8t5g6l.js} +1 -1
  37. package/dist/plugin-sdk/{active-listener-D6_pdTMC.js → active-listener-Dx1SEGDn.js} +1 -1
  38. package/dist/plugin-sdk/{audio-preflight-Dx8J7XhV.js → audio-preflight-BPK8t0tI.js} +22 -22
  39. package/dist/plugin-sdk/{audio-preflight-CeRstduc.js → audio-preflight-kCa_KGRH.js} +22 -22
  40. package/dist/plugin-sdk/{audit-BImNOQvk.js → audit-DxB2Kml5.js} +11 -11
  41. package/dist/plugin-sdk/{audit-CKWkq0Un.js → audit-sSeRDwJb.js} +11 -11
  42. package/dist/plugin-sdk/channel-plugin-common.js +3 -2
  43. package/dist/plugin-sdk/{chunk-skAJZWCT.js → chunk-KCxbJLsV.js} +1 -1
  44. package/dist/plugin-sdk/{chunk-Cjw4712g.js → chunk-M2d7r-Zt.js} +1 -1
  45. package/dist/plugin-sdk/{chunk-items-Df5811D0.js → chunk-items-DGHUeFuI.js} +1 -1
  46. package/dist/plugin-sdk/command-status.js +22 -22
  47. package/dist/plugin-sdk/{commands-registry-5J619bQ2.js → commands-registry-CdT-nl7v.js} +2 -2
  48. package/dist/plugin-sdk/{commands-registry-CoLAT03J.js → commands-registry-DoWdYAJd.js} +2 -2
  49. package/dist/plugin-sdk/{common-CSTxewqY.js → common-Cpw-vuuM.js} +1 -1
  50. package/dist/plugin-sdk/{common-BiF0sTnY.js → common-DJWrErdE.js} +1 -1
  51. package/dist/plugin-sdk/{config-BxVjLTIv.js → config-C0qggNKQ.js} +7 -7
  52. package/dist/plugin-sdk/{config-TFokWwvO.js → config-CgwTaper.js} +45 -2
  53. package/dist/plugin-sdk/{config-DXlISPlx.js → config-DtqAN2L3.js} +45 -2
  54. package/dist/plugin-sdk/discord.js +2 -1
  55. package/dist/plugin-sdk/{fetch-guard-DPMynms8.js → fetch-guard-BLdujDnt.js} +1 -1
  56. package/dist/plugin-sdk/{fetch-guard-COLmW2TM.js → fetch-guard-DTKgMN9f.js} +1 -1
  57. package/dist/plugin-sdk/{handle-action-C-UndB8E.js → handle-action-B61D85yo.js} +12 -12
  58. package/dist/plugin-sdk/{handle-action-hhufKxmU.js → handle-action-CjcxGzQD.js} +12 -12
  59. package/dist/plugin-sdk/index.js +22 -22
  60. package/dist/plugin-sdk/{login-DL1oBBfL.js → login-CB_nCjX3.js} +3 -3
  61. package/dist/plugin-sdk/{login-Dnxas1dI.js → login-CzUuDd1v.js} +3 -3
  62. package/dist/plugin-sdk/{login-qr-CVQsGWKq.js → login-qr-D7MEpFza.js} +3 -3
  63. package/dist/plugin-sdk/{login-qr-B343JNXJ.js → login-qr-DgxfBFVN.js} +3 -3
  64. package/dist/plugin-sdk/{manager-BytwRIfK.js → manager-9see0IX5.js} +8 -3
  65. package/dist/plugin-sdk/{manager-BZjd_PCt.js → manager-D2kZ0Cps.js} +8 -3
  66. package/dist/plugin-sdk/{markdown-tables-krKiU4cT.js → markdown-tables-CCSD52rA.js} +1 -1
  67. package/dist/plugin-sdk/{markdown-tables-9rJDQ1X-.js → markdown-tables-DWUNvLrc.js} +1 -1
  68. package/dist/plugin-sdk/{media-D7ZkWU-A.js → media-C7YT4DeU.js} +13 -4
  69. package/dist/plugin-sdk/{media-C6C2c9a4.js → media-Cxw-xLGU.js} +13 -4
  70. package/dist/plugin-sdk/{monitor-BsLMfn3U.js → monitor-6lkdOqhQ.js} +26 -26
  71. package/dist/plugin-sdk/{monitor-BCxkQBoh.js → monitor-C6Ohw9BA.js} +26 -26
  72. package/dist/plugin-sdk/{monitor-BzDW50H6.js → monitor-o20KTHIi.js} +29 -29
  73. package/dist/plugin-sdk/{monitor-CVQNDfhs.js → monitor-rOs3mUol.js} +27 -27
  74. package/dist/plugin-sdk/{openresponses-http-BB67Z4QM.js → openresponses-http-BJONnUdy.js} +22 -22
  75. package/dist/plugin-sdk/{openresponses-http-NE76GGhj.js → openresponses-http-DkWqYznu.js} +22 -22
  76. package/dist/plugin-sdk/{ssrf-u0Ul7okY.js → optional-runtime-dependency-szS49voX.js} +46 -2
  77. package/dist/plugin-sdk/{outbound-PGNWU6T-.js → outbound-BCxOxdi-.js} +6 -6
  78. package/dist/plugin-sdk/{outbound-BHWJD9q0.js → outbound-JIwQbpRY.js} +6 -6
  79. package/dist/plugin-sdk/{outbound-attachment-BVzfhqnk.js → outbound-attachment-C-jSFaEB.js} +1 -1
  80. package/dist/plugin-sdk/{outbound-attachment-ClMvNW17.js → outbound-attachment-D6tj5Y5H.js} +1 -1
  81. package/dist/plugin-sdk/{pi-embedded-runner-DQ6UPx6-.js → pi-embedded-runner-DqDy0MWN.js} +72 -60
  82. package/dist/plugin-sdk/{pi-model-discovery-D51L5E9I.js → pi-model-discovery-DadlvsZW.js} +1 -1
  83. package/dist/plugin-sdk/{pi-model-discovery-runtime-DxoFm4Vd.js → pi-model-discovery-runtime-C5ssE6EW.js} +1 -1
  84. package/dist/plugin-sdk/{pi-model-discovery-runtime-DRuh4PNV.js → pi-model-discovery-runtime-DAaOPt64.js} +2 -2
  85. package/dist/plugin-sdk/{pi-model-discovery-runtime-BTWgkx4x.js → pi-model-discovery-runtime-gd77b0UF.js} +2 -2
  86. package/dist/plugin-sdk/{pi-model-discovery-CkH5l9X5.js → pi-model-discovery-yifTIDIn.js} +1 -1
  87. package/dist/plugin-sdk/{pw-ai-DZs6uYRd.js → pw-ai-Boor5vYw.js} +6 -5
  88. package/dist/plugin-sdk/{pw-ai-Cn-EUPZK.js → pw-ai-sH1Pi-Fh.js} +6 -5
  89. package/dist/plugin-sdk/{registry-B4HvWzob.js → registry-DNkJsNsz.js} +4 -3
  90. package/dist/plugin-sdk/{replies-jDMUBsNO.js → replies-fvzN5wnU.js} +2 -2
  91. package/dist/plugin-sdk/{replies-B_LW9c9f.js → replies-zebgZCHX.js} +2 -2
  92. package/dist/plugin-sdk/{resolve-route-_YZHbS-z.js → resolve-route-CNhTJG_2.js} +1 -1
  93. package/dist/plugin-sdk/{resolve-route-DlHzggnG.js → resolve-route-DO0WR4nB.js} +1 -1
  94. package/dist/plugin-sdk/{resolve-utils-zBhVX3Vx.js → resolve-utils-bixHYDBj.js} +1 -1
  95. package/dist/plugin-sdk/{retry-policy-DGLs5ebP.js → retry-policy-BLYEgIwP.js} +1 -1
  96. package/dist/plugin-sdk/{retry-policy-DaodShGa.js → retry-policy-sM01QZT2.js} +1 -1
  97. package/dist/plugin-sdk/{runtime-Dd0sswuW.js → runtime-BPyuAmka.js} +4 -3
  98. package/dist/plugin-sdk/{paths-CBCpJ5bu.js → runtime-dependency-Csswyv5r.js} +27 -3
  99. package/dist/plugin-sdk/{paths-DW-U2r7Y.js → runtime-dependency-Deg_sw2k.js} +36 -12
  100. package/dist/plugin-sdk/sat-runtime.js +1 -0
  101. package/dist/plugin-sdk/{send-8remZhsH.js → send-B09HRfHN.js} +6 -6
  102. package/dist/plugin-sdk/{send-DaaAscWj.js → send-BBYcgXA4.js} +5 -5
  103. package/dist/plugin-sdk/{send-DFTB0cnx.js → send-BQXQA81e.js} +6 -6
  104. package/dist/plugin-sdk/{send-CtMVvMJs.js → send-BzFdvD_i.js} +6 -6
  105. package/dist/plugin-sdk/{send-Di9irjS-.js → send-C3u-F_8s.js} +6 -6
  106. package/dist/plugin-sdk/{send-Dl88ZKq2.js → send-D3836_UN.js} +6 -6
  107. package/dist/plugin-sdk/{send-ifgZCHVB.js → send-D7tr-EsH.js} +5 -5
  108. package/dist/plugin-sdk/{send-DvUpYh0d.js → send-DRGzRdI3.js} +6 -6
  109. package/dist/plugin-sdk/{send-D2jlPeKH.js → send-DfkPKz_0.js} +4 -4
  110. package/dist/plugin-sdk/{send-QSt6nbny.js → send-DsxFXrCO.js} +4 -4
  111. package/dist/plugin-sdk/{session-CknrceVU.js → session-DG_bYisw.js} +2 -2
  112. package/dist/plugin-sdk/{session-CyRTcJ8N.js → session-EIStzK0E.js} +2 -2
  113. package/dist/plugin-sdk/{skill-commands-DHxkTA0s.js → skill-commands-CkgGmWBS.js} +2 -2
  114. package/dist/plugin-sdk/{skill-commands-CLXAG5sU.js → skill-commands-hyZA0LE3.js} +2 -2
  115. package/dist/plugin-sdk/{slack-Br9c6Yzs.js → slack-BErIh0uj.js} +34 -34
  116. package/dist/plugin-sdk/{slack-BpJXkwxF.js → slack-CGTAN4UD.js} +34 -34
  117. package/dist/plugin-sdk/{slack-actions-8tLGt1Ht.js → slack-actions-BQawWGTJ.js} +11 -11
  118. package/dist/plugin-sdk/{slack-actions-CDSSRo8Y.js → slack-actions-DDMszx56.js} +11 -11
  119. package/dist/plugin-sdk/slack.js +2 -1
  120. package/dist/plugin-sdk/src/brand.d.ts +2 -2
  121. package/dist/plugin-sdk/src/browser/runtime-dependency.d.ts +3 -0
  122. package/dist/plugin-sdk/src/infra/optional-runtime-dependency.d.ts +6 -0
  123. package/dist/plugin-sdk/{ssrf-fG4UHKBw.js → ssrf-Cce6LubA.js} +41 -7
  124. package/dist/plugin-sdk/{status-DoxnzyMC.js → status-CCGFb0f2.js} +76 -64
  125. package/dist/plugin-sdk/{sticker-cache-zmJ39ByC.js → sticker-cache-CKAkkCoU.js} +3 -3
  126. package/dist/plugin-sdk/{sticker-cache-D7WcMYsF.js → sticker-cache-wxl1Me2i.js} +3 -3
  127. package/dist/plugin-sdk/{tables-CkxblF0u.js → tables-BD7H5ug-.js} +1 -1
  128. package/dist/plugin-sdk/{tables-RBtf8G4P.js → tables-CJE8kB35.js} +1 -1
  129. package/dist/plugin-sdk/{telegram-actions-DAeYXSLk.js → telegram-actions-BRUNEXMr.js} +25 -25
  130. package/dist/plugin-sdk/{telegram-actions-BDeS3ocd.js → telegram-actions-r86ntIKR.js} +25 -25
  131. package/dist/plugin-sdk/telegram.js +2 -1
  132. package/dist/plugin-sdk/{thread-bindings.lifecycle-DsB94WbE.js → thread-bindings.lifecycle-Djem8FWF.js} +2 -2
  133. package/dist/plugin-sdk/{thread-bindings.lifecycle-DMhV3TsR.js → thread-bindings.lifecycle-gmHv3hc7.js} +2 -2
  134. package/dist/plugin-sdk/{thread-bindings.state-DYWpy7hJ.js → thread-bindings.state-CNT2vxm3.js} +2 -2
  135. package/dist/plugin-sdk/{thread-bindings.state-DG5cBaRn.js → thread-bindings.state-D8kN2X6H.js} +2 -2
  136. package/dist/plugin-sdk/{web-DJwacWQo.js → web-02I0vmdu.js} +26 -26
  137. package/dist/plugin-sdk/{web-IJ4NoGPJ.js → web-CXz56wyu.js} +26 -26
  138. package/dist/plugin-sdk/{whatsapp-actions-DAY8hWpD.js → whatsapp-actions-68Mfs2dJ.js} +12 -12
  139. package/dist/plugin-sdk/{whatsapp-actions-6opfo-oB.js → whatsapp-actions-DRqtzvTO.js} +12 -12
  140. package/dist/plugin-sdk/whatsapp.js +2 -1
  141. package/dist/plugin-sdk/{wsl-B2euLEc2.js → wsl-DcM2PgSS.js} +3 -3
  142. package/dist/plugin-sdk/{wsl-B8eyviuv.js → wsl-PXrdWitE.js} +3 -3
  143. package/dist/plugins/update.js +1 -1
  144. package/dist/tts/tts-core.js +6 -1
  145. package/dist/wizard/onboarding.finalize.js +9 -0
  146. package/extensions/acpx/package.json +1 -1
  147. package/extensions/bluebubbles/package.json +1 -1
  148. package/extensions/copilot-proxy/package.json +1 -1
  149. package/extensions/diagnostics-otel/package.json +1 -1
  150. package/extensions/fased-federation/package.json +1 -1
  151. package/extensions/google-gemini-cli-auth/package.json +1 -1
  152. package/extensions/imessage/package.json +1 -1
  153. package/extensions/irc/package.json +1 -1
  154. package/extensions/line/package.json +1 -1
  155. package/extensions/llm-task/package.json +1 -1
  156. package/extensions/lobster/package.json +1 -1
  157. package/extensions/matrix/CHANGELOG.md +12 -0
  158. package/extensions/matrix/package.json +1 -1
  159. package/extensions/mattermost/package.json +1 -1
  160. package/extensions/memory-core/package.json +2 -2
  161. package/extensions/memory-lancedb/package.json +1 -1
  162. package/extensions/minimax-portal-auth/package.json +1 -1
  163. package/extensions/msteams/CHANGELOG.md +12 -0
  164. package/extensions/msteams/package.json +1 -1
  165. package/extensions/nextcloud-talk/package.json +1 -1
  166. package/extensions/nostr/CHANGELOG.md +12 -0
  167. package/extensions/nostr/package.json +1 -1
  168. package/extensions/open-prose/package.json +1 -1
  169. package/extensions/sat-mining/package.json +1 -1
  170. package/extensions/signal/package.json +1 -1
  171. package/extensions/synology-chat/package.json +1 -1
  172. package/extensions/tlon/package.json +1 -1
  173. package/extensions/twitch/CHANGELOG.md +12 -0
  174. package/extensions/twitch/package.json +1 -1
  175. package/extensions/voice-call/CHANGELOG.md +12 -0
  176. package/extensions/voice-call/package.json +1 -1
  177. package/extensions/zalo/CHANGELOG.md +12 -0
  178. package/extensions/zalo/package.json +1 -1
  179. package/extensions/zalouser/CHANGELOG.md +12 -0
  180. package/extensions/zalouser/package.json +1 -1
  181. package/package.json +22 -15
  182. package/extensions/feishu/fased.plugin.json +0 -10
  183. package/extensions/feishu/index.ts +0 -63
  184. package/extensions/feishu/package.json +0 -33
  185. package/extensions/feishu/skills/feishu-doc/SKILL.md +0 -211
  186. package/extensions/feishu/skills/feishu-doc/references/block-types.md +0 -103
  187. package/extensions/feishu/skills/feishu-drive/SKILL.md +0 -97
  188. package/extensions/feishu/skills/feishu-perm/SKILL.md +0 -119
  189. package/extensions/feishu/skills/feishu-wiki/SKILL.md +0 -111
  190. package/extensions/feishu/src/accounts.ts +0 -159
  191. package/extensions/feishu/src/bitable.ts +0 -713
  192. package/extensions/feishu/src/bot.ts +0 -979
  193. package/extensions/feishu/src/channel.ts +0 -359
  194. package/extensions/feishu/src/client.ts +0 -118
  195. package/extensions/feishu/src/config-schema.ts +0 -226
  196. package/extensions/feishu/src/dedup.ts +0 -54
  197. package/extensions/feishu/src/directory.ts +0 -177
  198. package/extensions/feishu/src/doc-schema.ts +0 -122
  199. package/extensions/feishu/src/docx.ts +0 -998
  200. package/extensions/feishu/src/drive-schema.ts +0 -46
  201. package/extensions/feishu/src/drive.ts +0 -232
  202. package/extensions/feishu/src/dynamic-agent.ts +0 -131
  203. package/extensions/feishu/src/external-keys.ts +0 -19
  204. package/extensions/feishu/src/media.ts +0 -447
  205. package/extensions/feishu/src/mention.ts +0 -133
  206. package/extensions/feishu/src/monitor.ts +0 -434
  207. package/extensions/feishu/src/onboarding.ts +0 -430
  208. package/extensions/feishu/src/outbound.ts +0 -56
  209. package/extensions/feishu/src/perm-schema.ts +0 -52
  210. package/extensions/feishu/src/perm.ts +0 -180
  211. package/extensions/feishu/src/policy.ts +0 -116
  212. package/extensions/feishu/src/probe.ts +0 -44
  213. package/extensions/feishu/src/reactions.ts +0 -160
  214. package/extensions/feishu/src/reply-dispatcher.ts +0 -239
  215. package/extensions/feishu/src/runtime.ts +0 -14
  216. package/extensions/feishu/src/send-result.ts +0 -29
  217. package/extensions/feishu/src/send-target.ts +0 -25
  218. package/extensions/feishu/src/send.ts +0 -313
  219. package/extensions/feishu/src/streaming-card.ts +0 -218
  220. package/extensions/feishu/src/targets.ts +0 -78
  221. package/extensions/feishu/src/tool-account.ts +0 -58
  222. package/extensions/feishu/src/tools-config.ts +0 -21
  223. package/extensions/feishu/src/types.ts +0 -81
  224. package/extensions/feishu/src/typing.ts +0 -80
  225. package/extensions/feishu/src/wiki-schema.ts +0 -55
  226. package/extensions/feishu/src/wiki.ts +0 -237
  227. package/extensions/googlechat/fased.plugin.json +0 -9
  228. package/extensions/googlechat/index.ts +0 -19
  229. package/extensions/googlechat/package.json +0 -37
  230. package/extensions/googlechat/src/accounts.ts +0 -147
  231. package/extensions/googlechat/src/actions.ts +0 -176
  232. package/extensions/googlechat/src/api.ts +0 -282
  233. package/extensions/googlechat/src/auth.ts +0 -137
  234. package/extensions/googlechat/src/channel.ts +0 -591
  235. package/extensions/googlechat/src/monitor.ts +0 -996
  236. package/extensions/googlechat/src/onboarding.ts +0 -344
  237. package/extensions/googlechat/src/runtime.ts +0 -14
  238. package/extensions/googlechat/src/targets.ts +0 -65
  239. package/extensions/googlechat/src/types.config.ts +0 -3
  240. 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
- }