@openclaw/feishu 2026.3.12 → 2026.5.1-beta.1

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 (188) hide show
  1. package/api.ts +31 -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/index.ts +70 -53
  6. package/openclaw.plugin.json +1653 -4
  7. package/package.json +32 -7
  8. package/runtime-api.ts +55 -0
  9. package/secret-contract-api.ts +5 -0
  10. package/security-contract-api.ts +1 -0
  11. package/session-key-api.ts +1 -0
  12. package/setup-api.ts +3 -0
  13. package/setup-entry.test.ts +14 -0
  14. package/setup-entry.ts +13 -0
  15. package/src/accounts.test.ts +115 -22
  16. package/src/accounts.ts +199 -117
  17. package/src/app-registration.ts +331 -0
  18. package/src/approval-auth.test.ts +24 -0
  19. package/src/approval-auth.ts +25 -0
  20. package/src/async.test.ts +35 -0
  21. package/src/async.ts +43 -1
  22. package/src/audio-preflight.runtime.ts +9 -0
  23. package/src/bitable.test.ts +131 -0
  24. package/src/bitable.ts +59 -22
  25. package/src/bot-content.ts +474 -0
  26. package/src/bot-group-name.test.ts +108 -0
  27. package/src/bot-runtime-api.ts +12 -0
  28. package/src/bot-sender-name.ts +125 -0
  29. package/src/bot.broadcast.test.ts +463 -0
  30. package/src/bot.card-action.test.ts +519 -5
  31. package/src/bot.checkBotMentioned.test.ts +92 -20
  32. package/src/bot.helpers.test.ts +118 -0
  33. package/src/bot.stripBotMention.test.ts +13 -21
  34. package/src/bot.test.ts +1334 -401
  35. package/src/bot.ts +798 -786
  36. package/src/card-action.ts +408 -40
  37. package/src/card-interaction.test.ts +129 -0
  38. package/src/card-interaction.ts +159 -0
  39. package/src/card-test-helpers.ts +47 -0
  40. package/src/card-ux-approval.ts +65 -0
  41. package/src/card-ux-launcher.test.ts +99 -0
  42. package/src/card-ux-launcher.ts +121 -0
  43. package/src/card-ux-shared.ts +33 -0
  44. package/src/channel-runtime-api.ts +16 -0
  45. package/src/channel.runtime.ts +47 -0
  46. package/src/channel.test.ts +914 -3
  47. package/src/channel.ts +1252 -309
  48. package/src/chat-schema.ts +5 -4
  49. package/src/chat.test.ts +84 -28
  50. package/src/chat.ts +68 -10
  51. package/src/client.test.ts +212 -103
  52. package/src/client.ts +115 -21
  53. package/src/comment-dispatcher-runtime-api.ts +6 -0
  54. package/src/comment-dispatcher.test.ts +169 -0
  55. package/src/comment-dispatcher.ts +107 -0
  56. package/src/comment-handler-runtime-api.ts +3 -0
  57. package/src/comment-handler.test.ts +486 -0
  58. package/src/comment-handler.ts +309 -0
  59. package/src/comment-reaction.test.ts +166 -0
  60. package/src/comment-reaction.ts +259 -0
  61. package/src/comment-shared.test.ts +182 -0
  62. package/src/comment-shared.ts +365 -0
  63. package/src/comment-target.ts +44 -0
  64. package/src/config-schema.test.ts +77 -25
  65. package/src/config-schema.ts +31 -4
  66. package/src/conversation-id.test.ts +18 -0
  67. package/src/conversation-id.ts +199 -0
  68. package/src/dedup-runtime-api.ts +1 -0
  69. package/src/dedup.ts +76 -35
  70. package/src/directory.static.ts +61 -0
  71. package/src/directory.test.ts +119 -20
  72. package/src/directory.ts +61 -91
  73. package/src/doc-schema.ts +1 -1
  74. package/src/docx-batch-insert.test.ts +39 -38
  75. package/src/docx-batch-insert.ts +55 -19
  76. package/src/docx-color-text.ts +9 -4
  77. package/src/docx-table-ops.test.ts +53 -0
  78. package/src/docx-table-ops.ts +52 -34
  79. package/src/docx-types.ts +38 -0
  80. package/src/docx.account-selection.test.ts +12 -3
  81. package/src/docx.test.ts +314 -74
  82. package/src/docx.ts +278 -122
  83. package/src/drive-schema.ts +47 -1
  84. package/src/drive.test.ts +1219 -0
  85. package/src/drive.ts +614 -13
  86. package/src/dynamic-agent.ts +10 -4
  87. package/src/event-types.ts +45 -0
  88. package/src/external-keys.ts +1 -1
  89. package/src/lifecycle.test-support.ts +220 -0
  90. package/src/media.test.ts +413 -87
  91. package/src/media.ts +488 -154
  92. package/src/mention-target.types.ts +5 -0
  93. package/src/mention.ts +32 -51
  94. package/src/message-action-contract.ts +13 -0
  95. package/src/monitor-state-runtime-api.ts +7 -0
  96. package/src/monitor-transport-runtime-api.ts +7 -0
  97. package/src/monitor.account.ts +220 -313
  98. package/src/monitor.acp-init-failure.lifecycle.test-support.ts +219 -0
  99. package/src/monitor.bot-identity.ts +86 -0
  100. package/src/monitor.bot-menu-handler.ts +165 -0
  101. package/src/monitor.bot-menu.lifecycle.test-support.ts +224 -0
  102. package/src/monitor.bot-menu.test.ts +178 -0
  103. package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +264 -0
  104. package/src/monitor.card-action.lifecycle.test-support.ts +373 -0
  105. package/src/monitor.cleanup.test.ts +376 -0
  106. package/src/monitor.comment-notice-handler.ts +105 -0
  107. package/src/monitor.comment.test.ts +937 -0
  108. package/src/monitor.comment.ts +1386 -0
  109. package/src/monitor.lifecycle.test.ts +4 -0
  110. package/src/monitor.message-handler.ts +339 -0
  111. package/src/monitor.reaction.lifecycle.test-support.ts +68 -0
  112. package/src/monitor.reaction.test.ts +194 -92
  113. package/src/monitor.reply-once.lifecycle.test-support.ts +190 -0
  114. package/src/monitor.startup.test.ts +24 -36
  115. package/src/monitor.startup.ts +26 -16
  116. package/src/monitor.state.ts +20 -5
  117. package/src/monitor.synthetic-error.ts +18 -0
  118. package/src/monitor.test-mocks.ts +2 -2
  119. package/src/monitor.transport.ts +297 -39
  120. package/src/monitor.ts +15 -10
  121. package/src/monitor.webhook-e2e.test.ts +272 -0
  122. package/src/monitor.webhook-security.test.ts +125 -91
  123. package/src/monitor.webhook.test-helpers.ts +116 -0
  124. package/src/outbound-runtime-api.ts +1 -0
  125. package/src/outbound.test.ts +627 -53
  126. package/src/outbound.ts +623 -81
  127. package/src/perm-schema.ts +1 -1
  128. package/src/perm.ts +1 -7
  129. package/src/pins.ts +108 -0
  130. package/src/policy.test.ts +297 -117
  131. package/src/policy.ts +142 -29
  132. package/src/post.ts +7 -6
  133. package/src/probe.test.ts +122 -118
  134. package/src/probe.ts +26 -16
  135. package/src/processing-claims.ts +59 -0
  136. package/src/qr-terminal.ts +1 -0
  137. package/src/reactions.ts +23 -60
  138. package/src/reasoning-preview.test.ts +59 -0
  139. package/src/reasoning-preview.ts +20 -0
  140. package/src/reply-dispatcher-runtime-api.ts +7 -0
  141. package/src/reply-dispatcher.test.ts +721 -168
  142. package/src/reply-dispatcher.ts +422 -172
  143. package/src/runtime.ts +6 -3
  144. package/src/secret-contract.ts +145 -0
  145. package/src/secret-input.ts +1 -13
  146. package/src/security-audit-shared.ts +69 -0
  147. package/src/security-audit.test.ts +61 -0
  148. package/src/security-audit.ts +1 -0
  149. package/src/send-result.ts +1 -1
  150. package/src/send-target.test.ts +9 -3
  151. package/src/send-target.ts +10 -4
  152. package/src/send.reply-fallback.test.ts +127 -42
  153. package/src/send.test.ts +386 -4
  154. package/src/send.ts +486 -164
  155. package/src/sequential-key.test.ts +72 -0
  156. package/src/sequential-key.ts +28 -0
  157. package/src/sequential-queue.test.ts +92 -0
  158. package/src/sequential-queue.ts +16 -0
  159. package/src/session-conversation.ts +42 -0
  160. package/src/session-route.ts +48 -0
  161. package/src/setup-core.ts +51 -0
  162. package/src/{onboarding.test.ts → setup-surface.test.ts} +52 -21
  163. package/src/setup-surface.ts +581 -0
  164. package/src/streaming-card.test.ts +138 -2
  165. package/src/streaming-card.ts +134 -18
  166. package/src/subagent-hooks.test.ts +603 -0
  167. package/src/subagent-hooks.ts +397 -0
  168. package/src/targets.ts +3 -13
  169. package/src/test-support/lifecycle-test-support.ts +479 -0
  170. package/src/thread-bindings.test.ts +143 -0
  171. package/src/thread-bindings.ts +330 -0
  172. package/src/tool-account-routing.test.ts +66 -8
  173. package/src/tool-account.test.ts +44 -0
  174. package/src/tool-account.ts +40 -17
  175. package/src/tool-factory-test-harness.ts +11 -8
  176. package/src/tool-result.ts +3 -1
  177. package/src/tools-config.ts +1 -1
  178. package/src/types.ts +16 -15
  179. package/src/typing.ts +10 -6
  180. package/src/wiki-schema.ts +1 -1
  181. package/src/wiki.ts +1 -7
  182. package/subagent-hooks-api.ts +31 -0
  183. package/tsconfig.json +16 -0
  184. package/src/feishu-command-handler.ts +0 -59
  185. package/src/onboarding.status.test.ts +0 -25
  186. package/src/onboarding.ts +0 -489
  187. package/src/send-message.ts +0 -71
  188. package/src/targets.test.ts +0 -70
