@meet-im/meet 3.5.0 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/approval-native.js +28 -3
- package/dist/src/bot.js +64 -20
- package/dist/src/client.js +0 -1
- package/dist/src/dm-topic-codec.d.ts +4 -0
- package/dist/src/dm-topic-codec.js +33 -0
- package/dist/src/generated/plugin-build-meta.d.ts +1 -1
- package/dist/src/generated/plugin-build-meta.js +1 -1
- package/dist/src/reply-dispatcher.d.ts +12 -2
- package/dist/src/reply-dispatcher.js +5 -1
- package/dist/src/route-peer.d.ts +6 -0
- package/dist/src/route-peer.js +6 -0
- package/dist/src/sdk-bridge.d.ts +10 -0
- package/dist/src/sdk-bridge.js +46 -0
- package/dist/src/send.d.ts +6 -0
- package/dist/src/send.js +28 -8
- package/dist/src/types.d.ts +1 -0
- package/package.json +2 -2
- package/skills/meet-lxcli-guide/SKILL.md +1 -1
|
@@ -5,6 +5,7 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "openc
|
|
|
5
5
|
import { listMeetAccountIds, resolveMeetAccount } from "./accounts.js";
|
|
6
6
|
import { createChannelApproverDmTargetResolver, createChannelNativeOriginTargetResolver, createApproverRestrictedNativeApprovalCapability, splitChannelApprovalCapability, } from "openclaw/plugin-sdk/approval-runtime";
|
|
7
7
|
import { getMeetExecApprovalApprovers, isMeetExecApprovalApprover, isMeetExecApprovalClientEnabled, } from "./exec-approvals.js";
|
|
8
|
+
import { encodeMeetDmTopicId } from "./dm-topic-codec.js";
|
|
8
9
|
import { sendMessageMeet } from "./send.js";
|
|
9
10
|
function shouldHandleMeetApprovalRequest(_params) {
|
|
10
11
|
return true;
|
|
@@ -33,6 +34,21 @@ function normalizeMeetOriginChannelId(value) {
|
|
|
33
34
|
}
|
|
34
35
|
return /^-?\d+$/.test(trimmed) ? trimmed : null;
|
|
35
36
|
}
|
|
37
|
+
function normalizeMeetOriginTarget(value) {
|
|
38
|
+
if (!value) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const trimmed = value.trim();
|
|
42
|
+
if (!trimmed) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const prefixed = trimmed.match(/^(?:channel|user):(-?\d+)$/i);
|
|
46
|
+
if (prefixed) {
|
|
47
|
+
const kind = trimmed.toLowerCase().startsWith("user:") ? "user" : "channel";
|
|
48
|
+
return `${kind}:${prefixed[1]}`;
|
|
49
|
+
}
|
|
50
|
+
return /^-?\d+$/.test(trimmed) ? trimmed : null;
|
|
51
|
+
}
|
|
36
52
|
function normalizeMeetThreadId(value) {
|
|
37
53
|
if (typeof value === "number") {
|
|
38
54
|
return Number.isFinite(value) ? String(value) : undefined;
|
|
@@ -41,7 +57,7 @@ function normalizeMeetThreadId(value) {
|
|
|
41
57
|
return undefined;
|
|
42
58
|
}
|
|
43
59
|
const normalized = value.trim();
|
|
44
|
-
return
|
|
60
|
+
return normalized || undefined;
|
|
45
61
|
}
|
|
46
62
|
function createMeetOriginTargetResolver(_configOverride) {
|
|
47
63
|
return createChannelNativeOriginTargetResolver({
|
|
@@ -60,12 +76,21 @@ function createMeetOriginTargetResolver(_configOverride) {
|
|
|
60
76
|
const sessionKind = extractMeetSessionKind(normalizeOptionalString(request.request.sessionKey) ?? null);
|
|
61
77
|
const turnSourceChannel = normalizeLowercaseStringOrEmpty(request.request.turnSourceChannel);
|
|
62
78
|
const rawTurnSourceTo = normalizeOptionalString(request.request.turnSourceTo) ?? "";
|
|
79
|
+
const turnSourceTarget = normalizeMeetOriginTarget(rawTurnSourceTo);
|
|
63
80
|
const turnSourceTo = normalizeMeetOriginChannelId(rawTurnSourceTo);
|
|
64
|
-
const threadId = normalizeMeetThreadId(request.request.turnSourceThreadId) ??
|
|
81
|
+
const threadId = encodeMeetDmTopicId(normalizeMeetThreadId(request.request.turnSourceThreadId)) ??
|
|
65
82
|
normalizeMeetThreadId(sessionConversation?.threadId) ??
|
|
66
83
|
undefined;
|
|
67
84
|
const hasExplicitOriginTarget = /^(?:channel):/i.test(rawTurnSourceTo);
|
|
68
|
-
if (turnSourceChannel !== "meet"
|
|
85
|
+
if (turnSourceChannel !== "meet") {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (sessionKind === "dm") {
|
|
89
|
+
return turnSourceTarget && /^user:/i.test(turnSourceTarget)
|
|
90
|
+
? { to: turnSourceTarget, threadId }
|
|
91
|
+
: null;
|
|
92
|
+
}
|
|
93
|
+
if (!turnSourceTo) {
|
|
69
94
|
return null;
|
|
70
95
|
}
|
|
71
96
|
return hasExplicitOriginTarget || sessionKind === "channel" || sessionKind === "group"
|
package/dist/src/bot.js
CHANGED
|
@@ -2,7 +2,8 @@ import { buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, record
|
|
|
2
2
|
import { buildChannelInboundEventContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
3
3
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
4
4
|
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
|
5
|
-
import { msgContentToContext, extractQuoteMessageMedia } from "./sdk-bridge.js";
|
|
5
|
+
import { msgContentToContext, extractQuoteMessageMedia, parseMergedForwardContent } from "./sdk-bridge.js";
|
|
6
|
+
import { buildMeetRoutePeerId } from "./route-peer.js";
|
|
6
7
|
import { getMeetRuntime } from "./runtime.js";
|
|
7
8
|
import { resolveMeetAllowlistMatch, resolveMeetGroupPolicy, resolveMeetGroupConfig, resolveMeetGroupUserPolicy } from "./policy.js";
|
|
8
9
|
import { sendMessageMeet } from "./send.js";
|
|
@@ -12,6 +13,9 @@ const DEFAULT_DM_SYSTEM_PROMPT = "你正在 Meet 私聊中对话。注意:Meet
|
|
|
12
13
|
function formatHistoryEntry(entry) {
|
|
13
14
|
return `${entry.sender}: ${entry.body}`;
|
|
14
15
|
}
|
|
16
|
+
function isSessionBoundaryCommand(text) {
|
|
17
|
+
return /^\/(?:new|reset)(?:\s|$)/i.test(text.trim());
|
|
18
|
+
}
|
|
15
19
|
export async function handleMeetMessage(params) {
|
|
16
20
|
const { cfg, msg, botUserId, runtime, accountId, account, bot, groupHistories, quoteMsgMap, ctx: providedCtx } = params;
|
|
17
21
|
const log = runtime?.log ?? console.log;
|
|
@@ -36,11 +40,10 @@ export async function handleMeetMessage(params) {
|
|
|
36
40
|
const historyLimit = isGroup
|
|
37
41
|
? Math.max(0, meetCfg.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? 20)
|
|
38
42
|
: Math.max(0, meetCfg.dmHistoryLimit ?? 0);
|
|
43
|
+
const historyKey = isGroup
|
|
44
|
+
? ctx.chatId
|
|
45
|
+
: (ctx.threadId ? `${ctx.chatId}__topic__${ctx.threadId}` : ctx.chatId);
|
|
39
46
|
const speaker = ctx.senderName ?? ctx.senderId;
|
|
40
|
-
// Discord 做法:文字优先,无文字时用媒体占位符
|
|
41
|
-
const messageBody = ctx.content.trim()
|
|
42
|
-
? `${speaker}: ${ctx.content.trim()}`
|
|
43
|
-
: `${speaker}: ${ctx.placeholder || ""}`;
|
|
44
47
|
const pendingEntry = {
|
|
45
48
|
sender: speaker,
|
|
46
49
|
body: ctx.content.trim() || ctx.placeholder || "",
|
|
@@ -149,16 +152,48 @@ export async function handleMeetMessage(params) {
|
|
|
149
152
|
log(`[${accountId}]: message in group ${ctx.chatId} skipped (mention required)`);
|
|
150
153
|
recordPendingHistoryEntryIfEnabled({
|
|
151
154
|
historyMap: groupHistories,
|
|
152
|
-
historyKey
|
|
155
|
+
historyKey,
|
|
153
156
|
entry: pendingEntry,
|
|
154
157
|
limit: historyLimit,
|
|
155
158
|
});
|
|
156
159
|
return;
|
|
157
160
|
}
|
|
158
161
|
}
|
|
162
|
+
// 合并转发消息(msgType=19):只进上下文,不发给 LLM
|
|
163
|
+
const msgType = Number(msg.extraInfo?.msgType);
|
|
164
|
+
if (msgType === 19) {
|
|
165
|
+
log(`[${accountId}]: skipping merged forward message ${ctx.messageId} (msgType=19, context only)`);
|
|
166
|
+
if (historyLimit > 0) {
|
|
167
|
+
const subEntries = parseMergedForwardContent(msg.content ?? "");
|
|
168
|
+
const entries = groupHistories.get(historyKey) ?? [];
|
|
169
|
+
if (subEntries.length > 0) {
|
|
170
|
+
for (const sub of subEntries) {
|
|
171
|
+
entries.push({
|
|
172
|
+
sender: sub.sender,
|
|
173
|
+
body: sub.body,
|
|
174
|
+
timestamp: sub.timestamp,
|
|
175
|
+
messageId: sub.messageId,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
entries.push(pendingEntry);
|
|
181
|
+
}
|
|
182
|
+
while (entries.length > historyLimit) {
|
|
183
|
+
entries.shift();
|
|
184
|
+
}
|
|
185
|
+
groupHistories.set(historyKey, entries);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
159
189
|
const meetFrom = `meet:${ctx.senderId}`;
|
|
160
190
|
const meetTo = ctx.chatId;
|
|
161
|
-
const peerId =
|
|
191
|
+
const peerId = buildMeetRoutePeerId({
|
|
192
|
+
isGroup,
|
|
193
|
+
senderId: ctx.senderId,
|
|
194
|
+
chatId: ctx.chatId,
|
|
195
|
+
threadId: ctx.threadId,
|
|
196
|
+
});
|
|
162
197
|
const route = core.channel.routing.resolveAgentRoute({
|
|
163
198
|
cfg,
|
|
164
199
|
channel: "meet",
|
|
@@ -167,6 +202,7 @@ export async function handleMeetMessage(params) {
|
|
|
167
202
|
kind: isGroup ? "group" : "direct",
|
|
168
203
|
id: peerId,
|
|
169
204
|
},
|
|
205
|
+
...(ctx.threadId ? { threadId: ctx.threadId } : {}),
|
|
170
206
|
});
|
|
171
207
|
// 处理媒体附件
|
|
172
208
|
let mediaContext = "";
|
|
@@ -257,6 +293,9 @@ export async function handleMeetMessage(params) {
|
|
|
257
293
|
? ctx.content.trim()
|
|
258
294
|
: (ctx.placeholder || "");
|
|
259
295
|
const finalContent = `${quoteContext}${userBody}${mentionsContext}${mediaContext}`;
|
|
296
|
+
if (isSessionBoundaryCommand(ctx.rawBody ?? ctx.content)) {
|
|
297
|
+
groupHistories.set(historyKey, []);
|
|
298
|
+
}
|
|
260
299
|
// Discord 做法:跳过空内容消息
|
|
261
300
|
if (!finalContent.trim() && mediaPaths.length === 0) {
|
|
262
301
|
log(`[${accountId}]: skip message ${ctx.messageId} (empty content)`);
|
|
@@ -273,22 +312,22 @@ export async function handleMeetMessage(params) {
|
|
|
273
312
|
const envelopeFrom = isGroup ? `${ctx.chatId}:${ctx.senderId}` : ctx.senderId;
|
|
274
313
|
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
275
314
|
// 将当前消息添加到历史(在确认要处理消息之后)
|
|
276
|
-
const historyEntries =
|
|
315
|
+
const historyEntries = historyLimit > 0
|
|
277
316
|
? (() => {
|
|
278
|
-
const entries = groupHistories.get(
|
|
317
|
+
const entries = groupHistories.get(historyKey) ?? [];
|
|
279
318
|
entries.push(pendingEntry);
|
|
280
319
|
while (entries.length > historyLimit) {
|
|
281
320
|
entries.shift();
|
|
282
321
|
}
|
|
283
|
-
groupHistories.set(
|
|
322
|
+
groupHistories.set(historyKey, entries);
|
|
284
323
|
return entries;
|
|
285
324
|
})()
|
|
286
325
|
: [];
|
|
287
326
|
const bodyWithContext = buildPendingHistoryContextFromMap({
|
|
288
327
|
historyMap: groupHistories,
|
|
289
|
-
historyKey
|
|
328
|
+
historyKey,
|
|
290
329
|
limit: historyLimit,
|
|
291
|
-
currentMessage:
|
|
330
|
+
currentMessage: finalContent,
|
|
292
331
|
formatEntry: formatHistoryEntry,
|
|
293
332
|
});
|
|
294
333
|
const body = core.channel.reply.formatAgentEnvelope({
|
|
@@ -298,7 +337,7 @@ export async function handleMeetMessage(params) {
|
|
|
298
337
|
envelope: envelopeOptions,
|
|
299
338
|
body: bodyWithContext,
|
|
300
339
|
});
|
|
301
|
-
const inboundHistory =
|
|
340
|
+
const inboundHistory = historyLimit > 0
|
|
302
341
|
? historyEntries.map((entry) => ({
|
|
303
342
|
sender: entry.sender,
|
|
304
343
|
body: entry.body,
|
|
@@ -326,6 +365,7 @@ export async function handleMeetMessage(params) {
|
|
|
326
365
|
kind: isGroup ? "group" : "direct",
|
|
327
366
|
id: ctx.chatId,
|
|
328
367
|
label: isGroup ? (groupConfig?.groupConfig?.name ?? ctx.chatId) : ctx.chatId,
|
|
368
|
+
...(ctx.threadId ? { threadId: ctx.threadId } : {}),
|
|
329
369
|
routePeer: {
|
|
330
370
|
kind: isGroup ? "group" : "direct",
|
|
331
371
|
id: peerId,
|
|
@@ -339,11 +379,12 @@ export async function handleMeetMessage(params) {
|
|
|
339
379
|
reply: {
|
|
340
380
|
to: meetTo,
|
|
341
381
|
originatingTo: meetTo,
|
|
382
|
+
...(ctx.threadId ? { messageThreadId: ctx.threadId } : {}),
|
|
342
383
|
},
|
|
343
384
|
message: {
|
|
344
385
|
body,
|
|
345
386
|
rawBody: ctx.content,
|
|
346
|
-
bodyForAgent:
|
|
387
|
+
bodyForAgent: bodyWithContext,
|
|
347
388
|
commandBody: ctx.rawBody ?? ctx.content,
|
|
348
389
|
envelopeFrom,
|
|
349
390
|
inboundHistory,
|
|
@@ -389,6 +430,7 @@ export async function handleMeetMessage(params) {
|
|
|
389
430
|
agentId: route.agentId,
|
|
390
431
|
runtime: runtime,
|
|
391
432
|
chatId: ctx.chatId,
|
|
433
|
+
threadId: ctx.threadId,
|
|
392
434
|
senderId: ctx.senderId,
|
|
393
435
|
mentionedBot: ctx.mentionedBot,
|
|
394
436
|
replyToMessageId: ctx.messageId,
|
|
@@ -402,7 +444,7 @@ export async function handleMeetMessage(params) {
|
|
|
402
444
|
apiEndpoint: account.apiEndpoint,
|
|
403
445
|
typingMode: effectiveTypingMode,
|
|
404
446
|
});
|
|
405
|
-
log(`[${accountId}]: dispatch ctx replyToId=${inboundCtx.ReplyToId ?? "undefined"} replyToBody=${JSON.stringify(inboundCtx.ReplyToBody ?? "")} rawBody=${JSON.stringify(inboundCtx.RawBody ?? "")} commandBody=${JSON.stringify(inboundCtx.CommandBody ?? "")} bodyForAgent=${JSON.stringify(inboundCtx.BodyForAgent ?? "")}`);
|
|
447
|
+
log(`[${accountId}]: dispatch ctx historyKey=${historyKey} routeSessionKey=${route.sessionKey} replyToId=${inboundCtx.ReplyToId ?? "undefined"} replyToBody=${JSON.stringify(inboundCtx.ReplyToBody ?? "")} rawBody=${JSON.stringify(inboundCtx.RawBody ?? "")} commandBody=${JSON.stringify(inboundCtx.CommandBody ?? "")} bodyForAgent=${JSON.stringify(inboundCtx.BodyForAgent ?? "")}`);
|
|
406
448
|
log(`[${accountId}]: dispatching to AI agent=${route.agentId} session=${route.sessionKey} history=${inboundHistory?.length ?? 0}`);
|
|
407
449
|
const dispatchResult = await core.channel.reply.dispatchReplyFromConfig({
|
|
408
450
|
ctx: inboundCtx,
|
|
@@ -414,11 +456,13 @@ export async function handleMeetMessage(params) {
|
|
|
414
456
|
log(`[${accountId}]: AI response completed for message ${ctx.messageId}`);
|
|
415
457
|
markRunComplete();
|
|
416
458
|
markDispatchIdle();
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
459
|
+
if (isGroup) {
|
|
460
|
+
clearHistoryEntriesIfEnabled({
|
|
461
|
+
historyMap: groupHistories,
|
|
462
|
+
historyKey,
|
|
463
|
+
limit: historyLimit,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
422
466
|
}
|
|
423
467
|
catch (err) {
|
|
424
468
|
error(`[${accountId}]: error processing message: ${String(err)}`);
|
package/dist/src/client.js
CHANGED
|
@@ -39,7 +39,6 @@ export function createMeetClient(account) {
|
|
|
39
39
|
pollingLimit: account.config.pollLimit ?? POLLING.DEFAULT_LIMIT,
|
|
40
40
|
longPollingTimeout: pollTimeoutSec,
|
|
41
41
|
logLevel,
|
|
42
|
-
useV2: true,
|
|
43
42
|
userAgent: buildUserAgent(),
|
|
44
43
|
});
|
|
45
44
|
botInstances.set(account.accountId, bot);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const MEET_DM_TOPIC_THREAD_PREFIX = "meetdm_b64_";
|
|
2
|
+
export declare function isEncodedMeetDmTopicThreadId(threadId?: string | null): boolean;
|
|
3
|
+
export declare function encodeMeetDmTopicId(raw?: string | null): string | undefined;
|
|
4
|
+
export declare function decodeMeetDmTopicThreadId(threadId?: string | null): string | undefined;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const MEET_DM_TOPIC_THREAD_PREFIX = "meetdm_b64_";
|
|
2
|
+
function normalizeNonEmpty(value) {
|
|
3
|
+
const trimmed = value?.trim();
|
|
4
|
+
return trimmed ? trimmed : undefined;
|
|
5
|
+
}
|
|
6
|
+
function toBase64Url(input) {
|
|
7
|
+
return Buffer.from(input, "utf8")
|
|
8
|
+
.toString("base64")
|
|
9
|
+
.replace(/\+/g, "-")
|
|
10
|
+
.replace(/\//g, "_")
|
|
11
|
+
.replace(/=+$/g, "");
|
|
12
|
+
}
|
|
13
|
+
function fromBase64Url(input) {
|
|
14
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
15
|
+
const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
|
|
16
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
17
|
+
}
|
|
18
|
+
export function isEncodedMeetDmTopicThreadId(threadId) {
|
|
19
|
+
const normalized = normalizeNonEmpty(threadId);
|
|
20
|
+
return normalized?.startsWith(MEET_DM_TOPIC_THREAD_PREFIX) ?? false;
|
|
21
|
+
}
|
|
22
|
+
export function encodeMeetDmTopicId(raw) {
|
|
23
|
+
const normalized = normalizeNonEmpty(raw);
|
|
24
|
+
return normalized ? `${MEET_DM_TOPIC_THREAD_PREFIX}${toBase64Url(normalized)}` : undefined;
|
|
25
|
+
}
|
|
26
|
+
export function decodeMeetDmTopicThreadId(threadId) {
|
|
27
|
+
const normalized = normalizeNonEmpty(threadId);
|
|
28
|
+
if (!normalized)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (!normalized.startsWith(MEET_DM_TOPIC_THREAD_PREFIX))
|
|
31
|
+
return normalized;
|
|
32
|
+
return fromBase64Url(normalized.slice(MEET_DM_TOPIC_THREAD_PREFIX.length));
|
|
33
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const MEET_PLUGIN_VERSION = "3.
|
|
1
|
+
export declare const MEET_PLUGIN_VERSION = "3.6.1";
|
|
2
2
|
export declare const MEET_OPENCLAW_VERSION = "2026.5.18";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const MEET_PLUGIN_VERSION = "3.
|
|
1
|
+
export const MEET_PLUGIN_VERSION = "3.6.1";
|
|
2
2
|
export const MEET_OPENCLAW_VERSION = "2026.5.18";
|
|
@@ -15,6 +15,7 @@ export type CreateMeetReplyDispatcherOpts = {
|
|
|
15
15
|
agentId: string;
|
|
16
16
|
runtime: RuntimeEnv;
|
|
17
17
|
chatId: string;
|
|
18
|
+
threadId?: string;
|
|
18
19
|
senderId?: string;
|
|
19
20
|
mentionedBot?: boolean;
|
|
20
21
|
replyToMessageId?: string;
|
|
@@ -28,11 +29,20 @@ export type CreateMeetReplyDispatcherOpts = {
|
|
|
28
29
|
typingMode?: "none" | "instant" | "message";
|
|
29
30
|
};
|
|
30
31
|
export declare function createMeetReplyDispatcher(opts: CreateMeetReplyDispatcherOpts): Promise<{
|
|
31
|
-
dispatcher: import("node_modules/openclaw/dist/plugin-sdk/
|
|
32
|
+
dispatcher: import("node_modules/openclaw/dist/plugin-sdk/reply-dispatcher.types-B0sivCQE.js").r;
|
|
32
33
|
replyOptions: {
|
|
33
34
|
sourceReplyDeliveryMode?: import("openclaw/plugin-sdk/channel-reply-pipeline").SourceReplyDeliveryMode | undefined;
|
|
34
35
|
onReplyStart?: (() => Promise<void> | void) | undefined;
|
|
35
|
-
onTypingController?: ((typing:
|
|
36
|
+
onTypingController?: ((typing: {
|
|
37
|
+
onReplyStart: () => Promise<void>;
|
|
38
|
+
startTypingLoop: () => Promise<void>;
|
|
39
|
+
startTypingOnText: (text?: string) => Promise<void>;
|
|
40
|
+
refreshTypingTtl: () => void;
|
|
41
|
+
isActive: () => boolean;
|
|
42
|
+
markRunComplete: () => void;
|
|
43
|
+
markDispatchIdle: () => void;
|
|
44
|
+
cleanup: () => void;
|
|
45
|
+
}) => void) | undefined;
|
|
36
46
|
onTypingCleanup?: (() => void) | undefined;
|
|
37
47
|
};
|
|
38
48
|
markDispatchIdle: () => void;
|
|
@@ -83,7 +83,7 @@ export function protectMentionsInChunks(chunks) {
|
|
|
83
83
|
return result;
|
|
84
84
|
}
|
|
85
85
|
export async function createMeetReplyDispatcher(opts) {
|
|
86
|
-
const { cfg, agentId, chatId, senderId, mentionedBot, replyToMessageId, accountId, mediaLocalRoots, sessionInfo, apiToken, apiEndpoint, typingMode } = opts;
|
|
86
|
+
const { cfg, agentId, chatId, threadId, senderId, mentionedBot, replyToMessageId, accountId, mediaLocalRoots, sessionInfo, apiToken, apiEndpoint, typingMode } = opts;
|
|
87
87
|
const core = getMeetRuntime();
|
|
88
88
|
const textChunkLimit = core.channel.text.resolveTextChunkLimit(cfg, "meet", accountId, {
|
|
89
89
|
fallbackLimit: 4000,
|
|
@@ -210,6 +210,7 @@ export async function createMeetReplyDispatcher(opts) {
|
|
|
210
210
|
await sendMediaMeet({
|
|
211
211
|
cfg,
|
|
212
212
|
to: chatId,
|
|
213
|
+
threadId,
|
|
213
214
|
text: text.trim() || undefined,
|
|
214
215
|
mediaUrl: mediaUrls[0],
|
|
215
216
|
mediaLocalRoots,
|
|
@@ -221,6 +222,7 @@ export async function createMeetReplyDispatcher(opts) {
|
|
|
221
222
|
await sendMediaMeet({
|
|
222
223
|
cfg,
|
|
223
224
|
to: chatId,
|
|
225
|
+
threadId,
|
|
224
226
|
text: undefined,
|
|
225
227
|
mediaUrl: mediaUrls[i],
|
|
226
228
|
mediaLocalRoots,
|
|
@@ -242,6 +244,7 @@ export async function createMeetReplyDispatcher(opts) {
|
|
|
242
244
|
await sendMessageMeet({
|
|
243
245
|
cfg,
|
|
244
246
|
to: chatId,
|
|
247
|
+
threadId,
|
|
245
248
|
text: chunk,
|
|
246
249
|
accountId,
|
|
247
250
|
replyToMessageId,
|
|
@@ -290,6 +293,7 @@ export async function createMeetReplyDispatcher(opts) {
|
|
|
290
293
|
await sendMessageMeet({
|
|
291
294
|
cfg,
|
|
292
295
|
to: chatId,
|
|
296
|
+
threadId,
|
|
293
297
|
text: userMessage,
|
|
294
298
|
accountId,
|
|
295
299
|
runtime: opts.runtime,
|
package/dist/src/sdk-bridge.d.ts
CHANGED
|
@@ -25,3 +25,13 @@ export declare function msgContentToContext(msg: MsgContent, botUserId: string,
|
|
|
25
25
|
export declare function enrichContextWithUserNames(ctx: MeetMessageContext, bot: MeetBot, accountId?: string): Promise<void>;
|
|
26
26
|
export declare function parseTargetToSessionInfo(target: string, botUserId: number): SessionInfo;
|
|
27
27
|
export declare function buildMeetTarget(sessionInfo: SessionInfo, botUserId: number): string;
|
|
28
|
+
export interface MergedForwardEntry {
|
|
29
|
+
sender: string;
|
|
30
|
+
body: string;
|
|
31
|
+
timestamp?: number;
|
|
32
|
+
messageId: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 解析合并转发消息的 content,展开为多条可读记录
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseMergedForwardContent(content: string): MergedForwardEntry[];
|
package/dist/src/sdk-bridge.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodeMeetDmTopicId } from "./dm-topic-codec.js";
|
|
1
2
|
import { rememberMeetUser } from "./directory-cache.js";
|
|
2
3
|
export function mapSessionType(sessionType) {
|
|
3
4
|
return sessionType === 1 ? "direct" : "channel";
|
|
@@ -159,6 +160,9 @@ export function msgContentToContext(msg, botUserId, quoteMsgMap = {}) {
|
|
|
159
160
|
const chatId = chatType === "direct"
|
|
160
161
|
? `user:${msg.fromUid}`
|
|
161
162
|
: `channel:${msg.sessionInfo.secondID}`;
|
|
163
|
+
const threadId = chatType === "direct" && typeof msg.dmTopicID === "string" && msg.dmTopicID.trim()
|
|
164
|
+
? encodeMeetDmTopicId(msg.dmTopicID)
|
|
165
|
+
: undefined;
|
|
162
166
|
const mentionedBot = msg.atIds?.includes(Number(botUserId)) ?? false;
|
|
163
167
|
const replyContext = resolveQuoteMessage(msg, quoteMsgMap);
|
|
164
168
|
const media = extractMediaAttachments(msg);
|
|
@@ -171,6 +175,7 @@ export function msgContentToContext(msg, botUserId, quoteMsgMap = {}) {
|
|
|
171
175
|
}
|
|
172
176
|
return {
|
|
173
177
|
chatId,
|
|
178
|
+
threadId,
|
|
174
179
|
messageId: String(msg.seqId ?? 0),
|
|
175
180
|
senderId: String(msg.fromUid ?? 0),
|
|
176
181
|
senderOpenId: String(msg.fromUid ?? 0),
|
|
@@ -302,3 +307,44 @@ export function buildMeetTarget(sessionInfo, botUserId) {
|
|
|
302
307
|
}
|
|
303
308
|
return `channel:${sessionInfo.secondID}`;
|
|
304
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* 解析合并转发消息的 content,展开为多条可读记录
|
|
312
|
+
*/
|
|
313
|
+
export function parseMergedForwardContent(content) {
|
|
314
|
+
let parsed;
|
|
315
|
+
try {
|
|
316
|
+
parsed = JSON.parse(content);
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
const msgs = parsed.msgs;
|
|
322
|
+
if (!Array.isArray(msgs) || msgs.length === 0) {
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
const profileMap = parsed.userProfileMap ?? {};
|
|
326
|
+
const buildAttachmentPlaceholder = (sub) => {
|
|
327
|
+
const attachments = [
|
|
328
|
+
...(sub.extraInfo?.attechmentInfo ? [sub.extraInfo.attechmentInfo] : []),
|
|
329
|
+
...(Array.isArray(sub.extraInfo?.attechmentInfos) ? sub.extraInfo.attechmentInfos : []),
|
|
330
|
+
];
|
|
331
|
+
if (attachments.length === 0) {
|
|
332
|
+
return "";
|
|
333
|
+
}
|
|
334
|
+
return attachments
|
|
335
|
+
.map((attachment) => `[attachment:${attachment.fileName || attachment.mimeType || "file"}]`)
|
|
336
|
+
.join(" ");
|
|
337
|
+
};
|
|
338
|
+
return msgs.map((sub, i) => {
|
|
339
|
+
const senderId = String(sub.fromUid ?? 0);
|
|
340
|
+
const profile = profileMap[senderId];
|
|
341
|
+
const sender = profile?.nickName || senderId;
|
|
342
|
+
const attachmentPlaceholder = buildAttachmentPlaceholder(sub);
|
|
343
|
+
return {
|
|
344
|
+
sender,
|
|
345
|
+
body: [sub.content?.trim() || "", attachmentPlaceholder].filter(Boolean).join(" "),
|
|
346
|
+
timestamp: sub.timestamp,
|
|
347
|
+
messageId: String(sub.seqId ?? `merged-${i}`),
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
}
|
package/dist/src/send.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export declare function extractAtIds(text: string): {
|
|
|
15
15
|
atIds: number[];
|
|
16
16
|
};
|
|
17
17
|
export declare function mergeAtIds(explicitAtIds?: number[], extractedAtIds?: number[]): number[];
|
|
18
|
+
export declare function resolveMeetThreadTarget<T extends {
|
|
19
|
+
sessionType: number;
|
|
20
|
+
}>(sessionInfo: T, threadId?: string): T | (T & {
|
|
21
|
+
threadId?: number;
|
|
22
|
+
});
|
|
18
23
|
export type SendMessageMeetOpts = {
|
|
19
24
|
cfg: ClawdbotConfig;
|
|
20
25
|
to: string;
|
|
@@ -33,6 +38,7 @@ export declare function sendMessageMeet(opts: SendMessageMeetOpts): Promise<{
|
|
|
33
38
|
export type SendMediaMeetOpts = {
|
|
34
39
|
cfg: ClawdbotConfig;
|
|
35
40
|
to: string;
|
|
41
|
+
threadId?: string;
|
|
36
42
|
text?: string;
|
|
37
43
|
mediaUrl: string;
|
|
38
44
|
mediaLocalRoots?: readonly string[];
|
package/dist/src/send.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveMeetAccount } from "./accounts.js";
|
|
2
2
|
import { getMeetClient, createMeetClient } from "./client.js";
|
|
3
|
+
import { decodeMeetDmTopicThreadId } from "./dm-topic-codec.js";
|
|
3
4
|
import { parseTargetToSessionInfo } from "./sdk-bridge.js";
|
|
4
5
|
import { getMeetRuntime } from "./runtime.js";
|
|
5
6
|
import { rewriteMeetKnownMentions } from "./mentions.js";
|
|
@@ -122,6 +123,25 @@ export function extractAtIds(text) {
|
|
|
122
123
|
export function mergeAtIds(explicitAtIds, extractedAtIds) {
|
|
123
124
|
return [...new Set([...(explicitAtIds ?? []), ...(extractedAtIds ?? [])])];
|
|
124
125
|
}
|
|
126
|
+
// OpenClaw upper layers use a channel-agnostic `threadId` abstraction.
|
|
127
|
+
// Meet maps that abstraction differently by session type:
|
|
128
|
+
// - channel/group sessions use Meet `threadId`
|
|
129
|
+
// - direct-message sessions use Meet `dmTopicID`
|
|
130
|
+
export function resolveMeetThreadTarget(sessionInfo, threadId) {
|
|
131
|
+
if (!threadId) {
|
|
132
|
+
return sessionInfo;
|
|
133
|
+
}
|
|
134
|
+
if (sessionInfo.sessionType === 3) {
|
|
135
|
+
return {
|
|
136
|
+
...sessionInfo,
|
|
137
|
+
threadId: Number(threadId),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (sessionInfo.sessionType === 1) {
|
|
141
|
+
return sessionInfo;
|
|
142
|
+
}
|
|
143
|
+
return sessionInfo;
|
|
144
|
+
}
|
|
125
145
|
export async function sendMessageMeet(opts) {
|
|
126
146
|
const { cfg, to, text, accountId, threadId, atIds: explicitAtIds, runtime } = opts;
|
|
127
147
|
const log = runtime?.log ?? console.log;
|
|
@@ -170,16 +190,13 @@ export async function sendMessageMeet(opts) {
|
|
|
170
190
|
const finalAtIds = mergeAtIds(explicitAtIds, extractedAtIds);
|
|
171
191
|
log(`send message to=${to} atIds=${finalAtIds.join(",") || "none"}`);
|
|
172
192
|
const sessionInfo = parseTargetToSessionInfo(to, Number(botUserId));
|
|
173
|
-
const finalSessionInfo =
|
|
174
|
-
|
|
175
|
-
...sessionInfo,
|
|
176
|
-
threadId: Number(threadId),
|
|
177
|
-
}
|
|
178
|
-
: sessionInfo;
|
|
193
|
+
const finalSessionInfo = resolveMeetThreadTarget(sessionInfo, threadId);
|
|
194
|
+
const dmTopicID = sessionInfo.sessionType === 1 ? decodeMeetDmTopicThreadId(threadId) : undefined;
|
|
179
195
|
try {
|
|
180
196
|
const result = await bot.sendMessage(finalSessionInfo, {
|
|
181
197
|
content: cleanText,
|
|
182
198
|
atIds: finalAtIds,
|
|
199
|
+
...(dmTopicID ? { dmTopicID } : {}),
|
|
183
200
|
});
|
|
184
201
|
return {
|
|
185
202
|
messageId: String(result.msgContent?.seqId ?? 0),
|
|
@@ -192,7 +209,7 @@ export async function sendMessageMeet(opts) {
|
|
|
192
209
|
}
|
|
193
210
|
}
|
|
194
211
|
export async function sendMediaMeet(opts) {
|
|
195
|
-
const { cfg, to, text, mediaUrl, mediaLocalRoots, accountId, onProgress, runtime: logRuntime } = opts;
|
|
212
|
+
const { cfg, to, threadId, text, mediaUrl, mediaLocalRoots, accountId, onProgress, runtime: logRuntime } = opts;
|
|
196
213
|
const log = logRuntime?.log ?? console.log;
|
|
197
214
|
const logError = logRuntime?.error ?? console.error;
|
|
198
215
|
const account = resolveMeetAccount({ cfg, accountId });
|
|
@@ -242,6 +259,8 @@ export async function sendMediaMeet(opts) {
|
|
|
242
259
|
throw new Error(`Media file too large: ${media.buffer.length} bytes (max: ${maxBytes})`);
|
|
243
260
|
}
|
|
244
261
|
const sessionInfo = parseTargetToSessionInfo(to, Number(botUserId));
|
|
262
|
+
const finalSessionInfo = resolveMeetThreadTarget(sessionInfo, threadId);
|
|
263
|
+
const dmTopicID = sessionInfo.sessionType === 1 ? decodeMeetDmTopicThreadId(threadId) : undefined;
|
|
245
264
|
const rawFileName = media.fileName || "file";
|
|
246
265
|
const contentType = resolveContentType(rawFileName, media.contentType);
|
|
247
266
|
// 确保文件名有正确的扩展名
|
|
@@ -259,11 +278,12 @@ export async function sendMediaMeet(opts) {
|
|
|
259
278
|
}
|
|
260
279
|
: undefined;
|
|
261
280
|
try {
|
|
262
|
-
const result = await bot.sendMedia(
|
|
281
|
+
const result = await bot.sendMedia(finalSessionInfo, {
|
|
263
282
|
buffer: media.buffer,
|
|
264
283
|
fileName,
|
|
265
284
|
contentType,
|
|
266
285
|
content: text || "",
|
|
286
|
+
...(dmTopicID ? { dmTopicID } : {}),
|
|
267
287
|
onProgress: progressCallback,
|
|
268
288
|
});
|
|
269
289
|
return {
|
package/dist/src/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meet-im/meet",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw Meet channel plugin",
|
|
6
6
|
"scripts": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@meet-im/meet-bot-jssdk": "^1.
|
|
58
|
+
"@meet-im/meet-bot-jssdk": "^1.4.2",
|
|
59
59
|
"zod": "^4.4.3"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|