@meet-im/meet 3.4.5 → 3.6.0
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/src/approval-native.d.ts +30 -0
- package/dist/src/approval-native.js +260 -0
- package/dist/src/bot.js +64 -20
- package/dist/src/channel.js +2 -0
- package/dist/src/client.js +0 -1
- package/dist/src/config-schema.d.ts +44 -0
- package/dist/src/config-schema.js +9 -0
- package/dist/src/dm-topic-codec.d.ts +4 -0
- package/dist/src/dm-topic-codec.js +33 -0
- package/dist/src/exec-approvals.d.ts +26 -0
- package/dist/src/exec-approvals.js +71 -0
- package/dist/src/generated/plugin-build-meta.d.ts +1 -1
- package/dist/src/generated/plugin-build-meta.js +1 -1
- package/dist/src/outbound.js +9 -0
- package/dist/src/reply-dispatcher.d.ts +12 -2
- package/dist/src/reply-dispatcher.js +5 -1
- package/dist/src/route-peer.d.ts +6 -0
- package/dist/src/route-peer.js +6 -0
- package/dist/src/sdk-bridge.d.ts +10 -0
- package/dist/src/sdk-bridge.js +46 -0
- package/dist/src/send.d.ts +7 -0
- package/dist/src/send.js +30 -4
- package/dist/src/types.d.ts +3 -1
- package/openclaw.plugin.json +32 -0
- package/package.json +1 -1
- package/skills/meet-lxcli-guide/SKILL.md +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
|
|
2
|
+
import type { ChannelApprovalCapability } from "openclaw/plugin-sdk/channel-contract";
|
|
3
|
+
import type { MeetExecApprovalConfig } from "./types.js";
|
|
4
|
+
type MeetPendingPayload = {
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
type MeetPreparedTarget = {
|
|
8
|
+
to: string;
|
|
9
|
+
threadId?: string;
|
|
10
|
+
};
|
|
11
|
+
type MeetPendingEntry = {
|
|
12
|
+
chatId: string;
|
|
13
|
+
messageId: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const meetApprovalNativeRuntime: ChannelApprovalNativeRuntimeAdapter<MeetPendingPayload, MeetPreparedTarget, MeetPendingEntry, never, unknown>;
|
|
16
|
+
export declare function createMeetNativeApprovalAdapter(configOverride?: MeetExecApprovalConfig | null): {
|
|
17
|
+
auth: {
|
|
18
|
+
authorizeActorAction?: ChannelApprovalCapability["authorizeActorAction"];
|
|
19
|
+
getActionAvailabilityState?: ChannelApprovalCapability["getActionAvailabilityState"];
|
|
20
|
+
getExecInitiatingSurfaceState?: ChannelApprovalCapability["getExecInitiatingSurfaceState"];
|
|
21
|
+
resolveApproveCommandBehavior?: ChannelApprovalCapability["resolveApproveCommandBehavior"];
|
|
22
|
+
};
|
|
23
|
+
delivery: ChannelApprovalCapability["delivery"];
|
|
24
|
+
nativeRuntime: ChannelApprovalCapability["nativeRuntime"];
|
|
25
|
+
render: ChannelApprovalCapability["render"];
|
|
26
|
+
native: ChannelApprovalCapability["native"];
|
|
27
|
+
describeExecApprovalSetup: ChannelApprovalCapability["describeExecApprovalSetup"];
|
|
28
|
+
};
|
|
29
|
+
export declare function getMeetApprovalCapability(): ChannelApprovalCapability;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { createChannelApprovalNativeRuntimeAdapter, } from "openclaw/plugin-sdk/approval-handler-runtime";
|
|
2
|
+
import { buildChannelApprovalNativeTargetKey, resolveApprovalRequestSessionConversation, } from "openclaw/plugin-sdk/approval-native-runtime";
|
|
3
|
+
import { buildExecApprovalPendingReplyPayload, } from "openclaw/plugin-sdk/approval-runtime";
|
|
4
|
+
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
5
|
+
import { listMeetAccountIds, resolveMeetAccount } from "./accounts.js";
|
|
6
|
+
import { createChannelApproverDmTargetResolver, createChannelNativeOriginTargetResolver, createApproverRestrictedNativeApprovalCapability, splitChannelApprovalCapability, } from "openclaw/plugin-sdk/approval-runtime";
|
|
7
|
+
import { getMeetExecApprovalApprovers, isMeetExecApprovalApprover, isMeetExecApprovalClientEnabled, } from "./exec-approvals.js";
|
|
8
|
+
import { encodeMeetDmTopicId } from "./dm-topic-codec.js";
|
|
9
|
+
import { sendMessageMeet } from "./send.js";
|
|
10
|
+
function shouldHandleMeetApprovalRequest(_params) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
function extractMeetSessionKind(sessionKey) {
|
|
14
|
+
if (!sessionKey) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const match = sessionKey.match(/meet:(channel|group|dm):/);
|
|
18
|
+
if (!match) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return match[1];
|
|
22
|
+
}
|
|
23
|
+
function normalizeMeetOriginChannelId(value) {
|
|
24
|
+
if (!value) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const trimmed = value.trim();
|
|
28
|
+
if (!trimmed) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const prefixed = trimmed.match(/^(?:channel):(-?\d+)$/i);
|
|
32
|
+
if (prefixed) {
|
|
33
|
+
return prefixed[1];
|
|
34
|
+
}
|
|
35
|
+
return /^-?\d+$/.test(trimmed) ? trimmed : null;
|
|
36
|
+
}
|
|
37
|
+
function normalizeMeetOriginTarget(value) {
|
|
38
|
+
if (!value) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const trimmed = value.trim();
|
|
42
|
+
if (!trimmed) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const prefixed = trimmed.match(/^(?:channel|user):(-?\d+)$/i);
|
|
46
|
+
if (prefixed) {
|
|
47
|
+
const kind = trimmed.toLowerCase().startsWith("user:") ? "user" : "channel";
|
|
48
|
+
return `${kind}:${prefixed[1]}`;
|
|
49
|
+
}
|
|
50
|
+
return /^-?\d+$/.test(trimmed) ? trimmed : null;
|
|
51
|
+
}
|
|
52
|
+
function normalizeMeetThreadId(value) {
|
|
53
|
+
if (typeof value === "number") {
|
|
54
|
+
return Number.isFinite(value) ? String(value) : undefined;
|
|
55
|
+
}
|
|
56
|
+
if (typeof value !== "string") {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const normalized = value.trim();
|
|
60
|
+
return normalized || undefined;
|
|
61
|
+
}
|
|
62
|
+
function createMeetOriginTargetResolver(_configOverride) {
|
|
63
|
+
return createChannelNativeOriginTargetResolver({
|
|
64
|
+
channel: "meet",
|
|
65
|
+
shouldHandleRequest: ({ cfg, accountId, request }) => shouldHandleMeetApprovalRequest({
|
|
66
|
+
cfg,
|
|
67
|
+
accountId,
|
|
68
|
+
request,
|
|
69
|
+
}),
|
|
70
|
+
resolveTurnSourceTarget: (request) => {
|
|
71
|
+
const sessionConversation = resolveApprovalRequestSessionConversation({
|
|
72
|
+
request,
|
|
73
|
+
channel: "meet",
|
|
74
|
+
bundledFallback: false,
|
|
75
|
+
});
|
|
76
|
+
const sessionKind = extractMeetSessionKind(normalizeOptionalString(request.request.sessionKey) ?? null);
|
|
77
|
+
const turnSourceChannel = normalizeLowercaseStringOrEmpty(request.request.turnSourceChannel);
|
|
78
|
+
const rawTurnSourceTo = normalizeOptionalString(request.request.turnSourceTo) ?? "";
|
|
79
|
+
const turnSourceTarget = normalizeMeetOriginTarget(rawTurnSourceTo);
|
|
80
|
+
const turnSourceTo = normalizeMeetOriginChannelId(rawTurnSourceTo);
|
|
81
|
+
const threadId = encodeMeetDmTopicId(normalizeMeetThreadId(request.request.turnSourceThreadId)) ??
|
|
82
|
+
normalizeMeetThreadId(sessionConversation?.threadId) ??
|
|
83
|
+
undefined;
|
|
84
|
+
const hasExplicitOriginTarget = /^(?:channel):/i.test(rawTurnSourceTo);
|
|
85
|
+
if (turnSourceChannel !== "meet") {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (sessionKind === "dm") {
|
|
89
|
+
return turnSourceTarget && /^user:/i.test(turnSourceTarget)
|
|
90
|
+
? { to: turnSourceTarget, threadId }
|
|
91
|
+
: null;
|
|
92
|
+
}
|
|
93
|
+
if (!turnSourceTo) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return hasExplicitOriginTarget || sessionKind === "channel" || sessionKind === "group"
|
|
97
|
+
? { to: turnSourceTo, threadId }
|
|
98
|
+
: null;
|
|
99
|
+
},
|
|
100
|
+
resolveSessionTarget: (sessionTarget, request) => {
|
|
101
|
+
const sessionConversation = resolveApprovalRequestSessionConversation({
|
|
102
|
+
request,
|
|
103
|
+
channel: "meet",
|
|
104
|
+
bundledFallback: false,
|
|
105
|
+
});
|
|
106
|
+
const sessionKind = extractMeetSessionKind(request.request.sessionKey?.trim() || null);
|
|
107
|
+
if (sessionKind === "dm") {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const targetTo = normalizeMeetOriginChannelId(sessionTarget.to);
|
|
111
|
+
return targetTo
|
|
112
|
+
? {
|
|
113
|
+
to: targetTo,
|
|
114
|
+
threadId: normalizeMeetThreadId(sessionTarget.threadId) ??
|
|
115
|
+
normalizeMeetThreadId(sessionConversation?.threadId) ??
|
|
116
|
+
undefined,
|
|
117
|
+
}
|
|
118
|
+
: null;
|
|
119
|
+
},
|
|
120
|
+
resolveFallbackTarget: (request) => {
|
|
121
|
+
const sessionConversation = resolveApprovalRequestSessionConversation({
|
|
122
|
+
request,
|
|
123
|
+
channel: "meet",
|
|
124
|
+
bundledFallback: false,
|
|
125
|
+
});
|
|
126
|
+
const sessionKind = extractMeetSessionKind(request.request.sessionKey?.trim() || null);
|
|
127
|
+
if (sessionKind === "dm") {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const fallbackChannelId = normalizeMeetOriginChannelId(sessionConversation?.id);
|
|
131
|
+
return fallbackChannelId
|
|
132
|
+
? {
|
|
133
|
+
to: fallbackChannelId,
|
|
134
|
+
threadId: normalizeMeetThreadId(sessionConversation?.threadId) ?? undefined,
|
|
135
|
+
}
|
|
136
|
+
: null;
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function createMeetApproverDmTargetResolver(configOverride) {
|
|
141
|
+
return createChannelApproverDmTargetResolver({
|
|
142
|
+
shouldHandleRequest: ({ cfg, accountId, request }) => shouldHandleMeetApprovalRequest({
|
|
143
|
+
cfg,
|
|
144
|
+
accountId,
|
|
145
|
+
request,
|
|
146
|
+
}),
|
|
147
|
+
resolveApprovers: ({ cfg, accountId }) => getMeetExecApprovalApprovers({ cfg, accountId, configOverride }),
|
|
148
|
+
mapApprover: (approver) => ({ to: `user:${approver}` }),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function buildMeetPendingPayload(params) {
|
|
152
|
+
if (params.view.approvalKind !== "exec") {
|
|
153
|
+
return {
|
|
154
|
+
text: params.view.title,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const payload = buildExecApprovalPendingReplyPayload({
|
|
158
|
+
approvalId: params.request.id,
|
|
159
|
+
approvalSlug: params.request.id.slice(0, 8),
|
|
160
|
+
approvalCommandId: params.request.id,
|
|
161
|
+
warningText: params.view.warningText ?? normalizeOptionalString(params.request.request.warningText) ?? undefined,
|
|
162
|
+
ask: params.view.ask ?? normalizeOptionalString(params.request.request.ask) ?? undefined,
|
|
163
|
+
agentId: params.view.agentId ?? normalizeOptionalString(params.request.request.agentId) ?? undefined,
|
|
164
|
+
allowedDecisions: params.view.actions.map((action) => action.decision),
|
|
165
|
+
command: params.view.commandText,
|
|
166
|
+
cwd: params.view.cwd ?? normalizeOptionalString(params.request.request.cwd) ?? undefined,
|
|
167
|
+
host: params.view.host === "node" ? "node" : "gateway",
|
|
168
|
+
nodeId: params.view.nodeId ?? normalizeOptionalString(params.request.request.nodeId) ?? undefined,
|
|
169
|
+
sessionKey: params.view.sessionKey ?? normalizeOptionalString(params.request.request.sessionKey) ?? undefined,
|
|
170
|
+
expiresAtMs: params.request.expiresAtMs,
|
|
171
|
+
nowMs: params.nowMs,
|
|
172
|
+
});
|
|
173
|
+
const reason = params.view.ask?.trim();
|
|
174
|
+
const text = reason ? `${reason}\n\n${payload.text ?? ""}` : (payload.text ?? "");
|
|
175
|
+
return {
|
|
176
|
+
text,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export const meetApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter({
|
|
180
|
+
eventKinds: ["exec", "plugin"],
|
|
181
|
+
availability: {
|
|
182
|
+
isConfigured: ({ cfg, accountId }) => isMeetExecApprovalClientEnabled({ cfg, accountId }),
|
|
183
|
+
shouldHandle: ({ cfg, accountId, request }) => shouldHandleMeetApprovalRequest({
|
|
184
|
+
cfg,
|
|
185
|
+
accountId,
|
|
186
|
+
request,
|
|
187
|
+
}),
|
|
188
|
+
},
|
|
189
|
+
presentation: {
|
|
190
|
+
buildPendingPayload: ({ request, nowMs, view }) => buildMeetPendingPayload({
|
|
191
|
+
request: request,
|
|
192
|
+
nowMs,
|
|
193
|
+
view,
|
|
194
|
+
}),
|
|
195
|
+
buildResolvedResult: () => ({ kind: "leave" }),
|
|
196
|
+
buildExpiredResult: () => ({ kind: "leave" }),
|
|
197
|
+
},
|
|
198
|
+
transport: {
|
|
199
|
+
prepareTarget: ({ plannedTarget }) => ({
|
|
200
|
+
dedupeKey: buildChannelApprovalNativeTargetKey(plannedTarget.target),
|
|
201
|
+
target: {
|
|
202
|
+
to: plannedTarget.surface === "approver-dm" && !/^user:/i.test(plannedTarget.target.to)
|
|
203
|
+
? `user:${plannedTarget.target.to}`
|
|
204
|
+
: plannedTarget.target.to,
|
|
205
|
+
...(plannedTarget.target.threadId != null
|
|
206
|
+
? { threadId: String(plannedTarget.target.threadId) }
|
|
207
|
+
: {}),
|
|
208
|
+
},
|
|
209
|
+
}),
|
|
210
|
+
deliverPending: async ({ cfg, accountId, preparedTarget, pendingPayload }) => {
|
|
211
|
+
try {
|
|
212
|
+
const result = await sendMessageMeet({
|
|
213
|
+
cfg: cfg,
|
|
214
|
+
to: preparedTarget.to,
|
|
215
|
+
text: pendingPayload.text,
|
|
216
|
+
accountId: accountId ?? undefined,
|
|
217
|
+
threadId: preparedTarget.threadId,
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
chatId: result.chatId,
|
|
221
|
+
messageId: result.messageId,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
function createMeetApprovalCapability(configOverride) {
|
|
231
|
+
return createApproverRestrictedNativeApprovalCapability({
|
|
232
|
+
channel: "meet",
|
|
233
|
+
channelLabel: "Meet",
|
|
234
|
+
describeExecApprovalSetup: ({ accountId, }) => {
|
|
235
|
+
const prefix = accountId && accountId !== "default"
|
|
236
|
+
? `channels.meet.accounts.${accountId}`
|
|
237
|
+
: "channels.meet";
|
|
238
|
+
return `Approve it from the Web UI or terminal UI for now. Meet supports native exec approvals for this account. Configure \`${prefix}.execApprovals.approvers\` or \`commands.ownerAllowFrom\`; set \`${prefix}.execApprovals.enabled\` to \`auto\` or \`true\`.`;
|
|
239
|
+
},
|
|
240
|
+
listAccountIds: listMeetAccountIds,
|
|
241
|
+
hasApprovers: ({ cfg, accountId }) => getMeetExecApprovalApprovers({ cfg, accountId, configOverride }).length > 0,
|
|
242
|
+
isExecAuthorizedSender: ({ cfg, accountId, senderId }) => isMeetExecApprovalApprover({ cfg, accountId, senderId, configOverride }),
|
|
243
|
+
isNativeDeliveryEnabled: ({ cfg, accountId }) => isMeetExecApprovalClientEnabled({ cfg, accountId, configOverride }),
|
|
244
|
+
resolveNativeDeliveryMode: ({ cfg, accountId }) => configOverride?.target ??
|
|
245
|
+
resolveMeetAccount({ cfg, accountId }).config.execApprovals?.target ??
|
|
246
|
+
"dm",
|
|
247
|
+
resolveOriginTarget: createMeetOriginTargetResolver(configOverride),
|
|
248
|
+
resolveApproverDmTargets: createMeetApproverDmTargetResolver(configOverride),
|
|
249
|
+
notifyOriginWhenDmOnly: true,
|
|
250
|
+
nativeRuntime: meetApprovalNativeRuntime,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
export function createMeetNativeApprovalAdapter(configOverride) {
|
|
254
|
+
return splitChannelApprovalCapability(createMeetApprovalCapability(configOverride));
|
|
255
|
+
}
|
|
256
|
+
let cachedMeetApprovalCapability;
|
|
257
|
+
export function getMeetApprovalCapability() {
|
|
258
|
+
cachedMeetApprovalCapability ??= createMeetApprovalCapability();
|
|
259
|
+
return cachedMeetApprovalCapability;
|
|
260
|
+
}
|
package/dist/src/bot.js
CHANGED
|
@@ -2,7 +2,8 @@ import { buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, record
|
|
|
2
2
|
import { buildChannelInboundEventContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
3
3
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
4
4
|
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
|
5
|
-
import { msgContentToContext, extractQuoteMessageMedia } from "./sdk-bridge.js";
|
|
5
|
+
import { msgContentToContext, extractQuoteMessageMedia, parseMergedForwardContent } from "./sdk-bridge.js";
|
|
6
|
+
import { buildMeetRoutePeerId } from "./route-peer.js";
|
|
6
7
|
import { getMeetRuntime } from "./runtime.js";
|
|
7
8
|
import { resolveMeetAllowlistMatch, resolveMeetGroupPolicy, resolveMeetGroupConfig, resolveMeetGroupUserPolicy } from "./policy.js";
|
|
8
9
|
import { sendMessageMeet } from "./send.js";
|
|
@@ -12,6 +13,9 @@ const DEFAULT_DM_SYSTEM_PROMPT = "你正在 Meet 私聊中对话。注意:Meet
|
|
|
12
13
|
function formatHistoryEntry(entry) {
|
|
13
14
|
return `${entry.sender}: ${entry.body}`;
|
|
14
15
|
}
|
|
16
|
+
function isSessionBoundaryCommand(text) {
|
|
17
|
+
return /^\/(?:new|reset)(?:\s|$)/i.test(text.trim());
|
|
18
|
+
}
|
|
15
19
|
export async function handleMeetMessage(params) {
|
|
16
20
|
const { cfg, msg, botUserId, runtime, accountId, account, bot, groupHistories, quoteMsgMap, ctx: providedCtx } = params;
|
|
17
21
|
const log = runtime?.log ?? console.log;
|
|
@@ -36,11 +40,10 @@ export async function handleMeetMessage(params) {
|
|
|
36
40
|
const historyLimit = isGroup
|
|
37
41
|
? Math.max(0, meetCfg.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? 20)
|
|
38
42
|
: Math.max(0, meetCfg.dmHistoryLimit ?? 0);
|
|
43
|
+
const historyKey = isGroup
|
|
44
|
+
? ctx.chatId
|
|
45
|
+
: (ctx.threadId ? `${ctx.chatId}__topic__${ctx.threadId}` : ctx.chatId);
|
|
39
46
|
const speaker = ctx.senderName ?? ctx.senderId;
|
|
40
|
-
// Discord 做法:文字优先,无文字时用媒体占位符
|
|
41
|
-
const messageBody = ctx.content.trim()
|
|
42
|
-
? `${speaker}: ${ctx.content.trim()}`
|
|
43
|
-
: `${speaker}: ${ctx.placeholder || ""}`;
|
|
44
47
|
const pendingEntry = {
|
|
45
48
|
sender: speaker,
|
|
46
49
|
body: ctx.content.trim() || ctx.placeholder || "",
|
|
@@ -149,16 +152,48 @@ export async function handleMeetMessage(params) {
|
|
|
149
152
|
log(`[${accountId}]: message in group ${ctx.chatId} skipped (mention required)`);
|
|
150
153
|
recordPendingHistoryEntryIfEnabled({
|
|
151
154
|
historyMap: groupHistories,
|
|
152
|
-
historyKey
|
|
155
|
+
historyKey,
|
|
153
156
|
entry: pendingEntry,
|
|
154
157
|
limit: historyLimit,
|
|
155
158
|
});
|
|
156
159
|
return;
|
|
157
160
|
}
|
|
158
161
|
}
|
|
162
|
+
// 合并转发消息(msgType=19):只进上下文,不发给 LLM
|
|
163
|
+
const msgType = Number(msg.extraInfo?.msgType);
|
|
164
|
+
if (msgType === 19) {
|
|
165
|
+
log(`[${accountId}]: skipping merged forward message ${ctx.messageId} (msgType=19, context only)`);
|
|
166
|
+
if (historyLimit > 0) {
|
|
167
|
+
const subEntries = parseMergedForwardContent(msg.content ?? "");
|
|
168
|
+
const entries = groupHistories.get(historyKey) ?? [];
|
|
169
|
+
if (subEntries.length > 0) {
|
|
170
|
+
for (const sub of subEntries) {
|
|
171
|
+
entries.push({
|
|
172
|
+
sender: sub.sender,
|
|
173
|
+
body: sub.body,
|
|
174
|
+
timestamp: sub.timestamp,
|
|
175
|
+
messageId: sub.messageId,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
entries.push(pendingEntry);
|
|
181
|
+
}
|
|
182
|
+
while (entries.length > historyLimit) {
|
|
183
|
+
entries.shift();
|
|
184
|
+
}
|
|
185
|
+
groupHistories.set(historyKey, entries);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
159
189
|
const meetFrom = `meet:${ctx.senderId}`;
|
|
160
190
|
const meetTo = ctx.chatId;
|
|
161
|
-
const peerId =
|
|
191
|
+
const peerId = buildMeetRoutePeerId({
|
|
192
|
+
isGroup,
|
|
193
|
+
senderId: ctx.senderId,
|
|
194
|
+
chatId: ctx.chatId,
|
|
195
|
+
threadId: ctx.threadId,
|
|
196
|
+
});
|
|
162
197
|
const route = core.channel.routing.resolveAgentRoute({
|
|
163
198
|
cfg,
|
|
164
199
|
channel: "meet",
|
|
@@ -167,6 +202,7 @@ export async function handleMeetMessage(params) {
|
|
|
167
202
|
kind: isGroup ? "group" : "direct",
|
|
168
203
|
id: peerId,
|
|
169
204
|
},
|
|
205
|
+
...(ctx.threadId ? { threadId: ctx.threadId } : {}),
|
|
170
206
|
});
|
|
171
207
|
// 处理媒体附件
|
|
172
208
|
let mediaContext = "";
|
|
@@ -257,6 +293,9 @@ export async function handleMeetMessage(params) {
|
|
|
257
293
|
? ctx.content.trim()
|
|
258
294
|
: (ctx.placeholder || "");
|
|
259
295
|
const finalContent = `${quoteContext}${userBody}${mentionsContext}${mediaContext}`;
|
|
296
|
+
if (isSessionBoundaryCommand(ctx.rawBody ?? ctx.content)) {
|
|
297
|
+
groupHistories.set(historyKey, []);
|
|
298
|
+
}
|
|
260
299
|
// Discord 做法:跳过空内容消息
|
|
261
300
|
if (!finalContent.trim() && mediaPaths.length === 0) {
|
|
262
301
|
log(`[${accountId}]: skip message ${ctx.messageId} (empty content)`);
|
|
@@ -273,22 +312,22 @@ export async function handleMeetMessage(params) {
|
|
|
273
312
|
const envelopeFrom = isGroup ? `${ctx.chatId}:${ctx.senderId}` : ctx.senderId;
|
|
274
313
|
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
275
314
|
// 将当前消息添加到历史(在确认要处理消息之后)
|
|
276
|
-
const historyEntries =
|
|
315
|
+
const historyEntries = historyLimit > 0
|
|
277
316
|
? (() => {
|
|
278
|
-
const entries = groupHistories.get(
|
|
317
|
+
const entries = groupHistories.get(historyKey) ?? [];
|
|
279
318
|
entries.push(pendingEntry);
|
|
280
319
|
while (entries.length > historyLimit) {
|
|
281
320
|
entries.shift();
|
|
282
321
|
}
|
|
283
|
-
groupHistories.set(
|
|
322
|
+
groupHistories.set(historyKey, entries);
|
|
284
323
|
return entries;
|
|
285
324
|
})()
|
|
286
325
|
: [];
|
|
287
326
|
const bodyWithContext = buildPendingHistoryContextFromMap({
|
|
288
327
|
historyMap: groupHistories,
|
|
289
|
-
historyKey
|
|
328
|
+
historyKey,
|
|
290
329
|
limit: historyLimit,
|
|
291
|
-
currentMessage:
|
|
330
|
+
currentMessage: finalContent,
|
|
292
331
|
formatEntry: formatHistoryEntry,
|
|
293
332
|
});
|
|
294
333
|
const body = core.channel.reply.formatAgentEnvelope({
|
|
@@ -298,7 +337,7 @@ export async function handleMeetMessage(params) {
|
|
|
298
337
|
envelope: envelopeOptions,
|
|
299
338
|
body: bodyWithContext,
|
|
300
339
|
});
|
|
301
|
-
const inboundHistory =
|
|
340
|
+
const inboundHistory = historyLimit > 0
|
|
302
341
|
? historyEntries.map((entry) => ({
|
|
303
342
|
sender: entry.sender,
|
|
304
343
|
body: entry.body,
|
|
@@ -326,6 +365,7 @@ export async function handleMeetMessage(params) {
|
|
|
326
365
|
kind: isGroup ? "group" : "direct",
|
|
327
366
|
id: ctx.chatId,
|
|
328
367
|
label: isGroup ? (groupConfig?.groupConfig?.name ?? ctx.chatId) : ctx.chatId,
|
|
368
|
+
...(ctx.threadId ? { threadId: ctx.threadId } : {}),
|
|
329
369
|
routePeer: {
|
|
330
370
|
kind: isGroup ? "group" : "direct",
|
|
331
371
|
id: peerId,
|
|
@@ -339,11 +379,12 @@ export async function handleMeetMessage(params) {
|
|
|
339
379
|
reply: {
|
|
340
380
|
to: meetTo,
|
|
341
381
|
originatingTo: meetTo,
|
|
382
|
+
...(ctx.threadId ? { messageThreadId: ctx.threadId } : {}),
|
|
342
383
|
},
|
|
343
384
|
message: {
|
|
344
385
|
body,
|
|
345
386
|
rawBody: ctx.content,
|
|
346
|
-
bodyForAgent:
|
|
387
|
+
bodyForAgent: bodyWithContext,
|
|
347
388
|
commandBody: ctx.rawBody ?? ctx.content,
|
|
348
389
|
envelopeFrom,
|
|
349
390
|
inboundHistory,
|
|
@@ -389,6 +430,7 @@ export async function handleMeetMessage(params) {
|
|
|
389
430
|
agentId: route.agentId,
|
|
390
431
|
runtime: runtime,
|
|
391
432
|
chatId: ctx.chatId,
|
|
433
|
+
threadId: ctx.threadId,
|
|
392
434
|
senderId: ctx.senderId,
|
|
393
435
|
mentionedBot: ctx.mentionedBot,
|
|
394
436
|
replyToMessageId: ctx.messageId,
|
|
@@ -402,7 +444,7 @@ export async function handleMeetMessage(params) {
|
|
|
402
444
|
apiEndpoint: account.apiEndpoint,
|
|
403
445
|
typingMode: effectiveTypingMode,
|
|
404
446
|
});
|
|
405
|
-
log(`[${accountId}]: dispatch ctx replyToId=${inboundCtx.ReplyToId ?? "undefined"} replyToBody=${JSON.stringify(inboundCtx.ReplyToBody ?? "")} rawBody=${JSON.stringify(inboundCtx.RawBody ?? "")} commandBody=${JSON.stringify(inboundCtx.CommandBody ?? "")} bodyForAgent=${JSON.stringify(inboundCtx.BodyForAgent ?? "")}`);
|
|
447
|
+
log(`[${accountId}]: dispatch ctx historyKey=${historyKey} routeSessionKey=${route.sessionKey} replyToId=${inboundCtx.ReplyToId ?? "undefined"} replyToBody=${JSON.stringify(inboundCtx.ReplyToBody ?? "")} rawBody=${JSON.stringify(inboundCtx.RawBody ?? "")} commandBody=${JSON.stringify(inboundCtx.CommandBody ?? "")} bodyForAgent=${JSON.stringify(inboundCtx.BodyForAgent ?? "")}`);
|
|
406
448
|
log(`[${accountId}]: dispatching to AI agent=${route.agentId} session=${route.sessionKey} history=${inboundHistory?.length ?? 0}`);
|
|
407
449
|
const dispatchResult = await core.channel.reply.dispatchReplyFromConfig({
|
|
408
450
|
ctx: inboundCtx,
|
|
@@ -414,11 +456,13 @@ export async function handleMeetMessage(params) {
|
|
|
414
456
|
log(`[${accountId}]: AI response completed for message ${ctx.messageId}`);
|
|
415
457
|
markRunComplete();
|
|
416
458
|
markDispatchIdle();
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
459
|
+
if (isGroup) {
|
|
460
|
+
clearHistoryEntriesIfEnabled({
|
|
461
|
+
historyMap: groupHistories,
|
|
462
|
+
historyKey,
|
|
463
|
+
limit: historyLimit,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
422
466
|
}
|
|
423
467
|
catch (err) {
|
|
424
468
|
error(`[${accountId}]: error processing message: ${String(err)}`);
|
package/dist/src/channel.js
CHANGED
|
@@ -7,6 +7,7 @@ import { sendMessageMeet } from "./send.js";
|
|
|
7
7
|
import { getMeetClient } from "./client.js";
|
|
8
8
|
import { getAllCachedUsers, rememberMeetUser } from "./directory-cache.js";
|
|
9
9
|
import { MeetPluginConfigSchema } from "./config-schema.js";
|
|
10
|
+
import { getMeetApprovalCapability } from "./approval-native.js";
|
|
10
11
|
const meta = {
|
|
11
12
|
id: "meet",
|
|
12
13
|
label: "Meet",
|
|
@@ -298,6 +299,7 @@ export const meetPlugin = {
|
|
|
298
299
|
listGroupsLive: async () => [],
|
|
299
300
|
},
|
|
300
301
|
outbound: meetOutbound,
|
|
302
|
+
approvalCapability: getMeetApprovalCapability(),
|
|
301
303
|
status: {
|
|
302
304
|
defaultRuntime: {
|
|
303
305
|
accountId: DEFAULT_ACCOUNT_ID,
|
package/dist/src/client.js
CHANGED
|
@@ -39,7 +39,6 @@ export function createMeetClient(account) {
|
|
|
39
39
|
pollingLimit: account.config.pollLimit ?? POLLING.DEFAULT_LIMIT,
|
|
40
40
|
longPollingTimeout: pollTimeoutSec,
|
|
41
41
|
logLevel,
|
|
42
|
-
useV2: true,
|
|
43
42
|
userAgent: buildUserAgent(),
|
|
44
43
|
});
|
|
45
44
|
botInstances.set(account.accountId, bot);
|
|
@@ -16,6 +16,17 @@ export declare const MeetGroupConfigSchema: z.ZodObject<{
|
|
|
16
16
|
disabled: "disabled";
|
|
17
17
|
}>>;
|
|
18
18
|
}, z.core.$strip>;
|
|
19
|
+
export declare const MeetExecApprovalConfigSchema: z.ZodObject<{
|
|
20
|
+
enabled: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
|
|
21
|
+
approvers: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
22
|
+
target: z.ZodOptional<z.ZodEnum<{
|
|
23
|
+
dm: "dm";
|
|
24
|
+
channel: "channel";
|
|
25
|
+
both: "both";
|
|
26
|
+
}>>;
|
|
27
|
+
agentFilter: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
28
|
+
sessionFilter: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
29
|
+
}, z.core.$strip>;
|
|
19
30
|
export declare const MeetAccountConfigSchema: z.ZodObject<{
|
|
20
31
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
21
32
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -63,6 +74,17 @@ export declare const MeetAccountConfigSchema: z.ZodObject<{
|
|
|
63
74
|
disabled: "disabled";
|
|
64
75
|
}>>;
|
|
65
76
|
}, z.core.$strip>>>;
|
|
77
|
+
execApprovals: z.ZodOptional<z.ZodObject<{
|
|
78
|
+
enabled: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
|
|
79
|
+
approvers: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
80
|
+
target: z.ZodOptional<z.ZodEnum<{
|
|
81
|
+
dm: "dm";
|
|
82
|
+
channel: "channel";
|
|
83
|
+
both: "both";
|
|
84
|
+
}>>;
|
|
85
|
+
agentFilter: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
86
|
+
sessionFilter: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
87
|
+
}, z.core.$strip>>;
|
|
66
88
|
}, z.core.$strip>;
|
|
67
89
|
export declare const MeetConfigSchema: z.ZodObject<{
|
|
68
90
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -163,6 +185,28 @@ export declare const MeetConfigSchema: z.ZodObject<{
|
|
|
163
185
|
disabled: "disabled";
|
|
164
186
|
}>>;
|
|
165
187
|
}, z.core.$strip>>>;
|
|
188
|
+
execApprovals: z.ZodOptional<z.ZodObject<{
|
|
189
|
+
enabled: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
|
|
190
|
+
approvers: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
191
|
+
target: z.ZodOptional<z.ZodEnum<{
|
|
192
|
+
dm: "dm";
|
|
193
|
+
channel: "channel";
|
|
194
|
+
both: "both";
|
|
195
|
+
}>>;
|
|
196
|
+
agentFilter: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
197
|
+
sessionFilter: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
198
|
+
}, z.core.$strip>>;
|
|
166
199
|
}, z.core.$strip>>>;
|
|
200
|
+
execApprovals: z.ZodOptional<z.ZodObject<{
|
|
201
|
+
enabled: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
|
|
202
|
+
approvers: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
203
|
+
target: z.ZodOptional<z.ZodEnum<{
|
|
204
|
+
dm: "dm";
|
|
205
|
+
channel: "channel";
|
|
206
|
+
both: "both";
|
|
207
|
+
}>>;
|
|
208
|
+
agentFilter: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
209
|
+
sessionFilter: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
210
|
+
}, z.core.$strip>>;
|
|
167
211
|
}, z.core.$strip>;
|
|
168
212
|
export declare const MeetPluginConfigSchema: import("openclaw/plugin-sdk").ChannelConfigSchema;
|
|
@@ -13,6 +13,13 @@ export const MeetGroupConfigSchema = z.object({
|
|
|
13
13
|
users: z.array(z.union([z.string(), z.number()])).optional(),
|
|
14
14
|
groupPolicy: z.enum(["open", "allowlist", "disabled"]).optional(),
|
|
15
15
|
});
|
|
16
|
+
export const MeetExecApprovalConfigSchema = z.object({
|
|
17
|
+
enabled: z.union([z.boolean(), z.literal("auto")]).optional(),
|
|
18
|
+
approvers: z.array(z.union([z.string(), z.number()])).optional(),
|
|
19
|
+
target: z.enum(["dm", "channel", "both"]).optional(),
|
|
20
|
+
agentFilter: z.array(z.string()).optional(),
|
|
21
|
+
sessionFilter: z.array(z.string()).optional(),
|
|
22
|
+
});
|
|
16
23
|
export const MeetAccountConfigSchema = z.object({
|
|
17
24
|
enabled: z.boolean().optional(),
|
|
18
25
|
name: z.string().optional(),
|
|
@@ -34,6 +41,7 @@ export const MeetAccountConfigSchema = z.object({
|
|
|
34
41
|
mediaMaxMb: z.number().min(0).optional(),
|
|
35
42
|
typingMode: z.enum(["none", "instant", "message"]).optional(),
|
|
36
43
|
groups: z.record(z.string(), MeetGroupConfigSchema).optional(),
|
|
44
|
+
execApprovals: MeetExecApprovalConfigSchema.optional(),
|
|
37
45
|
});
|
|
38
46
|
export const MeetConfigSchema = z.object({
|
|
39
47
|
enabled: z.boolean().optional(),
|
|
@@ -58,5 +66,6 @@ export const MeetConfigSchema = z.object({
|
|
|
58
66
|
mediaMaxMb: z.number().min(0).optional(),
|
|
59
67
|
typingMode: z.enum(["none", "instant", "message"]).optional(),
|
|
60
68
|
accounts: z.record(z.string(), MeetAccountConfigSchema).optional(),
|
|
69
|
+
execApprovals: MeetExecApprovalConfigSchema.optional(),
|
|
61
70
|
});
|
|
62
71
|
export const MeetPluginConfigSchema = buildChannelConfigSchema(MeetConfigSchema);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const MEET_DM_TOPIC_THREAD_PREFIX = "meetdm_b64_";
|
|
2
|
+
export declare function isEncodedMeetDmTopicThreadId(threadId?: string | null): boolean;
|
|
3
|
+
export declare function encodeMeetDmTopicId(raw?: string | null): string | undefined;
|
|
4
|
+
export declare function decodeMeetDmTopicThreadId(threadId?: string | null): string | undefined;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const MEET_DM_TOPIC_THREAD_PREFIX = "meetdm_b64_";
|
|
2
|
+
function normalizeNonEmpty(value) {
|
|
3
|
+
const trimmed = value?.trim();
|
|
4
|
+
return trimmed ? trimmed : undefined;
|
|
5
|
+
}
|
|
6
|
+
function toBase64Url(input) {
|
|
7
|
+
return Buffer.from(input, "utf8")
|
|
8
|
+
.toString("base64")
|
|
9
|
+
.replace(/\+/g, "-")
|
|
10
|
+
.replace(/\//g, "_")
|
|
11
|
+
.replace(/=+$/g, "");
|
|
12
|
+
}
|
|
13
|
+
function fromBase64Url(input) {
|
|
14
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
15
|
+
const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
|
|
16
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
17
|
+
}
|
|
18
|
+
export function isEncodedMeetDmTopicThreadId(threadId) {
|
|
19
|
+
const normalized = normalizeNonEmpty(threadId);
|
|
20
|
+
return normalized?.startsWith(MEET_DM_TOPIC_THREAD_PREFIX) ?? false;
|
|
21
|
+
}
|
|
22
|
+
export function encodeMeetDmTopicId(raw) {
|
|
23
|
+
const normalized = normalizeNonEmpty(raw);
|
|
24
|
+
return normalized ? `${MEET_DM_TOPIC_THREAD_PREFIX}${toBase64Url(normalized)}` : undefined;
|
|
25
|
+
}
|
|
26
|
+
export function decodeMeetDmTopicThreadId(threadId) {
|
|
27
|
+
const normalized = normalizeNonEmpty(threadId);
|
|
28
|
+
if (!normalized)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (!normalized.startsWith(MEET_DM_TOPIC_THREAD_PREFIX))
|
|
31
|
+
return normalized;
|
|
32
|
+
return fromBase64Url(normalized.slice(MEET_DM_TOPIC_THREAD_PREFIX.length));
|
|
33
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ChannelOutboundPayloadHint } from "openclaw/plugin-sdk/channel-contract";
|
|
2
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|
3
|
+
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
|
4
|
+
import type { MeetExecApprovalConfig } from "./types.js";
|
|
5
|
+
export declare function getMeetExecApprovalApprovers(params: {
|
|
6
|
+
cfg: OpenClawConfig;
|
|
7
|
+
accountId?: string | null;
|
|
8
|
+
configOverride?: MeetExecApprovalConfig | null;
|
|
9
|
+
}): string[];
|
|
10
|
+
export declare function isMeetExecApprovalClientEnabled(params: {
|
|
11
|
+
cfg: OpenClawConfig;
|
|
12
|
+
accountId?: string | null;
|
|
13
|
+
configOverride?: MeetExecApprovalConfig | null;
|
|
14
|
+
}): boolean;
|
|
15
|
+
export declare function isMeetExecApprovalApprover(params: {
|
|
16
|
+
cfg: OpenClawConfig;
|
|
17
|
+
accountId?: string | null;
|
|
18
|
+
senderId?: string | null;
|
|
19
|
+
configOverride?: MeetExecApprovalConfig | null;
|
|
20
|
+
}): boolean;
|
|
21
|
+
export declare function shouldSuppressLocalMeetExecApprovalPrompt(params: {
|
|
22
|
+
cfg: OpenClawConfig;
|
|
23
|
+
accountId?: string | null;
|
|
24
|
+
payload: ReplyPayload;
|
|
25
|
+
hint?: ChannelOutboundPayloadHint;
|
|
26
|
+
}): boolean;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { resolveMeetAccount } from "./accounts.js";
|
|
2
|
+
import { getExecApprovalReplyMetadata, isChannelExecApprovalClientEnabledFromConfig, matchesApprovalRequestFilters, resolveApprovalApprovers, } from "openclaw/plugin-sdk/approval-runtime";
|
|
3
|
+
function normalizeMeetApproverId(value) {
|
|
4
|
+
const trimmed = value.trim();
|
|
5
|
+
if (!trimmed) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
// Meet 用户ID是数字
|
|
9
|
+
if (/^-?\d+$/.test(trimmed)) {
|
|
10
|
+
return trimmed;
|
|
11
|
+
}
|
|
12
|
+
// 支持 user:ID 格式
|
|
13
|
+
const match = trimmed.match(/^user:(-?\d+)$/i);
|
|
14
|
+
return match ? match[1] : undefined;
|
|
15
|
+
}
|
|
16
|
+
function resolveMeetOwnerApprovers(cfg) {
|
|
17
|
+
const ownerAllowFrom = cfg.commands?.ownerAllowFrom;
|
|
18
|
+
if (!Array.isArray(ownerAllowFrom) || ownerAllowFrom.length === 0) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
return resolveApprovalApprovers({
|
|
22
|
+
explicit: ownerAllowFrom,
|
|
23
|
+
normalizeApprover: (value) => normalizeMeetApproverId(String(value)),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function getMeetExecApprovalApprovers(params) {
|
|
27
|
+
return resolveApprovalApprovers({
|
|
28
|
+
explicit: params.configOverride?.approvers ??
|
|
29
|
+
resolveMeetAccount(params).config.execApprovals?.approvers ??
|
|
30
|
+
resolveMeetOwnerApprovers(params.cfg),
|
|
31
|
+
normalizeApprover: (value) => normalizeMeetApproverId(String(value)),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export function isMeetExecApprovalClientEnabled(params) {
|
|
35
|
+
const config = params.configOverride ?? resolveMeetAccount(params).config.execApprovals;
|
|
36
|
+
return isChannelExecApprovalClientEnabledFromConfig({
|
|
37
|
+
enabled: config?.enabled,
|
|
38
|
+
approverCount: getMeetExecApprovalApprovers({
|
|
39
|
+
cfg: params.cfg,
|
|
40
|
+
accountId: params.accountId,
|
|
41
|
+
configOverride: params.configOverride,
|
|
42
|
+
}).length,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export function isMeetExecApprovalApprover(params) {
|
|
46
|
+
const senderId = params.senderId?.trim();
|
|
47
|
+
if (!senderId) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return getMeetExecApprovalApprovers({
|
|
51
|
+
cfg: params.cfg,
|
|
52
|
+
accountId: params.accountId,
|
|
53
|
+
configOverride: params.configOverride,
|
|
54
|
+
}).includes(senderId);
|
|
55
|
+
}
|
|
56
|
+
export function shouldSuppressLocalMeetExecApprovalPrompt(params) {
|
|
57
|
+
const metadata = getExecApprovalReplyMetadata(params.payload);
|
|
58
|
+
const config = resolveMeetAccount(params).config.execApprovals;
|
|
59
|
+
return (params.hint?.kind === "approval-pending" &&
|
|
60
|
+
params.hint.nativeRouteActive === true &&
|
|
61
|
+
isMeetExecApprovalClientEnabled(params) &&
|
|
62
|
+
metadata !== null &&
|
|
63
|
+
matchesApprovalRequestFilters({
|
|
64
|
+
request: {
|
|
65
|
+
agentId: metadata.agentId,
|
|
66
|
+
sessionKey: metadata.sessionKey,
|
|
67
|
+
},
|
|
68
|
+
agentFilter: config?.agentFilter,
|
|
69
|
+
sessionFilter: config?.sessionFilter,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const MEET_PLUGIN_VERSION = "3.
|
|
1
|
+
export declare const MEET_PLUGIN_VERSION = "3.6.0";
|
|
2
2
|
export declare const MEET_OPENCLAW_VERSION = "2026.5.18";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const MEET_PLUGIN_VERSION = "3.
|
|
1
|
+
export const MEET_PLUGIN_VERSION = "3.6.0";
|
|
2
2
|
export const MEET_OPENCLAW_VERSION = "2026.5.18";
|
package/dist/src/outbound.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getMeetRuntime } from "./runtime.js";
|
|
2
2
|
import { sendMessageMeet, sendMediaMeet } from "./send.js";
|
|
3
|
+
import { shouldSuppressLocalMeetExecApprovalPrompt } from "./exec-approvals.js";
|
|
3
4
|
export const meetOutbound = {
|
|
4
5
|
deliveryMode: "direct",
|
|
5
6
|
chunker: (text, limit) => {
|
|
@@ -31,4 +32,12 @@ export const meetOutbound = {
|
|
|
31
32
|
});
|
|
32
33
|
return { channel: "meet", messageId: result.messageId, chatId: result.chatId };
|
|
33
34
|
},
|
|
35
|
+
shouldSuppressLocalPayloadPrompt: (params) => {
|
|
36
|
+
return shouldSuppressLocalMeetExecApprovalPrompt({
|
|
37
|
+
cfg: params.cfg,
|
|
38
|
+
accountId: params.accountId,
|
|
39
|
+
payload: params.payload,
|
|
40
|
+
hint: params.hint,
|
|
41
|
+
});
|
|
42
|
+
},
|
|
34
43
|
};
|
|
@@ -15,6 +15,7 @@ export type CreateMeetReplyDispatcherOpts = {
|
|
|
15
15
|
agentId: string;
|
|
16
16
|
runtime: RuntimeEnv;
|
|
17
17
|
chatId: string;
|
|
18
|
+
threadId?: string;
|
|
18
19
|
senderId?: string;
|
|
19
20
|
mentionedBot?: boolean;
|
|
20
21
|
replyToMessageId?: string;
|
|
@@ -28,11 +29,20 @@ export type CreateMeetReplyDispatcherOpts = {
|
|
|
28
29
|
typingMode?: "none" | "instant" | "message";
|
|
29
30
|
};
|
|
30
31
|
export declare function createMeetReplyDispatcher(opts: CreateMeetReplyDispatcherOpts): Promise<{
|
|
31
|
-
dispatcher: import("node_modules/openclaw/dist/plugin-sdk/
|
|
32
|
+
dispatcher: import("node_modules/openclaw/dist/plugin-sdk/reply-dispatcher.types-B0sivCQE.js").r;
|
|
32
33
|
replyOptions: {
|
|
33
34
|
sourceReplyDeliveryMode?: import("openclaw/plugin-sdk/channel-reply-pipeline").SourceReplyDeliveryMode | undefined;
|
|
34
35
|
onReplyStart?: (() => Promise<void> | void) | undefined;
|
|
35
|
-
onTypingController?: ((typing:
|
|
36
|
+
onTypingController?: ((typing: {
|
|
37
|
+
onReplyStart: () => Promise<void>;
|
|
38
|
+
startTypingLoop: () => Promise<void>;
|
|
39
|
+
startTypingOnText: (text?: string) => Promise<void>;
|
|
40
|
+
refreshTypingTtl: () => void;
|
|
41
|
+
isActive: () => boolean;
|
|
42
|
+
markRunComplete: () => void;
|
|
43
|
+
markDispatchIdle: () => void;
|
|
44
|
+
cleanup: () => void;
|
|
45
|
+
}) => void) | undefined;
|
|
36
46
|
onTypingCleanup?: (() => void) | undefined;
|
|
37
47
|
};
|
|
38
48
|
markDispatchIdle: () => void;
|
|
@@ -83,7 +83,7 @@ export function protectMentionsInChunks(chunks) {
|
|
|
83
83
|
return result;
|
|
84
84
|
}
|
|
85
85
|
export async function createMeetReplyDispatcher(opts) {
|
|
86
|
-
const { cfg, agentId, chatId, senderId, mentionedBot, replyToMessageId, accountId, mediaLocalRoots, sessionInfo, apiToken, apiEndpoint, typingMode } = opts;
|
|
86
|
+
const { cfg, agentId, chatId, threadId, senderId, mentionedBot, replyToMessageId, accountId, mediaLocalRoots, sessionInfo, apiToken, apiEndpoint, typingMode } = opts;
|
|
87
87
|
const core = getMeetRuntime();
|
|
88
88
|
const textChunkLimit = core.channel.text.resolveTextChunkLimit(cfg, "meet", accountId, {
|
|
89
89
|
fallbackLimit: 4000,
|
|
@@ -210,6 +210,7 @@ export async function createMeetReplyDispatcher(opts) {
|
|
|
210
210
|
await sendMediaMeet({
|
|
211
211
|
cfg,
|
|
212
212
|
to: chatId,
|
|
213
|
+
threadId,
|
|
213
214
|
text: text.trim() || undefined,
|
|
214
215
|
mediaUrl: mediaUrls[0],
|
|
215
216
|
mediaLocalRoots,
|
|
@@ -221,6 +222,7 @@ export async function createMeetReplyDispatcher(opts) {
|
|
|
221
222
|
await sendMediaMeet({
|
|
222
223
|
cfg,
|
|
223
224
|
to: chatId,
|
|
225
|
+
threadId,
|
|
224
226
|
text: undefined,
|
|
225
227
|
mediaUrl: mediaUrls[i],
|
|
226
228
|
mediaLocalRoots,
|
|
@@ -242,6 +244,7 @@ export async function createMeetReplyDispatcher(opts) {
|
|
|
242
244
|
await sendMessageMeet({
|
|
243
245
|
cfg,
|
|
244
246
|
to: chatId,
|
|
247
|
+
threadId,
|
|
245
248
|
text: chunk,
|
|
246
249
|
accountId,
|
|
247
250
|
replyToMessageId,
|
|
@@ -290,6 +293,7 @@ export async function createMeetReplyDispatcher(opts) {
|
|
|
290
293
|
await sendMessageMeet({
|
|
291
294
|
cfg,
|
|
292
295
|
to: chatId,
|
|
296
|
+
threadId,
|
|
293
297
|
text: userMessage,
|
|
294
298
|
accountId,
|
|
295
299
|
runtime: opts.runtime,
|
package/dist/src/sdk-bridge.d.ts
CHANGED
|
@@ -25,3 +25,13 @@ export declare function msgContentToContext(msg: MsgContent, botUserId: string,
|
|
|
25
25
|
export declare function enrichContextWithUserNames(ctx: MeetMessageContext, bot: MeetBot, accountId?: string): Promise<void>;
|
|
26
26
|
export declare function parseTargetToSessionInfo(target: string, botUserId: number): SessionInfo;
|
|
27
27
|
export declare function buildMeetTarget(sessionInfo: SessionInfo, botUserId: number): string;
|
|
28
|
+
export interface MergedForwardEntry {
|
|
29
|
+
sender: string;
|
|
30
|
+
body: string;
|
|
31
|
+
timestamp?: number;
|
|
32
|
+
messageId: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 解析合并转发消息的 content,展开为多条可读记录
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseMergedForwardContent(content: string): MergedForwardEntry[];
|
package/dist/src/sdk-bridge.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodeMeetDmTopicId } from "./dm-topic-codec.js";
|
|
1
2
|
import { rememberMeetUser } from "./directory-cache.js";
|
|
2
3
|
export function mapSessionType(sessionType) {
|
|
3
4
|
return sessionType === 1 ? "direct" : "channel";
|
|
@@ -159,6 +160,9 @@ export function msgContentToContext(msg, botUserId, quoteMsgMap = {}) {
|
|
|
159
160
|
const chatId = chatType === "direct"
|
|
160
161
|
? `user:${msg.fromUid}`
|
|
161
162
|
: `channel:${msg.sessionInfo.secondID}`;
|
|
163
|
+
const threadId = chatType === "direct" && typeof msg.dmTopicID === "string" && msg.dmTopicID.trim()
|
|
164
|
+
? encodeMeetDmTopicId(msg.dmTopicID)
|
|
165
|
+
: undefined;
|
|
162
166
|
const mentionedBot = msg.atIds?.includes(Number(botUserId)) ?? false;
|
|
163
167
|
const replyContext = resolveQuoteMessage(msg, quoteMsgMap);
|
|
164
168
|
const media = extractMediaAttachments(msg);
|
|
@@ -171,6 +175,7 @@ export function msgContentToContext(msg, botUserId, quoteMsgMap = {}) {
|
|
|
171
175
|
}
|
|
172
176
|
return {
|
|
173
177
|
chatId,
|
|
178
|
+
threadId,
|
|
174
179
|
messageId: String(msg.seqId ?? 0),
|
|
175
180
|
senderId: String(msg.fromUid ?? 0),
|
|
176
181
|
senderOpenId: String(msg.fromUid ?? 0),
|
|
@@ -302,3 +307,44 @@ export function buildMeetTarget(sessionInfo, botUserId) {
|
|
|
302
307
|
}
|
|
303
308
|
return `channel:${sessionInfo.secondID}`;
|
|
304
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* 解析合并转发消息的 content,展开为多条可读记录
|
|
312
|
+
*/
|
|
313
|
+
export function parseMergedForwardContent(content) {
|
|
314
|
+
let parsed;
|
|
315
|
+
try {
|
|
316
|
+
parsed = JSON.parse(content);
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
const msgs = parsed.msgs;
|
|
322
|
+
if (!Array.isArray(msgs) || msgs.length === 0) {
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
const profileMap = parsed.userProfileMap ?? {};
|
|
326
|
+
const buildAttachmentPlaceholder = (sub) => {
|
|
327
|
+
const attachments = [
|
|
328
|
+
...(sub.extraInfo?.attechmentInfo ? [sub.extraInfo.attechmentInfo] : []),
|
|
329
|
+
...(Array.isArray(sub.extraInfo?.attechmentInfos) ? sub.extraInfo.attechmentInfos : []),
|
|
330
|
+
];
|
|
331
|
+
if (attachments.length === 0) {
|
|
332
|
+
return "";
|
|
333
|
+
}
|
|
334
|
+
return attachments
|
|
335
|
+
.map((attachment) => `[attachment:${attachment.fileName || attachment.mimeType || "file"}]`)
|
|
336
|
+
.join(" ");
|
|
337
|
+
};
|
|
338
|
+
return msgs.map((sub, i) => {
|
|
339
|
+
const senderId = String(sub.fromUid ?? 0);
|
|
340
|
+
const profile = profileMap[senderId];
|
|
341
|
+
const sender = profile?.nickName || senderId;
|
|
342
|
+
const attachmentPlaceholder = buildAttachmentPlaceholder(sub);
|
|
343
|
+
return {
|
|
344
|
+
sender,
|
|
345
|
+
body: [sub.content?.trim() || "", attachmentPlaceholder].filter(Boolean).join(" "),
|
|
346
|
+
timestamp: sub.timestamp,
|
|
347
|
+
messageId: String(sub.seqId ?? `merged-${i}`),
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
}
|
package/dist/src/send.d.ts
CHANGED
|
@@ -15,11 +15,17 @@ export declare function extractAtIds(text: string): {
|
|
|
15
15
|
atIds: number[];
|
|
16
16
|
};
|
|
17
17
|
export declare function mergeAtIds(explicitAtIds?: number[], extractedAtIds?: number[]): number[];
|
|
18
|
+
export declare function resolveMeetThreadTarget<T extends {
|
|
19
|
+
sessionType: number;
|
|
20
|
+
}>(sessionInfo: T, threadId?: string): T | (T & {
|
|
21
|
+
threadId?: number;
|
|
22
|
+
});
|
|
18
23
|
export type SendMessageMeetOpts = {
|
|
19
24
|
cfg: ClawdbotConfig;
|
|
20
25
|
to: string;
|
|
21
26
|
text: string;
|
|
22
27
|
accountId?: string;
|
|
28
|
+
threadId?: string;
|
|
23
29
|
replyToMessageId?: string;
|
|
24
30
|
atIds?: number[];
|
|
25
31
|
/** Optional runtime for consistent logging. If not provided, falls back to console. */
|
|
@@ -32,6 +38,7 @@ export declare function sendMessageMeet(opts: SendMessageMeetOpts): Promise<{
|
|
|
32
38
|
export type SendMediaMeetOpts = {
|
|
33
39
|
cfg: ClawdbotConfig;
|
|
34
40
|
to: string;
|
|
41
|
+
threadId?: string;
|
|
35
42
|
text?: string;
|
|
36
43
|
mediaUrl: string;
|
|
37
44
|
mediaLocalRoots?: readonly string[];
|
package/dist/src/send.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveMeetAccount } from "./accounts.js";
|
|
2
2
|
import { getMeetClient, createMeetClient } from "./client.js";
|
|
3
|
+
import { decodeMeetDmTopicThreadId } from "./dm-topic-codec.js";
|
|
3
4
|
import { parseTargetToSessionInfo } from "./sdk-bridge.js";
|
|
4
5
|
import { getMeetRuntime } from "./runtime.js";
|
|
5
6
|
import { rewriteMeetKnownMentions } from "./mentions.js";
|
|
@@ -122,8 +123,27 @@ export function extractAtIds(text) {
|
|
|
122
123
|
export function mergeAtIds(explicitAtIds, extractedAtIds) {
|
|
123
124
|
return [...new Set([...(explicitAtIds ?? []), ...(extractedAtIds ?? [])])];
|
|
124
125
|
}
|
|
126
|
+
// OpenClaw upper layers use a channel-agnostic `threadId` abstraction.
|
|
127
|
+
// Meet maps that abstraction differently by session type:
|
|
128
|
+
// - channel/group sessions use Meet `threadId`
|
|
129
|
+
// - direct-message sessions use Meet `dmTopicID`
|
|
130
|
+
export function resolveMeetThreadTarget(sessionInfo, threadId) {
|
|
131
|
+
if (!threadId) {
|
|
132
|
+
return sessionInfo;
|
|
133
|
+
}
|
|
134
|
+
if (sessionInfo.sessionType === 3) {
|
|
135
|
+
return {
|
|
136
|
+
...sessionInfo,
|
|
137
|
+
threadId: Number(threadId),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (sessionInfo.sessionType === 1) {
|
|
141
|
+
return sessionInfo;
|
|
142
|
+
}
|
|
143
|
+
return sessionInfo;
|
|
144
|
+
}
|
|
125
145
|
export async function sendMessageMeet(opts) {
|
|
126
|
-
const { cfg, to, text, accountId, atIds: explicitAtIds, runtime } = opts;
|
|
146
|
+
const { cfg, to, text, accountId, threadId, atIds: explicitAtIds, runtime } = opts;
|
|
127
147
|
const log = runtime?.log ?? console.log;
|
|
128
148
|
const logError = runtime?.error ?? console.error;
|
|
129
149
|
const account = resolveMeetAccount({ cfg, accountId });
|
|
@@ -170,10 +190,13 @@ export async function sendMessageMeet(opts) {
|
|
|
170
190
|
const finalAtIds = mergeAtIds(explicitAtIds, extractedAtIds);
|
|
171
191
|
log(`send message to=${to} atIds=${finalAtIds.join(",") || "none"}`);
|
|
172
192
|
const sessionInfo = parseTargetToSessionInfo(to, Number(botUserId));
|
|
193
|
+
const finalSessionInfo = resolveMeetThreadTarget(sessionInfo, threadId);
|
|
194
|
+
const dmTopicID = sessionInfo.sessionType === 1 ? decodeMeetDmTopicThreadId(threadId) : undefined;
|
|
173
195
|
try {
|
|
174
|
-
const result = await bot.sendMessage(
|
|
196
|
+
const result = await bot.sendMessage(finalSessionInfo, {
|
|
175
197
|
content: cleanText,
|
|
176
198
|
atIds: finalAtIds,
|
|
199
|
+
...(dmTopicID ? { dmTopicID } : {}),
|
|
177
200
|
});
|
|
178
201
|
return {
|
|
179
202
|
messageId: String(result.msgContent?.seqId ?? 0),
|
|
@@ -186,7 +209,7 @@ export async function sendMessageMeet(opts) {
|
|
|
186
209
|
}
|
|
187
210
|
}
|
|
188
211
|
export async function sendMediaMeet(opts) {
|
|
189
|
-
const { cfg, to, text, mediaUrl, mediaLocalRoots, accountId, onProgress, runtime: logRuntime } = opts;
|
|
212
|
+
const { cfg, to, threadId, text, mediaUrl, mediaLocalRoots, accountId, onProgress, runtime: logRuntime } = opts;
|
|
190
213
|
const log = logRuntime?.log ?? console.log;
|
|
191
214
|
const logError = logRuntime?.error ?? console.error;
|
|
192
215
|
const account = resolveMeetAccount({ cfg, accountId });
|
|
@@ -236,6 +259,8 @@ export async function sendMediaMeet(opts) {
|
|
|
236
259
|
throw new Error(`Media file too large: ${media.buffer.length} bytes (max: ${maxBytes})`);
|
|
237
260
|
}
|
|
238
261
|
const sessionInfo = parseTargetToSessionInfo(to, Number(botUserId));
|
|
262
|
+
const finalSessionInfo = resolveMeetThreadTarget(sessionInfo, threadId);
|
|
263
|
+
const dmTopicID = sessionInfo.sessionType === 1 ? decodeMeetDmTopicThreadId(threadId) : undefined;
|
|
239
264
|
const rawFileName = media.fileName || "file";
|
|
240
265
|
const contentType = resolveContentType(rawFileName, media.contentType);
|
|
241
266
|
// 确保文件名有正确的扩展名
|
|
@@ -253,11 +278,12 @@ export async function sendMediaMeet(opts) {
|
|
|
253
278
|
}
|
|
254
279
|
: undefined;
|
|
255
280
|
try {
|
|
256
|
-
const result = await bot.sendMedia(
|
|
281
|
+
const result = await bot.sendMedia(finalSessionInfo, {
|
|
257
282
|
buffer: media.buffer,
|
|
258
283
|
fileName,
|
|
259
284
|
contentType,
|
|
260
285
|
content: text || "",
|
|
286
|
+
...(dmTopicID ? { dmTopicID } : {}),
|
|
261
287
|
onProgress: progressCallback,
|
|
262
288
|
});
|
|
263
289
|
return {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { SessionInfo, AttachmentInfo, UploadProgress } from "@meet-im/meet-bot-jssdk";
|
|
2
2
|
import type { z } from "zod";
|
|
3
|
-
import { MeetConfigSchema, MeetAccountConfigSchema, MeetChannelConfigSchema, MeetGroupConfigSchema } from "./config-schema.js";
|
|
3
|
+
import { MeetConfigSchema, MeetAccountConfigSchema, MeetChannelConfigSchema, MeetGroupConfigSchema, MeetExecApprovalConfigSchema } from "./config-schema.js";
|
|
4
4
|
export type MeetConfig = z.infer<typeof MeetConfigSchema>;
|
|
5
5
|
export type MeetAccountConfig = z.infer<typeof MeetAccountConfigSchema>;
|
|
6
6
|
export type MeetChannelConfig = z.infer<typeof MeetChannelConfigSchema>;
|
|
7
7
|
export type MeetGroupConfig = z.infer<typeof MeetGroupConfigSchema>;
|
|
8
|
+
export type MeetExecApprovalConfig = z.infer<typeof MeetExecApprovalConfigSchema>;
|
|
8
9
|
export type { AttachmentInfo, UploadProgress };
|
|
9
10
|
export type ResolvedMeetAccount = {
|
|
10
11
|
accountId: string;
|
|
@@ -29,6 +30,7 @@ export type MeetMention = {
|
|
|
29
30
|
};
|
|
30
31
|
export type MeetMessageContext = {
|
|
31
32
|
chatId: string;
|
|
33
|
+
threadId?: string;
|
|
32
34
|
messageId: string;
|
|
33
35
|
senderId: string;
|
|
34
36
|
senderOpenId: string;
|
package/openclaw.plugin.json
CHANGED
|
@@ -28,6 +28,22 @@
|
|
|
28
28
|
"type": "array",
|
|
29
29
|
"items": { "anyOf": [{ "type": "string" }, { "type": "number" }] }
|
|
30
30
|
},
|
|
31
|
+
"execApprovals": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"properties": {
|
|
35
|
+
"enabled": {
|
|
36
|
+
"anyOf": [{ "type": "boolean" }, { "type": "string", "const": "auto" }]
|
|
37
|
+
},
|
|
38
|
+
"approvers": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"items": { "anyOf": [{ "type": "string" }, { "type": "number" }] }
|
|
41
|
+
},
|
|
42
|
+
"target": { "type": "string", "enum": ["dm", "channel", "both"] },
|
|
43
|
+
"agentFilter": { "type": "array", "items": { "type": "string" } },
|
|
44
|
+
"sessionFilter": { "type": "array", "items": { "type": "string" } }
|
|
45
|
+
}
|
|
46
|
+
},
|
|
31
47
|
"requireMention": { "type": "boolean" },
|
|
32
48
|
"systemPrompt": { "type": "string" },
|
|
33
49
|
"channels": {
|
|
@@ -88,6 +104,22 @@
|
|
|
88
104
|
"type": "array",
|
|
89
105
|
"items": { "anyOf": [{ "type": "string" }, { "type": "number" }] }
|
|
90
106
|
},
|
|
107
|
+
"execApprovals": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"additionalProperties": false,
|
|
110
|
+
"properties": {
|
|
111
|
+
"enabled": {
|
|
112
|
+
"anyOf": [{ "type": "boolean" }, { "type": "string", "const": "auto" }]
|
|
113
|
+
},
|
|
114
|
+
"approvers": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"items": { "anyOf": [{ "type": "string" }, { "type": "number" }] }
|
|
117
|
+
},
|
|
118
|
+
"target": { "type": "string", "enum": ["dm", "channel", "both"] },
|
|
119
|
+
"agentFilter": { "type": "array", "items": { "type": "string" } },
|
|
120
|
+
"sessionFilter": { "type": "array", "items": { "type": "string" } }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
91
123
|
"requireMention": { "type": "boolean" },
|
|
92
124
|
"systemPrompt": { "type": "string" },
|
|
93
125
|
"historyLimit": { "type": "number", "minimum": 0 },
|
package/package.json
CHANGED