@@ -0,0 +1,59 @@
1
+ const EVENT_DEDUP_TTL_MS = 5 * 60 * 1000;
2
+ const EVENT_MEMORY_MAX_SIZE = 2_000;
3
+
4
+ const processingClaims = new Map<string, number>();
5
+
6
+ function resolveEventDedupeKey(
7
+ namespace: string,
8
+ messageId: string | undefined | null,
9
+ ): string | null {
10
+ const trimmed = messageId?.trim();
11
+ return trimmed ? `${namespace}:${trimmed}` : null;
12
+ }
13
+
14
+ function pruneProcessingClaims(now: number): void {
15
+ const cutoff = now - EVENT_DEDUP_TTL_MS;
16
+ for (const [key, seenAt] of processingClaims) {
17
+ if (seenAt < cutoff) {
18
+ processingClaims.delete(key);
19
+ }
20
+ }
21
+ while (processingClaims.size > EVENT_MEMORY_MAX_SIZE) {
22
+ const oldestKey = processingClaims.keys().next().value;
23
+ if (!oldestKey) {
24
+ return;
25
+ }
26
+ processingClaims.delete(oldestKey);
27
+ }
28
+ }
29
+
30
+ export function tryBeginFeishuMessageProcessing(
31
+ messageId: string | undefined | null,
32
+ namespace = "global",
33
+ ): boolean {
34
+ const key = resolveEventDedupeKey(namespace, messageId);
35
+ if (!key) {
36
+ return true;
37
+ }
38
+ const now = Date.now();
39
+ pruneProcessingClaims(now);
40
+ if (processingClaims.has(key)) {
41
+ processingClaims.delete(key);
42
+ processingClaims.set(key, now);
43
+ pruneProcessingClaims(now);
44
+ return false;
45
+ }
46
+ processingClaims.set(key, now);
47
+ pruneProcessingClaims(now);
48
+ return true;
49
+ }
50
+
51
+ export function releaseFeishuMessageProcessing(
52
+ messageId: string | undefined | null,
53
+ namespace = "global",
54
+ ): void {
55
+ const key = resolveEventDedupeKey(namespace, messageId);
56
+ if (key) {
57
+ processingClaims.delete(key);
58
+ }
59
+ }
@@ -0,0 +1 @@
1
+ export { renderQrTerminal } from "openclaw/plugin-sdk/media-runtime";
package/src/reactions.ts CHANGED
@@ -1,14 +1,28 @@
1
- import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
2
- import { resolveFeishuAccount } from "./accounts.js";
1
+ import type { ClawdbotConfig } from "../runtime-api.js";
2
+ import { resolveFeishuRuntimeAccount } from "./accounts.js";
3
3
  import { createFeishuClient } from "./client.js";
