@larksuite/openclaw-lark 2026.7.10-beta.0 → 2026.7.16-beta.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larksuite/openclaw-lark",
3
- "version": "2026.7.10-beta.0",
3
+ "version": "2026.7.16-beta.0",
4
4
  "description": "OpenClaw Lark/Feishu channel plugin",
5
5
  "exports": {
6
6
  ".": {
@@ -9,6 +9,39 @@
9
9
  * function size. Each handler receives a MonitorContext with all
10
10
  * dependencies needed to process the event.
11
11
  */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
12
45
  Object.defineProperty(exports, "__esModule", { value: true });
13
46
  exports.handleMessageEvent = handleMessageEvent;
14
47
  exports.handleReactionEvent = handleReactionEvent;
@@ -16,6 +49,7 @@ exports.handleBotMembershipEvent = handleBotMembershipEvent;
16
49
  exports.handleVcMeetingInvitedEvent = handleVcMeetingInvitedEvent;
17
50
  exports.handleCommentEvent = handleCommentEvent;
18
51
  exports.handleCardActionEvent = handleCardActionEvent;
52
+ const crypto = __importStar(require("node:crypto"));
19
53
  const handler_1 = require("../messaging/inbound/handler.js");
20
54
  const reaction_handler_1 = require("../messaging/inbound/reaction-handler.js");
21
55
  const comment_handler_1 = require("../messaging/inbound/comment-handler.js");
@@ -27,6 +61,9 @@ const lark_ticket_1 = require("../core/lark-ticket.js");
27
61
  const lark_logger_1 = require("../core/lark-logger.js");
28
62
  const auto_auth_1 = require("../tools/auto-auth.js");
29
63
  const ask_user_question_1 = require("../tools/ask-user-question.js");
64
+ const synthetic_message_1 = require("../messaging/inbound/synthetic-message.js");
65
+ const chat_info_cache_1 = require("../core/chat-info-cache.js");
66
+ const message_lookup_1 = require("../messaging/shared/message-lookup.js");
30
67
  const chat_queue_1 = require("./chat-queue.js");
31
68
  const abort_detect_1 = require("./abort-detect.js");
32
69
  const interactive_dispatch_1 = require("./interactive-dispatch.js");
@@ -358,11 +395,107 @@ async function handleCommentEvent(ctx, data) {
358
395
  error(`feishu[${accountId}]: error handling comment event: ${String(err)}`);
359
396
  }
360
397
  }
361
- // ---------------------------------------------------------------------------
362
- // Card action handler
363
- // ---------------------------------------------------------------------------
398
+ /**
399
+ * Generic "button click = inject a user message" mechanism.
400
+ *
401
+ * When a card button's `value` is `{ action: "inject_prompt", prompt: "<text>" }`,
402
+ * clicking it dispatches `<text>` as a synthetic user message (via the standard
403
+ * inbound pipeline, `replyToMessageId` set to the card's own message so the agent
404
+ * replies in the same conversation) and returns a toast receipt. This lets any
405
+ * card offer functional buttons — click a button, the corresponding request runs,
406
+ * no typing needed — without registering a business plugin handler.
407
+ *
408
+ * Returns the toast receipt when the event is an inject_prompt action, or
409
+ * `undefined` so the caller falls through to the other card handlers.
410
+ *
411
+ * Security / correctness (mirrors reaction-handler, which faces the same
412
+ * "card-ish event carries no chat_type/thread_id" problem):
413
+ * - Chat type is resolved from the cached chat-info helper and the dispatch
414
+ * fails closed if it can't be determined, so a group card is never
415
+ * mis-routed as a p2p direct session.
416
+ * - The synthetic message goes through the real access-control gate
417
+ * (`cardActionGate`): the click satisfies the mention requirement, but
418
+ * group/DM admission and sender allowlists are still enforced.
419
+ * - The synthetic message id is unique per click (event_id / token / uuid), so
420
+ * inbound MessageSid dedup does not silently drop a second button click.
421
+ * - Only a real `operator.open_id` is used as the sender identity; a Schema-2
422
+ * `user_id` is never smuggled into an `open_id` field (fail closed instead).
423
+ *
424
+ * Note: `card.action.trigger` is a synchronous request/response — the handler
425
+ * MUST return a card receipt (here a toast). Returning `undefined` makes the SDK
426
+ * fall back to a card update it cannot resolve, which Feishu rejects with
427
+ * `99992354 Invalid ids:[card]`. So we dispatch the synthetic message
428
+ * asynchronously (`setImmediate`) and return the toast synchronously.
429
+ */
430
+ function handleInjectPromptAction(ctx, data) {
431
+ const ev = data;
432
+ const val = ev.action?.value;
433
+ if (!val || val.action !== 'inject_prompt' || typeof val.prompt !== 'string' || !val.prompt.trim()) {
434
+ return undefined;
435
+ }
436
+ // Use only a real open_id as the sender identity. A Schema-2 callback may carry
437
+ // only operator.user_id; that is a different namespace and must not be written
438
+ // into an open_id field (would corrupt session routing, sender allowlists,
439
+ // user-token lookup, and owner checks). Fail closed until user_id→open_id
440
+ // conversion is supported.
441
+ const senderOpenId = ev.operator?.open_id?.trim();
442
+ const chatId = ev.open_chat_id ?? ev.context?.open_chat_id;
443
+ const cardMsgId = ev.open_message_id ?? ev.context?.open_message_id;
444
+ if (!senderOpenId || !chatId) {
445
+ elog.warn('inject_prompt: missing operator open_id or chatId, ignoring');
446
+ return { toast: { type: 'error', content: '无法处理该操作' } };
447
+ }
448
+ const prompt = val.prompt;
449
+ // Unique per click so inbound dedup (by MessageSid, ~20min) does not drop a
450
+ // second click on a different button of the same card. Prefer the callback's
451
+ // unique event_id; fall back to token, then a random uuid.
452
+ const syntheticMessageId = ev.event_id?.trim() || ev.token?.trim() || crypto.randomUUID();
453
+ // Dispatch asynchronously so the synchronous toast receipt is not blocked.
454
+ // The card trigger carries no chat_type or thread_id, so resolve both here.
455
+ setImmediate(async () => {
456
+ try {
457
+ // Resolve the real chat type; fail closed if it can't be determined
458
+ // (do NOT assume p2p — that would mis-route a group card).
459
+ const info = await (0, chat_info_cache_1.getChatInfo)({ cfg: ctx.cfg, chatId, accountId: ctx.accountId });
460
+ if (!info) {
461
+ elog.warn(`inject_prompt: could not resolve chat type for ${chatId}, skipping dispatch`);
462
+ return;
463
+ }
464
+ const chatType = info.chatMode === 'group' || info.chatMode === 'topic' ? 'group' : 'p2p';
465
+ // Recover the thread id from the card's own message (the trigger has none)
466
+ // so thread session isolation is preserved.
467
+ let threadId;
468
+ if (cardMsgId) {
469
+ const msg = await (0, message_lookup_1.getMessageFeishu)({ cfg: ctx.cfg, messageId: cardMsgId, accountId: ctx.accountId }).catch(() => null);
470
+ threadId = msg?.threadId;
471
+ }
472
+ await (0, synthetic_message_1.dispatchSyntheticTextMessage)({
473
+ cfg: ctx.cfg,
474
+ accountId: ctx.accountId,
475
+ chatId,
476
+ senderOpenId,
477
+ text: prompt,
478
+ syntheticMessageId,
479
+ replyToMessageId: cardMsgId ?? '',
480
+ chatType,
481
+ threadId,
482
+ forceMention: false,
483
+ cardActionGate: true,
484
+ runtime: ctx.runtime,
485
+ });
486
+ }
487
+ catch (e) {
488
+ elog.warn(`inject_prompt dispatch failed: ${e}`);
489
+ }
490
+ });
491
+ return { toast: { type: 'info', content: '收到,正在为你处理…' } };
492
+ }
364
493
  async function handleCardActionEvent(ctx, data) {
365
494
  try {
495
+ // inject_prompt:通用「按钮点击 = 注入一条用户消息」机制(渠道内建,优先拦截)。
496
+ const injectResult = handleInjectPromptAction(ctx, data);
497
+ if (injectResult !== undefined)
498
+ return injectResult;
366
499
  // AskUserQuestion:表单卡片交互(宿主内建能力优先)
367
500
  const askResult = (0, ask_user_question_1.handleAskUserAction)(data, ctx.cfg, ctx.accountId);
368
501
  if (askResult !== undefined)
@@ -9,10 +9,477 @@
9
9
  */
10
10
  import { z } from 'zod';
11
11
  export { z };
12
- export declare const UATConfigSchema: any;
13
- export declare const FeishuGroupSchema: any;
14
- export declare const FeishuAccountConfigSchema: any;
15
- export declare const FeishuConfigSchema: any;
12
+ export declare const UATConfigSchema: z.ZodOptional<z.ZodObject<{
13
+ enabled: z.ZodOptional<z.ZodBoolean>;
14
+ allowedScopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
15
+ blockedScopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
16
+ }, z.core.$strip>>;
17
+ export declare const FeishuGroupSchema: z.ZodObject<{
18
+ groupPolicy: z.ZodOptional<z.ZodEnum<{
19
+ open: "open";
20
+ allowlist: "allowlist";
21
+ disabled: "disabled";
22
+ }>>;
23
+ requireMention: z.ZodOptional<z.ZodBoolean>;
24
+ respondToMentionAll: z.ZodOptional<z.ZodBoolean>;
25
+ tools: z.ZodOptional<z.ZodObject<{
26
+ allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
27
+ deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
28
+ }, z.core.$strip>>;
29
+ skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
30
+ enabled: z.ZodOptional<z.ZodBoolean>;
31
+ allowFrom: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>, z.ZodTransform<string[] | undefined, string | string[] | undefined>>;
32
+ systemPrompt: z.ZodOptional<z.ZodString>;
33
+ allowBots: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"mentions">]>>;
34
+ replyInThread: z.ZodOptional<z.ZodBoolean>;
35
+ }, z.core.$strip>;
36
+ export declare const FeishuAccountConfigSchema: z.ZodObject<{
37
+ appId: z.ZodOptional<z.ZodString>;
38
+ appSecret: z.ZodOptional<z.ZodString>;
39
+ encryptKey: z.ZodOptional<z.ZodString>;
40
+ verificationToken: z.ZodOptional<z.ZodString>;
41
+ name: z.ZodOptional<z.ZodString>;
42
+ enabled: z.ZodOptional<z.ZodBoolean>;
43
+ domain: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"feishu">, z.ZodLiteral<"lark">, z.ZodString]>>;
44
+ connectionMode: z.ZodOptional<z.ZodEnum<{
45
+ websocket: "websocket";
46
+ webhook: "webhook";
47
+ }>>;
48
+ webhookPath: z.ZodOptional<z.ZodString>;
49
+ webhookPort: z.ZodOptional<z.ZodNumber>;
50
+ dmPolicy: z.ZodOptional<z.ZodEnum<{
51
+ open: "open";
52
+ pairing: "pairing";
53
+ allowlist: "allowlist";
54
+ disabled: "disabled";
55
+ }>>;
56
+ allowFrom: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>, z.ZodTransform<string[] | undefined, string | string[] | undefined>>;
57
+ groupPolicy: z.ZodOptional<z.ZodEnum<{
58
+ open: "open";
59
+ allowlist: "allowlist";
60
+ disabled: "disabled";
61
+ }>>;
62
+ groupAllowFrom: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>, z.ZodTransform<string[] | undefined, string | string[] | undefined>>;
63
+ requireMention: z.ZodOptional<z.ZodBoolean>;
64
+ respondToMentionAll: z.ZodOptional<z.ZodBoolean>;
65
+ groups: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
66
+ groupPolicy: z.ZodOptional<z.ZodEnum<{
67
+ open: "open";
68
+ allowlist: "allowlist";
69
+ disabled: "disabled";
70
+ }>>;
71
+ requireMention: z.ZodOptional<z.ZodBoolean>;
72
+ respondToMentionAll: z.ZodOptional<z.ZodBoolean>;
73
+ tools: z.ZodOptional<z.ZodObject<{
74
+ allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
75
+ deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
76
+ }, z.core.$strip>>;
77
+ skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
78
+ enabled: z.ZodOptional<z.ZodBoolean>;
79
+ allowFrom: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>, z.ZodTransform<string[] | undefined, string | string[] | undefined>>;
80
+ systemPrompt: z.ZodOptional<z.ZodString>;
81
+ allowBots: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"mentions">]>>;
82
+ replyInThread: z.ZodOptional<z.ZodBoolean>;
83
+ }, z.core.$strip>>>;
84
+ historyLimit: z.ZodOptional<z.ZodNumber>;
85
+ dmHistoryLimit: z.ZodOptional<z.ZodNumber>;
86
+ dms: z.ZodOptional<z.ZodObject<{
87
+ historyLimit: z.ZodOptional<z.ZodNumber>;
88
+ }, z.core.$strip>>;
89
+ textChunkLimit: z.ZodOptional<z.ZodNumber>;
90
+ chunkMode: z.ZodOptional<z.ZodEnum<{
91
+ newline: "newline";
92
+ paragraph: "paragraph";
93
+ none: "none";
94
+ }>>;
95
+ blockStreamingCoalesce: z.ZodOptional<z.ZodObject<{
96
+ minChars: z.ZodOptional<z.ZodNumber>;
97
+ maxChars: z.ZodOptional<z.ZodNumber>;
98
+ idleMs: z.ZodOptional<z.ZodNumber>;
99
+ }, z.core.$strip>>;
100
+ mediaMaxMb: z.ZodOptional<z.ZodNumber>;
101
+ heartbeat: z.ZodOptional<z.ZodObject<{
102
+ every: z.ZodOptional<z.ZodString>;
103
+ activeHours: z.ZodOptional<z.ZodObject<{
104
+ start: z.ZodOptional<z.ZodString>;
105
+ end: z.ZodOptional<z.ZodString>;
106
+ timezone: z.ZodOptional<z.ZodString>;
107
+ }, z.core.$strip>>;
108
+ target: z.ZodOptional<z.ZodString>;
109
+ to: z.ZodOptional<z.ZodString>;
110
+ prompt: z.ZodOptional<z.ZodString>;
111
+ accountId: z.ZodOptional<z.ZodString>;
112
+ }, z.core.$strip>>;
113
+ replyMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
114
+ auto: "auto";
115
+ static: "static";
116
+ streaming: "streaming";
117
+ }>, z.ZodObject<{
118
+ default: z.ZodOptional<z.ZodEnum<{
119
+ auto: "auto";
120
+ static: "static";
121
+ streaming: "streaming";
122
+ }>>;
123
+ group: z.ZodOptional<z.ZodEnum<{
124
+ auto: "auto";
125
+ static: "static";
126
+ streaming: "streaming";
127
+ }>>;
128
+ direct: z.ZodOptional<z.ZodEnum<{
129
+ auto: "auto";
130
+ static: "static";
131
+ streaming: "streaming";
132
+ }>>;
133
+ }, z.core.$strip>]>>;
134
+ streaming: z.ZodOptional<z.ZodBoolean>;
135
+ blockStreaming: z.ZodOptional<z.ZodBoolean>;
136
+ toolUseDisplay: z.ZodOptional<z.ZodObject<{
137
+ showFullPaths: z.ZodOptional<z.ZodBoolean>;
138
+ }, z.core.$strip>>;
139
+ tools: z.ZodOptional<z.ZodObject<{
140
+ doc: z.ZodOptional<z.ZodBoolean>;
141
+ wiki: z.ZodOptional<z.ZodBoolean>;
142
+ drive: z.ZodOptional<z.ZodBoolean>;
143
+ perm: z.ZodOptional<z.ZodBoolean>;
144
+ scopes: z.ZodOptional<z.ZodBoolean>;
145
+ }, z.core.$strip>>;
146
+ footer: z.ZodOptional<z.ZodObject<{
147
+ status: z.ZodOptional<z.ZodBoolean>;
148
+ elapsed: z.ZodOptional<z.ZodBoolean>;
149
+ tokens: z.ZodOptional<z.ZodBoolean>;
150
+ cache: z.ZodOptional<z.ZodBoolean>;
151
+ context: z.ZodOptional<z.ZodBoolean>;
152
+ model: z.ZodOptional<z.ZodBoolean>;
153
+ }, z.core.$strip>>;
154
+ markdown: z.ZodOptional<z.ZodObject<{
155
+ tables: z.ZodOptional<z.ZodEnum<{
156
+ off: "off";
157
+ bullets: "bullets";
158
+ code: "code";
159
+ }>>;
160
+ }, z.core.$strip>>;
161
+ configWrites: z.ZodOptional<z.ZodBoolean>;
162
+ capabilities: z.ZodOptional<z.ZodObject<{
163
+ image: z.ZodOptional<z.ZodBoolean>;
164
+ audio: z.ZodOptional<z.ZodBoolean>;
165
+ video: z.ZodOptional<z.ZodBoolean>;
166
+ }, z.core.$strip>>;
167
+ dedup: z.ZodOptional<z.ZodObject<{
168
+ ttlMs: z.ZodOptional<z.ZodNumber>;
169
+ maxEntries: z.ZodOptional<z.ZodNumber>;
170
+ }, z.core.$strip>>;
171
+ reactionNotifications: z.ZodOptional<z.ZodEnum<{
172
+ off: "off";
173
+ own: "own";
174
+ all: "all";
175
+ }>>;
176
+ threadSession: z.ZodOptional<z.ZodBoolean>;
177
+ allowBots: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"mentions">]>>;
178
+ replyInThread: z.ZodOptional<z.ZodBoolean>;
179
+ uat: z.ZodOptional<z.ZodObject<{
180
+ enabled: z.ZodOptional<z.ZodBoolean>;
181
+ allowedScopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
182
+ blockedScopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
183
+ }, z.core.$strip>>;
184
+ }, z.core.$strip>;
185
+ export declare const FeishuConfigSchema: z.ZodObject<{
186
+ appId: z.ZodOptional<z.ZodString>;
187
+ appSecret: z.ZodOptional<z.ZodString>;
188
+ encryptKey: z.ZodOptional<z.ZodString>;
189
+ verificationToken: z.ZodOptional<z.ZodString>;
190
+ name: z.ZodOptional<z.ZodString>;
191
+ enabled: z.ZodOptional<z.ZodBoolean>;
192
+ domain: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"feishu">, z.ZodLiteral<"lark">, z.ZodString]>>;
193
+ connectionMode: z.ZodOptional<z.ZodEnum<{
194
+ websocket: "websocket";
195
+ webhook: "webhook";
196
+ }>>;
197
+ webhookPath: z.ZodOptional<z.ZodString>;
198
+ webhookPort: z.ZodOptional<z.ZodNumber>;
199
+ dmPolicy: z.ZodOptional<z.ZodEnum<{
200
+ open: "open";
201
+ pairing: "pairing";
202
+ allowlist: "allowlist";
203
+ disabled: "disabled";
204
+ }>>;
205
+ allowFrom: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>, z.ZodTransform<string[] | undefined, string | string[] | undefined>>;
206
+ groupPolicy: z.ZodOptional<z.ZodEnum<{
207
+ open: "open";
208
+ allowlist: "allowlist";
209
+ disabled: "disabled";
210
+ }>>;
211
+ groupAllowFrom: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>, z.ZodTransform<string[] | undefined, string | string[] | undefined>>;
212
+ requireMention: z.ZodOptional<z.ZodBoolean>;
213
+ respondToMentionAll: z.ZodOptional<z.ZodBoolean>;
214
+ groups: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
215
+ groupPolicy: z.ZodOptional<z.ZodEnum<{
216
+ open: "open";
217
+ allowlist: "allowlist";
218
+ disabled: "disabled";
219
+ }>>;
220
+ requireMention: z.ZodOptional<z.ZodBoolean>;
221
+ respondToMentionAll: z.ZodOptional<z.ZodBoolean>;
222
+ tools: z.ZodOptional<z.ZodObject<{
223
+ allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
224
+ deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
225
+ }, z.core.$strip>>;
226
+ skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
227
+ enabled: z.ZodOptional<z.ZodBoolean>;
228
+ allowFrom: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>, z.ZodTransform<string[] | undefined, string | string[] | undefined>>;
229
+ systemPrompt: z.ZodOptional<z.ZodString>;
230
+ allowBots: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"mentions">]>>;
231
+ replyInThread: z.ZodOptional<z.ZodBoolean>;
232
+ }, z.core.$strip>>>;
233
+ historyLimit: z.ZodOptional<z.ZodNumber>;
234
+ dmHistoryLimit: z.ZodOptional<z.ZodNumber>;
235
+ dms: z.ZodOptional<z.ZodObject<{
236
+ historyLimit: z.ZodOptional<z.ZodNumber>;
237
+ }, z.core.$strip>>;
238
+ textChunkLimit: z.ZodOptional<z.ZodNumber>;
239
+ chunkMode: z.ZodOptional<z.ZodEnum<{
240
+ newline: "newline";
241
+ paragraph: "paragraph";
242
+ none: "none";
243
+ }>>;
244
+ blockStreamingCoalesce: z.ZodOptional<z.ZodObject<{
245
+ minChars: z.ZodOptional<z.ZodNumber>;
246
+ maxChars: z.ZodOptional<z.ZodNumber>;
247
+ idleMs: z.ZodOptional<z.ZodNumber>;
248
+ }, z.core.$strip>>;
249
+ mediaMaxMb: z.ZodOptional<z.ZodNumber>;
250
+ heartbeat: z.ZodOptional<z.ZodObject<{
251
+ every: z.ZodOptional<z.ZodString>;
252
+ activeHours: z.ZodOptional<z.ZodObject<{
253
+ start: z.ZodOptional<z.ZodString>;
254
+ end: z.ZodOptional<z.ZodString>;
255
+ timezone: z.ZodOptional<z.ZodString>;
256
+ }, z.core.$strip>>;
257
+ target: z.ZodOptional<z.ZodString>;
258
+ to: z.ZodOptional<z.ZodString>;
259
+ prompt: z.ZodOptional<z.ZodString>;
260
+ accountId: z.ZodOptional<z.ZodString>;
261
+ }, z.core.$strip>>;
262
+ replyMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
263
+ auto: "auto";
264
+ static: "static";
265
+ streaming: "streaming";
266
+ }>, z.ZodObject<{
267
+ default: z.ZodOptional<z.ZodEnum<{
268
+ auto: "auto";
269
+ static: "static";
270
+ streaming: "streaming";
271
+ }>>;
272
+ group: z.ZodOptional<z.ZodEnum<{
273
+ auto: "auto";
274
+ static: "static";
275
+ streaming: "streaming";
276
+ }>>;
277
+ direct: z.ZodOptional<z.ZodEnum<{
278
+ auto: "auto";
279
+ static: "static";
280
+ streaming: "streaming";
281
+ }>>;
282
+ }, z.core.$strip>]>>;
283
+ streaming: z.ZodOptional<z.ZodBoolean>;
284
+ blockStreaming: z.ZodOptional<z.ZodBoolean>;
285
+ toolUseDisplay: z.ZodOptional<z.ZodObject<{
286
+ showFullPaths: z.ZodOptional<z.ZodBoolean>;
287
+ }, z.core.$strip>>;
288
+ tools: z.ZodOptional<z.ZodObject<{
289
+ doc: z.ZodOptional<z.ZodBoolean>;
290
+ wiki: z.ZodOptional<z.ZodBoolean>;
291
+ drive: z.ZodOptional<z.ZodBoolean>;
292
+ perm: z.ZodOptional<z.ZodBoolean>;
293
+ scopes: z.ZodOptional<z.ZodBoolean>;
294
+ }, z.core.$strip>>;
295
+ footer: z.ZodOptional<z.ZodObject<{
296
+ status: z.ZodOptional<z.ZodBoolean>;
297
+ elapsed: z.ZodOptional<z.ZodBoolean>;
298
+ tokens: z.ZodOptional<z.ZodBoolean>;
299
+ cache: z.ZodOptional<z.ZodBoolean>;
300
+ context: z.ZodOptional<z.ZodBoolean>;
301
+ model: z.ZodOptional<z.ZodBoolean>;
302
+ }, z.core.$strip>>;
303
+ markdown: z.ZodOptional<z.ZodObject<{
304
+ tables: z.ZodOptional<z.ZodEnum<{
305
+ off: "off";
306
+ bullets: "bullets";
307
+ code: "code";
308
+ }>>;
309
+ }, z.core.$strip>>;
310
+ configWrites: z.ZodOptional<z.ZodBoolean>;
311
+ capabilities: z.ZodOptional<z.ZodObject<{
312
+ image: z.ZodOptional<z.ZodBoolean>;
313
+ audio: z.ZodOptional<z.ZodBoolean>;
314
+ video: z.ZodOptional<z.ZodBoolean>;
315
+ }, z.core.$strip>>;
316
+ dedup: z.ZodOptional<z.ZodObject<{
317
+ ttlMs: z.ZodOptional<z.ZodNumber>;
318
+ maxEntries: z.ZodOptional<z.ZodNumber>;
319
+ }, z.core.$strip>>;
320
+ reactionNotifications: z.ZodOptional<z.ZodEnum<{
321
+ off: "off";
322
+ own: "own";
323
+ all: "all";
324
+ }>>;
325
+ threadSession: z.ZodOptional<z.ZodBoolean>;
326
+ allowBots: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"mentions">]>>;
327
+ replyInThread: z.ZodOptional<z.ZodBoolean>;
328
+ uat: z.ZodOptional<z.ZodObject<{
329
+ enabled: z.ZodOptional<z.ZodBoolean>;
330
+ allowedScopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
331
+ blockedScopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
332
+ }, z.core.$strip>>;
333
+ accounts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
334
+ appId: z.ZodOptional<z.ZodString>;
335
+ appSecret: z.ZodOptional<z.ZodString>;
336
+ encryptKey: z.ZodOptional<z.ZodString>;
337
+ verificationToken: z.ZodOptional<z.ZodString>;
338
+ name: z.ZodOptional<z.ZodString>;
339
+ enabled: z.ZodOptional<z.ZodBoolean>;
340
+ domain: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"feishu">, z.ZodLiteral<"lark">, z.ZodString]>>;
341
+ connectionMode: z.ZodOptional<z.ZodEnum<{
342
+ websocket: "websocket";
343
+ webhook: "webhook";
344
+ }>>;
345
+ webhookPath: z.ZodOptional<z.ZodString>;
346
+ webhookPort: z.ZodOptional<z.ZodNumber>;
347
+ dmPolicy: z.ZodOptional<z.ZodEnum<{
348
+ open: "open";
349
+ pairing: "pairing";
350
+ allowlist: "allowlist";
351
+ disabled: "disabled";
352
+ }>>;
353
+ allowFrom: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>, z.ZodTransform<string[] | undefined, string | string[] | undefined>>;
354
+ groupPolicy: z.ZodOptional<z.ZodEnum<{
355
+ open: "open";
356
+ allowlist: "allowlist";
357
+ disabled: "disabled";
358
+ }>>;
359
+ groupAllowFrom: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>, z.ZodTransform<string[] | undefined, string | string[] | undefined>>;
360
+ requireMention: z.ZodOptional<z.ZodBoolean>;
361
+ respondToMentionAll: z.ZodOptional<z.ZodBoolean>;
362
+ groups: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
363
+ groupPolicy: z.ZodOptional<z.ZodEnum<{
364
+ open: "open";
365
+ allowlist: "allowlist";
366
+ disabled: "disabled";
367
+ }>>;
368
+ requireMention: z.ZodOptional<z.ZodBoolean>;
369
+ respondToMentionAll: z.ZodOptional<z.ZodBoolean>;
370
+ tools: z.ZodOptional<z.ZodObject<{
371
+ allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
372
+ deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
373
+ }, z.core.$strip>>;
374
+ skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
375
+ enabled: z.ZodOptional<z.ZodBoolean>;
376
+ allowFrom: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>, z.ZodTransform<string[] | undefined, string | string[] | undefined>>;
377
+ systemPrompt: z.ZodOptional<z.ZodString>;
378
+ allowBots: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"mentions">]>>;
379
+ replyInThread: z.ZodOptional<z.ZodBoolean>;
380
+ }, z.core.$strip>>>;
381
+ historyLimit: z.ZodOptional<z.ZodNumber>;
382
+ dmHistoryLimit: z.ZodOptional<z.ZodNumber>;
383
+ dms: z.ZodOptional<z.ZodObject<{
384
+ historyLimit: z.ZodOptional<z.ZodNumber>;
385
+ }, z.core.$strip>>;
386
+ textChunkLimit: z.ZodOptional<z.ZodNumber>;
387
+ chunkMode: z.ZodOptional<z.ZodEnum<{
388
+ newline: "newline";
389
+ paragraph: "paragraph";
390
+ none: "none";
391
+ }>>;
392
+ blockStreamingCoalesce: z.ZodOptional<z.ZodObject<{
393
+ minChars: z.ZodOptional<z.ZodNumber>;
394
+ maxChars: z.ZodOptional<z.ZodNumber>;
395
+ idleMs: z.ZodOptional<z.ZodNumber>;
396
+ }, z.core.$strip>>;
397
+ mediaMaxMb: z.ZodOptional<z.ZodNumber>;
398
+ heartbeat: z.ZodOptional<z.ZodObject<{
399
+ every: z.ZodOptional<z.ZodString>;
400
+ activeHours: z.ZodOptional<z.ZodObject<{
401
+ start: z.ZodOptional<z.ZodString>;
402
+ end: z.ZodOptional<z.ZodString>;
403
+ timezone: z.ZodOptional<z.ZodString>;
404
+ }, z.core.$strip>>;
405
+ target: z.ZodOptional<z.ZodString>;
406
+ to: z.ZodOptional<z.ZodString>;
407
+ prompt: z.ZodOptional<z.ZodString>;
408
+ accountId: z.ZodOptional<z.ZodString>;
409
+ }, z.core.$strip>>;
410
+ replyMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
411
+ auto: "auto";
412
+ static: "static";
413
+ streaming: "streaming";
414
+ }>, z.ZodObject<{
415
+ default: z.ZodOptional<z.ZodEnum<{
416
+ auto: "auto";
417
+ static: "static";
418
+ streaming: "streaming";
419
+ }>>;
420
+ group: z.ZodOptional<z.ZodEnum<{
421
+ auto: "auto";
422
+ static: "static";
423
+ streaming: "streaming";
424
+ }>>;
425
+ direct: z.ZodOptional<z.ZodEnum<{
426
+ auto: "auto";
427
+ static: "static";
428
+ streaming: "streaming";
429
+ }>>;
430
+ }, z.core.$strip>]>>;
431
+ streaming: z.ZodOptional<z.ZodBoolean>;
432
+ blockStreaming: z.ZodOptional<z.ZodBoolean>;
433
+ toolUseDisplay: z.ZodOptional<z.ZodObject<{
434
+ showFullPaths: z.ZodOptional<z.ZodBoolean>;
435
+ }, z.core.$strip>>;
436
+ tools: z.ZodOptional<z.ZodObject<{
437
+ doc: z.ZodOptional<z.ZodBoolean>;
438
+ wiki: z.ZodOptional<z.ZodBoolean>;
439
+ drive: z.ZodOptional<z.ZodBoolean>;
440
+ perm: z.ZodOptional<z.ZodBoolean>;
441
+ scopes: z.ZodOptional<z.ZodBoolean>;
442
+ }, z.core.$strip>>;
443
+ footer: z.ZodOptional<z.ZodObject<{
444
+ status: z.ZodOptional<z.ZodBoolean>;
445
+ elapsed: z.ZodOptional<z.ZodBoolean>;
446
+ tokens: z.ZodOptional<z.ZodBoolean>;
447
+ cache: z.ZodOptional<z.ZodBoolean>;
448
+ context: z.ZodOptional<z.ZodBoolean>;
449
+ model: z.ZodOptional<z.ZodBoolean>;
450
+ }, z.core.$strip>>;
451
+ markdown: z.ZodOptional<z.ZodObject<{
452
+ tables: z.ZodOptional<z.ZodEnum<{
453
+ off: "off";
454
+ bullets: "bullets";
455
+ code: "code";
456
+ }>>;
457
+ }, z.core.$strip>>;
458
+ configWrites: z.ZodOptional<z.ZodBoolean>;
459
+ capabilities: z.ZodOptional<z.ZodObject<{
460
+ image: z.ZodOptional<z.ZodBoolean>;
461
+ audio: z.ZodOptional<z.ZodBoolean>;
462
+ video: z.ZodOptional<z.ZodBoolean>;
463
+ }, z.core.$strip>>;
464
+ dedup: z.ZodOptional<z.ZodObject<{
465
+ ttlMs: z.ZodOptional<z.ZodNumber>;
466
+ maxEntries: z.ZodOptional<z.ZodNumber>;
467
+ }, z.core.$strip>>;
468
+ reactionNotifications: z.ZodOptional<z.ZodEnum<{
469
+ off: "off";
470
+ own: "own";
471
+ all: "all";
472
+ }>>;
473
+ threadSession: z.ZodOptional<z.ZodBoolean>;
474
+ allowBots: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"mentions">]>>;
475
+ replyInThread: z.ZodOptional<z.ZodBoolean>;
476
+ uat: z.ZodOptional<z.ZodObject<{
477
+ enabled: z.ZodOptional<z.ZodBoolean>;
478
+ allowedScopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
479
+ blockedScopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
480
+ }, z.core.$strip>>;
481
+ }, z.core.$strip>>>;
482
+ }, z.core.$strip>;
16
483
  /**
17
484
  * JSON Schema derived from FeishuConfigSchema.
18
485
  *
@@ -88,4 +88,11 @@ export declare function checkMessageGate(params: {
88
88
  /** account 级别的 ClawdbotConfig(channels.feishu 已替换为 per-account 合并后的配置) */
