@meet-im/meet 1.1.1 → 1.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meet-im/meet",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "type": "module",
5
5
  "description": "OpenClaw Meet channel plugin",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: meet-at
3
- description: "Meet IM messaging. To mention users, ALWAYS use format <@USER_ID> in message text. Examples: <@553> mentions user 553, <@-1> mentions everyone. Do NOT use @553 format."
3
+ description: "Meet IM messaging. CRITICAL: Use EXACT format <@USER_ID> with BOTH angle brackets. <@553> is CORRECT. <@553 or @553> is WRONG - missing bracket. Always include opening < and closing > around the user ID."
4
4
  metadata: { "openclaw": { "emoji": "💬", "requires": { "config": ["channels.meet"] } } }
5
5
  allowed-tools: ["message"]
6
6
  ---
@@ -9,16 +9,19 @@ allowed-tools: ["message"]
9
9
 
10
10
  Use the `message` tool for all Meet messaging operations.
11
11
 
12
- ## CRITICAL: Mention Format
12
+ ## CRITICAL: Mention Format - MUST HAVE BOTH BRACKETS
13
13
 
14
- **Always use <@USER_ID> format to mention users in message text.**
14
+ **Correct format: `<@USER_ID>` with opening `<` and closing `>`**
15
15
 
16
- | Format | Description | Example |
17
- |--------|-------------|---------|
18
- | <@USER_ID> | Mention specific user | <@553> |
19
- | <@-1> | Mention everyone | <@-1> |
16
+ | Format | Status | Example |
17
+ |--------|--------|---------|
18
+ | <@USER_ID> | CORRECT | <@553> |
19
+ | <@-1> | CORRECT | <@-1> |
20
+ | <@553 | ❌ WRONG - missing `>` | Will not trigger mention |
21
+ | @553> | ❌ WRONG - missing `<` | Will not trigger mention |
22
+ | @553 | ❌ WRONG - no brackets | Will not trigger mention |
20
23
 
21
- **Do NOT use** @553 format (without brackets).
24
+ **The format MUST be complete: `<@` + number + `>`**
22
25
 
23
26
  ## Examples
24
27
 
package/src/channel.ts CHANGED
@@ -18,7 +18,6 @@ import {
18
18
  } from "./accounts.js";
19
19
  import { meetOutbound } from "./outbound.js";
20
20
  import { probeMeet } from "./probe.js";
21
- import { resolveMeetGroupPolicy } from "./policy.js";
22
21
  import {
23
22
  parseMeetTarget,
24
23
  looksLikeMeetId,
@@ -74,17 +73,9 @@ export const meetPlugin: ChannelPlugin<ResolvedMeetAccount> = {
74
73
  accountId,
75
74
  groupId,
76
75
  }): GroupToolPolicyConfig | undefined => {
77
- const account = resolveMeetAccount({ cfg, accountId });
78
- const groupPolicy = resolveMeetGroupPolicy({
79
- groupPolicy: account.config.groupPolicy,
80
- groupAllowFrom: account.config.groupAllowFrom ?? [],
81
- chatId: groupId ?? "",
82
- groups: account.config.groups,
83
- });
84
- if (!groupPolicy.allowed) {
85
- return { deny: ["*"] };
86
- }
87
- return undefined;
76
+ // 群组访问控制在 bot.ts 中处理,这里只返回工具策略配置
77
+ // 不使用 { deny: ["*"] } 拒绝所有工具,否则会导致 tools: []
78
+ return undefined
88
79
  },
89
80
  },
90
81
  reload: { configPrefixes: ["channels.meet"] },