4
4
 
5
- export type FeishuReaction = {
5
+ type FeishuReaction = {
6
6
  reactionId: string;
7
7
  emojiType: string;
8
8
  operatorType: "app" | "user";
9
9
  operatorId: string;
10
10
  };
11
11
 
12
+ function resolveConfiguredFeishuClient(params: { cfg: ClawdbotConfig; accountId?: string }) {
13
+ const account = resolveFeishuRuntimeAccount(params);
14
+ if (!account.configured) {
15
+ throw new Error(`Feishu account "${account.accountId}" not configured`);
16
+ }
17
+ return createFeishuClient(account);
18
+ }
19
+
20
+ function assertFeishuReactionApiSuccess(response: { code?: number; msg?: string }, action: string) {
21
+ if (response.code !== 0) {
22
+ throw new Error(`Feishu ${action} failed: ${response.msg || `code ${response.code}`}`);
23
+ }
24
+ }
25
+
12
26
  /**
13
27
  * Add a reaction (emoji) to a message.
14
28
  * @param emojiType - Feishu emoji type, e.g., "SMILE", "THUMBSUP", "HEART"
@@ -21,12 +35,7 @@ export async function addReactionFeishu(params: {
21
35
  accountId?: string;
22
36
  }): Promise<{ reactionId: string }> {
23
37
  const { cfg, messageId, emojiType, accountId } = params;
24
- const account = resolveFeishuAccount({ cfg, accountId });
25
- if (!account.configured) {
26
- throw new Error(`Feishu account "${account.accountId}" not configured`);
27
- }
28
-
29
- const client = createFeishuClient(account);
38
+ const client = resolveConfiguredFeishuClient({ cfg, accountId });
30
39
 
31
40
  const response = (await client.im.messageReaction.create({
32
41
  path: { message_id: messageId },
@@ -41,9 +50,7 @@ export async function addReactionFeishu(params: {
41
50
  data?: { reaction_id?: string };
42
51
  };
43
52
 
44
- if (response.code !== 0) {
45
- throw new Error(`Feishu add reaction failed: ${response.msg || `code ${response.code}`}`);
46
- }
53
+ assertFeishuReactionApiSuccess(response, "add reaction");
47
54
 
48
55
  const reactionId = response.data?.reaction_id;
49
56
  if (!reactionId) {
@@ -63,12 +70,7 @@ export async function removeReactionFeishu(params: {
63
70
  accountId?: string;
64
71
  }): Promise<void> {
65
72
  const { cfg, messageId, reactionId, accountId } = params;
66
- const account = resolveFeishuAccount({ cfg, accountId });
67
- if (!account.configured) {
68
- throw new Error(`Feishu account "${account.accountId}" not configured`);
69
- }
70
-
71
- const client = createFeishuClient(account);
73
+ const client = resolveConfiguredFeishuClient({ cfg, accountId });
72
74
 
73
75
  const response = (await client.im.messageReaction.delete({
74
76
  path: {
@@ -77,9 +79,7 @@ export async function removeReactionFeishu(params: {
77
79
  },
78
80
  })) as { code?: number; msg?: string };
79
81
 
80
- if (response.code !== 0) {
81
- throw new Error(`Feishu remove reaction failed: ${response.msg || `code ${response.code}`}`);
82
- }
82
+ assertFeishuReactionApiSuccess(response, "remove reaction");
83
83
  }
84
84
 
85
85
  /**
@@ -92,12 +92,7 @@ export async function listReactionsFeishu(params: {
92
92
  accountId?: string;
93
93
  }): Promise<FeishuReaction[]> {
94
94
  const { cfg, messageId, emojiType, accountId } = params;
95
- const account = resolveFeishuAccount({ cfg, accountId });
96
- if (!account.configured) {
97
- throw new Error(`Feishu account "${account.accountId}" not configured`);
98
- }
99
-
100
- const client = createFeishuClient(account);
95
+ const client = resolveConfiguredFeishuClient({ cfg, accountId });
101
96
 
102
97
  const response = (await client.im.messageReaction.list({
103
98
  path: { message_id: messageId },
@@ -115,9 +110,7 @@ export async function listReactionsFeishu(params: {
115
110
  };
116
111
  };
117
112
 
118
- if (response.code !== 0) {
119
- throw new Error(`Feishu list reactions failed: ${response.msg || `code ${response.code}`}`);
120
- }
113
+ assertFeishuReactionApiSuccess(response, "list reactions");
121
114
 
122
115
  const items = response.data?.items ?? [];
123
116
  return items.map((item) => ({
@@ -128,33 +121,3 @@ export async function listReactionsFeishu(params: {
128
121
  item.operator_id?.open_id ?? item.operator_id?.user_id ?? item.operator_id?.union_id ?? "",
129
122
  }));
130
123
  }
131
-
132
- /**
133
- * Common Feishu emoji types for convenience.
134
- * @see https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce
135
- */
136
- export const FeishuEmoji = {
137
- // Common reactions
138
- THUMBSUP: "THUMBSUP",
139
- THUMBSDOWN: "THUMBSDOWN",
140
- HEART: "HEART",
141
- SMILE: "SMILE",
142
- GRINNING: "GRINNING",
143
- LAUGHING: "LAUGHING",
144
- CRY: "CRY",
145
- ANGRY: "ANGRY",
146
- SURPRISED: "SURPRISED",
147
- THINKING: "THINKING",
148
- CLAP: "CLAP",
149
- OK: "OK",
150
- FIST: "FIST",
151
- PRAY: "PRAY",
152
- FIRE: "FIRE",
153
- PARTY: "PARTY",
154
- CHECK: "CHECK",
155
- CROSS: "CROSS",
156
- QUESTION: "QUESTION",
157
- EXCLAMATION: "EXCLAMATION",
158
- } as const;
159
-
160
- export type FeishuEmojiType = (typeof FeishuEmoji)[keyof typeof FeishuEmoji];
@@ -0,0 +1,59 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { resolveFeishuReasoningPreviewEnabled } from "./reasoning-preview.js";
3
+
4
+ const { loadSessionStoreMock } = vi.hoisted(() => ({
5
+ loadSessionStoreMock: vi.fn(),
6
+ }));
7
+
8
+ vi.mock("./bot-runtime-api.js", async () => {
9
+ const actual =
10
+ await vi.importActual<typeof import("./bot-runtime-api.js")>("./bot-runtime-api.js");
11
+ return {
12
+ ...actual,
13
+ loadSessionStore: loadSessionStoreMock,
14
+ };
15
+ });
16
+
17
+ describe("resolveFeishuReasoningPreviewEnabled", () => {
18
+ beforeEach(() => {
19
+ vi.clearAllMocks();
20
+ });
21
+
22
+ it("enables previews only for stream reasoning sessions", () => {
23
+ loadSessionStoreMock.mockReturnValue({
24
+ "agent:main:feishu:dm:ou_sender_1": { reasoningLevel: "stream" },
25
+ "agent:main:feishu:dm:ou_sender_2": { reasoningLevel: "on" },
26
+ });
27
+
28
+ expect(
29
+ resolveFeishuReasoningPreviewEnabled({
30
+ storePath: "/tmp/feishu-sessions.json",
31
+ sessionKey: "agent:main:feishu:dm:ou_sender_1",
32
+ }),
33
+ ).toBe(true);
34
+ expect(
35
+ resolveFeishuReasoningPreviewEnabled({
36
+ storePath: "/tmp/feishu-sessions.json",
37
+ sessionKey: "agent:main:feishu:dm:ou_sender_2",
38
+ }),
39
+ ).toBe(false);
40
+ });
41
+
42
+ it("returns false for missing sessions or load failures", () => {
43
+ loadSessionStoreMock.mockImplementationOnce(() => {
44
+ throw new Error("disk unavailable");
45
+ });
46
+
47
+ expect(
48
+ resolveFeishuReasoningPreviewEnabled({
49
+ storePath: "/tmp/feishu-sessions.json",
50
+ sessionKey: "agent:main:feishu:dm:ou_sender_1",
51
+ }),
52
+ ).toBe(false);
53
+ expect(
54
+ resolveFeishuReasoningPreviewEnabled({
55
+ storePath: "/tmp/feishu-sessions.json",
56
+ }),
57
+ ).toBe(false);
58
+ });
59
+ });
@@ -0,0 +1,20 @@
1
+ import { loadSessionStore, resolveSessionStoreEntry } from "./bot-runtime-api.js";
2
+
3
+ export function resolveFeishuReasoningPreviewEnabled(params: {
4
+ storePath: string;
5
+ sessionKey?: string;
6
+ }): boolean {
7
+ if (!params.sessionKey) {
8
+ return false;
9
+ }
10
+
11
+ try {
12
+ const store = loadSessionStore(params.storePath, { skipCache: true });
13
+ return (
14
+ resolveSessionStoreEntry({ store, sessionKey: params.sessionKey }).existing
15
+ ?.reasoningLevel === "stream"
16
+ );
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ createReplyPrefixContext,
3
+ type ClawdbotConfig,
4
+ type OutboundIdentity,
5
+ type ReplyPayload,
6
+ type RuntimeEnv,
7
+ } from "../runtime-api.js";