@larksuite/openclaw-lark 2026.6.10 → 2026.7.9-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 +1 -1
- package/src/card/reply-dispatcher.js +1 -9
- package/src/card/reply-mode.d.ts +6 -3
- package/src/card/reply-mode.js +6 -15
- package/src/channel/abort-detect.js +4 -1
- package/src/channel/interactive-dispatch.js +2 -1
- package/src/core/config-schema.d.ts +4 -471
- package/src/messaging/converters/post.js +100 -4
- package/src/messaging/outbound/deliver.js +3 -18
- package/src/messaging/outbound/send.d.ts +2 -2
- package/src/messaging/outbound/send.js +3 -34
- package/src/tools/ask-user-question.js +50 -7
- package/src/tools/oapi/im/message.js +2 -131
- package/tsdown.config.d.ts +1 -1
- package/vitest.config.d.ts +1 -1
package/package.json
CHANGED
|
@@ -37,8 +37,6 @@ function createFeishuReplyDispatcher(params) {
|
|
|
37
37
|
// Resolve account so we can read per-account config (e.g. replyMode)
|
|
38
38
|
const account = (0, accounts_1.getLarkAccount)(cfg, accountId);
|
|
39
39
|
const feishuCfg = account.config;
|
|
40
|
-
// accountScopedCfg 用于需要 account-level 覆盖的配置项(如 tableMode)
|
|
41
|
-
const accountScopedCfg = (0, accounts_1.createAccountScopedConfig)(cfg, account.accountId);
|
|
42
40
|
const prefixContext = (0, channel_runtime_1.createReplyPrefixContext)({ cfg, agentId });
|
|
43
41
|
// ---- Reply mode resolution ----
|
|
44
42
|
const chatType = params.chatType;
|
|
@@ -69,11 +67,6 @@ function createFeishuReplyDispatcher(params) {
|
|
|
69
67
|
// ---- Chunk & render settings (static mode only) ----
|
|
70
68
|
const textChunkLimit = core.channel.text.resolveTextChunkLimit(cfg, 'feishu', accountId, { fallbackLimit: 4000 });
|
|
71
69
|
const chunkMode = core.channel.text.resolveChunkMode(cfg, 'feishu');
|
|
72
|
-
// 使用 accountScopedCfg 以支持 per-account tableMode 覆盖
|
|
73
|
-
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
|
74
|
-
cfg: accountScopedCfg,
|
|
75
|
-
channel: 'feishu',
|
|
76
|
-
});
|
|
77
70
|
// ---- Streaming card controller (instantiated only when needed) ----
|
|
78
71
|
const controller = useStreamingCards
|
|
79
72
|
? new streaming_card_controller_1.StreamingCardController({
|
|
@@ -300,8 +293,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
300
293
|
}
|
|
301
294
|
}
|
|
302
295
|
else {
|
|
303
|
-
const
|
|
304
|
-
const chunks = core.channel.text.chunkTextWithMode(converted, textChunkLimit, chunkMode);
|
|
296
|
+
const chunks = core.channel.text.chunkTextWithMode(text, textChunkLimit, chunkMode);
|
|
305
297
|
log.info('deliver: sending text chunks', {
|
|
306
298
|
count: chunks.length,
|
|
307
299
|
chatId,
|
package/src/card/reply-mode.d.ts
CHANGED
|
@@ -30,9 +30,12 @@ export declare function expandAutoMode(params: {
|
|
|
30
30
|
chatType?: 'p2p' | 'group';
|
|
31
31
|
}): 'static' | 'streaming';
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* scope A: rich text now renders natively as post(`tag:md`); we never force a
|
|
34
|
+
* card for code blocks OR tables anymore. Native rendering also keeps bot-at-bot
|
|
35
|
+
* @ delivery working — wrapping a reply in a card breaks it (cards have limited
|
|
36
|
+
* @ support). The only remaining card-path guard is the table-count hard limit,
|
|
37
|
+
* retained for the runtime fallback in reply-dispatcher (card rejected by
|
|
38
|
+
* Feishu → plain text).
|
|
36
39
|
*/
|
|
37
40
|
export declare function shouldUseCard(text: string): boolean;
|
|
38
41
|
export {};
|
package/src/card/reply-mode.js
CHANGED
|
@@ -54,26 +54,17 @@ function expandAutoMode(params) {
|
|
|
54
54
|
// shouldUseCard
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* scope A: rich text now renders natively as post(`tag:md`); we never force a
|
|
58
|
+
* card for code blocks OR tables anymore. Native rendering also keeps bot-at-bot
|
|
59
|
+
* @ delivery working — wrapping a reply in a card breaks it (cards have limited
|
|
60
|
+
* @ support). The only remaining card-path guard is the table-count hard limit,
|
|
61
|
+
* retained for the runtime fallback in reply-dispatcher (card rejected by
|
|
62
|
+
* Feishu → plain text).
|
|
60
63
|
*/
|
|
61
64
|
function shouldUseCard(text) {
|
|
62
|
-
// Markdown tables NO LONGER force a card. Feishu messages render markdown
|
|
63
|
-
// tables natively, and wrapping a reply in a card breaks bot-at-bot @
|
|
64
|
-
// delivery (cards have limited @ support). Only fenced code blocks still
|
|
65
|
-
// benefit from card rendering.
|
|
66
|
-
//
|
|
67
|
-
// The table-count guard is kept as a safety valve: when a reply also
|
|
68
|
-
// contains an excessive number of markdown tables, skip the card entirely
|
|
69
|
-
// rather than risk a card-render failure.
|
|
70
65
|
const tableMatches = (0, card_error_1.findMarkdownTablesOutsideCodeBlocks)(text);
|
|
71
66
|
if (tableMatches.length > card_error_1.FEISHU_CARD_TABLE_LIMIT) {
|
|
72
67
|
return false;
|
|
73
68
|
}
|
|
74
|
-
// Fenced code blocks
|
|
75
|
-
if (/```[\s\S]*?```/.test(text)) {
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
69
|
return false;
|
|
79
70
|
}
|
|
@@ -180,7 +180,10 @@ function isConversationStopIntent(text) {
|
|
|
180
180
|
if (!text)
|
|
181
181
|
return false;
|
|
182
182
|
// Drop bot mention placeholders so "@Bot 中断对话" → "中断对话".
|
|
183
|
-
const normalized = text
|
|
183
|
+
const normalized = text
|
|
184
|
+
.replace(/@_user_\d+/g, '')
|
|
185
|
+
.trim()
|
|
186
|
+
.toLowerCase();
|
|
184
187
|
if (!normalized)
|
|
185
188
|
return false;
|
|
186
189
|
if (isLikelyAbortText(normalized))
|
|
@@ -23,7 +23,7 @@ const log = (0, lark_logger_1.larkLogger)('channel/interactive-dispatch');
|
|
|
23
23
|
function extractBasics(data) {
|
|
24
24
|
try {
|
|
25
25
|
const ev = data;
|
|
26
|
-
const action = ev.action?.value?.action;
|
|
26
|
+
const action = ev.action?.value?.action ?? ev.action?.name;
|
|
27
27
|
if (!action || typeof action !== 'string')
|
|
28
28
|
return null;
|
|
29
29
|
const openChatId = ev.open_chat_id ?? ev.context?.open_chat_id;
|
|
@@ -172,6 +172,7 @@ async function dispatchFeishuPluginInteractiveHandler(params) {
|
|
|
172
172
|
return { handled: cardResponse !== undefined };
|
|
173
173
|
},
|
|
174
174
|
});
|
|
175
|
+
log.debug(`interactive dispatch result: action=${basics.action}, matched=${result.matched}, handled=${result.handled}`);
|
|
175
176
|
if (!result.matched)
|
|
176
177
|
return undefined;
|
|
177
178
|
return cardResponse;
|
|
@@ -9,477 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
export { z };
|
|
12
|
-
export declare const UATConfigSchema:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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>;
|
|
12
|
+
export declare const UATConfigSchema: any;
|
|
13
|
+
export declare const FeishuGroupSchema: any;
|
|
14
|
+
export declare const FeishuAccountConfigSchema: any;
|
|
15
|
+
export declare const FeishuConfigSchema: any;
|
|
483
16
|
/**
|
|
484
17
|
* JSON Schema derived from FeishuConfigSchema.
|
|
485
18
|
*
|
|
@@ -10,10 +10,30 @@
|
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.convertPost = void 0;
|
|
13
|
+
const lark_logger_1 = require("../../core/lark-logger.js");
|
|
13
14
|
const content_converter_helpers_1 = require("./content-converter-helpers.js");
|
|
14
15
|
const utils_1 = require("./utils.js");
|
|
16
|
+
const log = (0, lark_logger_1.larkLogger)('converters/post');
|
|
15
17
|
/** Preferred locale order for multi-language post unwrapping. */
|
|
16
18
|
const LOCALE_PRIORITY = ['zh_cn', 'en_us', 'ja_jp'];
|
|
19
|
+
/**
|
|
20
|
+
* Scan token for native markdown: a code region to SKIP, OR an image to intercept.
|
|
21
|
+
* Group 1 (code region) matches first so `` inside code is consumed
|
|
22
|
+
* verbatim and never intercepted:
|
|
23
|
+
* - fenced block: ```...``` or ~~~...~~~ (multi-line, lazy)
|
|
24
|
+
* - inline span: ``...`` or `...` (single-line)
|
|
25
|
+
* Group 2 (alt) + Group 3 (target) are the image when no code region matched.
|
|
26
|
+
*/
|
|
27
|
+
const MD_SCAN_RE = /(```[\s\S]*?```|~~~[\s\S]*?~~~|``[\s\S]*?``|`[^`\n]*`)|!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
28
|
+
/** Feishu image_key prefix (aligned with markdown-style.ts stripInvalidImageKeys & case 'img'). */
|
|
29
|
+
const FEISHU_IMAGE_KEY_RE = /^img_/;
|
|
30
|
+
/**
|
|
31
|
+
* Inbound native-markdown size ceiling (chars). Guards against pathological
|
|
32
|
+
* posts (huge tables / deep nesting) blowing up memory/token before the
|
|
33
|
+
* downstream chunker runs. One order above the outbound chunk magnitude
|
|
34
|
+
* (outbound default 15000). Tunable; per-account config override is a follow-up.
|
|
35
|
+
*/
|
|
36
|
+
const MAX_INBOUND_MD_LEN = 100_000;
|
|
17
37
|
/**
|
|
18
38
|
* Unwrap a parsed post object that may be locale-wrapped.
|
|
19
39
|
*
|
|
@@ -22,7 +42,7 @@ const LOCALE_PRIORITY = ['zh_cn', 'en_us', 'ja_jp'];
|
|
|
22
42
|
* - Locale: `{ zh_cn: { title, content }, en_us: { title, content } }`
|
|
23
43
|
*/
|
|
24
44
|
function unwrapLocale(parsed) {
|
|
25
|
-
if ('title' in parsed || 'content' in parsed) {
|
|
45
|
+
if ('title' in parsed || 'content' in parsed || 'content_v2' in parsed) {
|
|
26
46
|
return parsed;
|
|
27
47
|
}
|
|
28
48
|
for (const locale of LOCALE_PRIORITY) {
|
|
@@ -51,26 +71,36 @@ const convertPost = (raw, ctx) => {
|
|
|
51
71
|
}
|
|
52
72
|
const resources = [];
|
|
53
73
|
const lines = [];
|
|
74
|
+
const seenFileKeys = new Set();
|
|
54
75
|
// Title
|
|
55
76
|
if (parsed.title) {
|
|
56
77
|
lines.push(`**${parsed.title}**`, '');
|
|
57
78
|
}
|
|
58
|
-
const
|
|
79
|
+
const v2 = parsed.content_v2;
|
|
80
|
+
const usedV2 = Array.isArray(v2) && v2.length > 0;
|
|
81
|
+
const contentBlocks = usedV2 ? v2 : (parsed.content ?? []);
|
|
82
|
+
log.info('post content source selected', {
|
|
83
|
+
messageId: ctx.messageId,
|
|
84
|
+
source: usedV2 ? 'content_v2' : 'content',
|
|
85
|
+
hasV2: Array.isArray(v2),
|
|
86
|
+
v2Len: Array.isArray(v2) ? v2.length : 0,
|
|
87
|
+
});
|
|
59
88
|
for (const paragraph of contentBlocks) {
|
|
60
89
|
if (!Array.isArray(paragraph))
|
|
61
90
|
continue;
|
|
62
91
|
let line = '';
|
|
63
92
|
for (const el of paragraph) {
|
|
64
|
-
line += renderElement(el, ctx, resources);
|
|
93
|
+
line += renderElement(el, ctx, resources, seenFileKeys);
|
|
65
94
|
}
|
|
66
95
|
lines.push(line);
|
|
67
96
|
}
|
|
68
97
|
let content = lines.join('\n').trim() || '[rich text message]';
|
|
69
98
|
content = (0, content_converter_helpers_1.resolveMentions)(content, ctx);
|
|
99
|
+
content = capInboundLength(content, resources);
|
|
70
100
|
return { content, resources };
|
|
71
101
|
};
|
|
72
102
|
exports.convertPost = convertPost;
|
|
73
|
-
function renderElement(el, ctx, resources) {
|
|
103
|
+
function renderElement(el, ctx, resources, seenFileKeys) {
|
|
74
104
|
switch (el.tag) {
|
|
75
105
|
case 'text': {
|
|
76
106
|
let text = el.text ?? '';
|
|
@@ -110,6 +140,9 @@ function renderElement(el, ctx, resources) {
|
|
|
110
140
|
}
|
|
111
141
|
return '';
|
|
112
142
|
}
|
|
143
|
+
case 'md': {
|
|
144
|
+
return renderInlineImages(el.text ?? '', resources, seenFileKeys);
|
|
145
|
+
}
|
|
113
146
|
case 'code_block': {
|
|
114
147
|
const lang = el.language ?? '';
|
|
115
148
|
const code = el.text ?? '';
|
|
@@ -137,3 +170,66 @@ function applyStyle(text, style) {
|
|
|
137
170
|
result = `\`${result}\``;
|
|
138
171
|
return result;
|
|
139
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Scan native markdown for `` and intercept feishu image_keys.
|
|
175
|
+
* - target matches `^img_` → register a downloadable resource (Set-deduped) and
|
|
176
|
+
* normalize the FIRST occurrence to `` (the only form
|
|
177
|
+
* substituteMediaPaths can replace); later occurrences of the same key collapse
|
|
178
|
+
* to sanitized alt text so no bare key / un-replaceable marker leaks downstream.
|
|
179
|
+
* - everything else (https/http, data:, protocol-relative, relative/anchor) is kept
|
|
180
|
+
* verbatim and never downloaded.
|
|
181
|
+
* Image syntax inside a code region (fenced block or inline span) is left verbatim
|
|
182
|
+
* and never intercepted — code is shown as-is, not rendered.
|
|
183
|
+
*/
|
|
184
|
+
function renderInlineImages(md, resources, seen) {
|
|
185
|
+
return md.replace(MD_SCAN_RE, (whole, codeRegion, alt, target) => {
|
|
186
|
+
if (codeRegion !== undefined) {
|
|
187
|
+
return whole; // fenced block / inline code → keep verbatim, never intercept
|
|
188
|
+
}
|
|
189
|
+
if (!FEISHU_IMAGE_KEY_RE.test(target)) {
|
|
190
|
+
return whole;
|
|
191
|
+
}
|
|
192
|
+
if (!seen.has(target)) {
|
|
193
|
+
seen.add(target);
|
|
194
|
+
resources.push({ type: 'image', fileKey: target });
|
|
195
|
+
return ``;
|
|
196
|
+
}
|
|
197
|
+
return sanitizeAlt(alt);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/** Strip nested image syntax and bare feishu keys from alt so it cannot be
|
|
201
|
+
* re-matched by substituteMediaPaths nor inject UGC markers. */
|
|
202
|
+
function sanitizeAlt(alt) {
|
|
203
|
+
return alt
|
|
204
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
|
|
205
|
+
.replace(/img_[A-Za-z0-9_-]+/g, '')
|
|
206
|
+
.trim();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Cap inbound text length at a safe boundary (never split an ``
|
|
210
|
+
* marker), tag `[truncated]`, and drop image resources whose marker fell off the
|
|
211
|
+
* end so we never register an orphan download with no in-text marker.
|
|
212
|
+
*/
|
|
213
|
+
function capInboundLength(content, resources) {
|
|
214
|
+
if (content.length <= MAX_INBOUND_MD_LEN)
|
|
215
|
+
return content;
|
|
216
|
+
let cut = content.lastIndexOf('\n', MAX_INBOUND_MD_LEN);
|
|
217
|
+
if (cut <= 0) {
|
|
218
|
+
// Single huge line: back off so we never cut inside an image marker.
|
|
219
|
+
cut = MAX_INBOUND_MD_LEN;
|
|
220
|
+
const markerStart = content.lastIndexOf(';
|
|
221
|
+
if (markerStart !== -1) {
|
|
222
|
+
const markerEnd = content.indexOf(')', markerStart);
|
|
223
|
+
if (markerEnd === -1 || markerEnd >= cut)
|
|
224
|
+
cut = markerStart;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const truncated = `${content.slice(0, cut).replace(/\s+$/, '')}\n[truncated]`;
|
|
228
|
+
for (let i = resources.length - 1; i >= 0; i--) {
|
|
229
|
+
const r = resources[i];
|
|
230
|
+
if (r.type === 'image' && !truncated.includes(``)) {
|
|
231
|
+
resources.splice(i, 1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return truncated;
|
|
235
|
+
}
|
|
@@ -32,8 +32,9 @@ function buildPostContent(text) {
|
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Pre-processes text before Feishu delivery. Runs the full mention
|
|
35
|
-
* normalizer (tag rewrite + plain `@Name` resolution + `@all` aliases)
|
|
36
|
-
*
|
|
35
|
+
* normalizer (tag rewrite + plain `@Name` resolution + `@all` aliases)
|
|
36
|
+
* and Markdown style optimization. Markdown tables/code blocks are left
|
|
37
|
+
* as-is so Feishu renders them natively in the post(`tag:md`).
|
|
37
38
|
*
|
|
38
39
|
* Falls back to the raw text on any normalizer failure so a parser bug
|
|
39
40
|
* never blocks send. Sentinels collected during normalization are
|
|
@@ -60,22 +61,6 @@ async function prepareTextForLark(cfg, text, to, accountId) {
|
|
|
60
61
|
log.warn(`normalizeOutboundMentions failed in prepareTextForLark, using raw text: ${String(err)}`);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
// Convert markdown tables to Feishu-compatible format using per-account
|
|
64
|
-
// tableMode setting.
|
|
65
|
-
try {
|
|
66
|
-
const accountScopedCfg = (0, accounts_1.createAccountScopedConfig)(cfg, accountId);
|
|
67
|
-
const runtime = lark_client_1.LarkClient.runtime;
|
|
68
|
-
if (runtime?.channel?.text?.convertMarkdownTables && runtime.channel.text.resolveMarkdownTableMode) {
|
|
69
|
-
const tableMode = runtime.channel.text.resolveMarkdownTableMode({
|
|
70
|
-
cfg: accountScopedCfg,
|
|
71
|
-
channel: 'feishu',
|
|
72
|
-
});
|
|
73
|
-
processed = runtime.channel.text.convertMarkdownTables(processed, tableMode);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// Runtime not available -- use the text as-is.
|
|
78
|
-
}
|
|
79
64
|
return {
|
|
80
65
|
text: (0, markdown_style_1.optimizeMarkdownStyle)(processed, 1),
|
|
81
66
|
sentinels,
|
|
@@ -57,8 +57,8 @@ export interface SendFeishuCardParams {
|
|
|
57
57
|
* sent as a threaded reply; otherwise it is sent as a new message using
|
|
58
58
|
* the appropriate `receive_id_type`.
|
|
59
59
|
*
|
|
60
|
-
* Markdown tables
|
|
61
|
-
*
|
|
60
|
+
* Markdown tables and code blocks are sent as-is so Feishu renders them
|
|
61
|
+
* natively in the post(`tag:md`); only `optimizeMarkdownStyle` is applied.
|
|
62
62
|
*
|
|
63
63
|
* @param params - See {@link SendFeishuMessageParams}.
|
|
64
64
|
* @returns The send result containing the new message ID.
|
|
@@ -74,32 +74,6 @@ function recordFeishuSendSentinels(resolvedAccountId, to, threadId, sentinels) {
|
|
|
74
74
|
// ---------------------------------------------------------------------------
|
|
75
75
|
// sendMessageFeishu
|
|
76
76
|
// ---------------------------------------------------------------------------
|
|
77
|
-
/**
|
|
78
|
-
* Resolve the configured markdown table mode for Feishu and convert tables if
|
|
79
|
-
* the runtime converter is available.
|
|
80
|
-
*
|
|
81
|
-
* @param cfg - Plugin configuration
|
|
82
|
-
* @param text - Raw markdown text
|
|
83
|
-
* @param accountId - Optional account identifier for multi-account setups
|
|
84
|
-
* @returns Converted text, or the original text when runtime helpers are unavailable
|
|
85
|
-
*/
|
|
86
|
-
function convertMarkdownTablesForFeishu(cfg, text, accountId) {
|
|
87
|
-
try {
|
|
88
|
-
const accountScopedCfg = (0, accounts_1.createAccountScopedConfig)(cfg, accountId);
|
|
89
|
-
const runtime = lark_client_1.LarkClient.runtime;
|
|
90
|
-
if (runtime?.channel?.text?.convertMarkdownTables && runtime.channel.text.resolveMarkdownTableMode) {
|
|
91
|
-
const tableMode = runtime.channel.text.resolveMarkdownTableMode({
|
|
92
|
-
cfg: accountScopedCfg,
|
|
93
|
-
channel: 'feishu',
|
|
94
|
-
});
|
|
95
|
-
return runtime.channel.text.convertMarkdownTables(text, tableMode);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// Runtime not available -- use the text as-is.
|
|
100
|
-
}
|
|
101
|
-
return text;
|
|
102
|
-
}
|
|
103
77
|
/**
|
|
104
78
|
* Send a text message (rendered as a Feishu "post" with markdown support)
|
|
105
79
|
* to a chat or user.
|
|
@@ -109,8 +83,8 @@ function convertMarkdownTablesForFeishu(cfg, text, accountId) {
|
|
|
109
83
|
* sent as a threaded reply; otherwise it is sent as a new message using
|
|
110
84
|
* the appropriate `receive_id_type`.
|
|
111
85
|
*
|
|
112
|
-
* Markdown tables
|
|
113
|
-
*
|
|
86
|
+
* Markdown tables and code blocks are sent as-is so Feishu renders them
|
|
87
|
+
* natively in the post(`tag:md`); only `optimizeMarkdownStyle` is applied.
|
|
114
88
|
*
|
|
115
89
|
* @param params - See {@link SendFeishuMessageParams}.
|
|
116
90
|
* @returns The send result containing the new message ID.
|
|
@@ -141,8 +115,6 @@ async function sendMessageFeishu(params) {
|
|
|
141
115
|
if (mentions && mentions.length > 0) {
|
|
142
116
|
processed = (0, mention_1.buildMentionedMessage)(mentions, processed);
|
|
143
117
|
}
|
|
144
|
-
// Convert markdown tables to Feishu-compatible format.
|
|
145
|
-
processed = convertMarkdownTablesForFeishu(cfg, processed, accountId);
|
|
146
118
|
// Apply Markdown style optimization.
|
|
147
119
|
processed = (0, markdown_style_1.optimizeMarkdownStyle)(processed, 1);
|
|
148
120
|
postBody[locale] = {
|
|
@@ -158,8 +130,6 @@ async function sendMessageFeishu(params) {
|
|
|
158
130
|
if (mentions && mentions.length > 0) {
|
|
159
131
|
messageText = (0, mention_1.buildMentionedMessage)(mentions, messageText);
|
|
160
132
|
}
|
|
161
|
-
// Convert markdown tables to Feishu-compatible format.
|
|
162
|
-
messageText = convertMarkdownTablesForFeishu(cfg, messageText, accountId);
|
|
163
133
|
// Apply Markdown style optimization.
|
|
164
134
|
messageText = (0, markdown_style_1.optimizeMarkdownStyle)(messageText, 1);
|
|
165
135
|
contentPayload = JSON.stringify({
|
|
@@ -424,9 +394,8 @@ async function sendMarkdownCardFeishu(params) {
|
|
|
424
394
|
async function editMessageFeishu(params) {
|
|
425
395
|
const { cfg, messageId, text, accountId } = params;
|
|
426
396
|
const client = lark_client_1.LarkClient.fromCfg(cfg, accountId).sdk;
|
|
427
|
-
const convertedText = convertMarkdownTablesForFeishu(cfg, text, accountId);
|
|
428
397
|
// Use cardVersion=1 consistent with sendMessageFeishu post path.
|
|
429
|
-
const optimizedText = (0, markdown_style_1.optimizeMarkdownStyle)(
|
|
398
|
+
const optimizedText = (0, markdown_style_1.optimizeMarkdownStyle)(text, 1);
|
|
430
399
|
const contentPayload = JSON.stringify({
|
|
431
400
|
zh_cn: {
|
|
432
401
|
content: [[{ tag: 'md', text: optimizedText }]],
|
|
@@ -146,6 +146,9 @@ function getInputFieldName(questionIndex) {
|
|
|
146
146
|
function getSelectFieldName(questionIndex) {
|
|
147
147
|
return `${SELECT_FIELD_NAME}_${questionIndex}`;
|
|
148
148
|
}
|
|
149
|
+
function getCheckerFieldName(questionIndex, optionIndex) {
|
|
150
|
+
return `${SELECT_FIELD_NAME}_${questionIndex}_${optionIndex}`;
|
|
151
|
+
}
|
|
149
152
|
// ---------------------------------------------------------------------------
|
|
150
153
|
// Card Action Handler (used by event-handlers.ts)
|
|
151
154
|
// ---------------------------------------------------------------------------
|
|
@@ -187,12 +190,13 @@ function handleAskUserAction(data, _cfg, accountId) {
|
|
|
187
190
|
operationId = actionName.slice(SUBMIT_BUTTON_PREFIX.length);
|
|
188
191
|
}
|
|
189
192
|
}
|
|
190
|
-
// Detect form submit by tag + formValue
|
|
191
|
-
|
|
193
|
+
// Detect ask-user form submit by tag + formValue. Business plugins also use
|
|
194
|
+
// Feishu forms, so only consume buttons generated by this tool.
|
|
195
|
+
if (!action && actionTag === 'button' && formValue && actionName?.startsWith(SUBMIT_BUTTON_PREFIX)) {
|
|
192
196
|
action = ACTION_SUBMIT;
|
|
193
197
|
}
|
|
194
|
-
// Some SDK versions emit tag='form_submit'
|
|
195
|
-
if (!action && actionTag === 'form_submit') {
|
|
198
|
+
// Some SDK versions emit tag='form_submit' without button value/name.
|
|
199
|
+
if (!action && actionTag === 'form_submit' && (!actionName || actionName.startsWith(SUBMIT_BUTTON_PREFIX))) {
|
|
196
200
|
action = ACTION_SUBMIT;
|
|
197
201
|
if (!formValue && event.action) {
|
|
198
202
|
formValue = event.action;
|
|
@@ -244,8 +248,11 @@ function handleAskUserAction(data, _cfg, accountId) {
|
|
|
244
248
|
answer = readFormTextField(formValue, getInputFieldName(i));
|
|
245
249
|
}
|
|
246
250
|
else if (q.multiSelect) {
|
|
247
|
-
// Multi-select
|
|
248
|
-
|
|
251
|
+
// Multi-select — either a dropdown (multi_select_static) or a set of
|
|
252
|
+
// independent checker rows, depending on selectStyle.
|
|
253
|
+
const selected = q.selectStyle === 'checkbox'
|
|
254
|
+
? readFormCheckers(formValue, i, q.options)
|
|
255
|
+
: readFormMultiSelect(formValue, getSelectFieldName(i));
|
|
249
256
|
if (selected.length > 0) {
|
|
250
257
|
answer = selected.join(', ');
|
|
251
258
|
}
|
|
@@ -434,6 +441,22 @@ function readFormMultiSelect(formValue, fieldName) {
|
|
|
434
441
|
}
|
|
435
442
|
return [];
|
|
436
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Read the selected labels from a set of independent checker (checkbox)
|
|
446
|
+
* components — used when a multi-select question uses selectStyle 'checkbox'.
|
|
447
|
+
* Each option has its own boolean field (see getCheckerFieldName); Feishu may
|
|
448
|
+
* report the checked state as a boolean or a string.
|
|
449
|
+
*/
|
|
450
|
+
function readFormCheckers(formValue, questionIndex, options) {
|
|
451
|
+
const selected = [];
|
|
452
|
+
for (let optIndex = 0; optIndex < options.length; optIndex++) {
|
|
453
|
+
const raw = formValue[getCheckerFieldName(questionIndex, optIndex)];
|
|
454
|
+
if (raw === true || raw === 'true') {
|
|
455
|
+
selected.push(options[optIndex].label);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return selected;
|
|
459
|
+
}
|
|
437
460
|
// ---------------------------------------------------------------------------
|
|
438
461
|
// Card Builders — unified form layout
|
|
439
462
|
// ---------------------------------------------------------------------------
|
|
@@ -495,7 +518,24 @@ function buildQuestionFormElements(q, questionIndex) {
|
|
|
495
518
|
text: { tag: 'plain_text', content: opt.label },
|
|
496
519
|
value: opt.label,
|
|
497
520
|
}));
|
|
498
|
-
if (q.multiSelect) {
|
|
521
|
+
if (q.multiSelect && q.selectStyle === 'checkbox') {
|
|
522
|
+
// ---- Multi-select as independent checkbox rows ----
|
|
523
|
+
// Each option is its own checker so the user sees every selection at a
|
|
524
|
+
// glance before submitting (vs. the compact dropdown collapsed state).
|
|
525
|
+
// Note: checker requires a `value` field to pass Feishu's client-side
|
|
526
|
+
// interactive-component validation (otherwise code 200340).
|
|
527
|
+
elems.push(labelMd);
|
|
528
|
+
q.options.forEach((opt, optIndex) => {
|
|
529
|
+
elems.push({
|
|
530
|
+
tag: 'checker',
|
|
531
|
+
name: getCheckerFieldName(questionIndex, optIndex),
|
|
532
|
+
checked: false,
|
|
533
|
+
text: { tag: 'plain_text', content: opt.label },
|
|
534
|
+
value: { option: opt.label },
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
else if (q.multiSelect) {
|
|
499
539
|
// ---- Multi-select dropdown ----
|
|
500
540
|
elems.push(buildLabeledRow(labelMd, {
|
|
501
541
|
tag: 'multi_select_static',
|
|
@@ -778,6 +818,9 @@ const AskUserQuestionSchema = typebox_1.Type.Object({
|
|
|
778
818
|
multiSelect: typebox_1.Type.Boolean({
|
|
779
819
|
description: 'Whether multiple options can be selected (ignored when options is empty)',
|
|
780
820
|
}),
|
|
821
|
+
selectStyle: typebox_1.Type.Optional(typebox_1.Type.Union([typebox_1.Type.Literal('dropdown'), typebox_1.Type.Literal('checkbox')], {
|
|
822
|
+
description: 'For multi-select questions only: "dropdown" (default, compact multi-select dropdown) or "checkbox" (each option shown as a separate checkbox row, so the user can see all selected options at a glance before submitting). Ignored for single-select and free-text questions.',
|
|
823
|
+
})),
|
|
781
824
|
}), {
|
|
782
825
|
description: 'Questions to ask the user (1-6 questions)',
|
|
783
826
|
minItems: 1,
|
|
@@ -16,132 +16,7 @@
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.registerFeishuImUserMessageTool = registerFeishuImUserMessageTool;
|
|
18
18
|
const typebox_1 = require("@sinclair/typebox");
|
|
19
|
-
const accounts_1 = require("../../../core/accounts.js");
|
|
20
|
-
const lark_client_1 = require("../../../core/lark-client.js");
|
|
21
19
|
const helpers_1 = require("../helpers.js");
|
|
22
|
-
const FEISHU_POST_LOCALE_PRIORITY = ['zh_cn', 'en_us', 'ja_jp'];
|
|
23
|
-
/**
|
|
24
|
-
* Check whether a value is a non-null object whose properties can be read.
|
|
25
|
-
*
|
|
26
|
-
* @param value - The value to check
|
|
27
|
-
* @returns Whether the value is a non-null object
|
|
28
|
-
*/
|
|
29
|
-
function isRecord(value) {
|
|
30
|
-
return value != null && typeof value === 'object';
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Collect post content bodies from a parsed Feishu post payload.
|
|
34
|
-
* Handles both flat (title/content at root) and multi-locale wrapper structures.
|
|
35
|
-
*
|
|
36
|
-
* @param parsed - The parsed JSON object
|
|
37
|
-
* @returns List of post content bodies to process
|
|
38
|
-
*/
|
|
39
|
-
function collectPostContents(parsed) {
|
|
40
|
-
if ('title' in parsed || 'content' in parsed) {
|
|
41
|
-
return [parsed];
|
|
42
|
-
}
|
|
43
|
-
const bodies = [];
|
|
44
|
-
const seen = new Set();
|
|
45
|
-
// Process well-known locales first
|
|
46
|
-
for (const locale of FEISHU_POST_LOCALE_PRIORITY) {
|
|
47
|
-
const localeContent = parsed[locale];
|
|
48
|
-
if (!isRecord(localeContent)) {
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
const body = localeContent;
|
|
52
|
-
if (!seen.has(body)) {
|
|
53
|
-
bodies.push(body);
|
|
54
|
-
seen.add(body);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// Process remaining locales
|
|
58
|
-
for (const value of Object.values(parsed)) {
|
|
59
|
-
if (!isRecord(value)) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const body = value;
|
|
63
|
-
if (!seen.has(body)) {
|
|
64
|
-
bodies.push(body);
|
|
65
|
-
seen.add(body);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return bodies;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Convert markdown tables to the Feishu-compatible list format.
|
|
72
|
-
*
|
|
73
|
-
* Reuses the channel runtime's existing converter so the tool send path
|
|
74
|
-
* behaves identically to the main reply path.
|
|
75
|
-
*
|
|
76
|
-
* @param cfg - Current tool configuration
|
|
77
|
-
* @param text - Raw markdown text
|
|
78
|
-
* @returns Converted text, or the original text when runtime is unavailable
|
|
79
|
-
*/
|
|
80
|
-
function convertMarkdownTablesForLark(cfg, text) {
|
|
81
|
-
try {
|
|
82
|
-
const runtime = lark_client_1.LarkClient.runtime;
|
|
83
|
-
if (runtime?.channel?.text?.convertMarkdownTables && runtime.channel.text.resolveMarkdownTableMode) {
|
|
84
|
-
const tableMode = runtime.channel.text.resolveMarkdownTableMode({
|
|
85
|
-
cfg,
|
|
86
|
-
channel: 'feishu',
|
|
87
|
-
});
|
|
88
|
-
return runtime.channel.text.convertMarkdownTables(text, tableMode);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
// Runtime converter unavailable -- keep text as-is.
|
|
93
|
-
}
|
|
94
|
-
return text;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Pre-process `tag="md"` text nodes inside `post` messages so the tool send
|
|
98
|
-
* path also renders markdown tables correctly.
|
|
99
|
-
*
|
|
100
|
-
* @param cfg - Current tool configuration
|
|
101
|
-
* @param msgType - Feishu message type
|
|
102
|
-
* @param content - The JSON string from tool parameters
|
|
103
|
-
* @returns Pre-processed JSON string
|
|
104
|
-
*/
|
|
105
|
-
function preprocessPostContent(cfg, msgType, content) {
|
|
106
|
-
if (msgType !== 'post') {
|
|
107
|
-
return content;
|
|
108
|
-
}
|
|
109
|
-
try {
|
|
110
|
-
const parsed = JSON.parse(content);
|
|
111
|
-
if (!isRecord(parsed)) {
|
|
112
|
-
return content;
|
|
113
|
-
}
|
|
114
|
-
const postContents = collectPostContents(parsed);
|
|
115
|
-
if (postContents.length === 0) {
|
|
116
|
-
return content;
|
|
117
|
-
}
|
|
118
|
-
let changed = false;
|
|
119
|
-
for (const postContent of postContents) {
|
|
120
|
-
if (!postContent.content || !Array.isArray(postContent.content)) {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
for (const line of postContent.content) {
|
|
124
|
-
if (!Array.isArray(line)) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
for (const block of line) {
|
|
128
|
-
if (!isRecord(block) || block.tag !== 'md' || typeof block.text !== 'string') {
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
const convertedText = convertMarkdownTablesForLark(cfg, block.text);
|
|
132
|
-
if (convertedText !== block.text) {
|
|
133
|
-
block.text = convertedText;
|
|
134
|
-
changed = true;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return changed ? JSON.stringify(parsed) : content;
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
return content;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
20
|
// ---------------------------------------------------------------------------
|
|
146
21
|
// Schema
|
|
147
22
|
// ---------------------------------------------------------------------------
|
|
@@ -220,14 +95,12 @@ function registerFeishuImUserMessageTool(api) {
|
|
|
220
95
|
// -----------------------------------------------------------------
|
|
221
96
|
case 'send': {
|
|
222
97
|
log.info(`send: receive_id_type=${p.receive_id_type}, receive_id=${p.receive_id}, msg_type=${p.msg_type}`);
|
|
223
|
-
const accountScopedCfg = (0, accounts_1.createAccountScopedConfig)(cfg, client.account.accountId);
|
|
224
|
-
const processedContent = preprocessPostContent(accountScopedCfg, p.msg_type, p.content);
|
|
225
98
|
const res = await client.invoke('feishu_im_user_message.send', (sdk, opts) => sdk.im.v1.message.create({
|
|
226
99
|
params: { receive_id_type: p.receive_id_type },
|
|
227
100
|
data: {
|
|
228
101
|
receive_id: p.receive_id,
|
|
229
102
|
msg_type: p.msg_type,
|
|
230
|
-
content:
|
|
103
|
+
content: p.content,
|
|
231
104
|
uuid: p.uuid,
|
|
232
105
|
},
|
|
233
106
|
}, opts), {
|
|
@@ -248,12 +121,10 @@ function registerFeishuImUserMessageTool(api) {
|
|
|
248
121
|
// -----------------------------------------------------------------
|
|
249
122
|
case 'reply': {
|
|
250
123
|
log.info(`reply: message_id=${p.message_id}, msg_type=${p.msg_type}, reply_in_thread=${p.reply_in_thread ?? false}`);
|
|
251
|
-
const accountScopedCfg = (0, accounts_1.createAccountScopedConfig)(cfg, client.account.accountId);
|
|
252
|
-
const processedContent = preprocessPostContent(accountScopedCfg, p.msg_type, p.content);
|
|
253
124
|
const res = await client.invoke('feishu_im_user_message.reply', (sdk, opts) => sdk.im.v1.message.reply({
|
|
254
125
|
path: { message_id: p.message_id },
|
|
255
126
|
data: {
|
|
256
|
-
content:
|
|
127
|
+
content: p.content,
|
|
257
128
|
msg_type: p.msg_type,
|
|
258
129
|
reply_in_thread: p.reply_in_thread,
|
|
259
130
|
uuid: p.uuid,
|
package/tsdown.config.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default:
|
|
1
|
+
declare const _default: any;
|
|
2
2
|
export default _default;
|
package/vitest.config.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default:
|
|
1
|
+
declare const _default: any;
|
|
2
2
|
export default _default;
|