@meet-im/meet 2.0.0 → 2.0.2
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/bot.ts +4 -1
- package/src/reply-dispatcher.ts +82 -4
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
recordPendingHistoryEntryIfEnabled,
|
|
7
7
|
} from "openclaw/plugin-sdk/reply-history"
|
|
8
8
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id"
|
|
9
|
+
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime"
|
|
9
10
|
import type { MeetBot, MsgContent } from "@meet-im/meet-bot-jssdk"
|
|
10
11
|
import type { ResolvedMeetAccount, MeetMessageContext } from "./types.js"
|
|
11
12
|
import { msgContentToContext, extractQuoteMessageMedia } from "./sdk-bridge.js"
|
|
@@ -199,7 +200,7 @@ export async function handleMeetMessage(params: {
|
|
|
199
200
|
}
|
|
200
201
|
|
|
201
202
|
const meetFrom = `meet:${ctx.senderId}`
|
|
202
|
-
const meetTo =
|
|
203
|
+
const meetTo = ctx.chatId
|
|
203
204
|
|
|
204
205
|
const peerId = isGroup ? ctx.chatId : ctx.senderId
|
|
205
206
|
|
|
@@ -380,6 +381,7 @@ export async function handleMeetMessage(params: {
|
|
|
380
381
|
})
|
|
381
382
|
|
|
382
383
|
const { createMeetReplyDispatcher } = await import("./reply-dispatcher.js")
|
|
384
|
+
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId)
|
|
383
385
|
const { dispatcher, replyOptions, markDispatchIdle } = await createMeetReplyDispatcher({
|
|
384
386
|
cfg,
|
|
385
387
|
agentId: route.agentId,
|
|
@@ -389,6 +391,7 @@ export async function handleMeetMessage(params: {
|
|
|
389
391
|
accountId,
|
|
390
392
|
bot,
|
|
391
393
|
botUserId,
|
|
394
|
+
mediaLocalRoots,
|
|
392
395
|
})
|
|
393
396
|
|
|
394
397
|
log(`[${accountId}]: dispatching to AI agent=${route.agentId} session=${route.sessionKey} history=${inboundHistory?.length ?? 0}`)
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { MeetBot } from "@meet-im/meet-bot-jssdk"
|
|
|
2
2
|
import type { ClawdbotConfig, RuntimeEnv, ReplyPayload } from "openclaw/plugin-sdk"
|
|
3
3
|
import { createReplyPrefixContext } from "openclaw/plugin-sdk/channel-runtime"
|
|
4
4
|
import { getMeetRuntime } from "./runtime.js"
|
|
5
|
-
import { sendMessageMeet } from "./send.js"
|
|
5
|
+
import { sendMessageMeet, sendMediaMeet } from "./send.js"
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* 匹配末尾不完整的 mention 开始: <@ 或 <@xxx (没有闭合的 >)
|
|
@@ -73,12 +73,13 @@ export type CreateMeetReplyDispatcherOpts = {
|
|
|
73
73
|
accountId: string
|
|
74
74
|
bot: MeetBot
|
|
75
75
|
botUserId: string
|
|
76
|
+
mediaLocalRoots?: readonly string[]
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
export async function createMeetReplyDispatcher(
|
|
79
80
|
opts: CreateMeetReplyDispatcherOpts,
|
|
80
81
|
) {
|
|
81
|
-
const { cfg, agentId, chatId, replyToMessageId, accountId } = opts
|
|
82
|
+
const { cfg, agentId, chatId, replyToMessageId, accountId, mediaLocalRoots } = opts
|
|
82
83
|
const core = getMeetRuntime()
|
|
83
84
|
|
|
84
85
|
const textChunkLimit = core.channel.text.resolveTextChunkLimit(cfg, "meet", accountId, {
|
|
@@ -97,10 +98,40 @@ export async function createMeetReplyDispatcher(
|
|
|
97
98
|
},
|
|
98
99
|
deliver: async (payload: ReplyPayload, _info) => {
|
|
99
100
|
const text = payload.text ?? ""
|
|
100
|
-
|
|
101
|
+
const mediaUrls = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : [])
|
|
102
|
+
|
|
103
|
+
// 如果既没有文本也没有媒体,直接返回
|
|
104
|
+
if (!text.trim() && mediaUrls.length === 0) {
|
|
101
105
|
return
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
// 处理媒体文件:先发送媒体,再发送文本
|
|
109
|
+
// 如果有媒体,第一个媒体带上文本作为 caption,后续媒体不带文本
|
|
110
|
+
if (mediaUrls.length > 0) {
|
|
111
|
+
// 第一个媒体带上 caption
|
|
112
|
+
await sendMediaMeet({
|
|
113
|
+
cfg,
|
|
114
|
+
to: chatId,
|
|
115
|
+
text: text.trim() || undefined,
|
|
116
|
+
mediaUrl: mediaUrls[0],
|
|
117
|
+
mediaLocalRoots,
|
|
118
|
+
accountId,
|
|
119
|
+
})
|
|
120
|
+
// 后续媒体不带文本
|
|
121
|
+
for (let i = 1; i < mediaUrls.length; i++) {
|
|
122
|
+
await sendMediaMeet({
|
|
123
|
+
cfg,
|
|
124
|
+
to: chatId,
|
|
125
|
+
text: undefined,
|
|
126
|
+
mediaUrl: mediaUrls[i],
|
|
127
|
+
mediaLocalRoots,
|
|
128
|
+
accountId,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 只有文本,分片发送
|
|
104
135
|
const rawChunks = core.channel.text.chunkTextWithMode(text, textChunkLimit, chunkMode)
|
|
105
136
|
const protectedChunks = protectMentionsInChunks(rawChunks)
|
|
106
137
|
|
|
@@ -115,7 +146,54 @@ export async function createMeetReplyDispatcher(
|
|
|
115
146
|
}
|
|
116
147
|
},
|
|
117
148
|
onError: async (error, info) => {
|
|
118
|
-
|
|
149
|
+
const errorMessage = String(error)
|
|
150
|
+
opts.runtime.error?.(`meet[${accountId}] ${info.kind} reply failed: ${errorMessage}`)
|
|
151
|
+
// 发送错误提示给用户,让 AI 也能看到错误并修正
|
|
152
|
+
// 根据错误类型生成友好提示
|
|
153
|
+
let userMessage: string
|
|
154
|
+
const lowerError = errorMessage.toLowerCase()
|
|
155
|
+
if (
|
|
156
|
+
lowerError.includes("not found") ||
|
|
157
|
+
lowerError.includes("enoent") ||
|
|
158
|
+
lowerError.includes("does not exist") ||
|
|
159
|
+
lowerError.includes("local media file not found")
|
|
160
|
+
) {
|
|
161
|
+
userMessage = `发送失败: 文件不存在或无法访问。请提供有效的文件路径(建议使用绝对路径)。\n错误详情: ${errorMessage}`
|
|
162
|
+
} else if (
|
|
163
|
+
lowerError.includes("mediafetcherror") ||
|
|
164
|
+
lowerError.includes("fetch_failed") ||
|
|
165
|
+
lowerError.includes("failed to fetch")
|
|
166
|
+
) {
|
|
167
|
+
userMessage = `发送失败: 无法获取媒体文件。请检查 URL 是否正确或网络是否可用。\n错误详情: ${errorMessage}`
|
|
168
|
+
} else if (
|
|
169
|
+
lowerError.includes("localmediaaccesserror") ||
|
|
170
|
+
lowerError.includes("path-not-allowed") ||
|
|
171
|
+
lowerError.includes("not safe to read") ||
|
|
172
|
+
lowerError.includes("network-path-not-allowed")
|
|
173
|
+
) {
|
|
174
|
+
userMessage = `发送失败: 文件路径不在允许访问的目录内。请使用工作区内的文件路径,或联系管理员配置 mediaLocalRoots。\n错误详情: ${errorMessage}`
|
|
175
|
+
} else if (
|
|
176
|
+
lowerError.includes("max_bytes") ||
|
|
177
|
+
lowerError.includes("exceeds maxbytes") ||
|
|
178
|
+
lowerError.includes("exceeds") && lowerError.includes("limit")
|
|
179
|
+
) {
|
|
180
|
+
userMessage = `发送失败: 文件大小超过限制。请使用较小的文件。\n错误详情: ${errorMessage}`
|
|
181
|
+
} else if (lowerError.includes("not a file")) {
|
|
182
|
+
userMessage = `发送失败: 路径不是文件。请提供文件路径而非目录。\n错误详情: ${errorMessage}`
|
|
183
|
+
} else {
|
|
184
|
+
userMessage = `发送失败: ${errorMessage}`
|
|
185
|
+
}
|
|
186
|
+
// 尝试发送错误消息,忽略发送失败(避免递归)
|
|
187
|
+
try {
|
|
188
|
+
await sendMessageMeet({
|
|
189
|
+
cfg,
|
|
190
|
+
to: chatId,
|
|
191
|
+
text: userMessage,
|
|
192
|
+
accountId,
|
|
193
|
+
})
|
|
194
|
+
} catch {
|
|
195
|
+
// 忽略错误消息发送失败
|
|
196
|
+
}
|
|
119
197
|
},
|
|
120
198
|
onIdle: async () => {
|
|
121
199
|
},
|