@openclaw/feishu 2026.7.2-beta.2 → 2026.7.2-beta.3

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.
@@ -1,4 +1,5 @@
1
- import { collectConditionalChannelFieldAssignments, collectSecretInputAssignment, createChannelSecretTargetRegistryEntries, getChannelSurface, hasConfiguredSecretInputValue, hasOwnProperty, isBaseFieldActiveForChannelSurface, normalizeSecretStringValue } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
1
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
2
+ import { collectConditionalChannelFieldAssignments, createChannelSecretTargetRegistryEntries, getChannelSurface, hasConfiguredSecretInputValue, hasOwnProperty, normalizeSecretStringValue } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
2
3
  //#region extensions/feishu/src/secret-contract.ts
3
4
  const secretTargetRegistryEntries = createChannelSecretTargetRegistryEntries({
4
5
  channelKey: "feishu",
@@ -17,34 +18,24 @@ function collectRuntimeConfigAssignments(params) {
17
18
  const resolved = getChannelSurface(params.config, "feishu");
18
19
  if (!resolved) return;
19
20
  const { channel: feishu, surface } = resolved;
20
- const topLevelAppSecretActive = surface.channelEnabled && hasConfiguredSecretInputValue(feishu.appId, params.defaults) && hasConfiguredSecretInputValue(feishu.appSecret, params.defaults) || isBaseFieldActiveForChannelSurface(surface, "appSecret");
21
- collectSecretInputAssignment({
22
- value: feishu.appSecret,
23
- path: "channels.feishu.appSecret",
24
- expected: "string",
21
+ if (surface.channelEnabled && hasConfiguredSecretInputValue(feishu.appId, params.defaults) && hasConfiguredSecretInputValue(feishu.appSecret, params.defaults) && surface.hasExplicitAccounts && !surface.accounts.some(({ accountId }) => normalizeAccountId(accountId) === DEFAULT_ACCOUNT_ID)) surface.accounts.push({
22
+ accountId: "default",
23
+ account: {},
24
+ enabled: true
25
+ });
26
+ collectConditionalChannelFieldAssignments({
27
+ channelKey: "feishu",
28
+ field: "appSecret",
29
+ channel: feishu,
30
+ surface,
25
31
  defaults: params.defaults,
26
32
  context: params.context,
27
- active: topLevelAppSecretActive,
28
- inactiveReason: "no enabled account inherits this top-level Feishu appSecret.",
29
- apply: (value) => {
30
- feishu.appSecret = value;
31
- }
33
+ topLevelActiveWithoutAccounts: surface.channelEnabled,
34
+ topLevelInheritedAccountActive: ({ account, enabled }) => enabled && !hasOwnProperty(account, "appSecret"),
35
+ accountActive: ({ enabled }) => enabled,
36
+ topInactiveReason: "no enabled account inherits this top-level Feishu appSecret.",
37
+ accountInactiveReason: "Feishu account is disabled."
32
38
  });
33
- if (surface.hasExplicitAccounts) for (const { accountId, account, enabled } of surface.accounts) {
34
- if (!hasOwnProperty(account, "appSecret")) continue;
35
- collectSecretInputAssignment({
36
- value: account.appSecret,
37
- path: `channels.feishu.accounts.${accountId}.appSecret`,
38
- expected: "string",
39
- defaults: params.defaults,
40
- context: params.context,
41
- active: enabled,
42
- inactiveReason: "Feishu account is disabled.",
43
- apply: (value) => {
44
- account.appSecret = value;
45
- }
46
- });
47
- }
48
39
  const baseConnectionMode = normalizeSecretStringValue(feishu.connectionMode) === "webhook" ? "webhook" : "websocket";
49
40
  const resolveAccountMode = (account) => hasOwnProperty(account, "connectionMode") ? normalizeSecretStringValue(account.connectionMode) : baseConnectionMode;
50
41
  collectConditionalChannelFieldAssignments({
@@ -1,2 +1,2 @@
1
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-DnlMcrn5.js";
1
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-BCpDLdg9.js";
2
2
  export { channelSecrets, collectRuntimeConfigAssignments, secretTargetRegistryEntries };
@@ -1,6 +1,6 @@
1
1
  import { d as isRecord$1, h as requestFeishuApi, s as resolveFeishuRuntimeAccount } from "./accounts-BDoGHJDk.js";
2
2
  import { i as resolveReceiveIdType, r as normalizeFeishuTarget } from "./targets-BUjQ1TcA.js";
3
- import { c as assertFeishuPostWithinEnvelope, f as materializeFeishuPostMarkdownSoftBreaks, i as toFeishuSendResult, l as buildFeishuPostMessageContent, o as resolveFeishuCardTemplate, r as resolveFeishuReceiptKind, t as assertFeishuMessageApiSuccess } from "./send-result-DsqTk27v.js";
3
+ import { c as assertFeishuPostWithinEnvelope, f as materializeFeishuPostMarkdownSoftBreaks, i as toFeishuSendResult, l as buildFeishuPostMessageContent, o as resolveFeishuCardTemplate, r as resolveFeishuReceiptKind, t as assertFeishuMessageApiSuccess } from "./send-result-DfE5Fhz6.js";
4
4
  import { r as createFeishuClient } from "./client-87BmeMpj.js";
5
5
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
6
6
  import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
@@ -9,6 +9,7 @@ import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
9
9
  import fs from "node:fs";
10
10
  import path from "node:path";
11
11
  import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, runFfmpeg, runFfprobe } from "openclaw/plugin-sdk/media-runtime";
12
+ import { isBlockedHostnameOrIp, resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
12
13
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
13
14
  import { readRegularFile, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
14
15
  import { Readable } from "node:stream";
@@ -38,6 +39,33 @@ function resolveFeishuIdentityHeaderTitle(identity) {
38
39
  return (emoji ? `${emoji} ${name}` : name).trim();
39
40
  }
40
41
  //#endregion
42
+ //#region extensions/feishu/src/media-fallback.ts
43
+ const FEISHU_MEDIA_UPLOAD_FAILURE_FALLBACK_TEXT = "Media upload failed. Please try again.";
44
+ function hasAsciiControlCharacter(value) {
45
+ return Array.from(value).some((character) => {
46
+ const code = character.charCodeAt(0);
47
+ return code <= 31 || code === 127;
48
+ });
49
+ }
50
+ async function resolvePublicFeishuMediaReference(value) {
51
+ const raw = value?.trim();
52
+ if (!raw || hasAsciiControlCharacter(raw)) return;
53
+ try {
54
+ const parsed = new URL(raw);
55
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return;
56
+ if (parsed.username || parsed.password || isBlockedHostnameOrIp(parsed.hostname)) return;
57
+ await resolvePinnedHostnameWithPolicy(parsed.hostname);
58
+ return parsed.href;
59
+ } catch {
60
+ return;
61
+ }
62
+ }
63
+ async function buildFeishuMediaFallbackText(params) {
64
+ const mediaUrl = await resolvePublicFeishuMediaReference(params.mediaUrl);
65
+ const attachmentText = mediaUrl ? `${params.mediaLinkStyle === "plain" ? "" : "📎 "}${mediaUrl}` : FEISHU_MEDIA_UPLOAD_FAILURE_FALLBACK_TEXT;
66
+ return [params.text?.trim(), attachmentText].filter(Boolean).join("\n\n");
67
+ }
68
+ //#endregion
41
69
  //#region extensions/feishu/src/external-keys.ts
42
70
  const CONTROL_CHARS_RE = /\p{Cc}/u;
43
71
  const MAX_EXTERNAL_KEY_LENGTH = 512;
@@ -480,6 +508,7 @@ function normalizeMediaNameForExtension(raw) {
480
508
  }
481
509
  }
482
510
  function shouldSuppressFeishuTextForVoiceMedia(params) {
511
+ if (params.ttsSupplement) return params.ttsSupplement.visibleTextAlreadyDelivered === true;
483
512
  if (params.audioAsVoice === true) return true;
484
513
  if (params.fileName && isFeishuNativeVoiceAudio({
485
514
  fileName: params.fileName,
@@ -1285,4 +1314,4 @@ async function sendMarkdownCardFeishu(params) {
1285
1314
  });
1286
1315
  }
1287
1316
  //#endregion
1288
- export { resolveFeishuIdentityEmoji as _, sendMarkdownCardFeishu as a, parsePostContent as c, isMentionForwardRequest as d, isFeishuGroupChatType as f, normalizeFeishuExternalKey as g, shouldSuppressFeishuTextForVoiceMedia as h, sendCardFeishu as i, extractMentionTargets as l, sendMediaFeishu as m, getMessageFeishu as n, sendMessageFeishu as o, saveMessageResourceFeishu as p, listFeishuThreadMessages as r, sendStructuredCardFeishu as s, editMessageFeishu as t, isFeishuBroadcastMention as u, resolveFeishuIdentityHeaderTitle as v };
1317
+ export { buildFeishuMediaFallbackText as _, sendMarkdownCardFeishu as a, parsePostContent as c, isMentionForwardRequest as d, isFeishuGroupChatType as f, normalizeFeishuExternalKey as g, shouldSuppressFeishuTextForVoiceMedia as h, sendCardFeishu as i, extractMentionTargets as l, sendMediaFeishu as m, getMessageFeishu as n, sendMessageFeishu as o, saveMessageResourceFeishu as p, listFeishuThreadMessages as r, sendStructuredCardFeishu as s, editMessageFeishu as t, isFeishuBroadcastMention as u, resolveFeishuIdentityEmoji as v, resolveFeishuIdentityHeaderTitle as y };
@@ -1,5 +1,6 @@
1
1
  import { d as isRecord$1 } from "./accounts-BDoGHJDk.js";
2
2
  import { r as normalizeFeishuTarget, t as detectIdType } from "./targets-BUjQ1TcA.js";
3
+ import { compileAllowlist, resolveAllowlistMatchByCandidates } from "openclaw/plugin-sdk/allow-from";
3
4
  import { ToolAuthorizationError } from "openclaw/plugin-sdk/channel-actions";
4
5
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
5
6
  import { resolveScopeKeyCaseInsensitive, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
@@ -307,6 +308,9 @@ function normalizeChatId(raw) {
307
308
  if (!raw) return "";
308
309
  return normalizeFeishuTarget(raw) ?? raw.trim();
309
310
  }
311
+ function normalizeFeishuAllowlist(entries) {
312
+ return (entries ?? []).map((entry) => normalizeFeishuAllowEntry(String(entry))).filter(Boolean);
313
+ }
310
314
  function readContextFields(ctx) {
311
315
  if (isActionContext(ctx)) return {
312
316
  accountId: normalizeOptionalString(ctx.accountId),
@@ -350,10 +354,13 @@ function isFeishuGroupReadAllowed(cfg, account, chatId, current) {
350
354
  groupId: chatId
351
355
  });
352
356
  const normalizedChatId = normalizeFeishuAllowEntry(chatId);
353
- return explicitlyConfigured || (account.config.groupAllowFrom ?? []).some((entry) => {
354
- const normalized = normalizeFeishuAllowEntry(String(entry));
355
- return normalized === "*" || normalized === normalizedChatId;
356
- });
357
+ return explicitlyConfigured || resolveAllowlistMatchByCandidates({
358
+ allowList: normalizeFeishuAllowlist(account.config.groupAllowFrom),
359
+ candidates: [{
360
+ value: normalizedChatId,
361
+ source: "id"
362
+ }]
363
+ }).allowed;
357
364
  }
358
365
  function isFeishuGroupReadEnabled(cfg, account, chatId) {
359
366
  if (resolveFeishuReadGroupPolicy(cfg, account) === "disabled") return false;
@@ -363,7 +370,7 @@ function isFeishuGroupReadEnabled(cfg, account, chatId) {
363
370
  })?.enabled !== false;
364
371
  }
365
372
  function isDmUniversallyAllowed(account) {
366
- return (account.config.allowFrom ?? []).some((entry) => normalizeFeishuAllowEntry(String(entry)) === "*");
373
+ return compileAllowlist(normalizeFeishuAllowlist(account.config.allowFrom)).wildcard;
367
374
  }
368
375
  function assertFeishuChatReadAllowed(params) {
369
376
  const authorization = resolveFeishuChatReadPreliminaryAuthorization(params);
@@ -431,7 +438,7 @@ function authorizeFeishuChatMemberRead(params) {
431
438
  }
432
439
  function canEnumerateAllFeishuGroups(cfg, account) {
433
440
  const policy = resolveFeishuReadGroupPolicy(cfg, account);
434
- return policy === "open" || policy === "allowlist" && (account.config.groupAllowFrom ?? []).some((entry) => normalizeFeishuAllowEntry(String(entry)) === "*");
441
+ return policy === "open" || policy === "allowlist" && compileAllowlist(normalizeFeishuAllowlist(account.config.groupAllowFrom)).wildcard;
435
442
  }
436
443
  function canEnumerateAllFeishuPeers(account) {
437
444
  return isDmUniversallyAllowed(account);
@@ -536,13 +543,17 @@ function postContentBytes(messageText, mentions) {
536
543
  * Markdown wrappers and first-chunk mentions count toward the byte budget.
537
544
  */
538
545
  function chunkFeishuPostMarkdown(params) {
539
- const { text, firstChunkMentions } = params;
546
+ const { text, firstChunkMentions, chunkMentions } = params;
540
547
  if (!text) return [];
541
548
  const requestedLimit = Number.isFinite(params.limit) && params.limit > 0 ? Math.floor(params.limit) : text.length;
542
549
  const initialChunks = params.initialChunks ?? chunkFeishuMarkdownWithMode(text, requestedLimit, params.mode ?? "length");
543
550
  const output = [];
551
+ const resolveMentions = (isFirst) => {
552
+ const mentions = [...chunkMentions ?? [], ...isFirst ? firstChunkMentions ?? [] : []];
553
+ return mentions.length > 0 ? mentions : void 0;
554
+ };
544
555
  for (const initialChunk of initialChunks) {
545
- if (postContentBytes(initialChunk, output.length === 0 ? firstChunkMentions : void 0) <= FEISHU_POST_MAX_BYTES) {
556
+ if (postContentBytes(initialChunk, resolveMentions(output.length === 0)) <= FEISHU_POST_MAX_BYTES) {
546
557
  output.push(initialChunk);
547
558
  continue;
548
559
  }
@@ -553,12 +564,12 @@ function chunkFeishuPostMarkdown(params) {
553
564
  let oversizedChunk;
554
565
  let oversizedMentions;
555
566
  for (const [index, chunk] of chunks.entries()) {
556
- const chunkMentions = output.length === 0 && index === 0 ? firstChunkMentions : void 0;
557
- const contentBytes = postContentBytes(chunk, chunkMentions);
567
+ const mentionsForChunk = resolveMentions(output.length === 0 && index === 0);
568
+ const contentBytes = postContentBytes(chunk, mentionsForChunk);
558
569
  largestContentBytes = Math.max(largestContentBytes, contentBytes);
559
570
  if (contentBytes > FEISHU_POST_MAX_BYTES && oversizedChunk === void 0) {
560
571
  oversizedChunk = chunk;
561
- oversizedMentions = chunkMentions;
572
+ oversizedMentions = mentionsForChunk;
562
573
  }
563
574
  }
564
575
  if (oversizedChunk === void 0) {
package/dist/setup-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-X30GQVwc.js";
1
+ import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-BdnXYU6g.js";
2
2
  export { feishuPlugin, feishuSetupAdapter, feishuSetupWizard };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/feishu",
3
- "version": "2026.7.2-beta.2",
3
+ "version": "2026.7.2-beta.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/feishu",
9
- "version": "2026.7.2-beta.2",
9
+ "version": "2026.7.2-beta.3",
10
10
  "dependencies": {
11
11
  "@larksuiteoapi/node-sdk": "1.68.0",
12
12
  "mdast-util-from-markdown": "2.0.3",
@@ -16,7 +16,7 @@
16
16
  "zod": "4.4.3"
17
17
  },
18
18
  "peerDependencies": {
19
- "openclaw": ">=2026.7.2-beta.2"
19
+ "openclaw": ">=2026.7.2-beta.3"
20
20
  },
21
21
  "peerDependenciesMeta": {
22
22
  "openclaw": {
@@ -455,6 +455,12 @@
455
455
  "enabled"
456
456
  ]
457
457
  },
458
+ "allowBots": {
459
+ "type": "boolean"
460
+ },
461
+ "vcAutoJoin": {
462
+ "type": "boolean"
463
+ },
458
464
  "typingIndicator": {
459
465
  "type": "boolean"
460
466
  }
@@ -520,6 +526,12 @@
520
526
  "enabled"
521
527
  ]
522
528
  },
529
+ "allowBots": {
530
+ "type": "boolean"
531
+ },
532
+ "vcAutoJoin": {
533
+ "type": "boolean"
534
+ },
523
535
  "typingIndicator": {
524
536
  "type": "boolean"
525
537
  },
@@ -1178,6 +1190,12 @@
1178
1190
  "default": true,
1179
1191
  "type": "boolean"
1180
1192
  },
1193
+ "allowBots": {
1194
+ "type": "boolean"
1195
+ },
1196
+ "vcAutoJoin": {
1197
+ "type": "boolean"
1198
+ },
1181
1199
  "tts": {
1182
1200
  "type": "object",
1183
1201
  "properties": {
@@ -1936,6 +1954,12 @@
1936
1954
  "resolveSenderNames": {
1937
1955
  "type": "boolean"
1938
1956
  },
1957
+ "allowBots": {
1958
+ "type": "boolean"
1959
+ },
1960
+ "vcAutoJoin": {
1961
+ "type": "boolean"
1962
+ },
1939
1963
  "tts": {
1940
1964
  "type": "object",
1941
1965
  "properties": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/feishu",
3
- "version": "2026.7.2-beta.2",
3
+ "version": "2026.7.2-beta.3",
4
4
  "description": "OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,7 +16,7 @@
16
16
  "zod": "4.4.3"
17
17
  },
18
18
  "peerDependencies": {
19
- "openclaw": ">=2026.7.2-beta.2"
19
+ "openclaw": ">=2026.7.2-beta.3"
20
20
  },
21
21
  "peerDependenciesMeta": {
22
22
  "openclaw": {
@@ -47,10 +47,10 @@
47
47
  "minHostVersion": ">=2026.5.29"
48
48
  },
49
49
  "compat": {
50
- "pluginApi": ">=2026.7.2-beta.2"
50
+ "pluginApi": ">=2026.7.2-beta.3"
51
51
  },
52
52
  "build": {
53
- "openclawVersion": "2026.7.2-beta.2"
53
+ "openclawVersion": "2026.7.2-beta.3"
54
54
  },
55
55
  "release": {
56
56
  "publishToClawHub": true,