@meet-im/meet 3.4.1 → 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";
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";
1
+ export const MEET_PLUGIN_VERSION = "3.4.3";
2
2
  export const MEET_OPENCLAW_VERSION = "2026.5.18";
@@ -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
- return new Promise((resolve, reject) => {
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,6 +99,7 @@ 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
105
  // stop 请求需要等待最后一个 start 完成,避免乱序
@@ -170,6 +179,18 @@ export async function createMeetReplyDispatcher(opts) {
170
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}`);
171
180
  const text = payload.text ?? "";
172
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
+ }
173
194
  // 如果既没有文本也没有媒体,直接返回
174
195
  if (!text.trim() && mediaUrls.length === 0) {
175
196
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meet-im/meet",
3
- "version": "3.4.1",
3
+ "version": "3.4.3",
4
4
  "type": "module",
5
5
  "description": "OpenClaw Meet channel plugin",
6
6
  "scripts": {