@kodelyth/feishu 2026.5.39 → 2026.5.42

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 (238) hide show
  1. package/api.ts +32 -0
  2. package/channel-entry.ts +20 -0
  3. package/channel-plugin-api.ts +1 -0
  4. package/contract-api.ts +16 -0
  5. package/dist/accounts-D0ow-lRb.js +429 -0
  6. package/dist/api.js +2308 -0
  7. package/dist/app-registration-DBSnysKJ.js +184 -0
  8. package/dist/audio-preflight.runtime-Dpjbn-7r.js +7 -0
  9. package/dist/channel-13WQvQ0u.js +2115 -0
  10. package/dist/channel-entry.js +22 -0
  11. package/dist/channel-plugin-api.js +2 -0
  12. package/dist/channel.runtime-JMJonrJ4.js +729 -0
  13. package/dist/client-D1pzbBGo.js +157 -0
  14. package/dist/contract-api.js +9 -0
  15. package/dist/conversation-id-_58ecqlx.js +139 -0
  16. package/dist/drive-CgHOluXx.js +883 -0
  17. package/dist/index.js +68 -0
  18. package/dist/monitor-oWptK0zL.js +60 -0
  19. package/dist/monitor.account-DHaWlslg.js +5207 -0
  20. package/dist/monitor.state-C211a4tX.js +100 -0
  21. package/dist/probe-CF4duEpK.js +149 -0
  22. package/dist/rolldown-runtime-DUslC3ob.js +14 -0
  23. package/dist/runtime-DSh5rL_d.js +8 -0
  24. package/dist/runtime-api.js +14 -0
  25. package/dist/secret-contract-NSee-WzN.js +119 -0
  26. package/dist/secret-contract-api.js +2 -0
  27. package/dist/security-audit-DWVC0vSK.js +11 -0
  28. package/dist/security-audit-shared-Dpcwxeft.js +38 -0
  29. package/dist/security-contract-api.js +2 -0
  30. package/dist/send-DfZuV4Fi.js +1212 -0
  31. package/dist/session-conversation-Duaukbnl.js +27 -0
  32. package/dist/session-key-api.js +2 -0
  33. package/dist/setup-api.js +2 -0
  34. package/dist/setup-entry.js +15 -0
  35. package/dist/subagent-hooks-Dtegs0kh.js +235 -0
  36. package/dist/subagent-hooks-api.js +23 -0
  37. package/dist/targets-DFskxX4p.js +48 -0
  38. package/dist/thread-bindings-DI7lVSOE.js +222 -0
  39. package/index.ts +82 -0
  40. package/klaw.plugin.json +47 -1712
  41. package/package.json +4 -4
  42. package/runtime-api.ts +52 -0
  43. package/secret-contract-api.ts +5 -0
  44. package/security-contract-api.ts +1 -0
  45. package/session-key-api.ts +1 -0
  46. package/setup-api.ts +3 -0
  47. package/setup-entry.test.ts +19 -0
  48. package/setup-entry.ts +13 -0
  49. package/src/accounts.test.ts +480 -0
  50. package/src/accounts.ts +333 -0
  51. package/src/agent-config.ts +21 -0
  52. package/src/app-registration.ts +331 -0
  53. package/src/approval-auth.test.ts +24 -0
  54. package/src/approval-auth.ts +25 -0
  55. package/src/async.test.ts +35 -0
  56. package/src/async.ts +104 -0
  57. package/src/audio-preflight.runtime.ts +9 -0
  58. package/src/bitable.test.ts +136 -0
  59. package/src/bitable.ts +762 -0
  60. package/src/bot-content.ts +485 -0
  61. package/src/bot-group-name.test.ts +116 -0
  62. package/src/bot-runtime-api.ts +12 -0
  63. package/src/bot-sender-name.ts +125 -0
  64. package/src/bot.broadcast.test.ts +523 -0
  65. package/src/bot.card-action.test.ts +552 -0
  66. package/src/bot.checkBotMentioned.test.ts +265 -0
  67. package/src/bot.helpers.test.ts +135 -0
  68. package/src/bot.stripBotMention.test.ts +126 -0
  69. package/src/bot.test.ts +3671 -0
  70. package/src/bot.ts +1703 -0
  71. package/src/card-action.ts +447 -0
  72. package/src/card-interaction.test.ts +131 -0
  73. package/src/card-interaction.ts +159 -0
  74. package/src/card-test-helpers.ts +54 -0
  75. package/src/card-ux-approval.ts +65 -0
  76. package/src/card-ux-launcher.test.ts +106 -0
  77. package/src/card-ux-launcher.ts +121 -0
  78. package/src/card-ux-shared.ts +33 -0
  79. package/src/channel-runtime-api.ts +16 -0
  80. package/src/channel.runtime.ts +47 -0
  81. package/src/channel.test.ts +1151 -0
  82. package/src/channel.ts +1423 -0
  83. package/src/chat-schema.ts +25 -0
  84. package/src/chat.test.ts +240 -0
  85. package/src/chat.ts +188 -0
  86. package/src/client-timeout.ts +42 -0
  87. package/src/client.test.ts +447 -0
  88. package/src/client.ts +262 -0
  89. package/src/comment-dispatcher-runtime-api.ts +6 -0
  90. package/src/comment-dispatcher.test.ts +185 -0
  91. package/src/comment-dispatcher.ts +107 -0
  92. package/src/comment-handler-runtime-api.ts +3 -0
  93. package/src/comment-handler.test.ts +592 -0
  94. package/src/comment-handler.ts +303 -0
  95. package/src/comment-reaction.test.ts +138 -0
  96. package/src/comment-reaction.ts +259 -0
  97. package/src/comment-shared.test.ts +183 -0
  98. package/src/comment-shared.ts +406 -0
  99. package/src/comment-target.ts +44 -0
  100. package/src/config-schema.test.ts +326 -0
  101. package/src/config-schema.ts +335 -0
  102. package/src/conversation-id.test.ts +18 -0
  103. package/src/conversation-id.ts +199 -0
  104. package/src/dedup-runtime-api.ts +1 -0
  105. package/src/dedup.ts +141 -0
  106. package/src/dedupe-key.ts +72 -0
  107. package/src/directory.static.ts +61 -0
  108. package/src/directory.test.ts +141 -0
  109. package/src/directory.ts +124 -0
  110. package/src/doc-schema.ts +182 -0
  111. package/src/docx-batch-insert.test.ts +116 -0
  112. package/src/docx-batch-insert.ts +223 -0
  113. package/src/docx-color-text.ts +154 -0
  114. package/src/docx-table-ops.test.ts +53 -0
  115. package/src/docx-table-ops.ts +316 -0
  116. package/src/docx-types.ts +38 -0
  117. package/src/docx.account-selection.test.ts +95 -0
  118. package/src/docx.test.ts +701 -0
  119. package/src/docx.ts +1596 -0
  120. package/src/drive-schema.ts +92 -0
  121. package/src/drive.test.ts +1237 -0
  122. package/src/drive.ts +829 -0
  123. package/src/dynamic-agent.test.ts +155 -0
  124. package/src/dynamic-agent.ts +143 -0
  125. package/src/event-types.ts +45 -0
  126. package/src/external-keys.test.ts +20 -0
  127. package/src/external-keys.ts +19 -0
  128. package/src/lifecycle.test-support.ts +220 -0
  129. package/src/media.test.ts +955 -0
  130. package/src/media.ts +1105 -0
  131. package/src/mention-target.types.ts +5 -0
  132. package/src/mention.ts +114 -0
  133. package/src/message-action-contract.ts +13 -0
  134. package/src/monitor-state-runtime-api.ts +7 -0
  135. package/src/monitor-transport-runtime-api.ts +10 -0
  136. package/src/monitor.account.ts +492 -0
  137. package/src/monitor.acp-init-failure.lifecycle.test-support.ts +219 -0
  138. package/src/monitor.bot-identity.ts +86 -0
  139. package/src/monitor.bot-menu-handler.ts +165 -0
  140. package/src/monitor.bot-menu.lifecycle.test-support.ts +224 -0
  141. package/src/monitor.bot-menu.test.ts +188 -0
  142. package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +264 -0
  143. package/src/monitor.card-action.lifecycle.test-support.ts +421 -0
  144. package/src/monitor.cleanup.test.ts +383 -0
  145. package/src/monitor.comment-notice-handler.ts +105 -0
  146. package/src/monitor.comment.test.ts +967 -0
  147. package/src/monitor.comment.ts +1386 -0
  148. package/src/monitor.lifecycle.test.ts +4 -0
  149. package/src/monitor.message-handler.ts +350 -0
  150. package/src/monitor.reaction.lifecycle.test-support.ts +68 -0
  151. package/src/monitor.reaction.test.ts +739 -0
  152. package/src/monitor.startup.test.ts +213 -0
  153. package/src/monitor.startup.ts +74 -0
  154. package/src/monitor.state.defaults.test.ts +46 -0
  155. package/src/monitor.state.ts +170 -0
  156. package/src/monitor.synthetic-error.ts +18 -0
  157. package/src/monitor.test-mocks.ts +46 -0
  158. package/src/monitor.transport.ts +451 -0
  159. package/src/monitor.ts +100 -0
  160. package/src/monitor.webhook-e2e.test.ts +279 -0
  161. package/src/monitor.webhook-security.test.ts +389 -0
  162. package/src/monitor.webhook.test-helpers.ts +116 -0
  163. package/src/outbound-runtime-api.ts +1 -0
  164. package/src/outbound.test.ts +1118 -0
  165. package/src/outbound.ts +785 -0
  166. package/src/perm-schema.ts +52 -0
  167. package/src/perm.ts +170 -0
  168. package/src/pins.ts +108 -0
  169. package/src/policy.test.ts +223 -0
  170. package/src/policy.ts +318 -0
  171. package/src/post.test.ts +105 -0
  172. package/src/post.ts +275 -0
  173. package/src/probe.test.ts +283 -0
  174. package/src/probe.ts +166 -0
  175. package/src/processing-claims.ts +59 -0
  176. package/src/qr-terminal.ts +1 -0
  177. package/src/reactions.ts +123 -0
  178. package/src/reasoning-preview.test.ts +113 -0
  179. package/src/reasoning-preview.ts +28 -0
  180. package/src/reply-dispatcher-runtime-api.ts +7 -0
  181. package/src/reply-dispatcher.test.ts +1513 -0
  182. package/src/reply-dispatcher.ts +748 -0
  183. package/src/runtime.ts +9 -0
  184. package/src/secret-contract.ts +145 -0
  185. package/src/secret-input.ts +1 -0
  186. package/src/security-audit-shared.ts +69 -0
  187. package/src/security-audit.test.ts +59 -0
  188. package/src/security-audit.ts +1 -0
  189. package/src/send-result.ts +80 -0
  190. package/src/send-target.test.ts +86 -0
  191. package/src/send-target.ts +35 -0
  192. package/src/send.reply-fallback.test.ts +417 -0
  193. package/src/send.test.ts +621 -0
  194. package/src/send.ts +861 -0
  195. package/src/sequential-key.test.ts +72 -0
  196. package/src/sequential-key.ts +25 -0
  197. package/src/sequential-queue.test.ts +165 -0
  198. package/src/sequential-queue.ts +86 -0
  199. package/src/session-conversation.ts +42 -0
  200. package/src/session-route.ts +48 -0
  201. package/src/setup-core.ts +51 -0
  202. package/src/setup-surface.test.ts +484 -0
  203. package/src/setup-surface.ts +618 -0
  204. package/src/streaming-card.test.ts +397 -0
  205. package/src/streaming-card.ts +571 -0
  206. package/src/subagent-hooks.test.ts +627 -0
  207. package/src/subagent-hooks.ts +413 -0
  208. package/src/targets.ts +97 -0
  209. package/src/test-support/lifecycle-test-support.ts +454 -0
  210. package/src/thread-bindings.test.ts +180 -0
  211. package/src/thread-bindings.ts +331 -0
  212. package/src/tool-account-routing.test.ts +250 -0
  213. package/src/tool-account.test.ts +44 -0
  214. package/src/tool-account.ts +93 -0
  215. package/src/tool-factory-test-harness.ts +79 -0
  216. package/src/tool-result.test.ts +32 -0
  217. package/src/tool-result.ts +16 -0
  218. package/src/tools-config.test.ts +21 -0
  219. package/src/tools-config.ts +22 -0
  220. package/src/types.ts +106 -0
  221. package/src/typing.test.ts +144 -0
  222. package/src/typing.ts +214 -0
  223. package/src/wiki-schema.ts +69 -0
  224. package/src/wiki.ts +270 -0
  225. package/subagent-hooks-api.ts +31 -0
  226. package/tsconfig.json +16 -0
  227. package/api.js +0 -7
  228. package/channel-entry.js +0 -7
  229. package/channel-plugin-api.js +0 -7
  230. package/contract-api.js +0 -7
  231. package/index.js +0 -7
  232. package/runtime-api.js +0 -7
  233. package/secret-contract-api.js +0 -7
  234. package/security-contract-api.js +0 -7
  235. package/session-key-api.js +0 -7
  236. package/setup-api.js +0 -7
  237. package/setup-entry.js +0 -7
  238. package/subagent-hooks-api.js +0 -7