89
89
  accountScopedCfg?: ClawdbotConfig;
90
90
  log: (...args: unknown[]) => void;
91
+ /**
92
+ * When true, treat the mention requirement as already satisfied (e.g. a card
93
+ * click is itself an explicit user action targeting the bot). Group/DM
94
+ * admission and sender-allowlist checks still run — this only bypasses the
95
+ * "must @-mention the bot" requirement, not access control.
96
+ */
97
+ mentionSatisfied?: boolean;
91
98
  }): Promise<GateResult>;
@@ -216,7 +216,7 @@ function checkBotSenderGate(params) {
216
216
  // Internal: group gate
217
217
  // ---------------------------------------------------------------------------
218
218
  function checkGroupGate(params) {
219
- const { ctx, accountFeishuCfg, account, accountScopedCfg, log } = params;
219
+ const { ctx, accountFeishuCfg, account, accountScopedCfg, log, mentionSatisfied } = params;
220
220
  const core = lark_client_1.LarkClient.runtime;
221
221
  // ---- Layer 1: Group-level admission (shared with bot path) ----
222
222
  const access = resolveFeishuGroupAccess(params);
@@ -259,7 +259,7 @@ function checkGroupGate(params) {
259
259
  groupIdCaseInsensitive: true,
260
260
  requireMentionOverride: accountFeishuCfg?.requireMention,
261
261
  });
262
- if (requireMention && !(0, mention_1.mentionedBot)(ctx)) {
262
+ if (requireMention && !(0, mention_1.mentionedBot)(ctx) && !mentionSatisfied) {
263
263
  // Check if @all mention should bypass the mention requirement
264
264
  if (ctx.mentionAll) {
265
265
  const respondToAll = resolveRespondToMentionAll({
@@ -17,6 +17,10 @@ export interface InboundHandlerParams {
17
17
  accountId?: string;
18
18
  replyToMessageId?: string;
19
19
  forceMention?: boolean;
20
+ /** Run the real access-control gate but treat the mention requirement as
21
+ * satisfied (card-action synthetic messages). Takes precedence over
22
+ * forceMention. See handleFeishuMessage. */
23
+ cardActionGate?: boolean;
20
24
  skipTyping?: boolean;
21
25
  }
22
26
  type InboundHandler = (params: InboundHandlerParams) => Promise<void>;
@@ -32,6 +32,12 @@ export declare function handleFeishuMessage(params: {
32
32
  /** When true, skip the policy gate (mention requirement, allowlist).
33
33
  * Used for synthetic messages that are not real user messages. */
34
34
  forceMention?: boolean;
35
+ /** When true, run the real access-control gate but treat the mention
36
+ * requirement as already satisfied. Used for card-action synthetic messages:
37
+ * the click is an explicit user action targeting the bot (implicit mention),
38
+ * but group/DM admission and sender allowlists must still be enforced.
39
+ * Takes precedence over forceMention. */
40
+ cardActionGate?: boolean;
35
41
  /** When true, skip the typing indicator for this dispatch (e.g. reactions). */
36
42
  skipTyping?: boolean;
37
43
  }): Promise<void>;
@@ -40,7 +40,7 @@ const logger = (0, lark_logger_1.larkLogger)('inbound/handler');
40
40
  // Public: handle inbound message
41
41
  // ---------------------------------------------------------------------------
42
42
  async function handleFeishuMessage(params) {
43
- const { cfg, event, botOpenId, runtime, chatHistories, accountId, replyToMessageId, forceMention, skipTyping } = params;
43
+ const { cfg, event, botOpenId, runtime, chatHistories, accountId, replyToMessageId, forceMention, cardActionGate, skipTyping, } = params;
44
44
  // 1. Account resolution
45
45
  const account = (0, accounts_1.getLarkAccount)(cfg, accountId);
46
46
  const accountFeishuCfg = account.config;
@@ -116,10 +116,21 @@ async function handleFeishuMessage(params) {
116
116
  log(`feishu[${account.accountId}]: received message from ${ctx.senderId} in ${ctx.chatId} (${ctx.chatType})`);
117
117
  logger.info(`received from ${ctx.senderId} in ${ctx.chatId} (${ctx.chatType})`);
118
118
  const historyLimit = Math.max(0, accountFeishuCfg?.historyLimit ?? accountScopedCfg.messages?.groupChat?.historyLimit ?? reply_history_1.DEFAULT_GROUP_HISTORY_LIMIT);
119
- // 5. Gate: policy / access-control checks (skipped for synthetic messages)
120
- const gate = forceMention
119
+ // 5. Gate: policy / access-control checks (skipped for synthetic messages).
120
+ // cardActionGate runs the real gate with the mention requirement treated as
121
+ // satisfied (the click is an implicit mention) — it takes precedence over
122
+ // forceMention so card actions are still subject to group/DM admission and
123
+ // sender allowlists.
124
+ const gate = forceMention && !cardActionGate
121
125
  ? { allowed: true }
122
- : await (0, gate_1.checkMessageGate)({ ctx, accountFeishuCfg, account, accountScopedCfg, log });
126
+ : await (0, gate_1.checkMessageGate)({
127
+ ctx,
128
+ accountFeishuCfg,
129
+ account,
130
+ accountScopedCfg,
131
+ log,
132
+ mentionSatisfied: cardActionGate,
133
+ });
123
134
  if (!gate.allowed) {
124
135
  if (gate.reason === 'no_mention') {
125
136
  logger.info(`rejected: no bot mention in group ${ctx.chatId}`);
@@ -23,4 +23,9 @@ export declare function dispatchSyntheticTextMessage(params: {
23
23
  error?: (msg: string) => void;
24
24
  };
25
25
  forceMention?: boolean;
26
+ /** When true, run the real access-control gate with the mention requirement
27
+ * treated as satisfied (see handleFeishuMessage.cardActionGate). Use for
28
+ * card-action synthetic messages so group/DM admission + sender allowlists
29
+ * are still enforced. Takes precedence over forceMention. */
30
+ cardActionGate?: boolean;
26
31
  }): Promise<string>;
@@ -15,7 +15,7 @@ const lark_ticket_1 = require("../../core/lark-ticket.js");
15
15
  const handler_registry_1 = require("./handler-registry.js");
16
16
  async function dispatchSyntheticTextMessage(params) {
17
17
  const handleFeishuMessage = (0, handler_registry_1.getInboundHandler)();
18
- const { cfg, accountId, chatId, senderOpenId, text, syntheticMessageId, replyToMessageId, chatType, threadId, runtime, forceMention = true, } = params;
18
+ const { cfg, accountId, chatId, senderOpenId, text, syntheticMessageId, replyToMessageId, chatType, threadId, runtime, forceMention = true, cardActionGate, } = params;
19
19
  const syntheticEvent = {
20
20
  sender: {
21
21
  sender_id: { open_id: senderOpenId },
@@ -48,6 +48,7 @@ async function dispatchSyntheticTextMessage(params) {
48
48
  event: syntheticEvent,
49
49
  accountId,
50
50
  forceMention,
51
+ cardActionGate,
51
52
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
53
  runtime: runtime,
53
54
  replyToMessageId,
@@ -1,2 +1,2 @@
1
- declare const _default: any;
1
+ declare const _default: import("tsdown/types-D2Jnze_i").d;
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: any;
1
+ declare const _default: UserConfig;
2
2
  export default _default;