@openclaw/feishu 2026.5.2 → 2026.5.3-beta.2
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.
- package/dist/accounts-Ba3-WP1z.js +423 -0
- package/dist/api.js +2280 -0
- package/dist/app-registration-B8qc1MCM.js +184 -0
- package/dist/audio-preflight.runtime-BPlzkO3l.js +7 -0
- package/dist/card-interaction-BfRLgvw_.js +96 -0
- package/dist/channel-CSD_Jt8I.js +1668 -0
- package/dist/channel-entry.js +22 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-DYsXcD36.js +700 -0
- package/dist/client-DBVoQL5w.js +157 -0
- package/dist/contract-api.js +9 -0
- package/dist/conversation-id-DWS3Ep2A.js +139 -0
- package/dist/directory.static-f3EeoRJd.js +44 -0
- package/dist/drive-C5eJLJr7.js +883 -0
- package/dist/index.js +68 -0
- package/dist/monitor-CT189QfR.js +60 -0
- package/dist/monitor.account-dJV2jO8C.js +4990 -0
- package/dist/monitor.state-DYM02ipp.js +100 -0
- package/dist/policy-D6c-wMPl.js +118 -0
- package/dist/probe-BNzzU_uR.js +149 -0
- package/dist/rolldown-runtime-DUslC3ob.js +14 -0
- package/dist/runtime-CG0DuRCy.js +8 -0
- package/dist/runtime-api.js +14 -0
- package/dist/secret-contract-Dm4Z_zQN.js +119 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/security-audit-DqJdocrN.js +11 -0
- package/dist/security-audit-shared-ByuMx9cJ.js +38 -0
- package/dist/security-contract-api.js +2 -0
- package/dist/send-DowxxbpH.js +1218 -0
- package/dist/session-conversation-B4nrW-vo.js +27 -0
- package/dist/session-key-api.js +2 -0
- package/dist/setup-api.js +2 -0
- package/dist/setup-entry.js +15 -0
- package/dist/subagent-hooks-C3UhPVLV.js +227 -0
- package/dist/subagent-hooks-api.js +23 -0
- package/dist/targets-JMFJRKSe.js +48 -0
- package/dist/thread-bindings-BmS6TLes.js +222 -0
- package/package.json +15 -6
- package/api.ts +0 -31
- package/channel-entry.ts +0 -20
- package/channel-plugin-api.ts +0 -1
- package/contract-api.ts +0 -16
- package/index.ts +0 -82
- package/runtime-api.ts +0 -55
- package/secret-contract-api.ts +0 -5
- package/security-contract-api.ts +0 -1
- package/session-key-api.ts +0 -1
- package/setup-api.ts +0 -3
- package/setup-entry.test.ts +0 -14
- package/setup-entry.ts +0 -13
- package/src/accounts.test.ts +0 -459
- package/src/accounts.ts +0 -326
- package/src/app-registration.ts +0 -331
- package/src/approval-auth.test.ts +0 -24
- package/src/approval-auth.ts +0 -25
- package/src/async.test.ts +0 -35
- package/src/async.ts +0 -104
- package/src/audio-preflight.runtime.ts +0 -9
- package/src/bitable.test.ts +0 -131
- package/src/bitable.ts +0 -762
- package/src/bot-content.ts +0 -474
- package/src/bot-group-name.test.ts +0 -108
- package/src/bot-runtime-api.ts +0 -12
- package/src/bot-sender-name.ts +0 -125
- package/src/bot.broadcast.test.ts +0 -463
- package/src/bot.card-action.test.ts +0 -577
- package/src/bot.checkBotMentioned.test.ts +0 -265
- package/src/bot.helpers.test.ts +0 -118
- package/src/bot.stripBotMention.test.ts +0 -126
- package/src/bot.test.ts +0 -3040
- package/src/bot.ts +0 -1559
- package/src/card-action.ts +0 -447
- package/src/card-interaction.test.ts +0 -129
- package/src/card-interaction.ts +0 -159
- package/src/card-test-helpers.ts +0 -47
- package/src/card-ux-approval.ts +0 -65
- package/src/card-ux-launcher.test.ts +0 -99
- package/src/card-ux-launcher.ts +0 -121
- package/src/card-ux-shared.ts +0 -33
- package/src/channel-runtime-api.ts +0 -16
- package/src/channel.runtime.ts +0 -47
- package/src/channel.test.ts +0 -959
- package/src/channel.ts +0 -1313
- package/src/chat-schema.ts +0 -25
- package/src/chat.test.ts +0 -196
- package/src/chat.ts +0 -188
- package/src/client.test.ts +0 -433
- package/src/client.ts +0 -290
- package/src/comment-dispatcher-runtime-api.ts +0 -6
- package/src/comment-dispatcher.test.ts +0 -169
- package/src/comment-dispatcher.ts +0 -107
- package/src/comment-handler-runtime-api.ts +0 -3
- package/src/comment-handler.test.ts +0 -486
- package/src/comment-handler.ts +0 -309
- package/src/comment-reaction.test.ts +0 -166
- package/src/comment-reaction.ts +0 -259
- package/src/comment-shared.test.ts +0 -182
- package/src/comment-shared.ts +0 -406
- package/src/comment-target.ts +0 -44
- package/src/config-schema.test.ts +0 -309
- package/src/config-schema.ts +0 -333
- package/src/conversation-id.test.ts +0 -18
- package/src/conversation-id.ts +0 -199
- package/src/dedup-runtime-api.ts +0 -1
- package/src/dedup.ts +0 -141
- package/src/directory.static.ts +0 -61
- package/src/directory.test.ts +0 -136
- package/src/directory.ts +0 -124
- package/src/doc-schema.ts +0 -182
- package/src/docx-batch-insert.test.ts +0 -91
- package/src/docx-batch-insert.ts +0 -223
- package/src/docx-color-text.ts +0 -154
- package/src/docx-table-ops.test.ts +0 -53
- package/src/docx-table-ops.ts +0 -316
- package/src/docx-types.ts +0 -38
- package/src/docx.account-selection.test.ts +0 -79
- package/src/docx.test.ts +0 -685
- package/src/docx.ts +0 -1616
- package/src/drive-schema.ts +0 -92
- package/src/drive.test.ts +0 -1219
- package/src/drive.ts +0 -829
- package/src/dynamic-agent.ts +0 -137
- package/src/event-types.ts +0 -45
- package/src/external-keys.test.ts +0 -20
- package/src/external-keys.ts +0 -19
- package/src/lifecycle.test-support.ts +0 -220
- package/src/media.test.ts +0 -900
- package/src/media.ts +0 -861
- package/src/mention-target.types.ts +0 -5
- package/src/mention.ts +0 -114
- package/src/message-action-contract.ts +0 -13
- package/src/monitor-state-runtime-api.ts +0 -7
- package/src/monitor-transport-runtime-api.ts +0 -7
- package/src/monitor.account.ts +0 -468
- package/src/monitor.acp-init-failure.lifecycle.test-support.ts +0 -219
- package/src/monitor.bot-identity.ts +0 -86
- package/src/monitor.bot-menu-handler.ts +0 -165
- package/src/monitor.bot-menu.lifecycle.test-support.ts +0 -224
- package/src/monitor.bot-menu.test.ts +0 -178
- package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +0 -264
- package/src/monitor.card-action.lifecycle.test-support.ts +0 -373
- package/src/monitor.cleanup.test.ts +0 -376
- package/src/monitor.comment-notice-handler.ts +0 -105
- package/src/monitor.comment.test.ts +0 -937
- package/src/monitor.comment.ts +0 -1386
- package/src/monitor.lifecycle.test.ts +0 -4
- package/src/monitor.message-handler.ts +0 -339
- package/src/monitor.reaction.lifecycle.test-support.ts +0 -68
- package/src/monitor.reaction.test.ts +0 -713
- package/src/monitor.startup.test.ts +0 -192
- package/src/monitor.startup.ts +0 -74
- package/src/monitor.state.defaults.test.ts +0 -46
- package/src/monitor.state.ts +0 -170
- package/src/monitor.synthetic-error.ts +0 -18
- package/src/monitor.test-mocks.ts +0 -45
- package/src/monitor.transport.ts +0 -424
- package/src/monitor.ts +0 -100
- package/src/monitor.webhook-e2e.test.ts +0 -272
- package/src/monitor.webhook-security.test.ts +0 -264
- package/src/monitor.webhook.test-helpers.ts +0 -116
- package/src/outbound-runtime-api.ts +0 -1
- package/src/outbound.test.ts +0 -935
- package/src/outbound.ts +0 -718
- package/src/perm-schema.ts +0 -52
- package/src/perm.ts +0 -170
- package/src/pins.ts +0 -108
- package/src/policy.test.ts +0 -334
- package/src/policy.ts +0 -236
- package/src/post.test.ts +0 -105
- package/src/post.ts +0 -275
- package/src/probe.test.ts +0 -275
- package/src/probe.ts +0 -166
- package/src/processing-claims.ts +0 -59
- package/src/qr-terminal.ts +0 -1
- package/src/reactions.ts +0 -123
- package/src/reasoning-preview.test.ts +0 -59
- package/src/reasoning-preview.ts +0 -20
- package/src/reply-dispatcher-runtime-api.ts +0 -7
- package/src/reply-dispatcher.test.ts +0 -1144
- package/src/reply-dispatcher.ts +0 -650
- package/src/runtime.ts +0 -9
- package/src/secret-contract.ts +0 -145
- package/src/secret-input.ts +0 -1
- package/src/security-audit-shared.ts +0 -69
- package/src/security-audit.test.ts +0 -61
- package/src/security-audit.ts +0 -1
- package/src/send-result.ts +0 -29
- package/src/send-target.test.ts +0 -80
- package/src/send-target.ts +0 -35
- package/src/send.reply-fallback.test.ts +0 -292
- package/src/send.test.ts +0 -550
- package/src/send.ts +0 -800
- package/src/sequential-key.test.ts +0 -72
- package/src/sequential-key.ts +0 -28
- package/src/sequential-queue.test.ts +0 -92
- package/src/sequential-queue.ts +0 -16
- package/src/session-conversation.ts +0 -42
- package/src/session-route.ts +0 -48
- package/src/setup-core.ts +0 -51
- package/src/setup-surface.test.ts +0 -174
- package/src/setup-surface.ts +0 -581
- package/src/streaming-card.test.ts +0 -190
- package/src/streaming-card.ts +0 -490
- package/src/subagent-hooks.test.ts +0 -603
- package/src/subagent-hooks.ts +0 -397
- package/src/targets.ts +0 -97
- package/src/test-support/lifecycle-test-support.ts +0 -453
- package/src/thread-bindings.test.ts +0 -143
- package/src/thread-bindings.ts +0 -330
- package/src/tool-account-routing.test.ts +0 -187
- package/src/tool-account.test.ts +0 -44
- package/src/tool-account.ts +0 -93
- package/src/tool-factory-test-harness.ts +0 -79
- package/src/tool-result.test.ts +0 -32
- package/src/tool-result.ts +0 -16
- package/src/tools-config.test.ts +0 -21
- package/src/tools-config.ts +0 -22
- package/src/types.ts +0 -104
- package/src/typing.test.ts +0 -144
- package/src/typing.ts +0 -214
- package/src/wiki-schema.ts +0 -55
- package/src/wiki.ts +0 -227
- package/subagent-hooks-api.ts +0 -31
- package/tsconfig.json +0 -16
package/src/bot-content.ts
DELETED
|
@@ -1,474 +0,0 @@
|
|
|
1
|
-
import type { ClawdbotConfig } from "../runtime-api.js";
|
|
2
|
-
import { buildFeishuConversationId } from "./conversation-id.js";
|
|
3
|
-
import { normalizeFeishuExternalKey } from "./external-keys.js";
|
|
4
|
-
import { downloadMessageResourceFeishu } from "./media.js";
|
|
5
|
-
import { isFeishuBroadcastMention } from "./mention.js";
|
|
6
|
-
import { parsePostContent } from "./post.js";
|
|
7
|
-
import { getFeishuRuntime } from "./runtime.js";
|
|
8
|
-
import type { FeishuChatType, FeishuMediaInfo } from "./types.js";
|
|
9
|
-
|
|
10
|
-
type FeishuMention = {
|
|
11
|
-
key: string;
|
|
12
|
-
id: {
|
|
13
|
-
open_id?: string;
|
|
14
|
-
user_id?: string;
|
|
15
|
-
union_id?: string;
|
|
16
|
-
};
|
|
17
|
-
name: string;
|
|
18
|
-
tenant_key?: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
type FeishuMessageLike = {
|
|
22
|
-
message: {
|
|
23
|
-
content: string;
|
|
24
|
-
message_type: string;
|
|
25
|
-
mentions?: FeishuMention[];
|
|
26
|
-
chat_id: string;
|
|
27
|
-
root_id?: string;
|
|
28
|
-
parent_id?: string;
|
|
29
|
-
thread_id?: string;
|
|
30
|
-
message_id: string;
|
|
31
|
-
};
|
|
32
|
-
sender: {
|
|
33
|
-
sender_id: {
|
|
34
|
-
open_id?: string;
|
|
35
|
-
user_id?: string;
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type GroupSessionScope = "group" | "group_sender" | "group_topic" | "group_topic_sender";
|
|
41
|
-
|
|
42
|
-
type FeishuLogger = (...args: unknown[]) => void;
|
|
43
|
-
|
|
44
|
-
type ResolvedFeishuGroupSession = {
|
|
45
|
-
peerId: string;
|
|
46
|
-
parentPeer: { kind: "group"; id: string } | null;
|
|
47
|
-
groupSessionScope: GroupSessionScope;
|
|
48
|
-
replyInThread: boolean;
|
|
49
|
-
threadReply: boolean;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export function resolveFeishuGroupSession(params: {
|
|
53
|
-
chatId: string;
|
|
54
|
-
senderOpenId: string;
|
|
55
|
-
messageId: string;
|
|
56
|
-
rootId?: string;
|
|
57
|
-
threadId?: string;
|
|
58
|
-
chatType?: FeishuChatType;
|
|
59
|
-
groupConfig?: {
|
|
60
|
-
groupSessionScope?: GroupSessionScope;
|
|
61
|
-
topicSessionMode?: "enabled" | "disabled";
|
|
62
|
-
replyInThread?: "enabled" | "disabled";
|
|
63
|
-
};
|
|
64
|
-
feishuCfg?: {
|
|
65
|
-
groupSessionScope?: GroupSessionScope;
|
|
66
|
-
topicSessionMode?: "enabled" | "disabled";
|
|
67
|
-
replyInThread?: "enabled" | "disabled";
|
|
68
|
-
};
|
|
69
|
-
}): ResolvedFeishuGroupSession {
|
|
70
|
-
const { chatId, senderOpenId, messageId, rootId, threadId, chatType, groupConfig, feishuCfg } =
|
|
71
|
-
params;
|
|
72
|
-
const normalizedThreadId = threadId?.trim();
|
|
73
|
-
const normalizedRootId = rootId?.trim();
|
|
74
|
-
const threadReply = Boolean(normalizedThreadId || normalizedRootId);
|
|
75
|
-
const replyInThread =
|
|
76
|
-
(groupConfig?.replyInThread ?? feishuCfg?.replyInThread ?? "disabled") === "enabled" ||
|
|
77
|
-
threadReply;
|
|
78
|
-
const legacyTopicSessionMode =
|
|
79
|
-
groupConfig?.topicSessionMode ?? feishuCfg?.topicSessionMode ?? "disabled";
|
|
80
|
-
const groupSessionScope: GroupSessionScope =
|
|
81
|
-
groupConfig?.groupSessionScope ??
|
|
82
|
-
feishuCfg?.groupSessionScope ??
|
|
83
|
-
(legacyTopicSessionMode === "enabled" ? "group_topic" : "group");
|
|
84
|
-
const normalizedTopicGroupThreadId =
|
|
85
|
-
chatType === "topic_group" ? (normalizedThreadId ?? normalizedRootId) : undefined;
|
|
86
|
-
const topicScope =
|
|
87
|
-
groupSessionScope === "group_topic" || groupSessionScope === "group_topic_sender"
|
|
88
|
-
? (normalizedTopicGroupThreadId ??
|
|
89
|
-
normalizedRootId ??
|
|
90
|
-
normalizedThreadId ??
|
|
91
|
-
(replyInThread ? messageId : null))
|
|
92
|
-
: null;
|
|
93
|
-
|
|
94
|
-
let peerId = chatId;
|
|
95
|
-
switch (groupSessionScope) {
|
|
96
|
-
case "group_sender":
|
|
97
|
-
peerId = buildFeishuConversationId({ chatId, scope: "group_sender", senderOpenId });
|
|
98
|
-
break;
|
|
99
|
-
case "group_topic":
|
|
100
|
-
peerId = topicScope
|
|
101
|
-
? buildFeishuConversationId({ chatId, scope: "group_topic", topicId: topicScope })
|
|
102
|
-
: chatId;
|
|
103
|
-
break;
|
|
104
|
-
case "group_topic_sender":
|
|
105
|
-
peerId = topicScope
|
|
106
|
-
? buildFeishuConversationId({
|
|
107
|
-
chatId,
|
|
108
|
-
scope: "group_topic_sender",
|
|
109
|
-
topicId: topicScope,
|
|
110
|
-
senderOpenId,
|
|
111
|
-
})
|
|
112
|
-
: buildFeishuConversationId({ chatId, scope: "group_sender", senderOpenId });
|
|
113
|
-
break;
|
|
114
|
-
case "group":
|
|
115
|
-
default:
|
|
116
|
-
peerId = chatId;
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
peerId,
|
|
122
|
-
parentPeer:
|
|
123
|
-
topicScope &&
|
|
124
|
-
(groupSessionScope === "group_topic" || groupSessionScope === "group_topic_sender")
|
|
125
|
-
? { kind: "group", id: chatId }
|
|
126
|
-
: null,
|
|
127
|
-
groupSessionScope,
|
|
128
|
-
replyInThread,
|
|
129
|
-
threadReply,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function parseMessageContent(content: string, messageType: string): string {
|
|
134
|
-
if (messageType === "post") {
|
|
135
|
-
return parsePostContent(content).textContent;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
const parsed = JSON.parse(content);
|
|
140
|
-
if (messageType === "text") {
|
|
141
|
-
return parsed.text || "";
|
|
142
|
-
}
|
|
143
|
-
if (["image", "file", "audio", "video", "media", "sticker"].includes(messageType)) {
|
|
144
|
-
if (messageType === "audio") {
|
|
145
|
-
const speechToText =
|
|
146
|
-
typeof parsed.speech_to_text === "string" ? parsed.speech_to_text.trim() : "";
|
|
147
|
-
if (speechToText) {
|
|
148
|
-
return speechToText;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
const placeholder = inferPlaceholder(messageType);
|
|
152
|
-
const fileName = typeof parsed.file_name === "string" ? parsed.file_name.trim() : "";
|
|
153
|
-
return fileName ? `${placeholder} (${fileName})` : placeholder;
|
|
154
|
-
}
|
|
155
|
-
if (messageType === "share_chat") {
|
|
156
|
-
if (parsed && typeof parsed === "object") {
|
|
157
|
-
const share = parsed as { body?: unknown; summary?: unknown; share_chat_id?: unknown };
|
|
158
|
-
if (typeof share.body === "string" && share.body.trim()) {
|
|
159
|
-
return share.body.trim();
|
|
160
|
-
}
|
|
161
|
-
if (typeof share.summary === "string" && share.summary.trim()) {
|
|
162
|
-
return share.summary.trim();
|
|
163
|
-
}
|
|
164
|
-
if (typeof share.share_chat_id === "string" && share.share_chat_id.trim()) {
|
|
165
|
-
return `[Forwarded message: ${share.share_chat_id.trim()}]`;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return "[Forwarded message]";
|
|
169
|
-
}
|
|
170
|
-
if (messageType === "merge_forward") {
|
|
171
|
-
return "[Merged and Forwarded Message - loading...]";
|
|
172
|
-
}
|
|
173
|
-
return content;
|
|
174
|
-
} catch {
|
|
175
|
-
return content;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function formatSubMessageContent(content: string, contentType: string): string {
|
|
180
|
-
try {
|
|
181
|
-
const parsed = JSON.parse(content);
|
|
182
|
-
switch (contentType) {
|
|
183
|
-
case "text":
|
|
184
|
-
return parsed.text || content;
|
|
185
|
-
case "post":
|
|
186
|
-
return parsePostContent(content).textContent;
|
|
187
|
-
case "image":
|
|
188
|
-
return "[Image]";
|
|
189
|
-
case "file":
|
|
190
|
-
return `[File: ${parsed.file_name || "unknown"}]`;
|
|
191
|
-
case "audio":
|
|
192
|
-
return "[Audio]";
|
|
193
|
-
case "video":
|
|
194
|
-
return "[Video]";
|
|
195
|
-
case "sticker":
|
|
196
|
-
return "[Sticker]";
|
|
197
|
-
case "merge_forward":
|
|
198
|
-
return "[Nested Merged Forward]";
|
|
199
|
-
default:
|
|
200
|
-
return `[${contentType}]`;
|
|
201
|
-
}
|
|
202
|
-
} catch {
|
|
203
|
-
return content;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export function parseMergeForwardContent(params: { content: string; log?: FeishuLogger }): string {
|
|
208
|
-
const { content, log } = params;
|
|
209
|
-
const maxMessages = 50;
|
|
210
|
-
log?.("feishu: parsing merge_forward sub-messages from API response");
|
|
211
|
-
|
|
212
|
-
let items: Array<{
|
|
213
|
-
message_id?: string;
|
|
214
|
-
msg_type?: string;
|
|
215
|
-
body?: { content?: string };
|
|
216
|
-
sender?: { id?: string };
|
|
217
|
-
upper_message_id?: string;
|
|
218
|
-
create_time?: string;
|
|
219
|
-
}>;
|
|
220
|
-
try {
|
|
221
|
-
items = JSON.parse(content);
|
|
222
|
-
} catch {
|
|
223
|
-
log?.("feishu: merge_forward items parse failed");
|
|
224
|
-
return "[Merged and Forwarded Message - parse error]";
|
|
225
|
-
}
|
|
226
|
-
if (!Array.isArray(items) || items.length === 0) {
|
|
227
|
-
return "[Merged and Forwarded Message - no sub-messages]";
|
|
228
|
-
}
|
|
229
|
-
const subMessages = items.filter((item) => item.upper_message_id);
|
|
230
|
-
if (subMessages.length === 0) {
|
|
231
|
-
return "[Merged and Forwarded Message - no sub-messages found]";
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
log?.(`feishu: merge_forward contains ${subMessages.length} sub-messages`);
|
|
235
|
-
subMessages.sort(
|
|
236
|
-
(a, b) => Number.parseInt(a.create_time || "0", 10) - Number.parseInt(b.create_time || "0", 10),
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
const lines = ["[Merged and Forwarded Messages]"];
|
|
240
|
-
for (const item of subMessages.slice(0, maxMessages)) {
|
|
241
|
-
lines.push(`- ${formatSubMessageContent(item.body?.content || "", item.msg_type || "text")}`);
|
|
242
|
-
}
|
|
243
|
-
if (subMessages.length > maxMessages) {
|
|
244
|
-
lines.push(`... and ${subMessages.length - maxMessages} more messages`);
|
|
245
|
-
}
|
|
246
|
-
return lines.join("\n");
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export function checkBotMentioned(event: FeishuMessageLike, botOpenId?: string): boolean {
|
|
250
|
-
if (!botOpenId) {
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
const mentions = event.message.mentions ?? [];
|
|
254
|
-
if (mentions.length > 0) {
|
|
255
|
-
return mentions.some(
|
|
256
|
-
(mention) => !isFeishuBroadcastMention(mention) && mention.id.open_id === botOpenId,
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
if (event.message.message_type === "post") {
|
|
260
|
-
return parsePostContent(event.message.content).mentionedOpenIds.some(
|
|
261
|
-
(id) => id.trim().toLowerCase() !== "all" && id === botOpenId,
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export function normalizeMentions(
|
|
268
|
-
text: string,
|
|
269
|
-
mentions?: FeishuMention[],
|
|
270
|
-
botStripId?: string,
|
|
271
|
-
): string {
|
|
272
|
-
if (!mentions || mentions.length === 0) {
|
|
273
|
-
return text;
|
|
274
|
-
}
|
|
275
|
-
const escaped = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
276
|
-
const escapeName = (value: string) => value.replace(/</g, "<").replace(/>/g, ">");
|
|
277
|
-
let result = text;
|
|
278
|
-
for (const mention of mentions) {
|
|
279
|
-
const mentionId = mention.id.open_id;
|
|
280
|
-
const replacement =
|
|
281
|
-
botStripId && mentionId === botStripId
|
|
282
|
-
? ""
|
|
283
|
-
: mentionId
|
|
284
|
-
? `<at user_id="${mentionId}">${escapeName(mention.name)}</at>`
|
|
285
|
-
: `@${mention.name}`;
|
|
286
|
-
result = result.replace(new RegExp(escaped(mention.key), "g"), () => replacement).trim();
|
|
287
|
-
}
|
|
288
|
-
return result;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export function normalizeFeishuCommandProbeBody(text: string): string {
|
|
292
|
-
if (!text) {
|
|
293
|
-
return "";
|
|
294
|
-
}
|
|
295
|
-
return text
|
|
296
|
-
.replace(/<at\b[^>]*>[^<]*<\/at>/giu, " ")
|
|
297
|
-
.replace(/(^|\s)@[^/\s]+(?=\s|$|\/)/gu, "$1")
|
|
298
|
-
.replace(/\s+/g, " ")
|
|
299
|
-
.trim();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function parseMediaKeys(
|
|
303
|
-
content: string,
|
|
304
|
-
messageType: string,
|
|
305
|
-
): { imageKey?: string; fileKey?: string; fileName?: string } {
|
|
306
|
-
try {
|
|
307
|
-
const parsed = JSON.parse(content);
|
|
308
|
-
const imageKey = normalizeFeishuExternalKey(parsed.image_key);
|
|
309
|
-
const fileKey = normalizeFeishuExternalKey(parsed.file_key);
|
|
310
|
-
switch (messageType) {
|
|
311
|
-
case "image":
|
|
312
|
-
return { imageKey, fileName: parsed.file_name };
|
|
313
|
-
case "file":
|
|
314
|
-
case "audio":
|
|
315
|
-
case "sticker":
|
|
316
|
-
return { fileKey, fileName: parsed.file_name };
|
|
317
|
-
case "video":
|
|
318
|
-
case "media":
|
|
319
|
-
return { fileKey, imageKey, fileName: parsed.file_name };
|
|
320
|
-
default:
|
|
321
|
-
return {};
|
|
322
|
-
}
|
|
323
|
-
} catch {
|
|
324
|
-
return {};
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export function toMessageResourceType(messageType: string): "image" | "file" {
|
|
329
|
-
return messageType === "image" ? "image" : "file";
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function inferPlaceholder(messageType: string): string {
|
|
333
|
-
switch (messageType) {
|
|
334
|
-
case "image":
|
|
335
|
-
return "<media:image>";
|
|
336
|
-
case "file":
|
|
337
|
-
return "<media:document>";
|
|
338
|
-
case "audio":
|
|
339
|
-
return "<media:audio>";
|
|
340
|
-
case "video":
|
|
341
|
-
case "media":
|
|
342
|
-
return "<media:video>";
|
|
343
|
-
case "sticker":
|
|
344
|
-
return "<media:sticker>";
|
|
345
|
-
default:
|
|
346
|
-
return "<media:document>";
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
export async function resolveFeishuMediaList(params: {
|
|
351
|
-
cfg: ClawdbotConfig;
|
|
352
|
-
messageId: string;
|
|
353
|
-
messageType: string;
|
|
354
|
-
content: string;
|
|
355
|
-
maxBytes: number;
|
|
356
|
-
log?: (msg: string) => void;
|
|
357
|
-
accountId?: string;
|
|
358
|
-
}): Promise<FeishuMediaInfo[]> {
|
|
359
|
-
const { cfg, messageId, messageType, content, maxBytes, log, accountId } = params;
|
|
360
|
-
const mediaTypes = ["image", "file", "audio", "video", "media", "sticker", "post"];
|
|
361
|
-
if (!mediaTypes.includes(messageType)) {
|
|
362
|
-
return [];
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const out: FeishuMediaInfo[] = [];
|
|
366
|
-
const core = getFeishuRuntime();
|
|
367
|
-
|
|
368
|
-
if (messageType === "post") {
|
|
369
|
-
const { imageKeys, mediaKeys } = parsePostContent(content);
|
|
370
|
-
if (imageKeys.length === 0 && mediaKeys.length === 0) {
|
|
371
|
-
return [];
|
|
372
|
-
}
|
|
373
|
-
if (imageKeys.length > 0) {
|
|
374
|
-
log?.(`feishu: post message contains ${imageKeys.length} embedded image(s)`);
|
|
375
|
-
}
|
|
376
|
-
if (mediaKeys.length > 0) {
|
|
377
|
-
log?.(`feishu: post message contains ${mediaKeys.length} embedded media file(s)`);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
for (const imageKey of imageKeys) {
|
|
381
|
-
try {
|
|
382
|
-
const result = await downloadMessageResourceFeishu({
|
|
383
|
-
cfg,
|
|
384
|
-
messageId,
|
|
385
|
-
fileKey: imageKey,
|
|
386
|
-
type: "image",
|
|
387
|
-
accountId,
|
|
388
|
-
});
|
|
389
|
-
const contentType =
|
|
390
|
-
result.contentType ?? (await core.media.detectMime({ buffer: result.buffer }));
|
|
391
|
-
const saved = await core.channel.media.saveMediaBuffer(
|
|
392
|
-
result.buffer,
|
|
393
|
-
contentType,
|
|
394
|
-
"inbound",
|
|
395
|
-
maxBytes,
|
|
396
|
-
);
|
|
397
|
-
out.push({
|
|
398
|
-
path: saved.path,
|
|
399
|
-
contentType: saved.contentType,
|
|
400
|
-
placeholder: "<media:image>",
|
|
401
|
-
});
|
|
402
|
-
log?.(`feishu: downloaded embedded image ${imageKey}, saved to ${saved.path}`);
|
|
403
|
-
} catch (err) {
|
|
404
|
-
log?.(`feishu: failed to download embedded image ${imageKey}: ${String(err)}`);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
for (const media of mediaKeys) {
|
|
409
|
-
try {
|
|
410
|
-
const result = await downloadMessageResourceFeishu({
|
|
411
|
-
cfg,
|
|
412
|
-
messageId,
|
|
413
|
-
fileKey: media.fileKey,
|
|
414
|
-
type: "file",
|
|
415
|
-
accountId,
|
|
416
|
-
});
|
|
417
|
-
const contentType =
|
|
418
|
-
result.contentType ?? (await core.media.detectMime({ buffer: result.buffer }));
|
|
419
|
-
const saved = await core.channel.media.saveMediaBuffer(
|
|
420
|
-
result.buffer,
|
|
421
|
-
contentType,
|
|
422
|
-
"inbound",
|
|
423
|
-
maxBytes,
|
|
424
|
-
);
|
|
425
|
-
out.push({
|
|
426
|
-
path: saved.path,
|
|
427
|
-
contentType: saved.contentType,
|
|
428
|
-
placeholder: "<media:video>",
|
|
429
|
-
});
|
|
430
|
-
log?.(`feishu: downloaded embedded media ${media.fileKey}, saved to ${saved.path}`);
|
|
431
|
-
} catch (err) {
|
|
432
|
-
log?.(`feishu: failed to download embedded media ${media.fileKey}: ${String(err)}`);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
return out;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const mediaKeys = parseMediaKeys(content, messageType);
|
|
439
|
-
if (!mediaKeys.imageKey && !mediaKeys.fileKey) {
|
|
440
|
-
return [];
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
try {
|
|
444
|
-
const fileKey = mediaKeys.fileKey || mediaKeys.imageKey;
|
|
445
|
-
if (!fileKey) {
|
|
446
|
-
return [];
|
|
447
|
-
}
|
|
448
|
-
const result = await downloadMessageResourceFeishu({
|
|
449
|
-
cfg,
|
|
450
|
-
messageId,
|
|
451
|
-
fileKey,
|
|
452
|
-
type: toMessageResourceType(messageType),
|
|
453
|
-
accountId,
|
|
454
|
-
});
|
|
455
|
-
const contentType =
|
|
456
|
-
result.contentType ?? (await core.media.detectMime({ buffer: result.buffer }));
|
|
457
|
-
const saved = await core.channel.media.saveMediaBuffer(
|
|
458
|
-
result.buffer,
|
|
459
|
-
contentType,
|
|
460
|
-
"inbound",
|
|
461
|
-
maxBytes,
|
|
462
|
-
result.fileName || mediaKeys.fileName,
|
|
463
|
-
);
|
|
464
|
-
out.push({
|
|
465
|
-
path: saved.path,
|
|
466
|
-
contentType: saved.contentType,
|
|
467
|
-
placeholder: inferPlaceholder(messageType),
|
|
468
|
-
});
|
|
469
|
-
log?.(`feishu: downloaded ${messageType} media, saved to ${saved.path}`);
|
|
470
|
-
} catch (err) {
|
|
471
|
-
log?.(`feishu: failed to download ${messageType} media: ${String(err)}`);
|
|
472
|
-
}
|
|
473
|
-
return out;
|
|
474
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { resolveGroupName, clearGroupNameCache } from "./bot.js";
|
|
3
|
-
import type { ResolvedFeishuAccount } from "./types.js";
|
|
4
|
-
|
|
5
|
-
const mockGetChatInfo = vi.hoisted(() => vi.fn());
|
|
6
|
-
const mockCreateFeishuClient = vi.hoisted(() => vi.fn());
|
|
7
|
-
|
|
8
|
-
vi.mock("./chat.js", () => ({ getChatInfo: mockGetChatInfo }));
|
|
9
|
-
vi.mock("./client.js", () => ({ createFeishuClient: mockCreateFeishuClient }));
|
|
10
|
-
|
|
11
|
-
function makeAccount(id = "test-account"): ResolvedFeishuAccount {
|
|
12
|
-
return {
|
|
13
|
-
accountId: id,
|
|
14
|
-
selectionSource: "explicit",
|
|
15
|
-
enabled: true,
|
|
16
|
-
configured: true,
|
|
17
|
-
appId: "cli_test",
|
|
18
|
-
appSecret: "secret",
|
|
19
|
-
domain: "feishu",
|
|
20
|
-
config: {
|
|
21
|
-
domain: "feishu",
|
|
22
|
-
connectionMode: "websocket",
|
|
23
|
-
webhookPath: "/feishu/events",
|
|
24
|
-
dmPolicy: "pairing",
|
|
25
|
-
reactionNotifications: "own",
|
|
26
|
-
groupPolicy: "allowlist",
|
|
27
|
-
typingIndicator: true,
|
|
28
|
-
resolveSenderNames: true,
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Unit tests for resolveGroupName.
|
|
35
|
-
*
|
|
36
|
-
* Covers: successful lookup, API failure, empty name, positive cache,
|
|
37
|
-
* negative cache, undefined response, and cross-account isolation.
|
|
38
|
-
*/
|
|
39
|
-
describe("resolveGroupName", () => {
|
|
40
|
-
const account = makeAccount();
|
|
41
|
-
const log = vi.fn();
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
vi.clearAllMocks();
|
|
45
|
-
mockGetChatInfo.mockReset();
|
|
46
|
-
mockCreateFeishuClient.mockReset();
|
|
47
|
-
mockCreateFeishuClient.mockReturnValue({});
|
|
48
|
-
clearGroupNameCache();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("returns the trimmed group name on successful API call", async () => {
|
|
52
|
-
mockGetChatInfo.mockResolvedValue({ name: " Engineering Team " });
|
|
53
|
-
const result = await resolveGroupName({ account, chatId: "oc_test1", log });
|
|
54
|
-
expect(result).toBe("Engineering Team");
|
|
55
|
-
expect(mockGetChatInfo).toHaveBeenCalledOnce();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("returns undefined and logs on API failure", async () => {
|
|
59
|
-
mockGetChatInfo.mockRejectedValue(new Error("network timeout"));
|
|
60
|
-
const result = await resolveGroupName({ account, chatId: "oc_test2", log });
|
|
61
|
-
expect(result).toBeUndefined();
|
|
62
|
-
expect(log).toHaveBeenCalledWith(expect.stringContaining("getChatInfo failed"));
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("returns undefined for whitespace-only name", async () => {
|
|
66
|
-
mockGetChatInfo.mockResolvedValue({ name: " " });
|
|
67
|
-
const result = await resolveGroupName({ account, chatId: "oc_test3", log });
|
|
68
|
-
expect(result).toBeUndefined();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("serves subsequent calls from cache (positive hit)", async () => {
|
|
72
|
-
mockGetChatInfo.mockResolvedValue({ name: "Cached Group" });
|
|
73
|
-
await resolveGroupName({ account, chatId: "oc_test4", log });
|
|
74
|
-
const result = await resolveGroupName({ account, chatId: "oc_test4", log });
|
|
75
|
-
expect(result).toBe("Cached Group");
|
|
76
|
-
expect(mockGetChatInfo).toHaveBeenCalledOnce(); // only 1 API call
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("caches negative result (API failure) and skips retry", async () => {
|
|
80
|
-
mockGetChatInfo.mockRejectedValue(new Error("fail"));
|
|
81
|
-
await resolveGroupName({ account, chatId: "oc_test5", log });
|
|
82
|
-
mockGetChatInfo.mockResolvedValue({ name: "Recovered" });
|
|
83
|
-
const result = await resolveGroupName({ account, chatId: "oc_test5", log });
|
|
84
|
-
expect(result).toBeUndefined(); // still cached negative
|
|
85
|
-
expect(mockGetChatInfo).toHaveBeenCalledOnce();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("returns undefined when API returns object with missing name field", async () => {
|
|
89
|
-
mockGetChatInfo.mockResolvedValue({ name: undefined });
|
|
90
|
-
const result = await resolveGroupName({ account, chatId: "oc_test6", log });
|
|
91
|
-
expect(result).toBeUndefined();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("isolates cache entries across different accounts", async () => {
|
|
95
|
-
const accountA = makeAccount("account-A");
|
|
96
|
-
const accountB = makeAccount("account-B");
|
|
97
|
-
mockGetChatInfo
|
|
98
|
-
.mockResolvedValueOnce({ name: "Team Alpha" })
|
|
99
|
-
.mockResolvedValueOnce({ name: "Team Beta" });
|
|
100
|
-
|
|
101
|
-
const nameA = await resolveGroupName({ account: accountA, chatId: "oc_shared", log });
|
|
102
|
-
const nameB = await resolveGroupName({ account: accountB, chatId: "oc_shared", log });
|
|
103
|
-
|
|
104
|
-
expect(nameA).toBe("Team Alpha");
|
|
105
|
-
expect(nameB).toBe("Team Beta");
|
|
106
|
-
expect(mockGetChatInfo).toHaveBeenCalledTimes(2); // separate API calls
|
|
107
|
-
});
|
|
108
|
-
});
|
package/src/bot-runtime-api.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
buildAgentMediaPayload,
|
|
3
|
-
resolveChannelContextVisibilityMode,
|
|
4
|
-
type ClawdbotConfig,
|
|
5
|
-
type RuntimeEnv,
|
|
6
|
-
} from "../runtime-api.js";
|
|
7
|
-
export {
|
|
8
|
-
evaluateSupplementalContextVisibility,
|
|
9
|
-
filterSupplementalContextItems,
|
|
10
|
-
normalizeAgentId,
|
|
11
|
-
} from "../runtime-api.js";
|
|
12
|
-
export { loadSessionStore, resolveSessionStoreEntry } from "../runtime-api.js";
|