@meet-im/meet 3.4.0 → 3.4.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,2 +1,2 @@
|
|
|
1
|
-
export declare const MEET_PLUGIN_VERSION = "3.4.
|
|
1
|
+
export declare const MEET_PLUGIN_VERSION = "3.4.3";
|
|
2
2
|
export declare const MEET_OPENCLAW_VERSION = "2026.5.18";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const MEET_PLUGIN_VERSION = "3.4.
|
|
1
|
+
export const MEET_PLUGIN_VERSION = "3.4.3";
|
|
2
2
|
export const MEET_OPENCLAW_VERSION = "2026.5.18";
|
package/dist/src/monitor.js
CHANGED
|
@@ -3,6 +3,7 @@ import { resolveMeetAccount, listEnabledMeetAccounts } from "./accounts.js";
|
|
|
3
3
|
import { createMeetClient, closeMeetClient, closeAllMeetClients, getPollingOptions } from "./client.js";
|
|
4
4
|
import { handleMeetMessage } from "./bot.js";
|
|
5
5
|
import { msgContentToContext, enrichContextWithUserNames } from "./sdk-bridge.js";
|
|
6
|
+
const activeMonitors = new Map();
|
|
6
7
|
export async function monitorMeetProvider(opts = {}) {
|
|
7
8
|
const cfg = opts.config;
|
|
8
9
|
if (!cfg) {
|
|
@@ -38,13 +39,32 @@ async function monitorSingleAccount(params) {
|
|
|
38
39
|
const { accountId } = account;
|
|
39
40
|
const log = runtime?.log ?? console.log;
|
|
40
41
|
const error = runtime?.error ?? console.error;
|
|
42
|
+
const existingMonitor = activeMonitors.get(accountId);
|
|
43
|
+
if (existingMonitor) {
|
|
44
|
+
if (abortSignal) {
|
|
45
|
+
const stopExistingMonitor = () => {
|
|
46
|
+
existingMonitor.stop();
|
|
47
|
+
};
|
|
48
|
+
if (abortSignal.aborted) {
|
|
49
|
+
stopExistingMonitor();
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
abortSignal.addEventListener("abort", stopExistingMonitor, { once: true });
|
|
53
|
+
void existingMonitor.promise.finally(() => {
|
|
54
|
+
abortSignal.removeEventListener("abort", stopExistingMonitor);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return existingMonitor.promise;
|
|
59
|
+
}
|
|
41
60
|
const pollTimeoutMs = account.config.pollTimeout ?? 30000;
|
|
42
61
|
log(`[${accountId}]: starting with pollTimeout=${pollTimeoutMs}ms`);
|
|
43
62
|
const bot = createMeetClient(account);
|
|
44
63
|
const botUserId = extractBotUserId(account.apiToken ?? "");
|
|
45
64
|
const groupHistories = new Map();
|
|
46
65
|
const messageQueue = new KeyedAsyncQueue();
|
|
47
|
-
|
|
66
|
+
let stopMonitor = () => { };
|
|
67
|
+
const monitorPromise = new Promise((resolve, reject) => {
|
|
48
68
|
let isCleaningUp = false;
|
|
49
69
|
const cleanup = () => {
|
|
50
70
|
if (isCleaningUp)
|
|
@@ -53,6 +73,7 @@ async function monitorSingleAccount(params) {
|
|
|
53
73
|
bot.stopPolling();
|
|
54
74
|
closeMeetClient(accountId);
|
|
55
75
|
};
|
|
76
|
+
stopMonitor = cleanup;
|
|
56
77
|
const handleAbort = () => {
|
|
57
78
|
log(`[${accountId}]: abort signal received, stopping`);
|
|
58
79
|
cleanup();
|
|
@@ -151,6 +172,17 @@ async function monitorSingleAccount(params) {
|
|
|
151
172
|
reject(err);
|
|
152
173
|
});
|
|
153
174
|
});
|
|
175
|
+
activeMonitors.set(accountId, {
|
|
176
|
+
promise: monitorPromise,
|
|
177
|
+
stop: () => {
|
|
178
|
+
stopMonitor();
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
return monitorPromise.finally(() => {
|
|
182
|
+
if (activeMonitors.get(accountId)?.promise === monitorPromise) {
|
|
183
|
+
activeMonitors.delete(accountId);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
154
186
|
}
|
|
155
187
|
export function stopMeetMonitor(accountId) {
|
|
156
188
|
if (accountId) {
|
|
@@ -4,6 +4,14 @@ import { createTypingCallbacks } from "openclaw/plugin-sdk/channel-message";
|
|
|
4
4
|
import { getMeetRuntime } from "./runtime.js";
|
|
5
5
|
import { sendMessageMeet, sendMediaMeet } from "./send.js";
|
|
6
6
|
import { sendTypingMeet, stopTypingMeet } from "./typing.js";
|
|
7
|
+
function normalizeVisibleTextForDedup(text) {
|
|
8
|
+
return normalizeStatusTextForDedup(text.replace(/\r\n/g, "\n").trim());
|
|
9
|
+
}
|
|
10
|
+
function normalizeStatusTextForDedup(text) {
|
|
11
|
+
return text
|
|
12
|
+
.replace(/(⏱️\s*Uptime:\s*gateway\s+)\d+s(\s*·\s*system\s+)/g, "$1<secs>$2")
|
|
13
|
+
.replace(/(⏱️\s*Uptime:\s*gateway\s+[^·\n]+·\s*system\s+)\d+s(\b)/g, "$1<secs>$2");
|
|
14
|
+
}
|
|
7
15
|
function resolveMeetConversationType(chatId) {
|
|
8
16
|
if (chatId.startsWith("channel:")) {
|
|
9
17
|
return "group";
|
|
@@ -91,42 +99,45 @@ export async function createMeetReplyDispatcher(opts) {
|
|
|
91
99
|
: undefined;
|
|
92
100
|
const shouldAutoMentionSender = chatType === "channel" && mentionedBot === true && !!senderId;
|
|
93
101
|
const senderMentionText = shouldAutoMentionSender ? `<@${senderId}>` : undefined;
|
|
102
|
+
let lastDeliveredVisibleText;
|
|
94
103
|
// 创建 typing callbacks(如果配置了 typing 且有必要参数)
|
|
95
104
|
const hasTypingParams = typingMode && typingMode !== "none" && sessionInfo && apiToken;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const next = typingRequestChain.then(async () => {
|
|
99
|
-
opts.runtime.log?.(`[${accountId}][${chatId}]: typing ${label} sending...`);
|
|
100
|
-
return await run();
|
|
101
|
-
});
|
|
102
|
-
typingRequestChain = next.then(() => undefined).catch(() => { });
|
|
103
|
-
return next;
|
|
104
|
-
};
|
|
105
|
+
// stop 请求需要等待最后一个 start 完成,避免乱序
|
|
106
|
+
let lastStartPromise = Promise.resolve();
|
|
105
107
|
const typingCallbacks = hasTypingParams
|
|
106
108
|
? createTypingCallbacks({
|
|
107
109
|
start: async () => {
|
|
108
|
-
|
|
110
|
+
// 直接发送,不串行化。typing start 是幂等的,重复发送无害
|
|
111
|
+
opts.runtime.log?.(`[${accountId}][${chatId}]: typing start sending...`);
|
|
112
|
+
const startPromise = sendTypingMeet({
|
|
109
113
|
accountId,
|
|
110
114
|
chatId,
|
|
111
115
|
chatType: chatType ?? "channel",
|
|
112
116
|
sessionInfo,
|
|
113
117
|
token: apiToken,
|
|
114
118
|
apiEndpoint,
|
|
115
|
-
}))
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
}).then((result) => {
|
|
120
|
+
if (!result.ok && result.reason === "error") {
|
|
121
|
+
throw result.error;
|
|
122
|
+
}
|
|
123
|
+
opts.runtime.log?.(`[${accountId}][${chatId}]: typing start sent ok=${result.ok}`);
|
|
124
|
+
});
|
|
125
|
+
// 记录当前 start 请求,供 stop 等待
|
|
126
|
+
lastStartPromise = startPromise;
|
|
127
|
+
await startPromise;
|
|
120
128
|
},
|
|
121
129
|
stop: async () => {
|
|
122
|
-
|
|
130
|
+
// 等待最后一个 start 完成,避免 stop 在 start 之前到达
|
|
131
|
+
await lastStartPromise;
|
|
132
|
+
opts.runtime.log?.(`[${accountId}][${chatId}]: typing stop sending...`);
|
|
133
|
+
await stopTypingMeet({
|
|
123
134
|
accountId,
|
|
124
135
|
chatId,
|
|
125
136
|
chatType: chatType ?? "channel",
|
|
126
137
|
sessionInfo,
|
|
127
138
|
token: apiToken,
|
|
128
139
|
apiEndpoint,
|
|
129
|
-
})
|
|
140
|
+
});
|
|
130
141
|
},
|
|
131
142
|
onStartError: (err) => {
|
|
132
143
|
opts.runtime.error?.(`[${accountId}][${chatId}]: typing start failed: ${String(err)}`);
|
|
@@ -168,6 +179,18 @@ export async function createMeetReplyDispatcher(opts) {
|
|
|
168
179
|
opts.runtime.log?.(`[${accountId}]: reply deliver kind=${_info.kind} text_len=${payload.text?.length ?? 0} media_count=${payload.mediaUrls?.length ?? (payload.mediaUrl ? 1 : 0)} reasoning=${payload.isReasoning === true} error=${payload.isError === true}`);
|
|
169
180
|
const text = payload.text ?? "";
|
|
170
181
|
const mediaUrls = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
|
182
|
+
if (mediaUrls.length === 0) {
|
|
183
|
+
const normalizedVisibleText = normalizeVisibleTextForDedup(text);
|
|
184
|
+
if (_info.kind === "final" &&
|
|
185
|
+
normalizedVisibleText &&
|
|
186
|
+
normalizedVisibleText === lastDeliveredVisibleText) {
|
|
187
|
+
opts.runtime.log?.(`[${accountId}]: suppress duplicate final visible text for ${chatId}`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (normalizedVisibleText) {
|
|
191
|
+
lastDeliveredVisibleText = normalizedVisibleText;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
171
194
|
// 如果既没有文本也没有媒体,直接返回
|
|
172
195
|
if (!text.trim() && mediaUrls.length === 0) {
|
|
173
196
|
return;
|