@openclaw/feishu 2026.3.2 → 2026.3.7

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 (70) hide show
  1. package/index.ts +2 -2
  2. package/package.json +1 -1
  3. package/src/accounts.test.ts +199 -13
  4. package/src/accounts.ts +45 -17
  5. package/src/bitable.ts +40 -28
  6. package/src/bot.checkBotMentioned.test.ts +8 -0
  7. package/src/bot.stripBotMention.test.ts +118 -22
  8. package/src/bot.test.ts +516 -9
  9. package/src/bot.ts +366 -109
  10. package/src/card-action.ts +1 -1
  11. package/src/channel.test.ts +1 -1
  12. package/src/channel.ts +52 -64
  13. package/src/chat.test.ts +2 -2
  14. package/src/chat.ts +1 -1
  15. package/src/client.test.ts +207 -4
  16. package/src/client.ts +70 -5
  17. package/src/config-schema.test.ts +14 -6
  18. package/src/config-schema.ts +5 -1
  19. package/src/dedup.ts +1 -1
  20. package/src/directory.test.ts +40 -0
  21. package/src/directory.ts +29 -50
  22. package/src/docx-batch-insert.test.ts +90 -0
  23. package/src/docx-batch-insert.ts +8 -11
  24. package/src/docx.account-selection.test.ts +3 -3
  25. package/src/docx.ts +1 -1
  26. package/src/drive.ts +13 -17
  27. package/src/dynamic-agent.ts +1 -1
  28. package/src/feishu-command-handler.ts +59 -0
  29. package/src/media.test.ts +60 -13
  30. package/src/media.ts +23 -9
  31. package/src/monitor.account.ts +19 -8
  32. package/src/monitor.reaction.test.ts +111 -105
  33. package/src/monitor.startup.test.ts +11 -10
  34. package/src/monitor.startup.ts +20 -7
  35. package/src/monitor.state.ts +4 -1
  36. package/src/monitor.test-mocks.ts +42 -9
  37. package/src/monitor.transport.ts +4 -1
  38. package/src/monitor.ts +4 -4
  39. package/src/monitor.webhook-security.test.ts +8 -23
  40. package/src/onboarding.status.test.ts +1 -1
  41. package/src/onboarding.test.ts +143 -0
  42. package/src/onboarding.ts +86 -71
  43. package/src/outbound.test.ts +178 -0
  44. package/src/outbound.ts +39 -6
  45. package/src/perm.ts +11 -15
  46. package/src/policy.test.ts +40 -0
  47. package/src/policy.ts +9 -10
  48. package/src/probe.test.ts +18 -18
  49. package/src/reactions.ts +1 -1
  50. package/src/reply-dispatcher.test.ts +175 -0
  51. package/src/reply-dispatcher.ts +69 -21
  52. package/src/runtime.ts +1 -1
  53. package/src/secret-input.ts +8 -14
  54. package/src/send-message.ts +71 -0
  55. package/src/send-target.test.ts +1 -1
  56. package/src/send-target.ts +1 -1
  57. package/src/send.reply-fallback.test.ts +74 -0
  58. package/src/send.test.ts +1 -1
  59. package/src/send.ts +88 -49
  60. package/src/streaming-card.test.ts +54 -0
  61. package/src/streaming-card.ts +96 -28
  62. package/src/targets.ts +5 -1
  63. package/src/tool-account-routing.test.ts +3 -3
  64. package/src/tool-account.ts +1 -1
  65. package/src/tool-factory-test-harness.ts +1 -1
  66. package/src/tool-result.test.ts +32 -0
  67. package/src/tool-result.ts +14 -0
  68. package/src/types.ts +2 -3
  69. package/src/typing.ts +1 -1
  70. package/src/wiki.ts +15 -19
@@ -1,38 +1,134 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { stripBotMention, type FeishuMessageEvent } from "./bot.js";
2
+ import { parseFeishuMessageEvent } from "./bot.js";
3
3
 
