@colinlu50/openclaw-lark-stream 2026.3.17

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 (361) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/README.zh.md +70 -0
  4. package/bin/openclaw-lark.js +48 -0
  5. package/index.d.ts +36 -0
  6. package/index.js +118 -0
  7. package/openclaw.plugin.json +10 -0
  8. package/package.json +66 -0
  9. package/skills/feishu-bitable/SKILL.md +248 -0
  10. package/skills/feishu-bitable/references/examples.md +813 -0
  11. package/skills/feishu-bitable/references/field-properties.md +763 -0
  12. package/skills/feishu-bitable/references/record-values.md +911 -0
  13. package/skills/feishu-calendar/SKILL.md +244 -0
  14. package/skills/feishu-channel-rules/SKILL.md +24 -0
  15. package/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
  16. package/skills/feishu-create-doc/SKILL.md +719 -0
  17. package/skills/feishu-fetch-doc/SKILL.md +93 -0
  18. package/skills/feishu-im-read/SKILL.md +163 -0
  19. package/skills/feishu-task/SKILL.md +293 -0
  20. package/skills/feishu-troubleshoot/SKILL.md +70 -0
  21. package/skills/feishu-update-doc/SKILL.md +285 -0
  22. package/src/card/builder.d.ts +106 -0
  23. package/src/card/builder.js +443 -0
  24. package/src/card/cardkit.d.ts +90 -0
  25. package/src/card/cardkit.js +181 -0
  26. package/src/card/flush-controller.d.ts +45 -0
  27. package/src/card/flush-controller.js +134 -0
  28. package/src/card/image-resolver.d.ts +45 -0
  29. package/src/card/image-resolver.js +112 -0
  30. package/src/card/markdown-style.d.ts +16 -0
  31. package/src/card/markdown-style.js +97 -0
  32. package/src/card/reply-dispatcher-types.d.ts +120 -0
  33. package/src/card/reply-dispatcher-types.js +57 -0
  34. package/src/card/reply-dispatcher.d.ts +15 -0
  35. package/src/card/reply-dispatcher.js +299 -0
  36. package/src/card/reply-mode.d.ts +38 -0
  37. package/src/card/reply-mode.js +65 -0
  38. package/src/card/streaming-card-controller.d.ts +101 -0
  39. package/src/card/streaming-card-controller.js +810 -0
  40. package/src/card/unavailable-guard.d.ts +35 -0
  41. package/src/card/unavailable-guard.js +83 -0
  42. package/src/channel/abort-detect.d.ts +34 -0
  43. package/src/channel/abort-detect.js +124 -0
  44. package/src/channel/chat-queue.d.ts +41 -0
  45. package/src/channel/chat-queue.js +58 -0
  46. package/src/channel/config-adapter.d.ts +23 -0
  47. package/src/channel/config-adapter.js +101 -0
  48. package/src/channel/directory.d.ts +57 -0
  49. package/src/channel/directory.js +191 -0
  50. package/src/channel/event-handlers.d.ts +15 -0
  51. package/src/channel/event-handlers.js +221 -0
  52. package/src/channel/monitor.d.ts +17 -0
  53. package/src/channel/monitor.js +129 -0
  54. package/src/channel/onboarding-config.d.ts +17 -0
  55. package/src/channel/onboarding-config.js +88 -0
  56. package/src/channel/onboarding-migrate.d.ts +25 -0
  57. package/src/channel/onboarding-migrate.js +67 -0
  58. package/src/channel/onboarding.d.ts +12 -0
  59. package/src/channel/onboarding.js +296 -0
  60. package/src/channel/plugin.d.ts +13 -0
  61. package/src/channel/plugin.js +278 -0
  62. package/src/channel/probe.d.ts +14 -0
  63. package/src/channel/probe.js +21 -0
  64. package/src/channel/types.d.ts +36 -0
  65. package/src/channel/types.js +7 -0
  66. package/src/commands/auth.d.ts +21 -0
  67. package/src/commands/auth.js +161 -0
  68. package/src/commands/diagnose.d.ts +69 -0
  69. package/src/commands/diagnose.js +807 -0
  70. package/src/commands/doctor.d.ts +26 -0
  71. package/src/commands/doctor.js +584 -0
  72. package/src/commands/index.d.ts +25 -0
  73. package/src/commands/index.js +212 -0
  74. package/src/commands/locale.d.ts +7 -0
  75. package/src/commands/locale.js +7 -0
  76. package/src/core/accounts.d.ts +37 -0
  77. package/src/core/accounts.js +163 -0
  78. package/src/core/agent-config.d.ts +100 -0
  79. package/src/core/agent-config.js +139 -0
  80. package/src/core/api-error.d.ts +48 -0
  81. package/src/core/api-error.js +112 -0
  82. package/src/core/app-owner-fallback.d.ts +21 -0
  83. package/src/core/app-owner-fallback.js +38 -0
  84. package/src/core/app-scope-checker.d.ts +87 -0
  85. package/src/core/app-scope-checker.js +190 -0
  86. package/src/core/auth-errors.d.ts +144 -0
  87. package/src/core/auth-errors.js +154 -0
  88. package/src/core/chat-info-cache.d.ts +57 -0
  89. package/src/core/chat-info-cache.js +152 -0
  90. package/src/core/config-schema.d.ts +448 -0
  91. package/src/core/config-schema.js +200 -0
  92. package/src/core/device-flow.d.ts +77 -0
  93. package/src/core/device-flow.js +212 -0
  94. package/src/core/domains.d.ts +18 -0
  95. package/src/core/domains.js +28 -0
  96. package/src/core/feishu-fetch.d.ts +18 -0
  97. package/src/core/feishu-fetch.js +25 -0
  98. package/src/core/footer-config.d.ts +24 -0
  99. package/src/core/footer-config.js +39 -0
  100. package/src/core/lark-client.d.ts +108 -0
  101. package/src/core/lark-client.js +353 -0
  102. package/src/core/lark-logger.d.ts +23 -0
  103. package/src/core/lark-logger.js +154 -0
  104. package/src/core/lark-ticket.d.ts +29 -0
  105. package/src/core/lark-ticket.js +35 -0
  106. package/src/core/message-unavailable.d.ts +53 -0
  107. package/src/core/message-unavailable.js +130 -0
  108. package/src/core/owner-policy.d.ts +31 -0
  109. package/src/core/owner-policy.js +52 -0
  110. package/src/core/permission-url.d.ts +22 -0
  111. package/src/core/permission-url.js +72 -0
  112. package/src/core/raw-request.d.ts +27 -0
  113. package/src/core/raw-request.js +62 -0
  114. package/src/core/scope-manager.d.ts +168 -0
  115. package/src/core/scope-manager.js +213 -0
  116. package/src/core/security-check.d.ts +72 -0
  117. package/src/core/security-check.js +174 -0
  118. package/src/core/shutdown-hooks.d.ts +22 -0
  119. package/src/core/shutdown-hooks.js +56 -0
  120. package/src/core/targets.d.ts +60 -0
  121. package/src/core/targets.js +164 -0
  122. package/src/core/token-store.d.ts +54 -0
  123. package/src/core/token-store.js +314 -0
  124. package/src/core/tool-client.d.ts +176 -0
  125. package/src/core/tool-client.js +380 -0
  126. package/src/core/tool-scopes.d.ts +153 -0
  127. package/src/core/tool-scopes.js +326 -0
  128. package/src/core/tools-config.d.ts +55 -0
  129. package/src/core/tools-config.js +137 -0
  130. package/src/core/types.d.ts +87 -0
  131. package/src/core/types.js +11 -0
  132. package/src/core/uat-client.d.ts +46 -0
  133. package/src/core/uat-client.js +187 -0
  134. package/src/core/version.d.ts +25 -0
  135. package/src/core/version.js +49 -0
  136. package/src/messaging/converters/audio.d.ts +8 -0
  137. package/src/messaging/converters/audio.js +21 -0
  138. package/src/messaging/converters/calendar.d.ts +13 -0
  139. package/src/messaging/converters/calendar.js +50 -0
  140. package/src/messaging/converters/content-converter.d.ts +41 -0
  141. package/src/messaging/converters/content-converter.js +106 -0
  142. package/src/messaging/converters/file.d.ts +8 -0
  143. package/src/messaging/converters/file.js +20 -0
  144. package/src/messaging/converters/folder.d.ts +8 -0
  145. package/src/messaging/converters/folder.js +20 -0
  146. package/src/messaging/converters/hongbao.d.ts +8 -0
  147. package/src/messaging/converters/hongbao.js +16 -0
  148. package/src/messaging/converters/image.d.ts +8 -0
  149. package/src/messaging/converters/image.js +18 -0
  150. package/src/messaging/converters/index.d.ts +8 -0
  151. package/src/messaging/converters/index.js +50 -0
  152. package/src/messaging/converters/interactive/card-converter.d.ts +76 -0
  153. package/src/messaging/converters/interactive/card-converter.js +1173 -0
  154. package/src/messaging/converters/interactive/card-utils.d.ts +9 -0
  155. package/src/messaging/converters/interactive/card-utils.js +42 -0
  156. package/src/messaging/converters/interactive/index.d.ts +8 -0
  157. package/src/messaging/converters/interactive/index.js +21 -0
  158. package/src/messaging/converters/interactive/legacy.d.ts +11 -0
  159. package/src/messaging/converters/interactive/legacy.js +57 -0
  160. package/src/messaging/converters/interactive/types.d.ts +23 -0
  161. package/src/messaging/converters/interactive/types.js +24 -0
  162. package/src/messaging/converters/location.d.ts +8 -0
  163. package/src/messaging/converters/location.js +19 -0
  164. package/src/messaging/converters/merge-forward.d.ts +32 -0
  165. package/src/messaging/converters/merge-forward.js +225 -0
  166. package/src/messaging/converters/post.d.ts +11 -0
  167. package/src/messaging/converters/post.js +135 -0
  168. package/src/messaging/converters/share.d.ts +9 -0
  169. package/src/messaging/converters/share.js +23 -0
  170. package/src/messaging/converters/sticker.d.ts +8 -0
  171. package/src/messaging/converters/sticker.js +18 -0
  172. package/src/messaging/converters/system.d.ts +12 -0
  173. package/src/messaging/converters/system.js +32 -0
  174. package/src/messaging/converters/text.d.ts +8 -0
  175. package/src/messaging/converters/text.js +14 -0
  176. package/src/messaging/converters/todo.d.ts +8 -0
  177. package/src/messaging/converters/todo.js +41 -0
  178. package/src/messaging/converters/types.d.ts +107 -0
  179. package/src/messaging/converters/types.js +7 -0
  180. package/src/messaging/converters/unknown.d.ts +8 -0
  181. package/src/messaging/converters/unknown.js +16 -0
  182. package/src/messaging/converters/utils.d.ts +22 -0
  183. package/src/messaging/converters/utils.js +51 -0
  184. package/src/messaging/converters/video-chat.d.ts +8 -0
  185. package/src/messaging/converters/video-chat.js +23 -0
  186. package/src/messaging/converters/video.d.ts +8 -0
  187. package/src/messaging/converters/video.js +32 -0
  188. package/src/messaging/converters/vote.d.ts +8 -0
  189. package/src/messaging/converters/vote.js +24 -0
  190. package/src/messaging/inbound/dedup.d.ts +59 -0
  191. package/src/messaging/inbound/dedup.js +116 -0
  192. package/src/messaging/inbound/dispatch-builders.d.ts +84 -0
  193. package/src/messaging/inbound/dispatch-builders.js +152 -0
  194. package/src/messaging/inbound/dispatch-commands.d.ts +27 -0
  195. package/src/messaging/inbound/dispatch-commands.js +112 -0
  196. package/src/messaging/inbound/dispatch-context.d.ts +67 -0
  197. package/src/messaging/inbound/dispatch-context.js +136 -0
  198. package/src/messaging/inbound/dispatch.d.ts +47 -0
  199. package/src/messaging/inbound/dispatch.js +264 -0
  200. package/src/messaging/inbound/enrich.d.ts +102 -0
  201. package/src/messaging/inbound/enrich.js +227 -0
  202. package/src/messaging/inbound/gate-effects.d.ts +23 -0
  203. package/src/messaging/inbound/gate-effects.js +43 -0
  204. package/src/messaging/inbound/gate.d.ts +60 -0
  205. package/src/messaging/inbound/gate.js +233 -0
  206. package/src/messaging/inbound/handler.d.ts +35 -0
  207. package/src/messaging/inbound/handler.js +173 -0
  208. package/src/messaging/inbound/media-resolver.d.ts +32 -0
  209. package/src/messaging/inbound/media-resolver.js +87 -0
  210. package/src/messaging/inbound/mention.d.ts +39 -0
  211. package/src/messaging/inbound/mention.js +81 -0
  212. package/src/messaging/inbound/parse-io.d.ts +50 -0
  213. package/src/messaging/inbound/parse-io.js +81 -0
  214. package/src/messaging/inbound/parse.d.ts +28 -0
  215. package/src/messaging/inbound/parse.js +106 -0
  216. package/src/messaging/inbound/permission.d.ts +17 -0
  217. package/src/messaging/inbound/permission.js +40 -0
  218. package/src/messaging/inbound/policy.d.ts +94 -0
  219. package/src/messaging/inbound/policy.js +160 -0
  220. package/src/messaging/inbound/reaction-handler.d.ts +61 -0
  221. package/src/messaging/inbound/reaction-handler.js +221 -0
  222. package/src/messaging/inbound/user-name-cache.d.ts +82 -0
  223. package/src/messaging/inbound/user-name-cache.js +241 -0
  224. package/src/messaging/outbound/actions.d.ts +16 -0
  225. package/src/messaging/outbound/actions.js +309 -0
  226. package/src/messaging/outbound/chat-manage.d.ts +64 -0
  227. package/src/messaging/outbound/chat-manage.js +111 -0
  228. package/src/messaging/outbound/deliver.d.ts +155 -0
  229. package/src/messaging/outbound/deliver.js +298 -0
  230. package/src/messaging/outbound/fetch.d.ts +12 -0
  231. package/src/messaging/outbound/fetch.js +12 -0
  232. package/src/messaging/outbound/forward.d.ts +26 -0
  233. package/src/messaging/outbound/forward.js +48 -0
  234. package/src/messaging/outbound/media-url-utils.d.ts +29 -0
  235. package/src/messaging/outbound/media-url-utils.js +130 -0
  236. package/src/messaging/outbound/media.d.ts +260 -0
  237. package/src/messaging/outbound/media.js +758 -0
  238. package/src/messaging/outbound/outbound.d.ts +89 -0
  239. package/src/messaging/outbound/outbound.js +121 -0
  240. package/src/messaging/outbound/reactions.d.ts +124 -0
  241. package/src/messaging/outbound/reactions.js +378 -0
  242. package/src/messaging/outbound/send.d.ts +152 -0
  243. package/src/messaging/outbound/send.js +355 -0
  244. package/src/messaging/outbound/typing.d.ts +71 -0
  245. package/src/messaging/outbound/typing.js +179 -0
  246. package/src/messaging/shared/message-lookup.d.ts +54 -0
  247. package/src/messaging/shared/message-lookup.js +117 -0
  248. package/src/messaging/types.d.ts +176 -0
  249. package/src/messaging/types.js +10 -0
  250. package/src/tools/auto-auth.d.ts +56 -0
  251. package/src/tools/auto-auth.js +919 -0
  252. package/src/tools/helpers.d.ts +260 -0
  253. package/src/tools/helpers.js +364 -0
  254. package/src/tools/mcp/doc/create.d.ts +12 -0
  255. package/src/tools/mcp/doc/create.js +44 -0
  256. package/src/tools/mcp/doc/fetch.d.ts +12 -0
  257. package/src/tools/mcp/doc/fetch.js +36 -0
  258. package/src/tools/mcp/doc/index.d.ts +12 -0
  259. package/src/tools/mcp/doc/index.js +41 -0
  260. package/src/tools/mcp/doc/update.d.ts +12 -0
  261. package/src/tools/mcp/doc/update.js +61 -0
  262. package/src/tools/mcp/shared.d.ts +59 -0
  263. package/src/tools/mcp/shared.js +226 -0
  264. package/src/tools/oapi/bitable/app-table-field.d.ts +16 -0
  265. package/src/tools/oapi/bitable/app-table-field.js +222 -0
  266. package/src/tools/oapi/bitable/app-table-record.d.ts +20 -0
  267. package/src/tools/oapi/bitable/app-table-record.js +436 -0
  268. package/src/tools/oapi/bitable/app-table-view.d.ts +17 -0
  269. package/src/tools/oapi/bitable/app-table-view.js +195 -0
  270. package/src/tools/oapi/bitable/app-table.d.ts +19 -0
  271. package/src/tools/oapi/bitable/app-table.js +247 -0
  272. package/src/tools/oapi/bitable/app.d.ts +18 -0
  273. package/src/tools/oapi/bitable/app.js +186 -0
  274. package/src/tools/oapi/bitable/index.d.ts +9 -0
  275. package/src/tools/oapi/bitable/index.js +9 -0
  276. package/src/tools/oapi/calendar/calendar.d.ts +15 -0
  277. package/src/tools/oapi/calendar/calendar.js +122 -0
  278. package/src/tools/oapi/calendar/event-attendee.d.ts +16 -0
  279. package/src/tools/oapi/calendar/event-attendee.js +263 -0
  280. package/src/tools/oapi/calendar/event.d.ts +16 -0
  281. package/src/tools/oapi/calendar/event.js +709 -0
  282. package/src/tools/oapi/calendar/freebusy.d.ts +13 -0
  283. package/src/tools/oapi/calendar/freebusy.js +111 -0
  284. package/src/tools/oapi/calendar/index.d.ts +8 -0
  285. package/src/tools/oapi/calendar/index.js +8 -0
  286. package/src/tools/oapi/chat/chat.d.ts +16 -0
  287. package/src/tools/oapi/chat/chat.js +124 -0
  288. package/src/tools/oapi/chat/index.d.ts +10 -0
  289. package/src/tools/oapi/chat/index.js +15 -0
  290. package/src/tools/oapi/chat/members.d.ts +11 -0
  291. package/src/tools/oapi/chat/members.js +81 -0
  292. package/src/tools/oapi/common/get-user.d.ts +12 -0
  293. package/src/tools/oapi/common/get-user.js +106 -0
  294. package/src/tools/oapi/common/index.d.ts +6 -0
  295. package/src/tools/oapi/common/index.js +6 -0
  296. package/src/tools/oapi/common/search-user.d.ts +11 -0
  297. package/src/tools/oapi/common/search-user.js +73 -0
  298. package/src/tools/oapi/drive/doc-comments.d.ts +15 -0
  299. package/src/tools/oapi/drive/doc-comments.js +279 -0
  300. package/src/tools/oapi/drive/doc-media.d.ts +19 -0
  301. package/src/tools/oapi/drive/doc-media.js +335 -0
  302. package/src/tools/oapi/drive/file.d.ts +19 -0
  303. package/src/tools/oapi/drive/file.js +483 -0
  304. package/src/tools/oapi/drive/index.d.ts +12 -0
  305. package/src/tools/oapi/drive/index.js +36 -0
  306. package/src/tools/oapi/helpers.d.ts +182 -0
  307. package/src/tools/oapi/helpers.js +354 -0
  308. package/src/tools/oapi/im/format-messages.d.ts +50 -0
  309. package/src/tools/oapi/im/format-messages.js +165 -0
  310. package/src/tools/oapi/im/index.d.ts +10 -0
  311. package/src/tools/oapi/im/index.js +17 -0
  312. package/src/tools/oapi/im/message-read.d.ts +13 -0
  313. package/src/tools/oapi/im/message-read.js +411 -0
  314. package/src/tools/oapi/im/message.d.ts +16 -0
  315. package/src/tools/oapi/im/message.js +149 -0
  316. package/src/tools/oapi/im/resource.d.ts +13 -0
  317. package/src/tools/oapi/im/resource.js +150 -0
  318. package/src/tools/oapi/im/time-utils.d.ts +46 -0
  319. package/src/tools/oapi/im/time-utils.js +201 -0
  320. package/src/tools/oapi/im/user-name-uat.d.ts +26 -0
  321. package/src/tools/oapi/im/user-name-uat.js +140 -0
  322. package/src/tools/oapi/index.d.ts +11 -0
  323. package/src/tools/oapi/index.js +58 -0
  324. package/src/tools/oapi/sdk-types.d.ts +96 -0
  325. package/src/tools/oapi/sdk-types.js +12 -0
  326. package/src/tools/oapi/search/doc-search.d.ts +13 -0
  327. package/src/tools/oapi/search/doc-search.js +191 -0
  328. package/src/tools/oapi/search/index.d.ts +12 -0
  329. package/src/tools/oapi/search/index.js +33 -0
  330. package/src/tools/oapi/sheets/index.d.ts +12 -0
  331. package/src/tools/oapi/sheets/index.js +31 -0
  332. package/src/tools/oapi/sheets/sheet.d.ts +16 -0
  333. package/src/tools/oapi/sheets/sheet.js +652 -0
  334. package/src/tools/oapi/task/comment.d.ts +15 -0
  335. package/src/tools/oapi/task/comment.js +140 -0
  336. package/src/tools/oapi/task/index.d.ts +8 -0
  337. package/src/tools/oapi/task/index.js +8 -0
  338. package/src/tools/oapi/task/subtask.d.ts +14 -0
  339. package/src/tools/oapi/task/subtask.js +162 -0
  340. package/src/tools/oapi/task/task.d.ts +16 -0
  341. package/src/tools/oapi/task/task.js +344 -0
  342. package/src/tools/oapi/task/tasklist.d.ts +21 -0
  343. package/src/tools/oapi/task/tasklist.js +321 -0
  344. package/src/tools/oapi/wiki/index.d.ts +12 -0
  345. package/src/tools/oapi/wiki/index.js +34 -0
  346. package/src/tools/oapi/wiki/space-node.d.ts +17 -0
  347. package/src/tools/oapi/wiki/space-node.js +230 -0
  348. package/src/tools/oapi/wiki/space.d.ts +15 -0
  349. package/src/tools/oapi/wiki/space.js +130 -0
  350. package/src/tools/oauth-batch-auth.d.ts +11 -0
  351. package/src/tools/oauth-batch-auth.js +142 -0
  352. package/src/tools/oauth-cards.d.ts +39 -0
  353. package/src/tools/oauth-cards.js +315 -0
  354. package/src/tools/oauth.d.ts +47 -0
  355. package/src/tools/oauth.js +620 -0
  356. package/src/tools/onboarding-auth.d.ts +27 -0
  357. package/src/tools/onboarding-auth.js +130 -0
  358. package/src/tools/tat/im/index.d.ts +15 -0
  359. package/src/tools/tat/im/index.js +18 -0
  360. package/src/tools/tat/im/resource.d.ts +15 -0
  361. package/src/tools/tat/im/resource.js +157 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * ChannelMessageActionAdapter for the Lark/Feishu channel plugin.