@@ -0,0 +1,729 @@
1
+ import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount, y as parseFeishuCommentTarget } from "./accounts-D0ow-lRb.js";
2
+ import { _ as listFeishuDirectoryPeers, g as listFeishuDirectoryGroups, y as createFeishuCardInteractionEnvelope } from "./channel-13WQvQ0u.js";
3
+ import { r as createFeishuClient } from "./client-D1pzbBGo.js";
4
+ import { c as getChatInfo, l as getChatMembers, r as cleanupAmbientCommentTypingReaction, t as deliverCommentThreadText, u as getFeishuMemberInfo } from "./drive-CgHOluXx.js";
5
+ import { chunkTextForOutbound } from "./runtime-api.js";
6
+ import { a as sendCardFeishu, c as sendStructuredCardFeishu, g as shouldSuppressFeishuTextForVoiceMedia, h as sendMediaFeishu, i as resolveFeishuCardTemplate, n as getMessageFeishu, o as sendMarkdownCardFeishu, s as sendMessageFeishu, t as editMessageFeishu } from "./send-DfZuV4Fi.js";
7
+ import { t as probeFeishu } from "./probe-CF4duEpK.js";
8
+ import { normalizeLowercaseStringOrEmpty } from "klaw/plugin-sdk/string-coerce-runtime";
9
+ import { interactiveReplyToPresentation, normalizeInteractiveReply, normalizeMessagePresentation, renderMessagePresentationFallbackText, resolveInteractiveTextFallback } from "klaw/plugin-sdk/interactive-runtime";
10
+ import path from "node:path";
11
+ import { attachChannelToResult, createAttachedChannelResultAdapter } from "klaw/plugin-sdk/channel-send-result";
12
+ import { resolvePayloadMediaUrls, sendPayloadMediaSequenceAndFinalize, sendTextMediaPayload } from "klaw/plugin-sdk/reply-payload";
13
+ import { statRegularFileSync } from "klaw/plugin-sdk/security-runtime";
14
+ //#region extensions/feishu/src/directory.ts
15
+ async function listFeishuDirectoryPeersLive(params) {
16
+ const account = resolveFeishuAccount({
17
+ cfg: params.cfg,
18
+ accountId: params.accountId
19
+ });
20
+ if (!account.configured) return listFeishuDirectoryPeers(params);
21
+ try {
22
+ const client = createFeishuClient(account);
23
+ const peers = [];
24
+ const limit = params.limit ?? 50;
25
+ const response = await client.contact.user.list({ params: { page_size: Math.min(limit, 50) } });
26
+ if (response.code !== 0) throw new Error(response.msg || `code ${response.code}`);
27
+ const q = normalizeLowercaseStringOrEmpty(params.query);
28
+ for (const user of response.data?.items ?? []) {
29
+ if (user.open_id) {
30
+ const name = user.name || "";
31
+ if (!q || normalizeLowercaseStringOrEmpty(user.open_id).includes(q) || normalizeLowercaseStringOrEmpty(name).includes(q)) peers.push({
32
+ kind: "user",
33
+ id: user.open_id,
34
+ name: name || void 0
35
+ });
36
+ }
37
+ if (peers.length >= limit) break;
38
+ }
39
+ return peers;
40
+ } catch (err) {
41
+ if (params.fallbackToStatic === false) throw err instanceof Error ? err : /* @__PURE__ */ new Error("Feishu live peer lookup failed");
42
+ return listFeishuDirectoryPeers(params);
43
+ }
44
+ }
45
+ async function listFeishuDirectoryGroupsLive(params) {
46
+ const account = resolveFeishuAccount({
47
+ cfg: params.cfg,
48
+ accountId: params.accountId
49
+ });
50
+ if (!account.configured) return listFeishuDirectoryGroups(params);
51
+ try {
52
+ const client = createFeishuClient(account);
53
+ const groups = [];
54
+ const limit = params.limit ?? 50;
55
+ const response = await client.im.chat.list({ params: { page_size: Math.min(limit, 100) } });
56
+ if (response.code !== 0) throw new Error(response.msg || `code ${response.code}`);
57
+ const q = normalizeLowercaseStringOrEmpty(params.query);
58
+ for (const chat of response.data?.items ?? []) {
59
+ if (chat.chat_id) {
60
+ const name = chat.name || "";
61
+ if (!q || normalizeLowercaseStringOrEmpty(chat.chat_id).includes(q) || normalizeLowercaseStringOrEmpty(name).includes(q)) groups.push({
62
+ kind: "group",
63
+ id: chat.chat_id,
64
+ name: name || void 0
65
+ });
66
+ }
67
+ if (groups.length >= limit) break;
68
+ }
69
+ return groups;
70
+ } catch (err) {
71
+ if (params.fallbackToStatic === false) throw err instanceof Error ? err : /* @__PURE__ */ new Error("Feishu live group lookup failed");
72
+ return listFeishuDirectoryGroups(params);
73
+ }
74
+ }
75
+ //#endregion
76
+ //#region extensions/feishu/src/outbound.ts
77
+ const RENDERED_FEISHU_CARD = Symbol("klaw.renderedFeishuCard");
78
+ function normalizePossibleLocalImagePath(text) {
79
+ const raw = text?.trim();
80
+ if (!raw) return null;
81
+ if (/\s/.test(raw)) return null;
82
+ if (/^(https?:\/\/|data:|file:\/\/)/i.test(raw)) return null;
83
+ const ext = normalizeLowercaseStringOrEmpty(path.extname(raw));
84
+ if (![
85
+ ".jpg",
86
+ ".jpeg",
87
+ ".png",
88
+ ".gif",
89
+ ".webp",
90
+ ".bmp",
91
+ ".ico",
92
+ ".tiff"
93
+ ].includes(ext)) return null;
94
+ if (!path.isAbsolute(raw)) return null;
95
+ try {
96
+ if (statRegularFileSync(raw).missing) return null;
97
+ } catch {
98
+ return null;
99
+ }
100
+ return raw;
101
+ }
102
+ function shouldUseCard(text) {
103
+ return /```[\s\S]*?```/.test(text) || /\|.+\|[\r\n]+\|[-:| ]+\|/.test(text);
104
+ }
105
+ function isRecord$1(value) {
106
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
107
+ }
108
+ function escapeFeishuCardMarkdownText(text) {
109
+ return text.replace(/[&<>]/g, (char) => {
110
+ switch (char) {
111
+ case "&": return "&amp;";
112
+ case "<": return "&lt;";
113
+ case ">": return "&gt;";
114
+ default: return char;
115
+ }
116
+ });
117
+ }
118
+ function resolveSafeFeishuButtonUrl(url) {
119
+ const trimmed = url?.trim();
120
+ if (!trimmed) return;
121
+ try {
122
+ const parsed = new URL(trimmed);
123
+ return parsed.protocol === "https:" || parsed.protocol === "http:" ? trimmed : void 0;
124
+ } catch {
125
+ return;
126
+ }
127
+ }
128
+ function markRenderedFeishuCard(card) {
129
+ Object.defineProperty(card, RENDERED_FEISHU_CARD, {
130
+ value: true,
131
+ enumerable: false
132
+ });
133
+ return card;
134
+ }
135
+ function sanitizeNativeFeishuCardButton(button) {
136
+ if (!isRecord$1(button)) return;
137
+ const text = isRecord$1(button.text) && typeof button.text.content === "string" ? button.text.content : void 0;
138
+ if (!text?.trim()) return;
139
+ const style = button.type === "danger" ? "danger" : button.type === "primary" ? "primary" : void 0;
140
+ const rendered = {
141
+ tag: "button",
142
+ text: {
143
+ tag: "plain_text",
144
+ content: text
145
+ },
146
+ type: mapFeishuButtonType(style)
147
+ };
148
+ const safeUrl = resolveSafeFeishuButtonUrl(typeof button.url === "string" ? button.url : void 0);
149
+ if (safeUrl) rendered.url = safeUrl;
150
+ if (isRecord$1(button.value) && button.value.oc === "ocf1") rendered.value = button.value;
151
+ return rendered.url || rendered.value ? rendered : void 0;
152
+ }
153
+ function sanitizeNativeFeishuCardElement(element) {
154
+ if (!isRecord$1(element) || typeof element.tag !== "string") return;
155
+ if (element.tag === "hr") return { tag: "hr" };
156
+ if (element.tag === "markdown" && typeof element.content === "string") return {
157
+ tag: "markdown",
158
+ content: escapeFeishuCardMarkdownText(element.content)
159
+ };
160
+ if (element.tag === "action" && Array.isArray(element.actions)) {
161
+ const actions = element.actions.map((action) => sanitizeNativeFeishuCardButton(action)).filter((action) => Boolean(action));
162
+ return actions.length > 0 ? {
163
+ tag: "action",
164
+ actions
165
+ } : void 0;
166
+ }
167
+ }
168
+ function sanitizeNativeFeishuCard(card) {
169
+ const body = isRecord$1(card.body) ? card.body : void 0;
170
+ const elements = (Array.isArray(body?.elements) ? body.elements : []).map((element) => sanitizeNativeFeishuCardElement(element)).filter((element) => Boolean(element));
171
+ if (elements.length === 0) return;
172
+ const header = isRecord$1(card.header) ? card.header : void 0;
173
+ const title = isRecord$1(header?.title) && typeof header.title.content === "string" ? header.title.content : void 0;
174
+ return markRenderedFeishuCard({
175
+ schema: "2.0",
176
+ config: { width_mode: "fill" },
177
+ ...title?.trim() ? { header: {
178
+ title: {
179
+ tag: "plain_text",
180
+ content: title
181
+ },
182
+ template: resolveFeishuCardTemplate(typeof header?.template === "string" ? header.template : void 0) ?? "blue"
183
+ } } : {},
184
+ body: { elements }
185
+ });
186
+ }
187
+ function readNativeFeishuCard(payload) {
188
+ const feishuData = payload.channelData?.feishu;
189
+ if (!isRecord$1(feishuData)) return;
190
+ const card = feishuData.card ?? feishuData.interactiveCard;
191
+ if (!isRecord$1(card)) return;
192
+ if (card[RENDERED_FEISHU_CARD] === true) return card;
193
+ return sanitizeNativeFeishuCard(card);
194
+ }
195
+ function mapFeishuButtonType(style) {
196
+ if (style === "primary" || style === "success") return "primary";
197
+ if (style === "danger") return "danger";
198
+ return "default";
199
+ }
200
+ function buildFeishuPayloadButton(button) {
201
+ const rendered = {
202
+ tag: "button",
203
+ text: {
204
+ tag: "plain_text",
205
+ content: button.label
206
+ },
207
+ type: mapFeishuButtonType(button.style)
208
+ };
209
+ if (button.url) {
210
+ const safeUrl = resolveSafeFeishuButtonUrl(button.url);
211
+ if (safeUrl) rendered.url = safeUrl;
212
+ }
213
+ if (button.value) rendered.value = createFeishuCardInteractionEnvelope({
214
+ k: "quick",
215
+ a: "feishu.payload.button",
216
+ q: button.value
217
+ });
218
+ return rendered.url || rendered.value ? rendered : void 0;
219
+ }
220
+ function buildFeishuCardElementForBlock(block) {
221
+ if (block.type === "text") return {
222
+ tag: "markdown",
223
+ content: escapeFeishuCardMarkdownText(block.text)
224
+ };
225
+ if (block.type === "context") return {
226
+ tag: "markdown",
227
+ content: `<font color='grey'>${escapeFeishuCardMarkdownText(block.text)}</font>`
228
+ };
229
+ if (block.type === "divider") return { tag: "hr" };
230
+ if (block.type === "buttons") {
231
+ const actions = block.buttons.map((button) => buildFeishuPayloadButton(button)).filter((button) => Boolean(button));
232
+ if (actions.length === 0) return;
233
+ return {
234
+ tag: "action",
235
+ actions
236
+ };
237
+ }
238
+ const labels = block.options.map((option) => `- ${option.label}`).join("\n");
239
+ return {
240
+ tag: "markdown",
241
+ content: `${escapeFeishuCardMarkdownText(block.placeholder?.trim() || "Options")}:\n${escapeFeishuCardMarkdownText(labels)}`
242
+ };
243
+ }
244
+ function buildFeishuPayloadCard(params) {
245
+ const nativeCard = readNativeFeishuCard(params.payload);
246
+ if (nativeCard) return nativeCard;
247
+ const interactive = normalizeInteractiveReply(params.payload.interactive);
248
+ const presentation = normalizeMessagePresentation(params.payload.presentation) ?? (interactive ? interactiveReplyToPresentation(interactive) : void 0);
249
+ if (!presentation && !interactive) return;
250
+ const text = resolveInteractiveTextFallback({
251
+ text: params.text ?? params.payload.text,
252
+ interactive
253
+ });
254
+ const elements = [];
255
+ if (text?.trim()) elements.push({
256
+ tag: "markdown",
257
+ content: escapeFeishuCardMarkdownText(text)
258
+ });
259
+ for (const block of presentation?.blocks ?? []) {
260
+ const element = buildFeishuCardElementForBlock(block);
261
+ if (element) elements.push(element);
262
+ }
263
+ if (elements.length === 0) elements.push({
264
+ tag: "markdown",
265
+ content: renderMessagePresentationFallbackText({
266
+ text,
267
+ presentation
268
+ })
269
+ });
270
+ const identityTitle = params.identity ? params.identity.emoji ? `${params.identity.emoji} ${params.identity.name ?? ""}`.trim() : params.identity.name ?? "" : "";
271
+ const title = presentation?.title ?? identityTitle;
272
+ const template = resolveFeishuCardTemplate(presentation?.tone === "danger" ? "red" : presentation?.tone === "warning" ? "orange" : presentation?.tone === "success" ? "green" : "blue");
273
+ return markRenderedFeishuCard({
274
+ schema: "2.0",
275
+ config: { width_mode: "fill" },
276
+ ...title ? { header: {
277
+ title: {
278
+ tag: "plain_text",
279
+ content: title
280
+ },
281
+ template: template ?? "blue"
282
+ } } : {},
283
+ body: { elements }
284
+ });
285
+ }
286
+ function renderFeishuPresentationPayload({ payload, presentation, ctx }) {
287
+ const card = buildFeishuPayloadCard({
288
+ payload,
289
+ text: payload.text,
290
+ identity: ctx.identity
291
+ });
292
+ if (!card) return null;
293
+ const existingFeishuData = isRecord$1(payload.channelData?.feishu) ? payload.channelData.feishu : void 0;
294
+ return {
295
+ ...payload,
296
+ text: renderMessagePresentationFallbackText({
297
+ text: payload.text,
298
+ presentation
299
+ }),
300
+ channelData: {
301
+ ...payload.channelData,
302
+ feishu: {
303
+ ...existingFeishuData,
304
+ card
305
+ }
306
+ }
307
+ };
308
+ }
309
+ function resolveReplyToMessageId(params) {
310
+ const replyToId = params.replyToId?.trim();
311
+ if (replyToId) return replyToId;
312
+ if (params.threadId == null) return;
313
+ return String(params.threadId).trim() || void 0;
314
+ }
315
+ function resolveFeishuMediaReplyMode(params) {
316
+ const trimmedReplyToId = params.replyToId?.trim() || void 0;
317
+ return {
318
+ replyToMessageId: resolveReplyToMessageId(params),
319
+ replyInThread: params.threadId != null && !trimmedReplyToId
320
+ };
321
+ }
322
+ async function sendCommentThreadReply(params) {
323
+ const target = parseFeishuCommentTarget(params.to);
324
+ if (!target) return null;
325
+ const client = createFeishuClient(resolveFeishuAccount({
326
+ cfg: params.cfg,
327
+ accountId: params.accountId
328
+ }));
329
+ const replyId = params.replyId?.trim();
330
+ try {
331
+ const result = await deliverCommentThreadText(client, {
332
+ file_token: target.fileToken,
333
+ file_type: target.fileType,
334
+ comment_id: target.commentId,
335
+ content: params.text
336
+ });
337
+ return {
338
+ messageId: typeof result.reply_id === "string" && result.reply_id || typeof result.comment_id === "string" && result.comment_id || "",
339
+ chatId: target.commentId,
340
+ result
341
+ };
342
+ } finally {
343
+ if (replyId) cleanupAmbientCommentTypingReaction({
344
+ client,
345
+ deliveryContext: {
346
+ channel: "feishu",
347
+ to: params.to,
348
+ threadId: replyId
349
+ }
350
+ });
351
+ }
352
+ }
353
+ async function sendOutboundText(params) {
354
+ const { cfg, to, text, accountId, replyToMessageId, replyInThread } = params;
355
+ const commentResult = await sendCommentThreadReply({
356
+ cfg,
357
+ to,
358
+ text,
359
+ replyId: replyToMessageId,
360
+ accountId
361
+ });
362
+ if (commentResult) return commentResult;
363
+ const renderMode = resolveFeishuAccount({
364
+ cfg,
365
+ accountId
366
+ }).config?.renderMode ?? "auto";
367
+ if (renderMode === "card" || renderMode === "auto" && shouldUseCard(text)) return sendMarkdownCardFeishu({
368
+ cfg,
369
+ to,
370
+ text,
371
+ accountId,
372
+ replyToMessageId,
373
+ replyInThread
374
+ });
375
+ return sendMessageFeishu({
376
+ cfg,
377
+ to,
378
+ text,
379
+ accountId,
380
+ replyToMessageId,
381
+ replyInThread
382
+ });
383
+ }
384
+ const feishuOutbound = {
385
+ deliveryMode: "direct",
386
+ chunker: chunkTextForOutbound,
387
+ chunkerMode: "markdown",
388
+ textChunkLimit: 4e3,
389
+ presentationCapabilities: {
390
+ supported: true,
391
+ buttons: true,
392
+ selects: false,
393
+ context: true,
394
+ divider: true,
395
+ limits: {
396
+ actions: {
397
+ maxActions: 20,
398
+ maxActionsPerRow: 5,
399
+ maxLabelLength: 40,
400
+ maxValueBytes: 1024
401
+ },
402
+ text: {
403
+ maxLength: 4e3,
404
+ encoding: "characters",
405
+ markdownDialect: "markdown"
406
+ }
407
+ }
408
+ },
409
+ renderPresentation: renderFeishuPresentationPayload,
410
+ sendPayload: async (ctx) => {
411
+ const card = buildFeishuPayloadCard({
412
+ payload: ctx.payload,
413
+ text: ctx.text,
414
+ identity: ctx.identity
415
+ });
416
+ if (!card) return await sendTextMediaPayload({
417
+ channel: "feishu",
418
+ ctx,
419
+ adapter: feishuOutbound
420
+ });
421
+ const replyToMessageId = resolveReplyToMessageId({
422
+ replyToId: ctx.replyToId,
423
+ threadId: ctx.threadId
424
+ });
425
+ if (parseFeishuCommentTarget(ctx.to)) return await sendTextMediaPayload({
426
+ channel: "feishu",
427
+ ctx: {
428
+ ...ctx,
429
+ payload: {
430
+ ...ctx.payload,
431
+ text: renderMessagePresentationFallbackText({
432
+ text: ctx.payload.text,
433
+ presentation: normalizeMessagePresentation(ctx.payload.presentation) ?? (() => {
434
+ const interactive = normalizeInteractiveReply(ctx.payload.interactive);
435
+ return interactive ? interactiveReplyToPresentation(interactive) : void 0;
436
+ })()
437
+ }),
438
+ interactive: void 0,
439
+ presentation: void 0,
440
+ channelData: void 0
441
+ }
442
+ },
443
+ adapter: feishuOutbound
444
+ });
445
+ const mediaUrls = resolvePayloadMediaUrls(ctx.payload).map((entry) => entry.trim()).filter(Boolean);
446
+ return attachChannelToResult("feishu", await sendPayloadMediaSequenceAndFinalize({
447
+ text: ctx.payload.text ?? "",
448
+ mediaUrls,
449
+ send: async ({ mediaUrl }) => await sendMediaFeishu({
450
+ cfg: ctx.cfg,
451
+ to: ctx.to,
452
+ mediaUrl,
453
+ accountId: ctx.accountId ?? void 0,
454
+ mediaLocalRoots: ctx.mediaLocalRoots,
455
+ replyToMessageId,
456
+ ...ctx.payload.audioAsVoice === true || ctx.audioAsVoice === true ? { audioAsVoice: true } : {}
457
+ }),
458
+ finalize: async () => await sendCardFeishu({
459
+ cfg: ctx.cfg,
460
+ to: ctx.to,
461
+ card,
462
+ replyToMessageId,
463
+ replyInThread: ctx.threadId != null && !ctx.replyToId,
464
+ accountId: ctx.accountId ?? void 0
465
+ })
466
+ }));
467
+ },
468
+ ...createAttachedChannelResultAdapter({
469
+ channel: "feishu",
470
+ sendText: async ({ cfg, to, text, accountId, replyToId, threadId, mediaLocalRoots, identity }) => {
471
+ const { replyToMessageId, replyInThread } = resolveFeishuMediaReplyMode({
472
+ replyToId,
473
+ threadId
474
+ });
475
+ const localImagePath = normalizePossibleLocalImagePath(text);
476
+ if (localImagePath) try {
477
+ return await sendMediaFeishu({
478
+ cfg,
479
+ to,
480
+ mediaUrl: localImagePath,
481
+ accountId: accountId ?? void 0,
482
+ replyToMessageId,
483
+ replyInThread,
484
+ mediaLocalRoots
485
+ });
486
+ } catch (err) {
487
+ console.error(`[feishu] local image path auto-send failed:`, err);
488
+ }
489
+ if (parseFeishuCommentTarget(to)) return await sendOutboundText({
490
+ cfg,
491
+ to,
492
+ text,
493
+ accountId: accountId ?? void 0,
494
+ replyToMessageId,
495
+ replyInThread
496
+ });
497
+ const renderMode = resolveFeishuAccount({
498
+ cfg,
499
+ accountId: accountId ?? void 0
500
+ }).config?.renderMode ?? "auto";
501
+ if (renderMode === "card" || renderMode === "auto" && shouldUseCard(text)) {
502
+ const header = identity ? {
503
+ title: identity.emoji ? `${identity.emoji} ${identity.name ?? ""}`.trim() : identity.name ?? "",
504
+ template: "blue"
505
+ } : void 0;
506
+ return await sendStructuredCardFeishu({
507
+ cfg,
508
+ to,
509
+ text,
510
+ replyToMessageId,
511
+ replyInThread,
512
+ accountId: accountId ?? void 0,
513
+ header: header?.title ? header : void 0
514
+ });
515
+ }
516
+ return await sendOutboundText({
517
+ cfg,
518
+ to,
519
+ text,
520
+ accountId: accountId ?? void 0,
521
+ replyToMessageId,
522
+ replyInThread
523
+ });
524
+ },
525
+ sendMedia: async ({ cfg, to, text, mediaUrl, audioAsVoice, accountId, mediaLocalRoots, replyToId, threadId }) => {
526
+ const { replyToMessageId, replyInThread } = resolveFeishuMediaReplyMode({
527
+ replyToId,
528
+ threadId
529
+ });
530
+ if (parseFeishuCommentTarget(to)) return await sendOutboundText({
531
+ cfg,
532
+ to,
533
+ text: [text?.trim(), mediaUrl?.trim()].filter(Boolean).join("\n\n") || mediaUrl || text || "",
534
+ accountId: accountId ?? void 0,
535
+ replyToMessageId,
536
+ replyInThread
537
+ });
538
+ const suppressTextForVoiceMedia = mediaUrl !== void 0 && shouldSuppressFeishuTextForVoiceMedia({
539
+ mediaUrl,
540
+ audioAsVoice
541
+ });
542
+ if (text?.trim() && !suppressTextForVoiceMedia) await sendOutboundText({
543
+ cfg,
544
+ to,
545
+ text,
546
+ accountId: accountId ?? void 0,
547
+ replyToMessageId,
548
+ replyInThread
549
+ });
550
+ if (mediaUrl) try {
551
+ const result = await sendMediaFeishu({
552
+ cfg,
553
+ to,
554
+ mediaUrl,
555
+ accountId: accountId ?? void 0,
556
+ mediaLocalRoots,
557
+ replyToMessageId,
558
+ replyInThread,
559
+ ...audioAsVoice === true ? { audioAsVoice: true } : {}
560
+ });
561
+ if (result.voiceIntentDegradedToFile && text?.trim()) await sendOutboundText({
562
+ cfg,
563
+ to,
564
+ text,
565
+ accountId: accountId ?? void 0,
566
+ replyToMessageId,
567
+ replyInThread
568
+ });
569
+ return result;
570
+ } catch (err) {
571
+ console.error(`[feishu] sendMediaFeishu failed:`, err);
572
+ return await sendOutboundText({
573
+ cfg,
574
+ to,
575
+ text: [text?.trim(), `📎 ${mediaUrl}`].filter(Boolean).join("\n\n"),
576
+ accountId: accountId ?? void 0,
577
+ replyToMessageId,
578
+ replyInThread
579
+ });
580
+ }
581
+ return await sendOutboundText({
582
+ cfg,
583
+ to,
584
+ text: text ?? "",
585
+ accountId: accountId ?? void 0,
586
+ replyToMessageId,
587
+ replyInThread
588
+ });
589
+ }
590
+ })
591
+ };
592
+ //#endregion
593
+ //#region extensions/feishu/src/pins.ts
594
+ function assertFeishuPinApiSuccess(response, action) {
595
+ if (response.code !== 0) throw new Error(`Feishu ${action} failed: ${response.msg || `code ${response.code}`}`);
596
+ }
597
+ function normalizePin(pin) {
598
+ return {
599
+ messageId: pin.message_id,
600
+ chatId: pin.chat_id,
601
+ operatorId: pin.operator_id,
602
+ operatorIdType: pin.operator_id_type,
603
+ createTime: pin.create_time
604
+ };
605
+ }
606
+ async function createPinFeishu(params) {
607
+ const account = resolveFeishuRuntimeAccount({
608
+ cfg: params.cfg,
609
+ accountId: params.accountId
610
+ });
611
+ if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
612
+ const response = await createFeishuClient(account).im.pin.create({ data: { message_id: params.messageId } });
613
+ assertFeishuPinApiSuccess(response, "pin create");
614
+ return response.data?.pin ? normalizePin(response.data.pin) : null;
615
+ }
616
+ async function removePinFeishu(params) {
617
+ const account = resolveFeishuRuntimeAccount({
618
+ cfg: params.cfg,
619
+ accountId: params.accountId
620
+ });
621
+ if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
622
+ assertFeishuPinApiSuccess(await createFeishuClient(account).im.pin.delete({ path: { message_id: params.messageId } }), "pin delete");
623
+ }
624
+ async function listPinsFeishu(params) {
625
+ const account = resolveFeishuRuntimeAccount({
626
+ cfg: params.cfg,
627
+ accountId: params.accountId
628
+ });
629
+ if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
630
+ const response = await createFeishuClient(account).im.pin.list({ params: {
631
+ chat_id: params.chatId,
632
+ ...params.startTime ? { start_time: params.startTime } : {},
633
+ ...params.endTime ? { end_time: params.endTime } : {},
634
+ ...typeof params.pageSize === "number" ? { page_size: Math.max(1, Math.min(100, Math.floor(params.pageSize))) } : {},
635
+ ...params.pageToken ? { page_token: params.pageToken } : {}
636
+ } });
637
+ assertFeishuPinApiSuccess(response, "pin list");
638
+ return {
639
+ chatId: params.chatId,
640
+ pins: (response.data?.items ?? []).map(normalizePin),
641
+ hasMore: response.data?.has_more === true,
642
+ pageToken: response.data?.page_token
643
+ };
644
+ }
645
+ //#endregion
646
+ //#region extensions/feishu/src/reactions.ts
647
+ function resolveConfiguredFeishuClient(params) {
648
+ const account = resolveFeishuRuntimeAccount(params);
649
+ if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
650
+ return createFeishuClient(account);
651
+ }
652
+ function assertFeishuReactionApiSuccess(response, action) {
653
+ if (response.code !== 0) throw new Error(`Feishu ${action} failed: ${response.msg || `code ${response.code}`}`);
654
+ }
655
+ /**
656
+ * Add a reaction (emoji) to a message.
657
+ * @param emojiType - Feishu emoji type, e.g., "SMILE", "THUMBSUP", "HEART"
658
+ * @see https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce
659
+ */
660
+ async function addReactionFeishu(params) {
661
+ const { cfg, messageId, emojiType, accountId } = params;
662
+ const response = await resolveConfiguredFeishuClient({
663
+ cfg,
664
+ accountId
665
+ }).im.messageReaction.create({
666
+ path: { message_id: messageId },
667
+ data: { reaction_type: { emoji_type: emojiType } }
668
+ });
669
+ assertFeishuReactionApiSuccess(response, "add reaction");
670
+ const reactionId = response.data?.reaction_id;
671
+ if (!reactionId) throw new Error("Feishu add reaction failed: no reaction_id returned");
672
+ return { reactionId };
673
+ }
674
+ /**
675
+ * Remove a reaction from a message.
676
+ */
677
+ async function removeReactionFeishu(params) {
678
+ const { cfg, messageId, reactionId, accountId } = params;
679
+ assertFeishuReactionApiSuccess(await resolveConfiguredFeishuClient({
680
+ cfg,
681
+ accountId
682
+ }).im.messageReaction.delete({ path: {
683
+ message_id: messageId,
684
+ reaction_id: reactionId
685
+ } }), "remove reaction");
686
+ }
687
+ /**
688
+ * List all reactions for a message.
689
+ */
690
+ async function listReactionsFeishu(params) {
691
+ const { cfg, messageId, emojiType, accountId } = params;
692
+ const response = await resolveConfiguredFeishuClient({
693
+ cfg,
694
+ accountId
695
+ }).im.messageReaction.list({
696
+ path: { message_id: messageId },
697
+ params: emojiType ? { reaction_type: emojiType } : void 0
698
+ });
699
+ assertFeishuReactionApiSuccess(response, "list reactions");
700
+ return (response.data?.items ?? []).map((item) => ({
701
+ reactionId: item.reaction_id ?? "",
702
+ emojiType: item.reaction_type?.emoji_type ?? "",
703
+ operatorType: item.operator_type === "app" ? "app" : "user",
704
+ operatorId: item.operator_id?.open_id ?? item.operator_id?.user_id ?? item.operator_id?.union_id ?? ""
705
+ }));
706
+ }
707
+ //#endregion
708
+ //#region extensions/feishu/src/channel.runtime.ts
709
+ const feishuChannelRuntime = {
710
+ listFeishuDirectoryGroupsLive,
711
+ listFeishuDirectoryPeersLive,
712
+ feishuOutbound: { ...feishuOutbound },
713
+ createPinFeishu,
714
+ listPinsFeishu,
715
+ removePinFeishu,
716
+ probeFeishu,
717
+ addReactionFeishu,
718
+ listReactionsFeishu,
719
+ removeReactionFeishu,
720
+ getChatInfo,
721
+ getChatMembers,
722
+ getFeishuMemberInfo,
723
+ editMessageFeishu,
724
+ getMessageFeishu,
725
+ sendCardFeishu,
726
+ sendMessageFeishu
727
+ };
728
+ //#endregion
729
+ export { feishuChannelRuntime };