@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,621 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import type { ClawdbotConfig } from "../runtime-api.js";
3
+ import { buildMarkdownCard } from "./send.js";
4
+
5
+ const {
6
+ mockConvertMarkdownTables,
7
+ mockClientGet,
8
+ mockClientList,
9
+ mockClientPatch,
10
+ mockCreateFeishuClient,
11
+ mockResolveMarkdownTableMode,
12
+ mockResolveFeishuAccount,
13
+ mockRuntimeConvertMarkdownTables,
14
+ mockRuntimeResolveMarkdownTableMode,
15
+ } = vi.hoisted(() => ({
16
+ mockConvertMarkdownTables: vi.fn((text: string) => text),
17
+ mockClientGet: vi.fn(),
18
+ mockClientList: vi.fn(),
19
+ mockClientPatch: vi.fn(),
20
+ mockCreateFeishuClient: vi.fn(),
21
+ mockResolveMarkdownTableMode: vi.fn(() => "preserve"),
22
+ mockResolveFeishuAccount: vi.fn(),
23
+ mockRuntimeConvertMarkdownTables: vi.fn((text: string) => text),
24
+ mockRuntimeResolveMarkdownTableMode: vi.fn(() => "preserve"),
25
+ }));
26
+
27
+ vi.mock("klaw/plugin-sdk/markdown-table-runtime", () => ({
28
+ resolveMarkdownTableMode: mockResolveMarkdownTableMode,
29
+ }));
30
+
31
+ vi.mock("klaw/plugin-sdk/text-chunking", async (importOriginal) => {
32
+ const actual = await importOriginal<typeof import("klaw/plugin-sdk/text-chunking")>();
33
+ return {
34
+ ...actual,
35
+ convertMarkdownTables: mockConvertMarkdownTables,
36
+ };
37
+ });
38
+
39
+ vi.mock("./client.js", () => ({
40
+ createFeishuClient: mockCreateFeishuClient,
41
+ }));
42
+
43
+ vi.mock("./accounts.js", () => ({
44
+ resolveFeishuAccount: mockResolveFeishuAccount,
45
+ resolveFeishuRuntimeAccount: mockResolveFeishuAccount,
46
+ }));
47
+
48
+ vi.mock("./runtime.js", () => ({
49
+ getFeishuRuntime: () => ({
50
+ channel: {
51
+ text: {
52
+ resolveMarkdownTableMode: mockRuntimeResolveMarkdownTableMode,
53
+ convertMarkdownTables: mockRuntimeConvertMarkdownTables,
54
+ },
55
+ },
56
+ }),
57
+ }));
58
+
59
+ let buildStructuredCard: typeof import("./send.js").buildStructuredCard;
60
+ let editMessageFeishu: typeof import("./send.js").editMessageFeishu;
61
+ let getMessageFeishu: typeof import("./send.js").getMessageFeishu;
62
+ let listFeishuThreadMessages: typeof import("./send.js").listFeishuThreadMessages;
63
+ let resolveFeishuCardTemplate: typeof import("./send.js").resolveFeishuCardTemplate;
64
+ let sendMessageFeishu: typeof import("./send.js").sendMessageFeishu;
65
+
66
+ describe("getMessageFeishu", () => {
67
+ beforeAll(async () => {
68
+ ({
69
+ buildStructuredCard,
70
+ editMessageFeishu,
71
+ getMessageFeishu,
72
+ listFeishuThreadMessages,
73
+ resolveFeishuCardTemplate,
74
+ sendMessageFeishu,
75
+ } = await import("./send.js"));
76
+ });
77
+
78
+ afterAll(() => {
79
+ vi.doUnmock("klaw/plugin-sdk/markdown-table-runtime");
80
+ vi.doUnmock("klaw/plugin-sdk/text-chunking");
81
+ vi.doUnmock("./client.js");
82
+ vi.doUnmock("./accounts.js");
83
+ vi.doUnmock("./runtime.js");
84
+ vi.resetModules();
85
+ });
86
+
87
+ beforeEach(() => {
88
+ vi.clearAllMocks();
89
+ mockResolveMarkdownTableMode.mockReturnValue("preserve");
90
+ mockConvertMarkdownTables.mockImplementation((text: string) => text);
91
+ mockRuntimeResolveMarkdownTableMode.mockReturnValue("preserve");
92
+ mockRuntimeConvertMarkdownTables.mockImplementation((text: string) => text);
93
+ mockResolveFeishuAccount.mockReturnValue({
94
+ accountId: "default",
95
+ configured: true,
96
+ });
97
+ mockCreateFeishuClient.mockReturnValue({
98
+ im: {
99
+ message: {
100
+ create: vi.fn(),
101
+ get: mockClientGet,
102
+ list: mockClientList,
103
+ patch: mockClientPatch,
104
+ },
105
+ },
106
+ });
107
+ });
108
+
109
+ it("sends text without requiring Feishu runtime text helpers", async () => {
110
+ mockRuntimeResolveMarkdownTableMode.mockImplementation(() => {
111
+ throw new Error("Feishu runtime not initialized");
112
+ });
113
+ mockRuntimeConvertMarkdownTables.mockImplementation(() => {
114
+ throw new Error("Feishu runtime not initialized");
115
+ });
116
+ mockClientPatch.mockResolvedValueOnce({ code: 0 });
117
+ mockCreateFeishuClient.mockReturnValue({
118
+ im: {
119
+ message: {
120
+ create: vi.fn().mockResolvedValue({ code: 0, data: { message_id: "om_send" } }),
121
+ reply: vi.fn(),
122
+ get: mockClientGet,
123
+ list: mockClientList,
124
+ patch: mockClientPatch,
125
+ },
126
+ },
127
+ });
128
+
129
+ const result = await sendMessageFeishu({
130
+ cfg: {} as ClawdbotConfig,
131
+ to: "oc_send",
132
+ text: "hello",
133
+ });
134
+
135
+ expect(mockResolveMarkdownTableMode).toHaveBeenCalledWith({
136
+ cfg: {},
137
+ channel: "feishu",
138
+ });
139
+ expect(mockConvertMarkdownTables).toHaveBeenCalledWith("hello", "preserve");
140
+ expect(typeof result.receipt.sentAt).toBe("number");
141
+ expect(result).toEqual({
142
+ messageId: "om_send",
143
+ chatId: "oc_send",
144
+ receipt: {
145
+ primaryPlatformMessageId: "om_send",
146
+ platformMessageIds: ["om_send"],
147
+ parts: [
148
+ {
149
+ platformMessageId: "om_send",
150
+ kind: "text",
151
+ index: 0,
152
+ raw: {
153
+ channel: "feishu",
154
+ messageId: "om_send",
155
+ chatId: "oc_send",
156
+ conversationId: "oc_send",
157
+ },
158
+ threadId: "oc_send",
159
+ },
160
+ ],
161
+ threadId: "oc_send",
162
+ sentAt: result.receipt.sentAt,
163
+ raw: [
164
+ {
165
+ channel: "feishu",
166
+ messageId: "om_send",
167
+ chatId: "oc_send",
168
+ conversationId: "oc_send",
169
+ },
170
+ ],
171
+ },
172
+ });
173
+ });
174
+
175
+ it("extracts text content from interactive card elements", async () => {
176
+ mockClientGet.mockResolvedValueOnce({
177
+ code: 0,
178
+ data: {
179
+ items: [
180
+ {
181
+ message_id: "om_1",
182
+ chat_id: "oc_1",
183
+ msg_type: "interactive",
184
+ body: {
185
+ content: JSON.stringify({
186
+ elements: [
187
+ { tag: "markdown", content: "hello markdown" },
188
+ { tag: "div", text: { content: "hello div" } },
189
+ ],
190
+ }),
191
+ },
192
+ },
193
+ ],
194
+ },
195
+ });
196
+
197
+ const result = await getMessageFeishu({
198
+ cfg: {} as ClawdbotConfig,
199
+ messageId: "om_1",
200
+ });
201
+
202
+ expect(result).toEqual({
203
+ messageId: "om_1",
204
+ chatId: "oc_1",
205
+ chatType: undefined,
206
+ senderId: undefined,
207
+ senderOpenId: undefined,
208
+ senderType: undefined,
209
+ content: "hello markdown\nhello div",
210
+ contentType: "interactive",
211
+ createTime: undefined,
212
+ threadId: undefined,
213
+ });
214
+ });
215
+
216
+ it("falls through empty interactive card element arrays and locale variants", async () => {
217
+ mockClientGet.mockResolvedValueOnce({
218
+ code: 0,
219
+ data: {
220
+ items: [
221
+ {
222
+ message_id: "om_i18n_card",
223
+ chat_id: "oc_i18n_card",
224
+ msg_type: "interactive",
225
+ body: {
226
+ content: JSON.stringify({
227
+ elements: [],
228
+ body: { elements: [] },
229
+ i18n_elements: {
230
+ zh_cn: [],
231
+ en_us: [
232
+ {
233
+ tag: "markdown",
234
+ content: "hello ${count} {{label}} {{metadata}}",
235
+ },
236
+ ],
237
+ },
238
+ template_variable: {
239
+ count: 2,
240
+ label: "tasks",
241
+ metadata: { ignored: true },
242
+ },
243
+ }),
244
+ },
245
+ },
246
+ ],
247
+ },
248
+ });
249
+
250
+ const result = await getMessageFeishu({
251
+ cfg: {} as ClawdbotConfig,
252
+ messageId: "om_i18n_card",
253
+ });
254
+
255
+ expect(result).toEqual({
256
+ messageId: "om_i18n_card",
257
+ chatId: "oc_i18n_card",
258
+ chatType: undefined,
259
+ senderId: undefined,
260
+ senderOpenId: undefined,
261
+ senderType: undefined,
262
+ content: "hello 2 tasks {{metadata}}",
263
+ contentType: "interactive",
264
+ createTime: undefined,
265
+ threadId: undefined,
266
+ });
267
+ });
268
+
269
+ it("falls back to post-format content when interactive card elements are empty", async () => {
270
+ mockClientGet.mockResolvedValueOnce({
271
+ code: 0,
272
+ data: {
273
+ items: [
274
+ {
275
+ message_id: "om_post_card",
276
+ chat_id: "oc_post_card",
277
+ msg_type: "interactive",
278
+ body: {
279
+ content: JSON.stringify({
280
+ elements: [],
281
+ post: {
282
+ zh_cn: {
283
+ title: "Card summary",
284
+ content: [[{ tag: "md", text: "**fallback** body" }]],
285
+ },
286
+ },
287
+ }),
288
+ },
289
+ },
290
+ ],
291
+ },
292
+ });
293
+
294
+ const result = await getMessageFeishu({
295
+ cfg: {} as ClawdbotConfig,
296
+ messageId: "om_post_card",
297
+ });
298
+
299
+ expect(result).toEqual({
300
+ messageId: "om_post_card",
301
+ chatId: "oc_post_card",
302
+ chatType: undefined,
303
+ senderId: undefined,
304
+ senderOpenId: undefined,
305
+ senderType: undefined,
306
+ content: "Card summary\n\n**fallback** body",
307
+ contentType: "interactive",
308
+ createTime: undefined,
309
+ threadId: undefined,
310
+ });
311
+ });
312
+
313
+ it("extracts text content from post messages", async () => {
314
+ mockClientGet.mockResolvedValueOnce({
315
+ code: 0,
316
+ data: {
317
+ items: [
318
+ {
319
+ message_id: "om_post",
320
+ chat_id: "oc_post",
321
+ msg_type: "post",
322
+ body: {
323
+ content: JSON.stringify({
324
+ zh_cn: {
325
+ title: "Summary",
326
+ content: [[{ tag: "text", text: "post body" }]],
327
+ },
328
+ }),
329
+ },
330
+ },
331
+ ],
332
+ },
333
+ });
334
+
335
+ const result = await getMessageFeishu({
336
+ cfg: {} as ClawdbotConfig,
337
+ messageId: "om_post",
338
+ });
339
+
340
+ expect(result).toEqual({
341
+ messageId: "om_post",
342
+ chatId: "oc_post",
343
+ chatType: undefined,
344
+ senderId: undefined,
345
+ senderOpenId: undefined,
346
+ senderType: undefined,
347
+ content: "Summary\n\npost body",
348
+ contentType: "post",
349
+ createTime: undefined,
350
+ threadId: undefined,
351
+ });
352
+ });
353
+
354
+ it("returns text placeholder instead of raw JSON for unsupported message types", async () => {
355
+ mockClientGet.mockResolvedValueOnce({
356
+ code: 0,
357
+ data: {
358
+ items: [
359
+ {
360
+ message_id: "om_file",
361
+ chat_id: "oc_file",
362
+ msg_type: "file",
363
+ body: {
364
+ content: JSON.stringify({ file_key: "file_v3_123" }),
365
+ },
366
+ },
367
+ ],
368
+ },
369
+ });
370
+
371
+ const result = await getMessageFeishu({
372
+ cfg: {} as ClawdbotConfig,
373
+ messageId: "om_file",
374
+ });
375
+
376
+ expect(result).toEqual({
377
+ messageId: "om_file",
378
+ chatId: "oc_file",
379
+ chatType: undefined,
380
+ senderId: undefined,
381
+ senderOpenId: undefined,
382
+ senderType: undefined,
383
+ content: "[file message]",
384
+ contentType: "file",
385
+ createTime: undefined,
386
+ threadId: undefined,
387
+ });
388
+ });
389
+
390
+ it("supports single-object response shape from Feishu API", async () => {
391
+ mockClientGet.mockResolvedValueOnce({
392
+ code: 0,
393
+ data: {
394
+ message_id: "om_single",
395
+ chat_id: "oc_single",
396
+ msg_type: "text",
397
+ body: {
398
+ content: JSON.stringify({ text: "single payload" }),
399
+ },
400
+ },
401
+ });
402
+
403
+ const result = await getMessageFeishu({
404
+ cfg: {} as ClawdbotConfig,
405
+ messageId: "om_single",
406
+ });
407
+
408
+ expect(result).toEqual({
409
+ messageId: "om_single",
410
+ chatId: "oc_single",
411
+ chatType: undefined,
412
+ senderId: undefined,
413
+ senderOpenId: undefined,
414
+ senderType: undefined,
415
+ content: "single payload",
416
+ contentType: "text",
417
+ createTime: undefined,
418
+ threadId: undefined,
419
+ });
420
+ });
421
+
422
+ it("reuses the same content parsing for thread history messages", async () => {
423
+ mockClientList.mockResolvedValueOnce({
424
+ code: 0,
425
+ data: {
426
+ items: [
427
+ {
428
+ message_id: "om_root",
429
+ msg_type: "text",
430
+ body: {
431
+ content: JSON.stringify({ text: "root starter" }),
432
+ },
433
+ },
434
+ {
435
+ message_id: "om_card",
436
+ msg_type: "interactive",
437
+ body: {
438
+ content: JSON.stringify({
439
+ body: {
440
+ elements: [{ tag: "markdown", content: "hello from card 2.0" }],
441
+ },
442
+ }),
443
+ },
444
+ sender: {
445
+ id: "app_1",
446
+ sender_type: "app",
447
+ },
448
+ create_time: "1710000000000",
449
+ },
450
+ {
451
+ message_id: "om_file",
452
+ msg_type: "file",
453
+ body: {
454
+ content: JSON.stringify({ file_key: "file_v3_123" }),
455
+ },
456
+ sender: {
457
+ id: "ou_1",
458
+ sender_type: "user",
459
+ },
460
+ create_time: "1710000001000",
461
+ },
462
+ ],
463
+ },
464
+ });
465
+
466
+ const result = await listFeishuThreadMessages({
467
+ cfg: {} as ClawdbotConfig,
468
+ threadId: "omt_1",
469
+ rootMessageId: "om_root",
470
+ });
471
+
472
+ expect(result).toEqual([
473
+ {
474
+ messageId: "om_file",
475
+ senderId: "ou_1",
476
+ senderType: "user",
477
+ contentType: "file",
478
+ content: "[file message]",
479
+ createTime: 1710000001000,
480
+ },
481
+ {
482
+ messageId: "om_card",
483
+ senderId: "app_1",
484
+ senderType: "app",
485
+ contentType: "interactive",
486
+ content: "hello from card 2.0",
487
+ createTime: 1710000000000,
488
+ },
489
+ ]);
490
+ });
491
+ });
492
+
493
+ describe("editMessageFeishu", () => {
494
+ beforeEach(() => {
495
+ vi.clearAllMocks();
496
+ mockResolveFeishuAccount.mockReturnValue({
497
+ accountId: "default",
498
+ configured: true,
499
+ });
500
+ mockCreateFeishuClient.mockReturnValue({
501
+ im: {
502
+ message: {
503
+ patch: mockClientPatch,
504
+ },
505
+ },
506
+ });
507
+ });
508
+
509
+ it("patches post content for text edits", async () => {
510
+ mockRuntimeResolveMarkdownTableMode.mockImplementation(() => {
511
+ throw new Error("Feishu runtime not initialized");
512
+ });
513
+ mockRuntimeConvertMarkdownTables.mockImplementation(() => {
514
+ throw new Error("Feishu runtime not initialized");
515
+ });
516
+ mockClientPatch.mockResolvedValueOnce({ code: 0 });
517
+
518
+ const result = await editMessageFeishu({
519
+ cfg: {} as ClawdbotConfig,
520
+ messageId: "om_edit",
521
+ text: "updated body",
522
+ });
523
+
524
+ expect(mockClientPatch).toHaveBeenCalledWith({
525
+ path: { message_id: "om_edit" },
526
+ data: {
527
+ content: JSON.stringify({
528
+ zh_cn: {
529
+ content: [
530
+ [
531
+ {
532
+ tag: "md",
533
+ text: "updated body",
534
+ },
535
+ ],
536
+ ],
537
+ },
538
+ }),
539
+ },
540
+ });
541
+ expect(result).toEqual({ messageId: "om_edit", contentType: "post" });
542
+ });
543
+
544
+ it("patches interactive content for card edits", async () => {
545
+ mockClientPatch.mockResolvedValueOnce({ code: 0 });
546
+
547
+ const result = await editMessageFeishu({
548
+ cfg: {} as ClawdbotConfig,
549
+ messageId: "om_card",
550
+ card: { schema: "2.0" },
551
+ });
552
+
553
+ expect(mockClientPatch).toHaveBeenCalledWith({
554
+ path: { message_id: "om_card" },
555
+ data: {
556
+ content: JSON.stringify({ schema: "2.0" }),
557
+ },
558
+ });
559
+ expect(result).toEqual({ messageId: "om_card", contentType: "interactive" });
560
+ });
561
+ });
562
+
563
+ describe("resolveFeishuCardTemplate", () => {
564
+ it("accepts supported Feishu templates", () => {
565
+ expect(resolveFeishuCardTemplate(" purple ")).toBe("purple");
566
+ });
567
+
568
+ it("drops unsupported free-form identity themes", () => {
569
+ expect(resolveFeishuCardTemplate("space lobster")).toBeUndefined();
570
+ });
571
+ });
572
+
573
+ function expectSchema2WidthConfig(card: unknown) {
574
+ const typedCard = card as {
575
+ config: {
576
+ width_mode?: string;
577
+ enable_forward?: boolean;
578
+ wide_screen_mode?: boolean;
579
+ };
580
+ };
581
+
582
+ expect(typedCard.config.width_mode).toBe("fill");
583
+ expect(typedCard.config.enable_forward).toBeUndefined();
584
+ expect(typedCard.config.wide_screen_mode).toBeUndefined();
585
+ }
586
+
587
+ describe("Feishu card schema config", () => {
588
+ it.each([
589
+ {
590
+ name: "structured card",
591
+ build: () => buildStructuredCard("hello"),
592
+ },
593
+ {
594
+ name: "markdown card",
595
+ build: () => buildMarkdownCard("hello"),
596
+ },
597
+ ])("$name uses schema-2.0 width config instead of legacy wide screen mode", ({ build }) => {
598
+ expectSchema2WidthConfig(build());
599
+ });
600
+ });
601
+
602
+ describe("buildStructuredCard", () => {
603
+ it("falls back to blue when the header template is unsupported", () => {
604
+ const card = buildStructuredCard("hello", {
605
+ header: {
606
+ title: "Agent",
607
+ template: "space lobster",
608
+ },
609
+ });
610
+
611
+ expect(card).toEqual({
612
+ schema: "2.0",
613
+ config: { width_mode: "fill" },
614
+ body: { elements: [{ tag: "markdown", content: "hello" }] },
615
+ header: {
616
+ title: { tag: "plain_text", content: "Agent" },
617
+ template: "blue",
618
+ },
619
+ });
620
+ });
621
+ });