4
- type Mentions = FeishuMessageEvent["message"]["mentions"];
4
+ function makeEvent(
5
+ text: string,
6
+ mentions?: Array<{ key: string; name: string; id: { open_id?: string; user_id?: string } }>,
7
+ chatType: "p2p" | "group" = "p2p",
8
+ ) {
9
+ return {
10
+ sender: { sender_id: { user_id: "u1", open_id: "ou_sender" } },
11
+ message: {
12
+ message_id: "msg_1",
13
+ chat_id: "oc_chat1",
14
+ chat_type: chatType,
15
+ message_type: "text",
16
+ content: JSON.stringify({ text }),
17
+ mentions,
18
+ },
19
+ };
20
+ }
5
21
 
6
- describe("stripBotMention", () => {
22
+ const BOT_OPEN_ID = "ou_bot";
23
+
24
+ describe("normalizeMentions (via parseFeishuMessageEvent)", () => {
7
25
  it("returns original text when mentions are missing", () => {
8
- expect(stripBotMention("hello world", undefined)).toBe("hello world");
26
+ const ctx = parseFeishuMessageEvent(makeEvent("hello world", undefined) as any, BOT_OPEN_ID);
27
+ expect(ctx.content).toBe("hello world");
28
+ });
29
+
30
+ it("strips bot mention in p2p (addressing prefix, not semantic content)", () => {
31
+ const ctx = parseFeishuMessageEvent(
32
+ makeEvent("@_bot_1 hello", [
33
+ { key: "@_bot_1", name: "Bot", id: { open_id: "ou_bot" } },
34
+ ]) as any,
35
+ BOT_OPEN_ID,
36
+ );
37
+ expect(ctx.content).toBe("hello");
38
+ });
39
+
40
+ it("strips bot mention in group so slash commands work (#35994)", () => {
41
+ const ctx = parseFeishuMessageEvent(
42
+ makeEvent(
43
+ "@_bot_1 hello",
44
+ [{ key: "@_bot_1", name: "Bot", id: { open_id: "ou_bot" } }],
45
+ "group",
46
+ ) as any,
47
+ BOT_OPEN_ID,
48
+ );
49
+ expect(ctx.content).toBe("hello");
50
+ });
51
+
52
+ it("strips bot mention in group preserving slash command prefix (#35994)", () => {
53
+ const ctx = parseFeishuMessageEvent(
54
+ makeEvent(
55
+ "@_bot_1 /model",
56
+ [{ key: "@_bot_1", name: "Bot", id: { open_id: "ou_bot" } }],
57
+ "group",
58
+ ) as any,
59
+ BOT_OPEN_ID,
60
+ );
61
+ expect(ctx.content).toBe("/model");
62
+ });
63
+
64
+ it("strips bot mention but normalizes other mentions in p2p (mention-forward)", () => {
65
+ const ctx = parseFeishuMessageEvent(
66
+ makeEvent("@_bot_1 @_user_alice hello", [
67
+ { key: "@_bot_1", name: "Bot", id: { open_id: "ou_bot" } },
68
+ { key: "@_user_alice", name: "Alice", id: { open_id: "ou_alice" } },
69
+ ]) as any,
70
+ BOT_OPEN_ID,
71
+ );
72
+ expect(ctx.content).toBe('<at user_id="ou_alice">Alice</at> hello');
73
+ });
74
+
75
+ it("falls back to @name when open_id is absent", () => {
76
+ const ctx = parseFeishuMessageEvent(
77
+ makeEvent("@_user_1 hi", [
78
+ { key: "@_user_1", name: "Alice", id: { user_id: "uid_alice" } },
79
+ ]) as any,
80
+ BOT_OPEN_ID,
81
+ );
82
+ expect(ctx.content).toBe("@Alice hi");
9
83
  });
10
84
 
11
- it("strips mention name and key for normal mentions", () => {
12
- const mentions: Mentions = [{ key: "@_bot_1", name: "Bot", id: { open_id: "ou_bot" } }];
13
- expect(stripBotMention("@Bot hello @_bot_1", mentions)).toBe("hello");
85
+ it("falls back to plain @name when no id is present", () => {
86
+ const ctx = parseFeishuMessageEvent(
87
+ makeEvent("@_unknown hey", [{ key: "@_unknown", name: "Nobody", id: {} }]) as any,
88
+ BOT_OPEN_ID,
89
+ );
90
+ expect(ctx.content).toBe("@Nobody hey");
14
91
  });
15
92
 
16
- it("treats mention.name regex metacharacters as literal text", () => {
17
- const mentions: Mentions = [{ key: "@_bot_1", name: ".*", id: { open_id: "ou_bot" } }];
18
- expect(stripBotMention("@NotBot hello", mentions)).toBe("@NotBot hello");
93
+ it("treats mention key regex metacharacters as literal text", () => {
94
+ const ctx = parseFeishuMessageEvent(
95
+ makeEvent("hello world", [{ key: ".*", name: "Bot", id: { open_id: "ou_bot" } }]) as any,
96
+ BOT_OPEN_ID,
97
+ );
98
+ expect(ctx.content).toBe("hello world");
19
99
  });
20
100
 
21
- it("treats mention.key regex metacharacters as literal text", () => {
22
- const mentions: Mentions = [{ key: ".*", name: "Bot", id: { open_id: "ou_bot" } }];
23
- expect(stripBotMention("hello world", mentions)).toBe("hello world");
101
+ it("normalizes multiple mentions in one pass", () => {
102
+ const ctx = parseFeishuMessageEvent(
103
+ makeEvent("@_bot_1 hi @_user_2", [
104
+ { key: "@_bot_1", name: "Bot One", id: { open_id: "ou_bot_1" } },
105
+ { key: "@_user_2", name: "User Two", id: { open_id: "ou_user_2" } },
106
+ ]) as any,
107
+ BOT_OPEN_ID,
108
+ );
109
+ expect(ctx.content).toBe(
110
+ '<at user_id="ou_bot_1">Bot One</at> hi <at user_id="ou_user_2">User Two</at>',
111
+ );
24
112
  });
25
113
 
26
- it("trims once after all mention replacements", () => {
27
- const mentions: Mentions = [{ key: "@_bot_1", name: "Bot", id: { open_id: "ou_bot" } }];
28
- expect(stripBotMention(" @_bot_1 hello ", mentions)).toBe("hello");
114
+ it("treats $ in display name as literal (no replacement-pattern interpolation)", () => {
115
+ const ctx = parseFeishuMessageEvent(
116
+ makeEvent("@_user_1 hi", [
117
+ { key: "@_user_1", name: "$& the user", id: { open_id: "ou_x" } },
118
+ ]) as any,
119
+ BOT_OPEN_ID,
120
+ );
121
+ // $ is preserved literally (no $& pattern substitution); & is not escaped in tag body
122
+ expect(ctx.content).toBe('<at user_id="ou_x">$& the user</at> hi');
29
123
  });
30
124
 
31
- it("strips multiple mentions in one pass", () => {
32
- const mentions: Mentions = [
33
- { key: "@_bot_1", name: "Bot One", id: { open_id: "ou_bot_1" } },
34
- { key: "@_bot_2", name: "Bot Two", id: { open_id: "ou_bot_2" } },
35
- ];
36
- expect(stripBotMention("@Bot One @_bot_1 hi @Bot Two @_bot_2", mentions)).toBe("hi");
125
+ it("escapes < and > in mention name to protect tag structure", () => {
126
+ const ctx = parseFeishuMessageEvent(
127
+ makeEvent("@_user_1 test", [
128
+ { key: "@_user_1", name: "<script>", id: { open_id: "ou_x" } },
129
+ ]) as any,
130
+ BOT_OPEN_ID,
131
+ );
132
+ expect(ctx.content).toBe('<at user_id="ou_x">&lt;script&gt;</at> test');
37
133
  });
38
134
  });