6
+ *
7
+ * Implements the standard message-action interface so the framework's
8
+ * built-in `message` tool can route send, react, delete and other
9
+ * actions to Feishu.
10
+ *
11
+ * The `send` action is the unified entry-point for text, card, media,
12
+ * reply and attachment delivery — matching the Telegram/Discord pattern
13
+ * where a single action handles all outbound message types.
14
+ */
15
+ import { extractToolSend, jsonResult, readStringParam, readReactionParams } from 'openclaw/plugin-sdk';
16
+ import { addReactionFeishu, removeReactionFeishu, listReactionsFeishu } from './reactions';
17
+ import { sendTextLark, sendCardLark } from './deliver';
18
+ import { uploadAndSendMediaLark } from './media';
19
+ import { LarkClient } from '../../core/lark-client';
20
+ import { getEnabledLarkAccounts } from '../../core/accounts';
21
+ import { larkLogger } from '../../core/lark-logger';
22
+ const log = larkLogger('outbound/actions');
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+ /** Assert that a Lark SDK response has code === 0 (or no code field). */
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ function assertLarkOk(res, context) {
29
+ const code = res?.code;
30
+ if (code !== undefined && code !== 0) {
31
+ const msg = res?.msg ?? 'unknown error';
32
+ throw new Error(`[feishu-actions] ${context}: code=${code}, msg=${msg}`);
33
+ }
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // Supported actions
37
+ // ---------------------------------------------------------------------------
38
+ const SUPPORTED_ACTIONS = new Set([
39
+ 'send',
40
+ 'react',
41
+ 'reactions',
42
+ 'delete',
43
+ 'unsend',
44
+ // "member-info",
45
+ ]);
46
+ // ---------------------------------------------------------------------------
47
+ // Send param extraction
48
+ // ---------------------------------------------------------------------------
49
+ /** Try to resolve a card param to a plain object. Accepts objects directly or JSON strings. */
50
+ function parseCardParam(raw) {
51
+ if (raw == null)
52
+ return undefined;
53
+ // Already a non-array object — use directly.
54
+ if (typeof raw === 'object' && !Array.isArray(raw)) {
55
+ return raw;
56
+ }
57
+ // String — attempt JSON.parse.
58
+ if (typeof raw === 'string') {
59
+ const trimmed = raw.trim();
60
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
61
+ log.warn('params.card is a string but not a JSON object, ignoring');
62
+ return undefined;
63
+ }
64
+ try {
65
+ const parsed = JSON.parse(trimmed);
66
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
67
+ log.info('params.card was a JSON string, parsed successfully');
68
+ return parsed;
69
+ }
70
+ log.warn('params.card JSON parsed but is not a plain object, ignoring');
71
+ return undefined;
72
+ }
73
+ catch {
74
+ log.warn('params.card is a string but failed to JSON.parse, ignoring');
75
+ return undefined;
76
+ }
77
+ }
78
+ // Other types (number, boolean, etc.) — ignore with warning.
79
+ log.warn(`params.card has unexpected type "${typeof raw}", ignoring`);
80
+ return undefined;
81
+ }
82
+ /**
83
+ * Extract and normalise all send-related parameters from the raw action params.
84
+ * When `toolContext` is provided, thread context is inherited so that replies
85
+ * are routed to the correct thread.
86
+ */
87
+ function readFeishuSendParams(params, toolContext) {
88
+ const to = readStringParam(params, 'to') ?? '';
89
+ const text = readStringParam(params, 'message', { allowEmpty: true }) ??
90
+ readStringParam(params, 'text', { allowEmpty: true }) ??
91
+ '';
92
+ const mediaUrl = readStringParam(params, 'media') ??
93
+ readStringParam(params, 'path') ??
94
+ readStringParam(params, 'filePath') ??
95
+ readStringParam(params, 'url');
96
+ const fileName = readStringParam(params, 'fileName') ?? readStringParam(params, 'name');
97
+ // Thread routing: when targeting the current chat (or unspecified),
98
+ // inherit thread context from SDK toolContext.
99
+ const sameChat = !to || to === toolContext?.currentChannelId;
100
+ const replyInThread = sameChat && Boolean(toolContext?.currentThreadTs);
101
+ const replyToMessageId = readStringParam(params, 'replyTo') ??
102
+ (replyInThread && toolContext?.currentMessageId ? String(toolContext.currentMessageId) : undefined);
103
+ const card = parseCardParam(params.card);
104
+ return {
105
+ to,
106
+ text,
107
+ mediaUrl: mediaUrl ?? undefined,
108
+ fileName: fileName ?? undefined,
109
+ replyToMessageId: replyToMessageId ?? undefined,
110
+ replyInThread,
111
+ card,
112
+ };
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // Adapter
116
+ // ---------------------------------------------------------------------------
117
+ export const feishuMessageActions = {
118
+ listActions: ({ cfg }) => {
119
+ const accounts = getEnabledLarkAccounts(cfg);
120
+ if (accounts.length === 0)
121
+ return [];
122
+ return Array.from(SUPPORTED_ACTIONS);
123
+ },
124
+ supportsAction: ({ action }) => SUPPORTED_ACTIONS.has(action),
125
+ supportsButtons: ({ cfg }) => getEnabledLarkAccounts(cfg).length > 0,
126
+ supportsCards: ({ cfg }) => getEnabledLarkAccounts(cfg).length > 0,
127
+ extractToolSend: ({ args }) => extractToolSend(args, 'sendMessage'),
128
+ handleAction: async (ctx) => {
129
+ const { action, params, cfg, accountId, toolContext } = ctx;
130
+ const aid = accountId ?? undefined;
131
+ log.info(`handleAction: action=${action}, accountId=${aid ?? 'default'}`);
132
+ try {
133
+ switch (action) {
134
+ case 'send':
135
+ return await deliverMessage(cfg, readFeishuSendParams(params, toolContext), aid, ctx.mediaLocalRoots);
136
+ case 'react':
137
+ return await handleReact(cfg, params, aid);
138
+ case 'reactions':
139
+ return await handleReactions(cfg, params, aid);
140
+ case 'delete':
141
+ case 'unsend':
142
+ return await handleDelete(cfg, params, aid);
143
+ default:
144
+ throw new Error(`Action "${action}" is not supported for Feishu. ` +
145
+ `Supported actions: ${Array.from(SUPPORTED_ACTIONS).join(', ')}.`);
146
+ }
147
+ }
148
+ catch (err) {
149
+ const errMsg = err instanceof Error ? err.message : String(err);
150
+ log.error(`handleAction failed: action=${action}, error=${errMsg}`);
151
+ throw err;
152
+ }
153
+ },
154
+ };
155
+ // ---------------------------------------------------------------------------
156
+ // Unified message delivery
157
+ // ---------------------------------------------------------------------------
158
+ /**
159
+ * Unified message delivery — handles text, card, and media payloads with
160
+ * optional reply-to and thread routing.
161
+ *
162
+ * Supports `fileName` for named file uploads via `uploadAndSendMediaLark`.
163
+ * On media upload failure, falls back to sending the URL as a text link.
164
+ */
165
+ async function deliverMessage(cfg, sp, accountId, mediaLocalRoots) {
166
+ const { to, text, mediaUrl, fileName, replyToMessageId, replyInThread, card } = sp;
167
+ const payloadType = card ? 'card' : mediaUrl ? 'media' : 'text';
168
+ const target = to || replyToMessageId || 'unknown';
169
+ log.info(`deliverMessage: type=${payloadType}, target=${target}, ` +
170
+ `isReply=${Boolean(replyToMessageId)}, replyInThread=${replyInThread}, ` +
171
+ `textLen=${text.trim().length}, hasMedia=${Boolean(mediaUrl)}, ` +
172
+ `fileName=${fileName ?? '(none)'}`);
173
+ if (!text.trim() && !card && !mediaUrl) {
174
+ log.warn('deliverMessage: no payload, rejecting');
175
+ throw new Error('send requires at least one of: message, card, or media.');
176
+ }
177
+ const sendCtx = { cfg, to, replyToMessageId, replyInThread, accountId };
178
+ // Send text first if both text and card/media are present.
179
+ if (text.trim() && (card || mediaUrl)) {
180
+ log.info(`deliverMessage: sending preceding text ` + `(${text.length} chars) before ${payloadType}`);
181
+ await sendTextLark({ ...sendCtx, text });
182
+ }
183
+ // Card path.
184
+ if (card) {
185
+ const result = await sendCardLark({ ...sendCtx, card });
186
+ log.info(`deliverMessage: card sent, messageId=${result.messageId}`);
187
+ return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId });
188
+ }
189
+ // Media path — uses uploadAndSendMediaLark directly to support fileName.
190
+ if (mediaUrl) {
191
+ return await deliverMedia(cfg, sp, accountId, mediaLocalRoots);
192
+ }
193
+ // Text-only path.
194
+ const result = await sendTextLark({ ...sendCtx, text });
195
+ log.info(`deliverMessage: text sent, messageId=${result.messageId}`);
196
+ return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId });
197
+ }
198
+ /**
199
+ * Upload and send a media file with text-link fallback on failure.
200
+ */
201
+ async function deliverMedia(cfg, sp, accountId, mediaLocalRoots) {
202
+ const { to, mediaUrl, fileName, replyToMessageId, replyInThread } = sp;
203
+ log.info(`deliverMedia: url=${mediaUrl}, fileName=${fileName ?? '(auto)'}`);
204
+ try {
205
+ const result = await uploadAndSendMediaLark({
206
+ cfg,
207
+ to,
208
+ mediaUrl,
209
+ fileName,
210
+ replyToMessageId,
211
+ replyInThread,
212
+ accountId,
213
+ mediaLocalRoots,
214
+ });
215
+ log.info(`deliverMedia: sent, messageId=${result.messageId}`);
216
+ return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId });
217
+ }
218
+ catch (err) {
219
+ const errMsg = err instanceof Error ? err.message : String(err);
220
+ log.error(`deliverMedia: upload failed for "${mediaUrl}": ${errMsg}`);
221
+ // Fallback: send the URL with error reason as a quote above.
222
+ log.info('deliverMedia: falling back to text link');
223
+ const fallback = await sendTextLark({
224
+ cfg,
225
+ to,
226
+ text: `> ${mediaUrl}`,
227
+ replyToMessageId,
228
+ replyInThread,
229
+ accountId,
230
+ });
231
+ return jsonResult({
232
+ ok: true,
233
+ messageId: fallback.messageId,
234
+ chatId: fallback.chatId,
235
+ warning: `Media upload failed (${errMsg}). A text link was sent instead.`,
236
+ });
237
+ }
238
+ }
239
+ // ---------------------------------------------------------------------------
240
+ // Reaction handlers
241
+ // ---------------------------------------------------------------------------
242
+ async function handleReact(cfg, params, accountId) {
243
+ const messageId = readStringParam(params, 'messageId', { required: true });
244
+ const { emoji, remove, isEmpty } = readReactionParams(params, {
245
+ removeErrorMessage: 'Emoji is required to remove a Feishu reaction.',
246
+ });
247
+ if (remove || isEmpty) {
248
+ log.info(`react: removing emoji=${emoji || 'all'} from messageId=${messageId}`);
249
+ const reactions = await listReactionsFeishu({
250
+ cfg,
251
+ messageId,
252
+ emojiType: emoji || undefined,
253
+ accountId,
254
+ });
255
+ const botReactions = reactions.filter((r) => r.operatorType === 'app');
256
+ for (const r of botReactions) {
257
+ await removeReactionFeishu({
258
+ cfg,
259
+ messageId,
260
+ reactionId: r.reactionId,
261
+ accountId,
262
+ });
263
+ }
264
+ log.info(`react: removed ${botReactions.length} bot reaction(s)`);
265
+ return jsonResult({ ok: true, removed: botReactions.length });
266
+ }
267
+ log.info(`react: adding emoji=${emoji} to messageId=${messageId}`);
268
+ const { reactionId } = await addReactionFeishu({
269
+ cfg,
270
+ messageId,
271
+ emojiType: emoji,
272
+ accountId,
273
+ });
274
+ log.info(`react: added reactionId=${reactionId}`);
275
+ return jsonResult({ ok: true, reactionId });
276
+ }
277
+ async function handleReactions(cfg, params, accountId) {
278
+ const messageId = readStringParam(params, 'messageId', { required: true });
279
+ const emojiType = readStringParam(params, 'emoji');
280
+ const reactions = await listReactionsFeishu({
281
+ cfg,
282
+ messageId,
283
+ emojiType: emojiType || undefined,
284
+ accountId,
285
+ });
286
+ return jsonResult({
287
+ ok: true,
288
+ reactions: reactions.map((r) => ({
289
+ reactionId: r.reactionId,
290
+ emoji: r.emojiType,
291
+ operatorType: r.operatorType,
292
+ operatorId: r.operatorId,
293
+ })),
294
+ });
295
+ }
296
+ // ---------------------------------------------------------------------------
297
+ // Delete
298
+ // ---------------------------------------------------------------------------
299
+ async function handleDelete(cfg, params, accountId) {
300
+ const messageId = readStringParam(params, 'messageId', { required: true });
301
+ log.info(`delete: messageId=${messageId}`);
302
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
303
+ const res = await client.im.message.delete({
304
+ path: { message_id: messageId },
305
+ });
306
+ assertLarkOk(res, `delete message ${messageId}`);
307
+ log.info(`delete: done, messageId=${messageId}`);
308
+ return jsonResult({ ok: true, messageId, deleted: true });
309
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Chat management for the Lark/Feishu channel plugin.
6
+ *
7
+ * Provides functions to update chat settings (name, avatar), manage
8
+ * members (add, remove, list) using the IM Chat API.
9
+ */
10
+ import type { OpenClawConfig } from 'openclaw/plugin-sdk';
11
+ export interface FeishuChatMember {
12
+ /** Member ID (open_id by default). */
13
+ memberId: string;
14
+ /** Display name of the member. */
15
+ name: string;
16
+ /** ID type: "open_id", "union_id", or "user_id". */
17
+ memberIdType: string;
18
+ }
19
+ /**
20
+ * Update chat settings such as name or avatar.
21
+ */
22
+ export declare function updateChatFeishu(params: {
23
+ cfg: OpenClawConfig;
24
+ chatId: string;
25
+ name?: string;
26
+ avatar?: string;
27
+ accountId?: string;
28
+ }): Promise<void>;
29
+ /**
30
+ * Add members to a chat by their open_id list.
31
+ */
32
+ export declare function addChatMembersFeishu(params: {
33
+ cfg: OpenClawConfig;
34
+ chatId: string;
35
+ memberIds: string[];
36
+ accountId?: string;
37
+ }): Promise<void>;
38
+ /**
39
+ * Remove members from a chat by their open_id list.
40
+ */
41
+ export declare function removeChatMembersFeishu(params: {
42
+ cfg: OpenClawConfig;
43
+ chatId: string;
44
+ memberIds: string[];
45
+ accountId?: string;
46
+ }): Promise<void>;
47
+ /**
48
+ * List members of a chat.
49
+ *
50
+ * Returns a single page (up to 100 members) to avoid unnecessary data
51
+ * overhead for large groups. Use the returned `pageToken` to fetch
52
+ * subsequent pages when needed.
53
+ */
54
+ export declare function listChatMembersFeishu(params: {
55
+ cfg: OpenClawConfig;
56
+ chatId: string;
57
+ accountId?: string;
58
+ /** Optional page token for pagination. */
59
+ pageToken?: string;
60
+ }): Promise<{
61
+ members: FeishuChatMember[];
62
+ pageToken?: string;
63
+ hasMore: boolean;
64
+ }>;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Chat management for the Lark/Feishu channel plugin.
6
+ *
7
+ * Provides functions to update chat settings (name, avatar), manage
8
+ * members (add, remove, list) using the IM Chat API.
9
+ */
10
+ /* eslint-disable @typescript-eslint/no-explicit-any */
11
+ import { LarkClient } from '../../core/lark-client';
12
+ // ---------------------------------------------------------------------------
13
+ // Helpers
14
+ // ---------------------------------------------------------------------------
15
+ /** Assert that a Lark SDK response has code === 0 (or no code field). */
16
+ function assertLarkOk(res, context) {
17
+ const code = res?.code;
18
+ if (code !== undefined && code !== 0) {
19
+ const msg = res?.msg ?? 'unknown error';
20
+ throw new Error(`[feishu-chat-manage] ${context}: code=${code}, msg=${msg}`);
21
+ }
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // updateChatFeishu
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Update chat settings such as name or avatar.
28
+ */
29
+ export async function updateChatFeishu(params) {
30
+ const { cfg, chatId, name, avatar, accountId } = params;
31
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
32
+ const body = {};
33
+ if (name)
34
+ body.name = name;
35
+ if (avatar)
36
+ body.avatar = avatar;
37
+ const res = await client.im.chat.update({
38
+ path: { chat_id: chatId },
39
+ data: body,
40
+ });
41
+ assertLarkOk(res, `updateChat for ${chatId}`);
42
+ }
43
+ // ---------------------------------------------------------------------------
44
+ // addChatMembersFeishu
45
+ // ---------------------------------------------------------------------------
46
+ /**
47
+ * Add members to a chat by their open_id list.
48
+ */
49
+ export async function addChatMembersFeishu(params) {
50
+ const { cfg, chatId, memberIds, accountId } = params;
51
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
52
+ const res = await client.im.v1.chatMembers.create({
53
+ path: { chat_id: chatId },
54
+ data: { id_list: memberIds },
55
+ params: { member_id_type: 'open_id' },
56
+ });
57
+ assertLarkOk(res, `addChatMembers for ${chatId}`);
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // removeChatMembersFeishu
61
+ // ---------------------------------------------------------------------------
62
+ /**
63
+ * Remove members from a chat by their open_id list.
64
+ */
65
+ export async function removeChatMembersFeishu(params) {
66
+ const { cfg, chatId, memberIds, accountId } = params;
67
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
68
+ const res = await client.im.v1.chatMembers.delete({
69
+ path: { chat_id: chatId },
70
+ data: { id_list: memberIds },
71
+ params: { member_id_type: 'open_id' },
72
+ });
73
+ assertLarkOk(res, `removeChatMembers for ${chatId}`);
74
+ }
75
+ // ---------------------------------------------------------------------------
76
+ // listChatMembersFeishu
77
+ // ---------------------------------------------------------------------------
78
+ /**
79
+ * List members of a chat.
80
+ *
81
+ * Returns a single page (up to 100 members) to avoid unnecessary data
82
+ * overhead for large groups. Use the returned `pageToken` to fetch
83
+ * subsequent pages when needed.
84
+ */
85
+ export async function listChatMembersFeishu(params) {
86
+ const { cfg, chatId, accountId, pageToken } = params;
87
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
88
+ const response = await client.im.v1.chatMembers.get({
89
+ path: { chat_id: chatId },
90
+ params: {
91
+ member_id_type: 'open_id',
92
+ page_size: 100,
93
+ ...(pageToken ? { page_token: pageToken } : {}),
94
+ },
95
+ });
96
+ assertLarkOk(response, `listChatMembers for ${chatId}`);
97
+ const members = [];
98
+ const items = response?.data?.items;
99
+ if (items && Array.isArray(items)) {
100
+ for (const item of items) {
101
+ members.push({
102
+ memberId: item.member_id ?? '',
103
+ name: item.name ?? '',
104
+ memberIdType: item.member_id_type ?? 'open_id',
105
+ });
106
+ }
107
+ }
108
+ const nextPageToken = response?.data?.page_token ?? undefined;
109
+ const hasMore = response?.data?.has_more === true && !!nextPageToken;
110
+ return { members, pageToken: nextPageToken, hasMore };
111
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Standalone text and media delivery functions for the Lark/Feishu channel.
3
+ *
4
+ * These functions operate directly on the Lark SDK without depending on
5
+ * {@link sendMessageFeishu} from `send.ts`. The outbound adapter delegates
6
+ * to these for its `sendText` and `sendMedia` implementations.
7
+ */
8
+ import type { ClawdbotConfig } from 'openclaw/plugin-sdk';
9
+ import type { FeishuSendResult } from '../types';
10
+ /**
11
+ * Parameters for sending a text message via Feishu.
12
+ */
13
+ export interface SendTextLarkParams {
14
+ /** Plugin configuration. */
15
+ cfg: ClawdbotConfig;
16
+ /** Target identifier (chat_id, open_id, or user_id). */
17
+ to: string;
18
+ /** Message text content (supports Feishu markdown subset). */
19
+ text: string;
20
+ /** When set, the message is sent as a threaded reply. */
21
+ replyToMessageId?: string;
22
+ /** When true, the reply appears in the thread instead of main chat. */
23
+ replyInThread?: boolean;
24
+ /** Optional account identifier for multi-account setups. */
25
+ accountId?: string;
26
+ }
27
+ /**
28
+ * Send a text message to a Feishu chat or user.
29
+ *
30
+ * Standalone implementation that directly operates the Lark SDK.
31
+ * The text is pre-processed (table conversion, style optimization)
32
+ * and sent as a Feishu "post" message with markdown rendering.
33
+ *
34
+ * If the entire text is a valid Feishu card JSON string (v1 or v2),
35
+ * it is automatically detected and routed to {@link sendCardLark}
36
+ * instead of being sent as plain text.
37
+ *
38
+ * @param params - See {@link SendTextLarkParams}.
39
+ * @returns The message ID and chat ID.
40
+ * @throws {Error} When the target is invalid or the API call fails.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const result = await sendTextLark({
45
+ * cfg,
46
+ * to: "oc_xxx",
47
+ * text: "Hello from Feishu",
48
+ * });
49
+ * ```
50
+ */
51
+ export declare function sendTextLark(params: SendTextLarkParams): Promise<FeishuSendResult>;
52
+ /**
53
+ * Parameters for sending an interactive card message via Feishu.
54
+ */
55
+ export interface SendCardLarkParams {
56
+ /** Plugin configuration. */
57
+ cfg: ClawdbotConfig;
58
+ /** Target identifier (chat_id, open_id, or user_id). */
59
+ to: string;
60
+ /**
61
+ * Complete card JSON object (v1 Message Card or v2 CardKit).
62
+ *
63
+ * - **v1**: top-level `config`, `header`, `elements`.
64
+ * - **v2**: `schema: "2.0"`, `config`, `header`, `body.elements`.
65
+ *
66
+ * The Feishu server determines the version by the presence of
67
+ * `schema: "2.0"`.
68
+ */
69
+ card: Record<string, unknown>;
70
+ /** When set, the card is sent as a threaded reply. */
71
+ replyToMessageId?: string;
72
+ /** When true, the reply appears in the thread instead of main chat. */
73
+ replyInThread?: boolean;
74
+ /** Optional account identifier for multi-account setups. */
75
+ accountId?: string;
76
+ }
77
+ /**
78
+ * Send an interactive card message to a Feishu chat or user.
79
+ *
80
+ * Supports both v1 (Message Card) and v2 (CardKit) card formats.
81
+ * The card JSON is serialised and sent as `msg_type: "interactive"`.
82
+ *
83
+ * @param params - See {@link SendCardLarkParams}.
84
+ * @returns The message ID and chat ID.
85
+ * @throws {Error} When the target is invalid or the API call fails.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * // v1 card
90
+ * const result = await sendCardLark({
91
+ * cfg,
92
+ * to: "oc_xxx",
93
+ * card: {
94
+ * config: { wide_screen_mode: true },
95
+ * header: { title: { tag: "plain_text", content: "Hello" }, template: "blue" },
96
+ * elements: [{ tag: "div", text: { tag: "lark_md", content: "world" } }],
97
+ * },
98
+ * });
99
+ *
100
+ * // v2 card
101
+ * const result2 = await sendCardLark({
102
+ * cfg,
103
+ * to: "oc_xxx",
104
+ * card: {
105
+ * schema: "2.0",
106
+ * config: { wide_screen_mode: true },
107
+ * body: { elements: [{ tag: "markdown", content: "Hello **world**" }] },
108
+ * },
109
+ * });
110
+ * ```
111
+ */
112
+ export declare function sendCardLark(params: SendCardLarkParams): Promise<FeishuSendResult>;
113
+ /**
114
+ * Parameters for sending a single media message via Feishu.
115
+ */
116
+ export interface SendMediaLarkParams {
117
+ /** Plugin configuration. */
118
+ cfg: ClawdbotConfig;
119
+ /** Target identifier (chat_id, open_id, or user_id). */
120
+ to: string;
121
+ /** Media URL to upload and send. */
122
+ mediaUrl: string;
123
+ /** When set, the message is sent as a threaded reply. */
124
+ replyToMessageId?: string;
125
+ /** When true, the reply appears in the thread instead of main chat. */
126
+ replyInThread?: boolean;
127
+ /** Optional account identifier for multi-account setups. */
128
+ accountId?: string;
129
+ /** Allowed root directories for local file access (SSRF prevention). */
130
+ mediaLocalRoots?: readonly string[];
131
+ }
132
+ /**
133
+ * Send a single media message to a Feishu chat or user.
134
+ *
135
+ * Pure atomic operation — uploads the media and sends it. On upload
136
+ * failure, falls back to sending the URL as a clickable text link.
137
+ *
138
+ * This function does **not** handle leading text or multi-media
139
+ * orchestration; those concerns belong to the adapter's `sendMedia`
140
+ * and `sendPayload` methods.
141
+ *
142
+ * @param params - See {@link SendMediaLarkParams}.
143
+ * @returns The message ID and chat ID of the sent message.
144
+ * @throws {Error} When the target is invalid or all send attempts fail.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * const result = await sendMediaLark({
149
+ * cfg,
150
+ * to: "oc_xxx",
151
+ * mediaUrl: "https://example.com/image.png",
152
+ * });
153
+ * ```
154
+ */
155
+ export declare function sendMediaLark(params: SendMediaLarkParams): Promise<FeishuSendResult>;