@@ -50,8 +50,12 @@ export function protectMentionsInChunks(chunks: string[]): string[] {
50
50
  if (endMatch) {
51
51
  // 将不完整的部分移到下一个 chunk 前面
52
52
  const splitIndex = chunk.lastIndexOf("<@")
53
- if (splitIndex > 0) {
54
- result.push(chunk.slice(0, splitIndex))
53
+ // splitIndex >= 0 时处理(包括 chunk 只有 "<@" 的情况)
54
+ if (splitIndex >= 0) {
55
+ // 如果 splitIndex 之前有内容,保留到 result
56
+ if (splitIndex > 0) {
57
+ result.push(chunk.slice(0, splitIndex))
58
+ }
55
59
  pendingSuffix = chunk.slice(splitIndex)
56
60
  continue
57
61
  }
package/src/send.ts CHANGED
@@ -52,6 +52,57 @@ export function inferContentTypeFromFileName(fileName: string): string | undefin
52
52
  return mimeMap[ext];
53
53
  }
54
54
 
55
+ /**
56
+ * 根据 MIME 类型获取文件扩展名
57
+ */
58
+ function getExtensionFromContentType(contentType: string): string | undefined {
59
+ const mimeToExt: Record<string, string> = {
60
+ "image/jpeg": ".jpg",
61
+ "image/png": ".png",
62
+ "image/gif": ".gif",
63
+ "image/webp": ".webp",
64
+ "image/avif": ".avif",
65
+ "image/heic": ".heic",
66
+ "image/heif": ".heif",
67
+ "image/bmp": ".bmp",
68
+ "image/x-icon": ".ico",
69
+ "image/svg+xml": ".svg",
70
+ "image/tiff": ".tiff",
71
+ "video/mp4": ".mp4",
72
+ "video/webm": ".webm",
73
+ "video/quicktime": ".mov",
74
+ "video/x-msvideo": ".avi",
75
+ "video/x-matroska": ".mkv",
76
+ "audio/mpeg": ".mp3",
77
+ "audio/wav": ".wav",
78
+ "audio/ogg": ".ogg",
79
+ "audio/flac": ".flac",
80
+ "audio/mp4": ".m4a",
81
+ "application/pdf": ".pdf",
82
+ "application/json": ".json",
83
+ "application/xml": ".xml",
84
+ };
85
+ return mimeToExt[contentType];
86
+ }
87
+
88
+ /**
89
+ * 确保文件名有正确的扩展名
90
+ * 如果文件名没有扩展名,根据 contentType 添加
91
+ */
92
+ function ensureFileNameExtension(fileName: string, contentType: string): string {
93
+ // 检查是否已有扩展名
94
+ const hasExtension = /\.[a-zA-Z0-9]+$/.test(fileName);
95
+ if (hasExtension) {
96
+ return fileName;
97
+ }
98
+ // 根据 contentType 添加扩展名
99
+ const ext = getExtensionFromContentType(contentType);
100
+ if (ext) {
101
+ return fileName + ext;
102
+ }
103
+ return fileName;
104
+ }
105
+
55
106
  /**
56
107
  * 获取最终的 contentType
57
108
  * 如果原始 contentType 缺失或为通用二进制流,则根据文件名推断
@@ -78,7 +129,9 @@ export function setSendMessageLogger(logger: RuntimeEnv): void {
78
129
  function log(message: string): void {
79
130
  if (_logger) {
80
131
  _logger.log(message);
132
+ return;
81
133
  }
134
+ console.log(message);
82
135
  }
83
136
 
84
137
  function logError(message: string): void {
@@ -118,6 +171,11 @@ export async function sendMessageMeet(
118
171
  if (!account.configured) {
119
172
  throw new Error(`Meet account not configured: ${accountId ?? "default"}`);
120
173
  }
174
+ // logLevel: info 时记录 AI 输出的原始内容,方便调试 mention 格式问题
175
+ // 默认为 silent,只有显式设置为 info 时才输出
176
+ if (account.config.logLevel === "info") {
177
+ console.log(`[${account.accountId}] AI output raw text: ${JSON.stringify(text)}`);
178
+ }
121
179
  const token = account.apiToken;
122
180
  if (!token) {
123
181
  throw new Error("Meet API token not configured");
@@ -200,8 +258,10 @@ export async function sendMediaMeet(
200
258
  }
201
259
 
202
260
  const sessionInfo = parseTargetToSessionInfo(to, Number(botUserId));
203
- const fileName = media.fileName || "file";
204
- const contentType = resolveContentType(fileName, media.contentType);
261
+ const rawFileName = media.fileName || "file";
262
+ const contentType = resolveContentType(rawFileName, media.contentType);
263
+ // 确保文件名有正确的扩展名
264
+ const fileName = ensureFileNameExtension(rawFileName, contentType);
205
265
 
206
266
  log(
207
267
  `sending media to=${to} fileName=${fileName} size=${media.buffer.length} contentType=${contentType}`,