@meet-im/meet 2.0.6 → 3.0.0-beta.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/account-inspect-api.d.ts +2 -0
- package/dist/account-inspect-api.js +4 -0
- package/dist/channel-plugin-api.d.ts +1 -0
- package/dist/channel-plugin-api.js +3 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +43 -0
- package/dist/monitor-api.d.ts +1 -0
- package/dist/monitor-api.js +1 -0
- package/dist/probe-api.d.ts +1 -0
- package/dist/probe-api.js +1 -0
- package/dist/runtime-setter-api.d.ts +1 -0
- package/dist/runtime-setter-api.js +2 -0
- package/dist/send-api.d.ts +1 -0
- package/dist/send-api.js +1 -0
- package/dist/src/account-inspect.d.ts +5 -0
- package/dist/src/account-inspect.js +9 -0
- package/dist/src/accounts.d.ts +12 -0
- package/dist/src/accounts.js +134 -0
- package/dist/src/bot.d.ts +15 -0
- package/dist/src/bot.js +355 -0
- package/dist/src/channel.d.ts +3 -0
- package/dist/src/channel.js +402 -0
- package/dist/src/client.d.ts +8 -0
- package/dist/src/client.js +49 -0
- package/dist/src/config-schema.d.ts +82 -0
- package/dist/src/config-schema.js +46 -0
- package/dist/src/media.d.ts +57 -0
- package/dist/src/media.js +140 -0
- package/dist/src/monitor.d.ts +9 -0
- package/dist/src/monitor.js +153 -0
- package/dist/src/outbound.d.ts +2 -0
- package/dist/src/outbound.js +34 -0
- package/dist/src/policy.d.ts +30 -0
- package/dist/src/policy.js +78 -0
- package/dist/src/probe.d.ts +10 -0
- package/dist/src/probe.js +56 -0
- package/dist/src/reply-dispatcher.d.ts +29 -0
- package/dist/src/reply-dispatcher.js +173 -0
- package/dist/src/runtime.d.ts +11 -0
- package/dist/src/runtime.js +6 -0
- package/dist/src/sdk-bridge.d.ts +21 -0
- package/dist/src/sdk-bridge.js +214 -0
- package/dist/src/send.d.ts +60 -0
- package/dist/src/send.js +317 -0
- package/dist/src/targets.d.ts +15 -0
- package/dist/src/targets.js +63 -0
- package/dist/src/types.d.ts +76 -0
- package/dist/src/types.js +1 -0
- package/dist/vitest.config.d.ts +8 -0
- package/dist/vitest.config.js +7 -0
- package/openclaw.plugin.json +116 -0
- package/package.json +18 -17
- package/index.ts +0 -26
- package/src/accounts.ts +0 -182
- package/src/bot.ts +0 -418
- package/src/channel.ts +0 -396
- package/src/client.ts +0 -63
- package/src/config-schema.ts +0 -50
- package/src/media.ts +0 -198
- package/src/monitor.ts +0 -195
- package/src/outbound.ts +0 -43
- package/src/policy.ts +0 -131
- package/src/probe.ts +0 -75
- package/src/reply-dispatcher.ts +0 -207
- package/src/runtime.ts +0 -14
- package/src/sdk-bridge.ts +0 -268
- package/src/send.ts +0 -383
- package/src/targets.ts +0 -101
- package/src/types.ts +0 -96
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { meetPlugin } from "./src/channel.js";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type MonitorMeetProvider = typeof import("./monitor-api.js").monitorMeetProvider;
|
|
2
|
+
type SendMessageMeet = typeof import("./send-api.js").sendMessageMeet;
|
|
3
|
+
type SendMediaMeet = typeof import("./send-api.js").sendMediaMeet;
|
|
4
|
+
type ResolveMeetMedia = typeof import("./send-api.js").resolveMeetMedia;
|
|
5
|
+
type ProbeMeet = typeof import("./probe-api.js").probeMeet;
|
|
6
|
+
type ClearProbeCache = typeof import("./probe-api.js").clearProbeCache;
|
|
7
|
+
export declare const monitorMeetProvider: MonitorMeetProvider;
|
|
8
|
+
export declare const sendMessageMeet: SendMessageMeet;
|
|
9
|
+
export declare const sendMediaMeet: SendMediaMeet;
|
|
10
|
+
export declare const resolveMeetMedia: ResolveMeetMedia;
|
|
11
|
+
export declare const probeMeet: ProbeMeet;
|
|
12
|
+
export declare const clearProbeCache: ClearProbeCache;
|
|
13
|
+
declare const _default: import("openclaw/plugin-sdk/channel-entry-contract").BundledChannelEntryContract<import("openclaw/plugin-sdk").ChannelPlugin>;
|
|
14
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { defineBundledChannelEntry, loadBundledEntryExportSync, } from "openclaw/plugin-sdk/channel-entry-contract";
|
|
2
|
+
export const monitorMeetProvider = ((...args) => loadBundledEntryExportSync(import.meta.url, {
|
|
3
|
+
specifier: "./monitor-api.js",
|
|
4
|
+
exportName: "monitorMeetProvider",
|
|
5
|
+
})(...args));
|
|
6
|
+
export const sendMessageMeet = ((...args) => loadBundledEntryExportSync(import.meta.url, {
|
|
7
|
+
specifier: "./send-api.js",
|
|
8
|
+
exportName: "sendMessageMeet",
|
|
9
|
+
})(...args));
|
|
10
|
+
export const sendMediaMeet = ((...args) => loadBundledEntryExportSync(import.meta.url, {
|
|
11
|
+
specifier: "./send-api.js",
|
|
12
|
+
exportName: "sendMediaMeet",
|
|
13
|
+
})(...args));
|
|
14
|
+
export const resolveMeetMedia = ((...args) => loadBundledEntryExportSync(import.meta.url, {
|
|
15
|
+
specifier: "./send-api.js",
|
|
16
|
+
exportName: "resolveMeetMedia",
|
|
17
|
+
})(...args));
|
|
18
|
+
export const probeMeet = ((...args) => loadBundledEntryExportSync(import.meta.url, {
|
|
19
|
+
specifier: "./probe-api.js",
|
|
20
|
+
exportName: "probeMeet",
|
|
21
|
+
})(...args));
|
|
22
|
+
export const clearProbeCache = ((...args) => loadBundledEntryExportSync(import.meta.url, {
|
|
23
|
+
specifier: "./probe-api.js",
|
|
24
|
+
exportName: "clearProbeCache",
|
|
25
|
+
})(...args));
|
|
26
|
+
export default defineBundledChannelEntry({
|
|
27
|
+
id: "meet",
|
|
28
|
+
name: "Meet",
|
|
29
|
+
description: "Meet channel plugin",
|
|
30
|
+
importMetaUrl: import.meta.url,
|
|
31
|
+
plugin: {
|
|
32
|
+
specifier: "./channel-plugin-api.js",
|
|
33
|
+
exportName: "meetPlugin",
|
|
34
|
+
},
|
|
35
|
+
runtime: {
|
|
36
|
+
specifier: "./runtime-setter-api.js",
|
|
37
|
+
exportName: "setMeetRuntime",
|
|
38
|
+
},
|
|
39
|
+
accountInspect: {
|
|
40
|
+
specifier: "./account-inspect-api.js",
|
|
41
|
+
exportName: "inspectMeetReadOnlyAccount",
|
|
42
|
+
},
|
|
43
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { monitorMeetProvider } from "./src/monitor.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { monitorMeetProvider } from "./src/monitor.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { probeMeet, clearProbeCache } from "./src/probe.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { probeMeet, clearProbeCache } from "./src/probe.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { setMeetRuntime } from "./src/runtime.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { sendMessageMeet, sendMediaMeet, resolveMeetMedia, } from "./src/send.js";
|
package/dist/send-api.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { sendMessageMeet, sendMediaMeet, resolveMeetMedia, } from "./src/send.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
|
2
|
+
import { resolveDefaultMeetAccountId, resolveMeetAccount } from "./accounts.js";
|
|
3
|
+
export function inspectMeetAccount(params) {
|
|
4
|
+
const accountId = normalizeAccountId(params.accountId ?? resolveDefaultMeetAccountId(params.cfg));
|
|
5
|
+
return resolveMeetAccount({
|
|
6
|
+
cfg: params.cfg,
|
|
7
|
+
accountId,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { ResolvedMeetAccount } from "./types.js";
|
|
3
|
+
export declare function getFlatAccountKey(accountId: string): string;
|
|
4
|
+
declare function listMeetAccountIds(cfg: ClawdbotConfig): string[];
|
|
5
|
+
declare function resolveDefaultMeetAccountId(cfg: ClawdbotConfig): string;
|
|
6
|
+
export { listMeetAccountIds, resolveDefaultMeetAccountId };
|
|
7
|
+
export declare function isFlatAccountConfig(cfg: ClawdbotConfig, accountId: string): boolean;
|
|
8
|
+
export declare function resolveMeetAccount(params: {
|
|
9
|
+
cfg: ClawdbotConfig;
|
|
10
|
+
accountId?: string | null;
|
|
11
|
+
}): ResolvedMeetAccount;
|
|
12
|
+
export declare function listEnabledMeetAccounts(cfg: ClawdbotConfig): ResolvedMeetAccount[];
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId, } from "openclaw/plugin-sdk/account-id";
|
|
2
|
+
import { createAccountListHelpers } from "openclaw/plugin-sdk/account-helpers";
|
|
3
|
+
const { listAccountIds: listNestedAccountIds, resolveDefaultAccountId: resolveNestedDefaultAccountId } = createAccountListHelpers("meet");
|
|
4
|
+
const MEET_PREFIX = "meet.";
|
|
5
|
+
export function getFlatAccountKey(accountId) {
|
|
6
|
+
return `${MEET_PREFIX}${accountId}`;
|
|
7
|
+
}
|
|
8
|
+
function listFlatAccountIds(cfg) {
|
|
9
|
+
const channels = cfg.channels;
|
|
10
|
+
if (!channels || typeof channels !== "object") {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
const ids = [];
|
|
14
|
+
for (const key of Object.keys(channels)) {
|
|
15
|
+
if (key.startsWith(MEET_PREFIX)) {
|
|
16
|
+
const accountId = key.slice(MEET_PREFIX.length);
|
|
17
|
+
if (accountId) {
|
|
18
|
+
ids.push(accountId);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return ids;
|
|
23
|
+
}
|
|
24
|
+
function resolveFlatAccountConfig(cfg, accountId) {
|
|
25
|
+
const key = `${MEET_PREFIX}${accountId}`;
|
|
26
|
+
const config = cfg.channels?.[key];
|
|
27
|
+
if (!config || typeof config !== "object") {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
function listMeetAccountIds(cfg) {
|
|
33
|
+
const nestedIds = listNestedAccountIds(cfg);
|
|
34
|
+
const flatIds = listFlatAccountIds(cfg);
|
|
35
|
+
if (flatIds.length > 0) {
|
|
36
|
+
const filteredNestedIds = nestedIds.filter(id => id !== DEFAULT_ACCOUNT_ID);
|
|
37
|
+
const allIds = new Set([...filteredNestedIds, ...flatIds]);
|
|
38
|
+
return [...allIds].sort((a, b) => a.localeCompare(b));
|
|
39
|
+
}
|
|
40
|
+
if (nestedIds.length === 0) {
|
|
41
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
42
|
+
}
|
|
43
|
+
return nestedIds;
|
|
44
|
+
}
|
|
45
|
+
function resolveDefaultMeetAccountId(cfg) {
|
|
46
|
+
const flatIds = listFlatAccountIds(cfg);
|
|
47
|
+
if (flatIds.length > 0) {
|
|
48
|
+
const nestedDefault = resolveNestedDefaultAccountId(cfg);
|
|
49
|
+
if (nestedDefault !== DEFAULT_ACCOUNT_ID && flatIds.includes(nestedDefault)) {
|
|
50
|
+
return nestedDefault;
|
|
51
|
+
}
|
|
52
|
+
return flatIds[0] ?? DEFAULT_ACCOUNT_ID;
|
|
53
|
+
}
|
|
54
|
+
return resolveNestedDefaultAccountId(cfg);
|
|
55
|
+
}
|
|
56
|
+
export { listMeetAccountIds, resolveDefaultMeetAccountId };
|
|
57
|
+
export function isFlatAccountConfig(cfg, accountId) {
|
|
58
|
+
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return resolveFlatAccountConfig(cfg, accountId) !== undefined;
|
|
62
|
+
}
|
|
63
|
+
function resolveAccountConfig(cfg, accountId) {
|
|
64
|
+
const nestedConfig = (() => {
|
|
65
|
+
const accounts = cfg.channels?.meet?.accounts;
|
|
66
|
+
if (!accounts || typeof accounts !== "object") {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return accounts[accountId];
|
|
70
|
+
})();
|
|
71
|
+
if (nestedConfig) {
|
|
72
|
+
return nestedConfig;
|
|
73
|
+
}
|
|
74
|
+
return resolveFlatAccountConfig(cfg, accountId);
|
|
75
|
+
}
|
|
76
|
+
function mergeMeetAccountConfig(cfg, accountId) {
|
|
77
|
+
const meetCfg = cfg.channels?.meet;
|
|
78
|
+
const { accounts: _ignored, ...base } = meetCfg ?? {};
|
|
79
|
+
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
80
|
+
const baseGroups = base.groups ?? {};
|
|
81
|
+
const accountGroups = account.groups ?? {};
|
|
82
|
+
const mergedGroups = { ...baseGroups, ...accountGroups };
|
|
83
|
+
return {
|
|
84
|
+
...base,
|
|
85
|
+
...account,
|
|
86
|
+
groups: Object.keys(mergedGroups).length > 0 ? mergedGroups : undefined,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function resolveMeetToken(cfg, accountId) {
|
|
90
|
+
const merged = mergeMeetAccountConfig(cfg, accountId);
|
|
91
|
+
const configToken = merged.token || merged.apiToken;
|
|
92
|
+
if (configToken) {
|
|
93
|
+
return {
|
|
94
|
+
token: configToken,
|
|
95
|
+
endpoint: merged.apiEndpoint ?? process.env.MEET_API_ENDPOINT ?? "https://staging-meet-api.miyachat.com",
|
|
96
|
+
source: "config",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const envToken = process.env.MEET_API_TOKEN;
|
|
100
|
+
if (envToken) {
|
|
101
|
+
return {
|
|
102
|
+
token: envToken,
|
|
103
|
+
endpoint: merged.apiEndpoint ?? process.env.MEET_API_ENDPOINT ?? "https://staging-meet-api.miyachat.com",
|
|
104
|
+
source: "env",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
token: "",
|
|
109
|
+
endpoint: "",
|
|
110
|
+
source: "none",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export function resolveMeetAccount(params) {
|
|
114
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
115
|
+
const baseEnabled = params.cfg.channels?.meet?.enabled !== false;
|
|
116
|
+
const merged = mergeMeetAccountConfig(params.cfg, accountId);
|
|
117
|
+
const accountEnabled = merged.enabled !== false;
|
|
118
|
+
const enabled = baseEnabled && accountEnabled;
|
|
119
|
+
const tokenResolution = resolveMeetToken(params.cfg, accountId);
|
|
120
|
+
return {
|
|
121
|
+
accountId,
|
|
122
|
+
enabled,
|
|
123
|
+
configured: tokenResolution.source !== "none",
|
|
124
|
+
name: merged.name?.trim() || undefined,
|
|
125
|
+
apiEndpoint: tokenResolution.endpoint,
|
|
126
|
+
apiToken: tokenResolution.token,
|
|
127
|
+
config: merged,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function listEnabledMeetAccounts(cfg) {
|
|
131
|
+
return listMeetAccountIds(cfg)
|
|
132
|
+
.map((accountId) => resolveMeetAccount({ cfg, accountId }))
|
|
133
|
+
.filter((account) => account.enabled && account.configured);
|
|
134
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { HistoryEntry } from "openclaw/plugin-sdk/reply-history";
|
|
3
|
+
import type { MeetBot, MsgContent } from "@meet-im/meet-bot-jssdk";
|
|
4
|
+
import type { ResolvedMeetAccount } from "./types.js";
|
|
5
|
+
export declare function handleMeetMessage(params: {
|
|
6
|
+
cfg: ClawdbotConfig;
|
|
7
|
+
msg: MsgContent;
|
|
8
|
+
botUserId: string;
|
|
9
|
+
runtime?: RuntimeEnv;
|
|
10
|
+
accountId: string;
|
|
11
|
+
account: ResolvedMeetAccount;
|
|
12
|
+
bot: MeetBot;
|
|
13
|
+
groupHistories: Map<string, HistoryEntry[]>;
|
|
14
|
+
quoteMsgMap?: Record<string, MsgContent>;
|
|
15
|
+
}): Promise<void>;
|
package/dist/src/bot.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, recordPendingHistoryEntryIfEnabled, } from "openclaw/plugin-sdk/reply-history";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
3
|
+
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
|
4
|
+
import { msgContentToContext, extractQuoteMessageMedia } from "./sdk-bridge.js";
|
|
5
|
+
import { getMeetRuntime } from "./runtime.js";
|
|
6
|
+
import { resolveMeetAllowlistMatch, resolveMeetGroupPolicy, resolveMeetGroupConfig, resolveMeetGroupUserPolicy } from "./policy.js";
|
|
7
|
+
import { sendMessageMeet } from "./send.js";
|
|
8
|
+
import { resolveMediaAttachments, setMediaDebugLogger } from "./media.js";
|
|
9
|
+
const DEFAULT_GROUP_SYSTEM_PROMPT = "你正在 Meet 群组中对话。请保持回复简洁明了,适合群聊场景。如需回复特定用户,请使用 <@USER_ID> 格式提及对方,例如 <@553>。注意:Meet 不支持用反引号包住 Markdown 语法标记,描述语法时直接写符号,不要加反引号。发送图片或文件时,使用 message 工具的 media 参数,不要用 Markdown 图片语法 。";
|
|
10
|
+
const DEFAULT_DM_SYSTEM_PROMPT = "你正在 Meet 私聊中对话。注意:Meet 不支持用反引号包住 Markdown 语法标记,描述语法时直接写符号,不要加反引号。发送图片或文件时,使用 message 工具的 media 参数,不要用 Markdown 图片语法 。";
|
|
11
|
+
function formatHistoryEntry(entry) {
|
|
12
|
+
return `${entry.sender}: ${entry.body}`;
|
|
13
|
+
}
|
|
14
|
+
export async function handleMeetMessage(params) {
|
|
15
|
+
const { cfg, msg, botUserId, runtime, accountId, account, bot, groupHistories, quoteMsgMap } = params;
|
|
16
|
+
const log = runtime?.log ?? console.log;
|
|
17
|
+
const error = runtime?.error ?? console.error;
|
|
18
|
+
let ctx;
|
|
19
|
+
try {
|
|
20
|
+
ctx = msgContentToContext(msg, botUserId, quoteMsgMap);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
error(`[${accountId}]: failed to parse message: ${String(err)}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const isGroup = ctx.chatType === "channel";
|
|
27
|
+
if (ctx.senderId === botUserId) {
|
|
28
|
+
log(`[${accountId}]: skipping own message ${ctx.messageId}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
log(`[${accountId}]: received message from ${ctx.senderId} in ${ctx.chatId} (${ctx.chatType})`);
|
|
32
|
+
const meetCfg = account.config;
|
|
33
|
+
const dmPolicy = meetCfg.dmPolicy ?? "pairing";
|
|
34
|
+
const allowFrom = meetCfg.allowFrom ?? [];
|
|
35
|
+
const historyLimit = isGroup
|
|
36
|
+
? Math.max(0, meetCfg.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? 20)
|
|
37
|
+
: Math.max(0, meetCfg.dmHistoryLimit ?? 0);
|
|
38
|
+
const speaker = ctx.senderName ?? ctx.senderId;
|
|
39
|
+
// Discord 做法:文字优先,无文字时用媒体占位符
|
|
40
|
+
const messageBody = ctx.content.trim()
|
|
41
|
+
? `${speaker}: ${ctx.content.trim()}`
|
|
42
|
+
: `${speaker}: ${ctx.placeholder || ""}`;
|
|
43
|
+
const pendingEntry = {
|
|
44
|
+
sender: speaker,
|
|
45
|
+
body: ctx.content.trim() || ctx.placeholder || "",
|
|
46
|
+
timestamp: ctx.timestamp,
|
|
47
|
+
messageId: ctx.messageId,
|
|
48
|
+
};
|
|
49
|
+
try {
|
|
50
|
+
const core = getMeetRuntime();
|
|
51
|
+
if (!isGroup) {
|
|
52
|
+
// pairing 模式:需要从 pairing store 读取已授权用户列表
|
|
53
|
+
// allowlist 模式:只使用配置中的 allowFrom
|
|
54
|
+
let effectiveAllowFrom = allowFrom;
|
|
55
|
+
if (dmPolicy === "pairing") {
|
|
56
|
+
try {
|
|
57
|
+
const storeAllowFrom = await core.channel.pairing.readAllowFromStore({
|
|
58
|
+
channel: "meet",
|
|
59
|
+
accountId,
|
|
60
|
+
});
|
|
61
|
+
// 合并配置中的 allowFrom 和 store 中的授权列表
|
|
62
|
+
effectiveAllowFrom = [...allowFrom, ...storeAllowFrom];
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
error(`[${accountId}]: failed to read pairing store: ${String(err)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const dmAllowed = resolveMeetAllowlistMatch({
|
|
69
|
+
allowFrom: effectiveAllowFrom,
|
|
70
|
+
senderId: ctx.senderId,
|
|
71
|
+
}).allowed;
|
|
72
|
+
// pairing 模式:创建配对请求并发送授权码给用户
|
|
73
|
+
// allowlist 模式:直接拒绝未授权用户
|
|
74
|
+
if (dmPolicy !== "open" && !dmAllowed) {
|
|
75
|
+
if (dmPolicy === "pairing") {
|
|
76
|
+
log(`[${accountId}]: pairing request from ${ctx.senderId}`);
|
|
77
|
+
// 创建或更新配对请求
|
|
78
|
+
const { code, created } = await core.channel.pairing.upsertPairingRequest({
|
|
79
|
+
channel: "meet",
|
|
80
|
+
id: ctx.senderId,
|
|
81
|
+
accountId,
|
|
82
|
+
meta: {
|
|
83
|
+
name: ctx.senderName,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
// 发送配对码给用户(新创建或已存在都发送)
|
|
87
|
+
if (code) {
|
|
88
|
+
const accountArg = accountId === DEFAULT_ACCOUNT_ID ? "" : ` --account ${accountId}`;
|
|
89
|
+
const lines = [
|
|
90
|
+
"OpenClaw: 尚未授权访问。",
|
|
91
|
+
"",
|
|
92
|
+
`您的 Meet 用户 ID: ${ctx.senderId}`,
|
|
93
|
+
"",
|
|
94
|
+
`配对码: ${code}`,
|
|
95
|
+
"",
|
|
96
|
+
"请联系机器人管理员审批:",
|
|
97
|
+
`openclaw pairing approve meet ${code}${accountArg}`,
|
|
98
|
+
];
|
|
99
|
+
// 如果是已存在的请求,添加提示
|
|
100
|
+
if (!created) {
|
|
101
|
+
lines.splice(2, 0, "(您的配对请求已在等待审批中)");
|
|
102
|
+
}
|
|
103
|
+
const replyText = lines.join("\n");
|
|
104
|
+
try {
|
|
105
|
+
await sendMessageMeet({
|
|
106
|
+
cfg,
|
|
107
|
+
to: `user:${ctx.senderId}`,
|
|
108
|
+
text: replyText,
|
|
109
|
+
accountId,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
error(`[${accountId}]: failed to send pairing reply to ${ctx.senderId}: ${String(err)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
log(`[${accountId}]: blocked unauthorized sender ${ctx.senderId} (dmPolicy=${dmPolicy})`);
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (isGroup) {
|
|
124
|
+
const groupPolicy = resolveMeetGroupPolicy({
|
|
125
|
+
groupPolicy: meetCfg.groupPolicy,
|
|
126
|
+
groupAllowFrom: meetCfg.groupAllowFrom ?? [],
|
|
127
|
+
chatId: ctx.chatId,
|
|
128
|
+
groups: meetCfg.groups,
|
|
129
|
+
});
|
|
130
|
+
if (!groupPolicy.allowed) {
|
|
131
|
+
log(`[${accountId}]: group ${ctx.chatId} not allowed (groupPolicy=${meetCfg.groupPolicy})`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const groupConfig = resolveMeetGroupConfig({
|
|
135
|
+
meetConfig: meetCfg,
|
|
136
|
+
chatId: ctx.chatId,
|
|
137
|
+
});
|
|
138
|
+
const groupUserPolicy = resolveMeetGroupUserPolicy({
|
|
139
|
+
groupConfig: groupConfig.groupConfig,
|
|
140
|
+
senderId: ctx.senderId,
|
|
141
|
+
});
|
|
142
|
+
if (!groupUserPolicy.allowed) {
|
|
143
|
+
log(`[${accountId}]: user ${ctx.senderId} not allowed in group ${ctx.chatId}`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (groupConfig.requireMention && !ctx.mentionedBot) {
|
|
147
|
+
log(`[${accountId}]: message in group ${ctx.chatId} skipped (mention required)`);
|
|
148
|
+
recordPendingHistoryEntryIfEnabled({
|
|
149
|
+
historyMap: groupHistories,
|
|
150
|
+
historyKey: ctx.chatId,
|
|
151
|
+
entry: pendingEntry,
|
|
152
|
+
limit: historyLimit,
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const meetFrom = `meet:${ctx.senderId}`;
|
|
158
|
+
const meetTo = ctx.chatId;
|
|
159
|
+
const peerId = isGroup ? ctx.chatId : ctx.senderId;
|
|
160
|
+
const route = core.channel.routing.resolveAgentRoute({
|
|
161
|
+
cfg,
|
|
162
|
+
channel: "meet",
|
|
163
|
+
accountId,
|
|
164
|
+
peer: {
|
|
165
|
+
kind: isGroup ? "group" : "direct",
|
|
166
|
+
id: peerId,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
// 处理媒体附件
|
|
170
|
+
let mediaContext = "";
|
|
171
|
+
let mediaPaths = [];
|
|
172
|
+
if (ctx.media && ctx.media.length > 0) {
|
|
173
|
+
log(`[${accountId}]: processing ${ctx.media.length} media attachment(s)`);
|
|
174
|
+
// 初始化媒体调试日志
|
|
175
|
+
setMediaDebugLogger(log, error);
|
|
176
|
+
try {
|
|
177
|
+
const maxBytes = meetCfg.mediaMaxMb ? meetCfg.mediaMaxMb * 1024 * 1024 : undefined;
|
|
178
|
+
const mediaInfos = await resolveMediaAttachments({
|
|
179
|
+
accountId,
|
|
180
|
+
attachments: ctx.media,
|
|
181
|
+
sessionInfo: {
|
|
182
|
+
firstId: ctx.sessionInfo.firstID,
|
|
183
|
+
secondId: ctx.sessionInfo.secondID,
|
|
184
|
+
sessionType: ctx.sessionInfo.sessionType,
|
|
185
|
+
companyId: ctx.sessionInfo.companyID,
|
|
186
|
+
},
|
|
187
|
+
seqId: Number(ctx.messageId),
|
|
188
|
+
maxBytes,
|
|
189
|
+
});
|
|
190
|
+
log(`[${accountId}]: resolved ${mediaInfos.length} media, paths=${mediaInfos.map(m => m.path).join(",")}`);
|
|
191
|
+
mediaPaths = mediaInfos.map((m) => m.path);
|
|
192
|
+
if (mediaInfos.length > 0) {
|
|
193
|
+
// 为 BodyForAgent 生成详细媒体描述(包含路径)
|
|
194
|
+
mediaContext = "\n\n" + mediaInfos.map((m) => {
|
|
195
|
+
const typeLabel = m.contentType?.startsWith("image/") ? "<media:image>"
|
|
196
|
+
: m.contentType?.startsWith("video/") ? "<media:video>"
|
|
197
|
+
: m.contentType?.startsWith("audio/") ? "<media:audio>"
|
|
198
|
+
: "<media:document>";
|
|
199
|
+
return `${typeLabel}: ${m.path}`;
|
|
200
|
+
}).join("\n");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
error(`[${accountId}]: failed to resolve media: ${String(err)}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// 处理引用消息的媒体附件
|
|
208
|
+
if (quoteMsgMap && ctx.replyContext) {
|
|
209
|
+
const quoteMedia = extractQuoteMessageMedia(msg, quoteMsgMap);
|
|
210
|
+
if (quoteMedia && quoteMedia.length > 0) {
|
|
211
|
+
log(`[${accountId}]: processing ${quoteMedia.length} quote message media attachment(s)`);
|
|
212
|
+
try {
|
|
213
|
+
const maxBytes = meetCfg.mediaMaxMb ? meetCfg.mediaMaxMb * 1024 * 1024 : undefined;
|
|
214
|
+
const quoteMediaInfos = await resolveMediaAttachments({
|
|
215
|
+
accountId,
|
|
216
|
+
attachments: quoteMedia,
|
|
217
|
+
sessionInfo: {
|
|
218
|
+
firstId: ctx.sessionInfo.firstID,
|
|
219
|
+
secondId: ctx.sessionInfo.secondID,
|
|
220
|
+
sessionType: ctx.sessionInfo.sessionType,
|
|
221
|
+
companyId: ctx.sessionInfo.companyID,
|
|
222
|
+
},
|
|
223
|
+
seqId: Number(ctx.replyContext.messageId),
|
|
224
|
+
maxBytes,
|
|
225
|
+
});
|
|
226
|
+
log(`[${accountId}]: resolved ${quoteMediaInfos.length} quote media, paths=${quoteMediaInfos.map(m => m.path).join(",")}`);
|
|
227
|
+
// 将引用消息的媒体路径合并到 mediaPaths
|
|
228
|
+
mediaPaths = [...mediaPaths, ...quoteMediaInfos.map((m) => m.path)];
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
error(`[${accountId}]: failed to resolve quote media: ${String(err)}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// 构建最终的消息内容
|
|
236
|
+
// Discord 做法:文字优先,无文字时用媒体占位符
|
|
237
|
+
// 媒体路径通过 MediaPaths 传递,mediaContext 仅作为 BodyForAgent 的补充描述
|
|
238
|
+
const finalContent = ctx.content.trim()
|
|
239
|
+
? `${ctx.content.trim()}${mediaContext}`
|
|
240
|
+
: (ctx.placeholder || "") + mediaContext;
|
|
241
|
+
// Discord 做法:跳过空内容消息
|
|
242
|
+
if (!finalContent.trim() && mediaPaths.length === 0) {
|
|
243
|
+
log(`[${accountId}]: skip message ${ctx.messageId} (empty content)`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const preview = finalContent.replace(/\s+/g, " ").slice(0, 160);
|
|
247
|
+
const inboundLabel = isGroup
|
|
248
|
+
? `Meet[${accountId}] message in group ${ctx.chatId}`
|
|
249
|
+
: `Meet[${accountId}] DM from ${ctx.senderId}`;
|
|
250
|
+
core.system.enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
|
|
251
|
+
sessionKey: route.sessionKey,
|
|
252
|
+
contextKey: `meet:message:${ctx.chatId}:${ctx.messageId}`,
|
|
253
|
+
});
|
|
254
|
+
const envelopeFrom = isGroup ? `${ctx.chatId}:${ctx.senderId}` : ctx.senderId;
|
|
255
|
+
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
256
|
+
// 将当前消息添加到历史(在确认要处理消息之后)
|
|
257
|
+
const historyEntries = isGroup && historyLimit > 0
|
|
258
|
+
? (() => {
|
|
259
|
+
const entries = groupHistories.get(ctx.chatId) ?? [];
|
|
260
|
+
entries.push(pendingEntry);
|
|
261
|
+
while (entries.length > historyLimit) {
|
|
262
|
+
entries.shift();
|
|
263
|
+
}
|
|
264
|
+
groupHistories.set(ctx.chatId, entries);
|
|
265
|
+
return entries;
|
|
266
|
+
})()
|
|
267
|
+
: [];
|
|
268
|
+
const bodyWithContext = buildPendingHistoryContextFromMap({
|
|
269
|
+
historyMap: groupHistories,
|
|
270
|
+
historyKey: ctx.chatId,
|
|
271
|
+
limit: historyLimit,
|
|
272
|
+
currentMessage: messageBody,
|
|
273
|
+
formatEntry: formatHistoryEntry,
|
|
274
|
+
});
|
|
275
|
+
const body = core.channel.reply.formatAgentEnvelope({
|
|
276
|
+
channel: "Meet",
|
|
277
|
+
from: envelopeFrom,
|
|
278
|
+
timestamp: new Date(),
|
|
279
|
+
envelope: envelopeOptions,
|
|
280
|
+
body: bodyWithContext,
|
|
281
|
+
});
|
|
282
|
+
const inboundHistory = isGroup && historyLimit > 0
|
|
283
|
+
? historyEntries.map((entry) => ({
|
|
284
|
+
sender: entry.sender,
|
|
285
|
+
body: entry.body,
|
|
286
|
+
timestamp: entry.timestamp,
|
|
287
|
+
}))
|
|
288
|
+
: undefined;
|
|
289
|
+
const channelConfig = isGroup ? meetCfg.channels?.[ctx.chatId] : undefined;
|
|
290
|
+
const groupConfig = isGroup ? resolveMeetGroupConfig({ meetConfig: meetCfg, chatId: ctx.chatId }) : undefined;
|
|
291
|
+
const systemPrompt = isGroup
|
|
292
|
+
? (groupConfig?.systemPrompt ?? channelConfig?.systemPrompt ?? meetCfg.systemPrompt ?? DEFAULT_GROUP_SYSTEM_PROMPT)
|
|
293
|
+
: (meetCfg.systemPrompt ?? DEFAULT_DM_SYSTEM_PROMPT);
|
|
294
|
+
const inboundCtx = core.channel.reply.finalizeInboundContext({
|
|
295
|
+
Body: body,
|
|
296
|
+
BodyForAgent: finalContent,
|
|
297
|
+
RawBody: ctx.content,
|
|
298
|
+
CommandBody: ctx.content,
|
|
299
|
+
From: meetFrom,
|
|
300
|
+
To: meetTo,
|
|
301
|
+
SessionKey: route.sessionKey,
|
|
302
|
+
AccountId: route.accountId,
|
|
303
|
+
ChatType: isGroup ? "group" : "direct",
|
|
304
|
+
GroupSubject: isGroup ? ctx.chatId : undefined,
|
|
305
|
+
GroupSystemPrompt: systemPrompt,
|
|
306
|
+
SenderName: ctx.senderName,
|
|
307
|
+
SenderId: ctx.senderId,
|
|
308
|
+
Provider: "meet",
|
|
309
|
+
Surface: "meet",
|
|
310
|
+
MessageSid: ctx.messageId,
|
|
311
|
+
Timestamp: ctx.timestamp ?? Date.now(),
|
|
312
|
+
WasMentioned: ctx.mentionedBot,
|
|
313
|
+
ReplyToId: ctx.replyContext?.messageId,
|
|
314
|
+
ReplyToBody: ctx.replyContext?.content,
|
|
315
|
+
ReplyToSender: ctx.replyContext?.senderId,
|
|
316
|
+
InboundHistory: inboundHistory,
|
|
317
|
+
CommandAuthorized: true,
|
|
318
|
+
OriginatingChannel: "meet",
|
|
319
|
+
OriginatingTo: meetTo,
|
|
320
|
+
MediaPaths: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
321
|
+
});
|
|
322
|
+
const { createMeetReplyDispatcher } = await import("./reply-dispatcher.js");
|
|
323
|
+
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId);
|
|
324
|
+
const { dispatcher, replyOptions, markDispatchIdle, markRunComplete } = await createMeetReplyDispatcher({
|
|
325
|
+
cfg,
|
|
326
|
+
agentId: route.agentId,
|
|
327
|
+
runtime: runtime,
|
|
328
|
+
chatId: ctx.chatId,
|
|
329
|
+
replyToMessageId: ctx.messageId,
|
|
330
|
+
accountId,
|
|
331
|
+
bot,
|
|
332
|
+
botUserId,
|
|
333
|
+
mediaLocalRoots,
|
|
334
|
+
});
|
|
335
|
+
log(`[${accountId}]: dispatching to AI agent=${route.agentId} session=${route.sessionKey} history=${inboundHistory?.length ?? 0}`);
|
|
336
|
+
const dispatchResult = await core.channel.reply.dispatchReplyFromConfig({
|
|
337
|
+
ctx: inboundCtx,
|
|
338
|
+
cfg,
|
|
339
|
+
dispatcher,
|
|
340
|
+
replyOptions,
|
|
341
|
+
});
|
|
342
|
+
log(`[${accountId}]: dispatch result queuedFinal=${String(dispatchResult.queuedFinal)} counts=${JSON.stringify(dispatchResult.counts)} failedCounts=${JSON.stringify(dispatchResult.failedCounts ?? {})} sourceReplyDeliveryMode=${dispatchResult.sourceReplyDeliveryMode ?? "default"}`);
|
|
343
|
+
log(`[${accountId}]: AI response completed for message ${ctx.messageId}`);
|
|
344
|
+
markRunComplete();
|
|
345
|
+
markDispatchIdle();
|
|
346
|
+
clearHistoryEntriesIfEnabled({
|
|
347
|
+
historyMap: groupHistories,
|
|
348
|
+
historyKey: ctx.chatId,
|
|
349
|
+
limit: historyLimit,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
error(`[${accountId}]: error processing message: ${String(err)}`);
|
|
354
|
+
}
|
|
355
|
+
}
|