@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/card-action.ts
DELETED
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
|
2
|
-
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
|
3
|
-
import { handleFeishuMessage, type FeishuMessageEvent } from "./bot.js";
|
|
4
|
-
import { decodeFeishuCardAction, buildFeishuCardActionTextFallback } from "./card-interaction.js";
|
|
5
|
-
import {
|
|
6
|
-
createApprovalCard,
|
|
7
|
-
FEISHU_APPROVAL_CANCEL_ACTION,
|
|
8
|
-
FEISHU_APPROVAL_CONFIRM_ACTION,
|
|
9
|
-
FEISHU_APPROVAL_REQUEST_ACTION,
|
|
10
|
-
} from "./card-ux-approval.js";
|
|
11
|
-
import { createFeishuClient } from "./client.js";
|
|
12
|
-
import { sendCardFeishu, sendMessageFeishu } from "./send.js";
|
|
13
|
-
|
|
14
|
-
export type FeishuCardActionEvent = {
|
|
15
|
-
operator: {
|
|
16
|
-
open_id: string;
|
|
17
|
-
user_id?: string;
|
|
18
|
-
union_id?: string;
|
|
19
|
-
};
|
|
20
|
-
token: string;
|
|
21
|
-
action: {
|
|
22
|
-
value: Record<string, unknown>;
|
|
23
|
-
tag: string;
|
|
24
|
-
};
|
|
25
|
-
open_message_id?: string;
|
|
26
|
-
context: {
|
|
27
|
-
open_message_id?: string;
|
|
28
|
-
open_id?: string;
|
|
29
|
-
user_id?: string;
|
|
30
|
-
chat_id?: string;
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const FEISHU_APPROVAL_CARD_TTL_MS = 5 * 60_000;
|
|
35
|
-
const FEISHU_CARD_ACTION_TOKEN_TTL_MS = 15 * 60_000;
|
|
36
|
-
const processedCardActionTokens = new Map<
|
|
37
|
-
string,
|
|
38
|
-
{ status: "inflight" | "completed"; expiresAt: number }
|
|
39
|
-
>();
|
|
40
|
-
|
|
41
|
-
export class FeishuRetryableCardActionError extends Error {
|
|
42
|
-
constructor(message: string, options?: ErrorOptions) {
|
|
43
|
-
super(message, options);
|
|
44
|
-
this.name = "FeishuRetryableCardActionError";
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function resetProcessedFeishuCardActionTokensForTests(): void {
|
|
49
|
-
processedCardActionTokens.clear();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function pruneProcessedCardActionTokens(now: number): void {
|
|
53
|
-
for (const [key, entry] of processedCardActionTokens.entries()) {
|
|
54
|
-
if (entry.expiresAt <= now) {
|
|
55
|
-
processedCardActionTokens.delete(key);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function beginFeishuCardActionToken(params: {
|
|
61
|
-
token: string;
|
|
62
|
-
accountId: string;
|
|
63
|
-
now?: number;
|
|
64
|
-
}): boolean {
|
|
65
|
-
const now = params.now ?? Date.now();
|
|
66
|
-
pruneProcessedCardActionTokens(now);
|
|
67
|
-
const normalizedToken = params.token.trim();
|
|
68
|
-
if (!normalizedToken) {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
const key = `${params.accountId}:${normalizedToken}`;
|
|
72
|
-
const existing = processedCardActionTokens.get(key);
|
|
73
|
-
if (existing && existing.expiresAt > now) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
processedCardActionTokens.set(key, {
|
|
77
|
-
status: "inflight",
|
|
78
|
-
expiresAt: now + FEISHU_CARD_ACTION_TOKEN_TTL_MS,
|
|
79
|
-
});
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function completeFeishuCardActionToken(params: {
|
|
84
|
-
token: string;
|
|
85
|
-
accountId: string;
|
|
86
|
-
now?: number;
|
|
87
|
-
}): void {
|
|
88
|
-
const now = params.now ?? Date.now();
|
|
89
|
-
const normalizedToken = params.token.trim();
|
|
90
|
-
if (!normalizedToken) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
processedCardActionTokens.set(`${params.accountId}:${normalizedToken}`, {
|
|
94
|
-
status: "completed",
|
|
95
|
-
expiresAt: now + FEISHU_CARD_ACTION_TOKEN_TTL_MS,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function releaseFeishuCardActionToken(params: { token: string; accountId: string }): void {
|
|
100
|
-
const normalizedToken = params.token.trim();
|
|
101
|
-
if (!normalizedToken) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
processedCardActionTokens.delete(`${params.accountId}:${normalizedToken}`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function buildSyntheticMessageEvent(
|
|
108
|
-
event: FeishuCardActionEvent,
|
|
109
|
-
content: string,
|
|
110
|
-
chatType: "p2p" | "group",
|
|
111
|
-
): FeishuMessageEvent {
|
|
112
|
-
const replyTargetMessageId = event.context.open_message_id ?? event.open_message_id;
|
|
113
|
-
return {
|
|
114
|
-
sender: {
|
|
115
|
-
sender_id: {
|
|
116
|
-
open_id: event.operator.open_id,
|
|
117
|
-
user_id: event.operator.user_id,
|
|
118
|
-
union_id: event.operator.union_id,
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
message: {
|
|
122
|
-
message_id: `card-action-${event.token}`,
|
|
123
|
-
...(replyTargetMessageId ? { reply_target_message_id: replyTargetMessageId } : {}),
|
|
124
|
-
...(!replyTargetMessageId ? { suppress_reply_target: true } : {}),
|
|
125
|
-
chat_id: event.context.chat_id || event.operator.open_id,
|
|
126
|
-
chat_type: chatType,
|
|
127
|
-
message_type: "text",
|
|
128
|
-
content: JSON.stringify({ text: content }),
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function resolveCallbackTarget(event: FeishuCardActionEvent): string {
|
|
134
|
-
const chatId = event.context.chat_id?.trim();
|
|
135
|
-
if (chatId) {
|
|
136
|
-
return `chat:${chatId}`;
|
|
137
|
-
}
|
|
138
|
-
return `user:${event.operator.open_id}`;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function dispatchSyntheticCommand(params: {
|
|
142
|
-
cfg: ClawdbotConfig;
|
|
143
|
-
event: FeishuCardActionEvent;
|
|
144
|
-
command: string;
|
|
145
|
-
account: ReturnType<typeof resolveFeishuRuntimeAccount>;
|
|
146
|
-
botOpenId?: string;
|
|
147
|
-
runtime?: RuntimeEnv;
|
|
148
|
-
accountId?: string;
|
|
149
|
-
chatType?: "p2p" | "group";
|
|
150
|
-
}): Promise<void> {
|
|
151
|
-
const resolvedChatType = await resolveCardActionChatType({
|
|
152
|
-
event: params.event,
|
|
153
|
-
account: params.account,
|
|
154
|
-
chatType: params.chatType,
|
|
155
|
-
log: params.runtime?.log ?? console.log,
|
|
156
|
-
});
|
|
157
|
-
await handleFeishuMessage({
|
|
158
|
-
cfg: params.cfg,
|
|
159
|
-
event: buildSyntheticMessageEvent(params.event, params.command, resolvedChatType),
|
|
160
|
-
botOpenId: params.botOpenId,
|
|
161
|
-
runtime: params.runtime,
|
|
162
|
-
accountId: params.accountId,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Feishu's im.chat.get returns two fields:
|
|
167
|
-
// chat_mode: conversation type — "p2p" | "group" | "topic"
|
|
168
|
-
// chat_type: privacy classification — "private" | "public"
|
|
169
|
-
// We check chat_mode first because it directly indicates conversation type.
|
|
170
|
-
// "private" maps to "p2p" as the safe-failure direction (restrictive DM
|
|
171
|
-
// policy) — a private group chat misclassified as p2p is safer than the
|
|
172
|
-
// reverse. "topic" and "public" are treated as group semantics.
|
|
173
|
-
function normalizeResolvedCardActionChatType(value: unknown): "p2p" | "group" | undefined {
|
|
174
|
-
if (value === "group" || value === "topic" || value === "public") {
|
|
175
|
-
return "group";
|
|
176
|
-
}
|
|
177
|
-
if (value === "p2p" || value === "private") {
|
|
178
|
-
return "p2p";
|
|
179
|
-
}
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const resolvedChatTypeCache = new Map<string, { value: "p2p" | "group"; expiresAt: number }>();
|
|
184
|
-
const CHAT_TYPE_CACHE_TTL_MS = 30 * 60_000;
|
|
185
|
-
const CHAT_TYPE_CACHE_MAX_SIZE = 5_000;
|
|
186
|
-
|
|
187
|
-
function pruneChatTypeCache(now: number): void {
|
|
188
|
-
for (const [key, entry] of resolvedChatTypeCache.entries()) {
|
|
189
|
-
if (entry.expiresAt <= now) {
|
|
190
|
-
resolvedChatTypeCache.delete(key);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (resolvedChatTypeCache.size > CHAT_TYPE_CACHE_MAX_SIZE) {
|
|
194
|
-
const excess = resolvedChatTypeCache.size - CHAT_TYPE_CACHE_MAX_SIZE;
|
|
195
|
-
const iter = resolvedChatTypeCache.keys();
|
|
196
|
-
for (let i = 0; i < excess; i++) {
|
|
197
|
-
const key = iter.next().value;
|
|
198
|
-
if (key !== undefined) {
|
|
199
|
-
resolvedChatTypeCache.delete(key);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function sanitizeLogValue(v: string): string {
|
|
206
|
-
return v.replace(/[\r\n]/g, " ").slice(0, 500);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async function resolveCardActionChatType(params: {
|
|
210
|
-
event: FeishuCardActionEvent;
|
|
211
|
-
account: ReturnType<typeof resolveFeishuRuntimeAccount>;
|
|
212
|
-
chatType?: "p2p" | "group";
|
|
213
|
-
log: (message: string) => void;
|
|
214
|
-
}): Promise<"p2p" | "group"> {
|
|
215
|
-
const explicitChatType = normalizeResolvedCardActionChatType(params.chatType);
|
|
216
|
-
if (explicitChatType) {
|
|
217
|
-
return explicitChatType;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const chatId = params.event.context.chat_id?.trim();
|
|
221
|
-
if (!chatId) {
|
|
222
|
-
return "p2p";
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const cacheKey = `${params.account.accountId}:${chatId}`;
|
|
226
|
-
const now = Date.now();
|
|
227
|
-
pruneChatTypeCache(now);
|
|
228
|
-
const cached = resolvedChatTypeCache.get(cacheKey);
|
|
229
|
-
if (cached) {
|
|
230
|
-
return cached.value;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
const response = (await createFeishuClient(params.account).im.chat.get({
|
|
235
|
-
path: { chat_id: chatId },
|
|
236
|
-
})) as { code?: number; msg?: string; data?: { chat_type?: unknown; chat_mode?: unknown } };
|
|
237
|
-
if (response.code === 0) {
|
|
238
|
-
const resolvedChatType =
|
|
239
|
-
normalizeResolvedCardActionChatType(response.data?.chat_mode) ??
|
|
240
|
-
normalizeResolvedCardActionChatType(response.data?.chat_type);
|
|
241
|
-
if (resolvedChatType) {
|
|
242
|
-
resolvedChatTypeCache.set(cacheKey, {
|
|
243
|
-
value: resolvedChatType,
|
|
244
|
-
expiresAt: now + CHAT_TYPE_CACHE_TTL_MS,
|
|
245
|
-
});
|
|
246
|
-
return resolvedChatType;
|
|
247
|
-
}
|
|
248
|
-
params.log(
|
|
249
|
-
`feishu[${params.account.accountId}]: card action missing chat type for chat; defaulting to p2p`,
|
|
250
|
-
);
|
|
251
|
-
} else {
|
|
252
|
-
params.log(
|
|
253
|
-
`feishu[${params.account.accountId}]: failed to resolve chat type: ${sanitizeLogValue(response.msg ?? "unknown error")}; defaulting to p2p`,
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
} catch (err) {
|
|
257
|
-
const message = err instanceof Error ? err.message : "unknown";
|
|
258
|
-
params.log(
|
|
259
|
-
`feishu[${params.account.accountId}]: failed to resolve chat type: ${sanitizeLogValue(message)}; defaulting to p2p`,
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return "p2p";
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function sendInvalidInteractionNotice(params: {
|
|
267
|
-
cfg: ClawdbotConfig;
|
|
268
|
-
event: FeishuCardActionEvent;
|
|
269
|
-
reason: "malformed" | "stale" | "wrong_user" | "wrong_conversation";
|
|
270
|
-
accountId?: string;
|
|
271
|
-
}): Promise<void> {
|
|
272
|
-
const reasonText =
|
|
273
|
-
params.reason === "stale"
|
|
274
|
-
? "This card action has expired. Open a fresh launcher card and try again."
|
|
275
|
-
: params.reason === "wrong_user"
|
|
276
|
-
? "This card action belongs to a different user."
|
|
277
|
-
: params.reason === "wrong_conversation"
|
|
278
|
-
? "This card action belongs to a different conversation."
|
|
279
|
-
: "This card action payload is invalid.";
|
|
280
|
-
|
|
281
|
-
await sendMessageFeishu({
|
|
282
|
-
cfg: params.cfg,
|
|
283
|
-
to: resolveCallbackTarget(params.event),
|
|
284
|
-
text: `⚠️ ${reasonText}`,
|
|
285
|
-
accountId: params.accountId,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
export async function handleFeishuCardAction(params: {
|
|
290
|
-
cfg: ClawdbotConfig;
|
|
291
|
-
event: FeishuCardActionEvent;
|
|
292
|
-
botOpenId?: string;
|
|
293
|
-
runtime?: RuntimeEnv;
|
|
294
|
-
accountId?: string;
|
|
295
|
-
}): Promise<void> {
|
|
296
|
-
const { cfg, event, runtime, accountId } = params;
|
|
297
|
-
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
|
298
|
-
const log = runtime?.log ?? console.log;
|
|
299
|
-
if (!event.token.trim()) {
|
|
300
|
-
log(
|
|
301
|
-
`feishu[${account.accountId}]: rejected card action from ${event.operator.open_id}: missing token`,
|
|
302
|
-
);
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
const decoded = decodeFeishuCardAction({ event });
|
|
306
|
-
const claimedToken = beginFeishuCardActionToken({
|
|
307
|
-
token: event.token,
|
|
308
|
-
accountId: account.accountId,
|
|
309
|
-
});
|
|
310
|
-
if (!claimedToken) {
|
|
311
|
-
log(`feishu[${account.accountId}]: skipping duplicate card action token ${event.token}`);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
if (decoded.kind === "invalid") {
|
|
317
|
-
log(
|
|
318
|
-
`feishu[${account.accountId}]: rejected card action from ${event.operator.open_id}: ${decoded.reason}`,
|
|
319
|
-
);
|
|
320
|
-
await sendInvalidInteractionNotice({
|
|
321
|
-
cfg,
|
|
322
|
-
event,
|
|
323
|
-
reason: decoded.reason,
|
|
324
|
-
accountId,
|
|
325
|
-
});
|
|
326
|
-
completeFeishuCardActionToken({ token: event.token, accountId: account.accountId });
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (decoded.kind === "structured") {
|
|
331
|
-
const { envelope } = decoded;
|
|
332
|
-
log(
|
|
333
|
-
`feishu[${account.accountId}]: handling structured card action ${envelope.a} from ${event.operator.open_id}`,
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
if (envelope.a === FEISHU_APPROVAL_REQUEST_ACTION) {
|
|
337
|
-
const command = typeof envelope.m?.command === "string" ? envelope.m.command.trim() : "";
|
|
338
|
-
if (!command) {
|
|
339
|
-
await sendInvalidInteractionNotice({
|
|
340
|
-
cfg,
|
|
341
|
-
event,
|
|
342
|
-
reason: "malformed",
|
|
343
|
-
accountId,
|
|
344
|
-
});
|
|
345
|
-
completeFeishuCardActionToken({ token: event.token, accountId: account.accountId });
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const prompt =
|
|
349
|
-
typeof envelope.m?.prompt === "string" && envelope.m.prompt.trim()
|
|
350
|
-
? envelope.m.prompt
|
|
351
|
-
: `Run \`${command}\` in this Feishu conversation?`;
|
|
352
|
-
await sendCardFeishu({
|
|
353
|
-
cfg,
|
|
354
|
-
to: resolveCallbackTarget(event),
|
|
355
|
-
card: createApprovalCard({
|
|
356
|
-
operatorOpenId: event.operator.open_id,
|
|
357
|
-
chatId: event.context.chat_id || undefined,
|
|
358
|
-
command,
|
|
359
|
-
prompt,
|
|
360
|
-
sessionKey: envelope.c?.s,
|
|
361
|
-
expiresAt: Date.now() + FEISHU_APPROVAL_CARD_TTL_MS,
|
|
362
|
-
chatType: await resolveCardActionChatType({
|
|
363
|
-
event,
|
|
364
|
-
account,
|
|
365
|
-
chatType: envelope.c?.t,
|
|
366
|
-
log,
|
|
367
|
-
}),
|
|
368
|
-
confirmLabel: command === "/reset" ? "Reset" : "Confirm",
|
|
369
|
-
}),
|
|
370
|
-
accountId,
|
|
371
|
-
});
|
|
372
|
-
completeFeishuCardActionToken({ token: event.token, accountId: account.accountId });
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (envelope.a === FEISHU_APPROVAL_CANCEL_ACTION) {
|
|
377
|
-
await sendMessageFeishu({
|
|
378
|
-
cfg,
|
|
379
|
-
to: resolveCallbackTarget(event),
|
|
380
|
-
text: "Cancelled.",
|
|
381
|
-
accountId,
|
|
382
|
-
});
|
|
383
|
-
completeFeishuCardActionToken({ token: event.token, accountId: account.accountId });
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (envelope.a === FEISHU_APPROVAL_CONFIRM_ACTION || envelope.k === "quick") {
|
|
388
|
-
const command = envelope.q?.trim();
|
|
389
|
-
if (!command) {
|
|
390
|
-
await sendInvalidInteractionNotice({
|
|
391
|
-
cfg,
|
|
392
|
-
event,
|
|
393
|
-
reason: "malformed",
|
|
394
|
-
accountId,
|
|
395
|
-
});
|
|
396
|
-
completeFeishuCardActionToken({ token: event.token, accountId: account.accountId });
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
await dispatchSyntheticCommand({
|
|
400
|
-
cfg,
|
|
401
|
-
event,
|
|
402
|
-
command,
|
|
403
|
-
account,
|
|
404
|
-
botOpenId: params.botOpenId,
|
|
405
|
-
runtime,
|
|
406
|
-
accountId,
|
|
407
|
-
chatType: envelope.c?.t,
|
|
408
|
-
});
|
|
409
|
-
completeFeishuCardActionToken({ token: event.token, accountId: account.accountId });
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
await sendInvalidInteractionNotice({
|
|
414
|
-
cfg,
|
|
415
|
-
event,
|
|
416
|
-
reason: "malformed",
|
|
417
|
-
accountId,
|
|
418
|
-
});
|
|
419
|
-
completeFeishuCardActionToken({ token: event.token, accountId: account.accountId });
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const content = buildFeishuCardActionTextFallback(event);
|
|
424
|
-
|
|
425
|
-
log(
|
|
426
|
-
`feishu[${account.accountId}]: handling card action from ${event.operator.open_id}: ${content}`,
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
await dispatchSyntheticCommand({
|
|
430
|
-
cfg,
|
|
431
|
-
event,
|
|
432
|
-
command: content,
|
|
433
|
-
account,
|
|
434
|
-
botOpenId: params.botOpenId,
|
|
435
|
-
runtime,
|
|
436
|
-
accountId,
|
|
437
|
-
});
|
|
438
|
-
completeFeishuCardActionToken({ token: event.token, accountId: account.accountId });
|
|
439
|
-
} catch (err) {
|
|
440
|
-
if (err instanceof FeishuRetryableCardActionError) {
|
|
441
|
-
releaseFeishuCardActionToken({ token: event.token, accountId: account.accountId });
|
|
442
|
-
} else {
|
|
443
|
-
completeFeishuCardActionToken({ token: event.token, accountId: account.accountId });
|
|
444
|
-
}
|
|
445
|
-
throw err;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
buildFeishuCardActionTextFallback,
|
|
4
|
-
createFeishuCardInteractionEnvelope,
|
|
5
|
-
decodeFeishuCardAction,
|
|
6
|
-
} from "./card-interaction.js";
|
|
7
|
-
|
|
8
|
-
describe("feishu card interaction decoder", () => {
|
|
9
|
-
it("decodes valid structured payloads", () => {
|
|
10
|
-
const result = decodeFeishuCardAction({
|
|
11
|
-
now: 1_700_000_000_000,
|
|
12
|
-
event: {
|
|
13
|
-
operator: { open_id: "u123" },
|
|
14
|
-
context: { chat_id: "chat1" },
|
|
15
|
-
action: {
|
|
16
|
-
value: createFeishuCardInteractionEnvelope({
|
|
17
|
-
k: "quick",
|
|
18
|
-
a: "feishu.quick_actions.help",
|
|
19
|
-
q: "/help",
|
|
20
|
-
c: { u: "u123", h: "chat1", t: "group", e: 1_700_000_060_000 },
|
|
21
|
-
}),
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
expect(result).toEqual(
|
|
27
|
-
expect.objectContaining({
|
|
28
|
-
kind: "structured",
|
|
29
|
-
envelope: expect.objectContaining({
|
|
30
|
-
q: "/help",
|
|
31
|
-
}),
|
|
32
|
-
}),
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("falls back for legacy text-like payloads", () => {
|
|
37
|
-
const result = decodeFeishuCardAction({
|
|
38
|
-
event: {
|
|
39
|
-
operator: { open_id: "u123" },
|
|
40
|
-
context: { chat_id: "chat1" },
|
|
41
|
-
action: { value: { text: "/ping" } },
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
expect(result).toEqual({ kind: "legacy", text: "/ping" });
|
|
46
|
-
expect(
|
|
47
|
-
buildFeishuCardActionTextFallback({
|
|
48
|
-
operator: { open_id: "u123" },
|
|
49
|
-
context: { chat_id: "chat1" },
|
|
50
|
-
action: { value: { command: "/new" } },
|
|
51
|
-
}),
|
|
52
|
-
).toBe("/new");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("rejects malformed structured payloads", () => {
|
|
56
|
-
const result = decodeFeishuCardAction({
|
|
57
|
-
event: {
|
|
58
|
-
operator: { open_id: "u123" },
|
|
59
|
-
context: { chat_id: "chat1" },
|
|
60
|
-
action: {
|
|
61
|
-
value: {
|
|
62
|
-
oc: "ocf1",
|
|
63
|
-
k: "quick",
|
|
64
|
-
a: "broken",
|
|
65
|
-
m: { bad: { nested: true } },
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
expect(result).toEqual({ kind: "invalid", reason: "malformed" });
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("rejects stale payloads", () => {
|
|
75
|
-
const result = decodeFeishuCardAction({
|
|
76
|
-
now: 100,
|
|
77
|
-
event: {
|
|
78
|
-
operator: { open_id: "u123" },
|
|
79
|
-
context: { chat_id: "chat1" },
|
|
80
|
-
action: {
|
|
81
|
-
value: createFeishuCardInteractionEnvelope({
|
|
82
|
-
k: "button",
|
|
83
|
-
a: "stale",
|
|
84
|
-
c: { e: 99, t: "group" },
|
|
85
|
-
}),
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
expect(result).toEqual({ kind: "invalid", reason: "stale" });
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("rejects wrong-conversation payloads when chat context is enforced", () => {
|
|
94
|
-
const result = decodeFeishuCardAction({
|
|
95
|
-
event: {
|
|
96
|
-
operator: { open_id: "u123" },
|
|
97
|
-
context: { chat_id: "chat2" },
|
|
98
|
-
action: {
|
|
99
|
-
value: createFeishuCardInteractionEnvelope({
|
|
100
|
-
k: "button",
|
|
101
|
-
a: "scoped",
|
|
102
|
-
c: { u: "u123", h: "chat1", t: "group", e: Date.now() + 60_000 },
|
|
103
|
-
}),
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
expect(result).toEqual({ kind: "invalid", reason: "wrong_conversation" });
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("rejects malformed chat-type context", () => {
|
|
112
|
-
const result = decodeFeishuCardAction({
|
|
113
|
-
event: {
|
|
114
|
-
operator: { open_id: "u123" },
|
|
115
|
-
context: { chat_id: "chat1" },
|
|
116
|
-
action: {
|
|
117
|
-
value: {
|
|
118
|
-
oc: "ocf1",
|
|
119
|
-
k: "button",
|
|
120
|
-
a: "bad",
|
|
121
|
-
c: { t: "private" },
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
expect(result).toEqual({ kind: "invalid", reason: "malformed" });
|
|
128
|
-
});
|
|
129
|
-
});
|