@openclaw/qqbot 2026.5.14-beta.1 → 2026.5.16-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/api.js +6 -6
- package/dist/channel-CsKOF-PJ.js +1102 -0
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-DWWV_GAa.js → channel.setup-D_8Ryfln.js} +1 -2
- package/dist/{config-DiBiPtf4.js → config-schema-BE5YP0NG.js} +316 -12
- package/dist/{gateway-DEJhmN-p.js → gateway-pvpMZ-Sn.js} +9 -15
- package/dist/{handler-runtime-COWL4RMq.js → handler-runtime-CsNYWx6E.js} +3 -3
- package/dist/{outbound-BdQEr78b.js → outbound-DekhyR4u.js} +2 -2
- package/dist/{request-context-Betw4nJr.js → request-context-FhYx1qjS.js} +2 -2
- package/dist/{sender-BeE_CBbN.js → runtime-BFcYWYuk.js} +17 -1
- package/dist/runtime-api.js +1 -1
- package/dist/setup-plugin-api.js +1 -1
- package/package.json +5 -5
- package/dist/channel-BYNY5Nwb.js +0 -606
- package/dist/config-schema-BeJQUheK.js +0 -308
- package/dist/exec-approvals-Deww_EGt.js +0 -238
- package/dist/narrowing-Bj4eMufj.js +0 -25
- package/dist/runtime-B-cqLImu.js +0 -18
- package/dist/target-parser-ByU3srDK.js +0 -245
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import { n as normalizeLowercaseStringOrEmpty } from "./string-normalize-C46CS-F1.js";
|
|
2
|
-
import { a as resolveQQBotAccount, c as describeAccount, i as resolveDefaultQQBotAccountId, l as formatAllowFrom, n as applyQQBotAccountConfig, r as listQQBotAccountIds, s as applyAccountConfig, u as isAccountConfigured } from "./config-DiBiPtf4.js";
|
|
3
|
-
import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";
|
|
4
|
-
import { applyAccountNameToChannelSection, deleteAccountFromConfigSection, setAccountEnabledInConfigSection } from "openclaw/plugin-sdk/core";
|
|
5
|
-
import { DEFAULT_ACCOUNT_ID, createStandardChannelSetupStatus, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
6
|
-
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
|
7
|
-
import { AllowFromListSchema, buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
8
|
-
import { z } from "zod";
|
|
9
|
-
//#region extensions/qqbot/src/engine/config/setup-logic.ts
|
|
10
|
-
/**
|
|
11
|
-
* QQBot setup business logic (pure layer).
|
|
12
|
-
* QQBot setup 相关纯业务逻辑。
|
|
13
|
-
*
|
|
14
|
-
* Token parsing, input validation, and setup config application.
|
|
15
|
-
* All functions are framework-agnostic and operate on plain objects.
|
|
16
|
-
*/
|
|
17
|
-
/** Parse an inline "appId:clientSecret" token string. */
|
|
18
|
-
function parseInlineToken(token) {
|
|
19
|
-
const colonIdx = token.indexOf(":");
|
|
20
|
-
if (colonIdx <= 0 || colonIdx === token.length - 1) return null;
|
|
21
|
-
const appId = token.slice(0, colonIdx).trim();
|
|
22
|
-
const clientSecret = token.slice(colonIdx + 1).trim();
|
|
23
|
-
if (!appId || !clientSecret) return null;
|
|
24
|
-
return {
|
|
25
|
-
appId,
|
|
26
|
-
clientSecret
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
/** Validate setup input for a QQBot account. Returns an error string or null. */
|
|
30
|
-
function validateSetupInput(accountId, input) {
|
|
31
|
-
if (!input.token && !input.tokenFile && !input.useEnv) return "QQBot requires --token (format: appId:clientSecret) or --use-env";
|
|
32
|
-
if (input.useEnv && accountId !== "default") return "QQBot --use-env only supports the default account";
|
|
33
|
-
if (input.token && !parseInlineToken(input.token)) return "QQBot --token must be in appId:clientSecret format";
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
/** Apply setup input to account config. Returns updated config. */
|
|
37
|
-
function applySetupAccountConfig(cfg, accountId, input) {
|
|
38
|
-
if (input.useEnv && accountId !== "default") return cfg;
|
|
39
|
-
let appId = "";
|
|
40
|
-
let clientSecret = "";
|
|
41
|
-
if (input.token) {
|
|
42
|
-
const parsed = parseInlineToken(input.token);
|
|
43
|
-
if (!parsed) return cfg;
|
|
44
|
-
appId = parsed.appId;
|
|
45
|
-
clientSecret = parsed.clientSecret;
|
|
46
|
-
}
|
|
47
|
-
if (!appId && !input.tokenFile && !input.useEnv) return cfg;
|
|
48
|
-
return applyAccountConfig(cfg, accountId, {
|
|
49
|
-
appId,
|
|
50
|
-
clientSecret,
|
|
51
|
-
clientSecretFile: input.tokenFile,
|
|
52
|
-
name: input.name
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
//#endregion
|
|
56
|
-
//#region extensions/qqbot/src/bridge/config-shared.ts
|
|
57
|
-
const qqbotMeta = {
|
|
58
|
-
id: "qqbot",
|
|
59
|
-
label: "QQ Bot",
|
|
60
|
-
selectionLabel: "QQ Bot (Bot API)",
|
|
61
|
-
docsPath: "/channels/qqbot",
|
|
62
|
-
blurb: "Connect to QQ via official QQ Bot API",
|
|
63
|
-
order: 50
|
|
64
|
-
};
|
|
65
|
-
function validateQQBotSetupInput(params) {
|
|
66
|
-
return validateSetupInput(params.accountId, params.input);
|
|
67
|
-
}
|
|
68
|
-
function applyQQBotSetupAccountConfig(params) {
|
|
69
|
-
return applySetupAccountConfig(params.cfg, params.accountId, params.input);
|
|
70
|
-
}
|
|
71
|
-
function isQQBotConfigured(account) {
|
|
72
|
-
return isAccountConfigured(account);
|
|
73
|
-
}
|
|
74
|
-
function describeQQBotAccount(account) {
|
|
75
|
-
return describeAccount(account);
|
|
76
|
-
}
|
|
77
|
-
function formatQQBotAllowFrom(params) {
|
|
78
|
-
return formatAllowFrom(params.allowFrom);
|
|
79
|
-
}
|
|
80
|
-
const qqbotConfigAdapter = {
|
|
81
|
-
listAccountIds: (cfg) => listQQBotAccountIds(cfg),
|
|
82
|
-
resolveAccount: (cfg, accountId) => resolveQQBotAccount(cfg, accountId, { allowUnresolvedSecretRef: true }),
|
|
83
|
-
defaultAccountId: (cfg) => resolveDefaultQQBotAccountId(cfg),
|
|
84
|
-
setAccountEnabled: ({ cfg, accountId, enabled }) => setAccountEnabledInConfigSection({
|
|
85
|
-
cfg,
|
|
86
|
-
sectionKey: "qqbot",
|
|
87
|
-
accountId,
|
|
88
|
-
enabled,
|
|
89
|
-
allowTopLevel: true
|
|
90
|
-
}),
|
|
91
|
-
deleteAccount: ({ cfg, accountId }) => deleteAccountFromConfigSection({
|
|
92
|
-
cfg,
|
|
93
|
-
sectionKey: "qqbot",
|
|
94
|
-
accountId,
|
|
95
|
-
clearBaseFields: [
|
|
96
|
-
"appId",
|
|
97
|
-
"clientSecret",
|
|
98
|
-
"clientSecretFile",
|
|
99
|
-
"name"
|
|
100
|
-
]
|
|
101
|
-
}),
|
|
102
|
-
isConfigured: isQQBotConfigured,
|
|
103
|
-
describeAccount: describeQQBotAccount,
|
|
104
|
-
resolveAllowFrom: ({ cfg, accountId }) => resolveQQBotAccount(cfg, accountId, { allowUnresolvedSecretRef: true }).config?.allowFrom,
|
|
105
|
-
formatAllowFrom: ({ allowFrom }) => formatQQBotAllowFrom({ allowFrom })
|
|
106
|
-
};
|
|
107
|
-
const qqbotSetupAdapterShared = {
|
|
108
|
-
resolveAccountId: ({ cfg, accountId }) => normalizeLowercaseStringOrEmpty(accountId) || resolveDefaultQQBotAccountId(cfg),
|
|
109
|
-
applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
|
|
110
|
-
cfg,
|
|
111
|
-
channelKey: "qqbot",
|
|
112
|
-
accountId,
|
|
113
|
-
name
|
|
114
|
-
}),
|
|
115
|
-
validateInput: ({ accountId, input }) => validateQQBotSetupInput({
|
|
116
|
-
accountId,
|
|
117
|
-
input
|
|
118
|
-
}),
|
|
119
|
-
applyAccountConfig: ({ cfg, accountId, input }) => applyQQBotSetupAccountConfig({
|
|
120
|
-
cfg,
|
|
121
|
-
accountId,
|
|
122
|
-
input
|
|
123
|
-
})
|
|
124
|
-
};
|
|
125
|
-
//#endregion
|
|
126
|
-
//#region extensions/qqbot/src/bridge/setup/finalize.ts
|
|
127
|
-
function isQQBotAccountConfigured(cfg, accountId) {
|
|
128
|
-
const account = resolveQQBotAccount(cfg, accountId, { allowUnresolvedSecretRef: true });
|
|
129
|
-
return Boolean(account.appId && account.clientSecret);
|
|
130
|
-
}
|
|
131
|
-
async function linkViaQrCode(params) {
|
|
132
|
-
try {
|
|
133
|
-
const { qrConnect } = await import("@tencent-connect/qqbot-connector");
|
|
134
|
-
const accounts = await qrConnect({ source: "openclaw" });
|
|
135
|
-
if (accounts.length === 0) {
|
|
136
|
-
await params.prompter.note("未获取到任何 QQ Bot 账号信息。", "QQ Bot");
|
|
137
|
-
return params.cfg;
|
|
138
|
-
}
|
|
139
|
-
let next = params.cfg;
|
|
140
|
-
for (let i = 0; i < accounts.length; i++) {
|
|
141
|
-
const { appId, appSecret } = accounts[i];
|
|
142
|
-
const targetAccountId = i === 0 ? params.accountId : appId;
|
|
143
|
-
next = applyQQBotAccountConfig(next, targetAccountId, {
|
|
144
|
-
appId,
|
|
145
|
-
clientSecret: appSecret
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
if (accounts.length === 1) params.runtime.log(`✔ QQ Bot 绑定成功!(AppID: ${accounts[0].appId})`);
|
|
149
|
-
else {
|
|
150
|
-
const idList = accounts.map((a) => a.appId).join(", ");
|
|
151
|
-
params.runtime.log(`✔ ${accounts.length} 个 QQ Bot 绑定成功!(AppID: ${idList})`);
|
|
152
|
-
}
|
|
153
|
-
return next;
|
|
154
|
-
} catch (error) {
|
|
155
|
-
params.runtime.error(`QQ Bot 绑定失败: ${String(error)}`);
|
|
156
|
-
await params.prompter.note(["绑定失败,您可以稍后手动配置。", `文档: ${formatDocsLink("/channels/qqbot", "qqbot")}`].join("\n"), "QQ Bot");
|
|
157
|
-
return params.cfg;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
async function linkViaManualInput(params) {
|
|
161
|
-
const appId = await params.prompter.text({
|
|
162
|
-
message: "请输入 QQ Bot AppID",
|
|
163
|
-
validate: (value) => value.trim() ? void 0 : "AppID 不能为空"
|
|
164
|
-
});
|
|
165
|
-
const appSecret = await params.prompter.text({
|
|
166
|
-
message: "请输入 QQ Bot AppSecret",
|
|
167
|
-
validate: (value) => value.trim() ? void 0 : "AppSecret 不能为空"
|
|
168
|
-
});
|
|
169
|
-
const next = applyQQBotAccountConfig(params.cfg, params.accountId, {
|
|
170
|
-
appId: appId.trim(),
|
|
171
|
-
clientSecret: appSecret.trim()
|
|
172
|
-
});
|
|
173
|
-
await params.prompter.note("✔ QQ Bot 配置完成!", "QQ Bot");
|
|
174
|
-
return next;
|
|
175
|
-
}
|
|
176
|
-
async function finalizeQQBotSetup(params) {
|
|
177
|
-
const accountId = params.accountId.trim() || DEFAULT_ACCOUNT_ID;
|
|
178
|
-
let next = params.cfg;
|
|
179
|
-
const configured = isQQBotAccountConfigured(next, accountId);
|
|
180
|
-
const mode = await params.prompter.select({
|
|
181
|
-
message: configured ? "QQ 已绑定,选择操作" : "选择 QQ 绑定方式",
|
|
182
|
-
options: [
|
|
183
|
-
{
|
|
184
|
-
value: "qr",
|
|
185
|
-
label: "扫码绑定(推荐)",
|
|
186
|
-
hint: "使用 QQ 扫描二维码自动完成绑定"
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
value: "manual",
|
|
190
|
-
label: "手动输入 QQ Bot AppID 和 AppSecret",
|
|
191
|
-
hint: "需到 QQ 开放平台 q.qq.com 查看"
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
value: "skip",
|
|
195
|
-
label: configured ? "保持当前配置" : "稍后配置"
|
|
196
|
-
}
|
|
197
|
-
]
|
|
198
|
-
});
|
|
199
|
-
if (mode === "qr") next = await linkViaQrCode({
|
|
200
|
-
cfg: next,
|
|
201
|
-
accountId,
|
|
202
|
-
prompter: params.prompter,
|
|
203
|
-
runtime: params.runtime
|
|
204
|
-
});
|
|
205
|
-
else if (mode === "manual") next = await linkViaManualInput({
|
|
206
|
-
cfg: next,
|
|
207
|
-
accountId,
|
|
208
|
-
prompter: params.prompter
|
|
209
|
-
});
|
|
210
|
-
else if (!configured) await params.prompter.note(["您可以稍后运行以下命令重新选择 QQ Bot 进行配置:", " openclaw channels add"].join("\n"), "QQ Bot");
|
|
211
|
-
return { cfg: next };
|
|
212
|
-
}
|
|
213
|
-
//#endregion
|
|
214
|
-
//#region extensions/qqbot/src/bridge/setup/surface.ts
|
|
215
|
-
const channel = "qqbot";
|
|
216
|
-
const qqbotSetupWizard = {
|
|
217
|
-
channel,
|
|
218
|
-
status: createStandardChannelSetupStatus({
|
|
219
|
-
channelLabel: "QQ Bot",
|
|
220
|
-
configuredLabel: "configured",
|
|
221
|
-
unconfiguredLabel: "needs AppID + AppSercet",
|
|
222
|
-
configuredHint: "configured",
|
|
223
|
-
unconfiguredHint: "needs AppID + AppSercet",
|
|
224
|
-
configuredScore: 1,
|
|
225
|
-
unconfiguredScore: 6,
|
|
226
|
-
resolveConfigured: ({ cfg, accountId }) => (accountId ? [accountId] : listQQBotAccountIds(cfg)).some((resolvedAccountId) => {
|
|
227
|
-
return isAccountConfigured(resolveQQBotAccount(cfg, resolvedAccountId, { allowUnresolvedSecretRef: true }));
|
|
228
|
-
})
|
|
229
|
-
}),
|
|
230
|
-
credentials: [],
|
|
231
|
-
finalize: async ({ cfg, accountId, forceAllowFrom, prompter, runtime }) => await finalizeQQBotSetup({
|
|
232
|
-
cfg,
|
|
233
|
-
accountId,
|
|
234
|
-
forceAllowFrom,
|
|
235
|
-
prompter,
|
|
236
|
-
runtime
|
|
237
|
-
}),
|
|
238
|
-
disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
|
|
239
|
-
};
|
|
240
|
-
//#endregion
|
|
241
|
-
//#region extensions/qqbot/src/config-schema.ts
|
|
242
|
-
const AudioFormatPolicySchema = z.object({
|
|
243
|
-
sttDirectFormats: z.array(z.string()).optional(),
|
|
244
|
-
uploadDirectFormats: z.array(z.string()).optional(),
|
|
245
|
-
transcodeEnabled: z.boolean().optional()
|
|
246
|
-
}).optional();
|
|
247
|
-
const QQBotSttSchema = z.object({
|
|
248
|
-
enabled: z.boolean().optional(),
|
|
249
|
-
provider: z.string().optional(),
|
|
250
|
-
baseUrl: z.string().optional(),
|
|
251
|
-
apiKey: z.string().optional(),
|
|
252
|
-
model: z.string().optional()
|
|
253
|
-
}).strict().optional();
|
|
254
|
-
/** When `true`, same as `mode: "partial"` and `c2cStreamApi: true` for C2C. Object form kept for legacy configs. */
|
|
255
|
-
const QQBotStreamingSchema = z.union([z.boolean(), z.object({
|
|
256
|
-
/** "partial" (default) enables block streaming; "off" disables it. */
|
|
257
|
-
mode: z.enum(["off", "partial"]).default("partial"),
|
|
258
|
-
/** @deprecated Prefer `streaming: true`. */
|
|
259
|
-
c2cStreamApi: z.boolean().optional()
|
|
260
|
-
}).passthrough()]).optional();
|
|
261
|
-
const QQBotExecApprovalsSchema = z.object({
|
|
262
|
-
enabled: z.union([z.boolean(), z.literal("auto")]).optional(),
|
|
263
|
-
approvers: z.array(z.string()).optional(),
|
|
264
|
-
agentFilter: z.array(z.string()).optional(),
|
|
265
|
-
sessionFilter: z.array(z.string()).optional(),
|
|
266
|
-
target: z.enum([
|
|
267
|
-
"dm",
|
|
268
|
-
"channel",
|
|
269
|
-
"both"
|
|
270
|
-
]).optional()
|
|
271
|
-
}).strict().optional();
|
|
272
|
-
const QQBotDmPolicySchema = z.enum([
|
|
273
|
-
"open",
|
|
274
|
-
"allowlist",
|
|
275
|
-
"disabled"
|
|
276
|
-
]).optional();
|
|
277
|
-
const QQBotGroupPolicySchema = z.enum([
|
|
278
|
-
"open",
|
|
279
|
-
"allowlist",
|
|
280
|
-
"disabled"
|
|
281
|
-
]).optional();
|
|
282
|
-
const QQBotAccountSchema = z.object({
|
|
283
|
-
enabled: z.boolean().optional(),
|
|
284
|
-
name: z.string().optional(),
|
|
285
|
-
appId: z.string().optional(),
|
|
286
|
-
clientSecret: buildSecretInputSchema().optional(),
|
|
287
|
-
clientSecretFile: z.string().optional(),
|
|
288
|
-
allowFrom: AllowFromListSchema,
|
|
289
|
-
groupAllowFrom: AllowFromListSchema,
|
|
290
|
-
dmPolicy: QQBotDmPolicySchema,
|
|
291
|
-
groupPolicy: QQBotGroupPolicySchema,
|
|
292
|
-
systemPrompt: z.string().optional(),
|
|
293
|
-
markdownSupport: z.boolean().optional(),
|
|
294
|
-
voiceDirectUploadFormats: z.array(z.string()).optional(),
|
|
295
|
-
audioFormatPolicy: AudioFormatPolicySchema,
|
|
296
|
-
urlDirectUpload: z.boolean().optional(),
|
|
297
|
-
upgradeUrl: z.string().optional(),
|
|
298
|
-
upgradeMode: z.enum(["doc", "hot-reload"]).optional(),
|
|
299
|
-
streaming: QQBotStreamingSchema,
|
|
300
|
-
execApprovals: QQBotExecApprovalsSchema
|
|
301
|
-
}).passthrough();
|
|
302
|
-
const qqbotChannelConfigSchema = buildChannelConfigSchema(QQBotAccountSchema.extend({
|
|
303
|
-
stt: QQBotSttSchema,
|
|
304
|
-
accounts: z.object({}).catchall(QQBotAccountSchema.passthrough()).optional(),
|
|
305
|
-
defaultAccount: z.string().optional()
|
|
306
|
-
}).passthrough());
|
|
307
|
-
//#endregion
|
|
308
|
-
export { qqbotSetupAdapterShared as a, qqbotMeta as i, qqbotSetupWizard as n, qqbotConfigAdapter as r, qqbotChannelConfigSchema as t };
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import { a as resolveQQBotAccount, r as listQQBotAccountIds } from "./config-DiBiPtf4.js";
|
|
2
|
-
import { resolveApprovalRequestChannelAccountId } from "openclaw/plugin-sdk/approval-native-runtime";
|
|
3
|
-
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
|
-
import { resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
5
|
-
import { createChannelExecApprovalProfile, isChannelExecApprovalClientEnabledFromConfig, matchesApprovalRequestFilters } from "openclaw/plugin-sdk/approval-client-runtime";
|
|
6
|
-
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
|
|
7
|
-
//#region extensions/qqbot/src/engine/approval/index.ts
|
|
8
|
-
function buildExecApprovalText(request) {
|
|
9
|
-
const expiresIn = Math.max(0, Math.round((request.expiresAtMs - Date.now()) / 1e3));
|
|
10
|
-
const lines = ["🔐 命令执行审批", ""];
|
|
11
|
-
const cmd = request.request.commandPreview ?? request.request.command ?? "";
|
|
12
|
-
if (cmd) lines.push(`\`\`\`\n${cmd.slice(0, 300)}\n\`\`\``);
|
|
13
|
-
if (request.request.cwd) lines.push(`\u{1f4c1} \u76ee\u5f55: ${request.request.cwd}`);
|
|
14
|
-
if (request.request.agentId) lines.push(`\u{1f916} Agent: ${request.request.agentId}`);
|
|
15
|
-
lines.push("", `\u23f1\ufe0f \u8d85\u65f6: ${expiresIn} \u79d2`);
|
|
16
|
-
return lines.join("\n");
|
|
17
|
-
}
|
|
18
|
-
function buildPluginApprovalText(request) {
|
|
19
|
-
const timeoutSec = Math.round((request.request.timeoutMs ?? 12e4) / 1e3);
|
|
20
|
-
const lines = [`${request.request.severity === "critical" ? "🔴" : request.request.severity === "info" ? "🔵" : "🟡"} \u5ba1\u6279\u8bf7\u6c42`, ""];
|
|
21
|
-
lines.push(`\u{1f4cb} ${request.request.title}`);
|
|
22
|
-
if (request.request.description) lines.push(`\u{1f4dd} ${request.request.description}`);
|
|
23
|
-
if (request.request.toolName) lines.push(`\u{1f527} \u5de5\u5177: ${request.request.toolName}`);
|
|
24
|
-
if (request.request.pluginId) lines.push(`\u{1f50c} \u63d2\u4ef6: ${request.request.pluginId}`);
|
|
25
|
-
if (request.request.agentId) lines.push(`\u{1f916} Agent: ${request.request.agentId}`);
|
|
26
|
-
lines.push("", `\u23f1\ufe0f \u8d85\u65f6: ${timeoutSec} \u79d2`);
|
|
27
|
-
return lines.join("\n");
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Build the three-button inline keyboard for approval messages.
|
|
31
|
-
*
|
|
32
|
-
* type=1 (Callback): click triggers INTERACTION_CREATE, button_data = data field.
|
|
33
|
-
* group_id "approval": clicking one button grays out the others (mutual exclusion).
|
|
34
|
-
* click_limit=1: each user can only click once.
|
|
35
|
-
* permission.type=2: all users can interact.
|
|
36
|
-
*/
|
|
37
|
-
function buildApprovalKeyboard(approvalId, allowedDecisions = [
|
|
38
|
-
"allow-once",
|
|
39
|
-
"allow-always",
|
|
40
|
-
"deny"
|
|
41
|
-
]) {
|
|
42
|
-
const makeBtn = (id, label, visitedLabel, data, style) => ({
|
|
43
|
-
id,
|
|
44
|
-
render_data: {
|
|
45
|
-
label,
|
|
46
|
-
visited_label: visitedLabel,
|
|
47
|
-
style
|
|
48
|
-
},
|
|
49
|
-
action: {
|
|
50
|
-
type: 1,
|
|
51
|
-
data,
|
|
52
|
-
permission: { type: 2 },
|
|
53
|
-
click_limit: 1
|
|
54
|
-
},
|
|
55
|
-
group_id: "approval"
|
|
56
|
-
});
|
|
57
|
-
const buttons = [];
|
|
58
|
-
if (allowedDecisions.includes("allow-once")) buttons.push(makeBtn("allow", "✅ 允许一次", "已允许", `approve:${approvalId}:allow-once`, 1));
|
|
59
|
-
if (allowedDecisions.includes("allow-always")) buttons.push(makeBtn("always", "⭐ 始终允许", "已始终允许", `approve:${approvalId}:allow-always`, 1));
|
|
60
|
-
if (allowedDecisions.includes("deny")) buttons.push(makeBtn("deny", "❌ 拒绝", "已拒绝", `approve:${approvalId}:deny`, 0));
|
|
61
|
-
return { content: { rows: [{ buttons }] } };
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Extract the delivery target from a sessionKey or turnSourceTo string.
|
|
65
|
-
*
|
|
66
|
-
* Expected formats:
|
|
67
|
-
* agent:main:qqbot:direct:OPENID -> { type: "c2c", id: "OPENID" }
|
|
68
|
-
* agent:main:qqbot:c2c:OPENID -> { type: "c2c", id: "OPENID" }
|
|
69
|
-
* agent:main:qqbot:group:GROUPID -> { type: "group", id: "GROUPID" }
|
|
70
|
-
*
|
|
71
|
-
* Returns null if neither field matches the expected pattern.
|
|
72
|
-
*/
|
|
73
|
-
function resolveApprovalTarget(sessionKey, turnSourceTo) {
|
|
74
|
-
const sk = sessionKey ?? turnSourceTo;
|
|
75
|
-
if (!sk) return null;
|
|
76
|
-
const m = sk.match(/qqbot:(c2c|direct|group):([A-F0-9]+)/i);
|
|
77
|
-
if (!m) return null;
|
|
78
|
-
return {
|
|
79
|
-
type: m[1].toLowerCase() === "group" ? "group" : "c2c",
|
|
80
|
-
id: m[2]
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Parse the button_data string from an INTERACTION_CREATE event.
|
|
85
|
-
*
|
|
86
|
-
* Expected format: `approve:<approvalId>:<decision>`
|
|
87
|
-
* where approvalId may be prefixed with "exec:" or "plugin:".
|
|
88
|
-
*
|
|
89
|
-
* Returns null if the data does not match the approval button format.
|
|
90
|
-
*/
|
|
91
|
-
function parseApprovalButtonData(buttonData) {
|
|
92
|
-
const m = buttonData.match(/^approve:((?:(?:exec|plugin):)?[0-9a-f-]+):(allow-once|allow-always|deny)$/i);
|
|
93
|
-
if (!m) return null;
|
|
94
|
-
return {
|
|
95
|
-
approvalId: m[1],
|
|
96
|
-
decision: m[2]
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
//#endregion
|
|
100
|
-
//#region extensions/qqbot/src/exec-approvals.ts
|
|
101
|
-
function normalizeApproverId(value) {
|
|
102
|
-
return normalizeOptionalString(String(value)) || void 0;
|
|
103
|
-
}
|
|
104
|
-
function resolveQQBotExecApprovalConfig(params) {
|
|
105
|
-
const account = resolveQQBotAccount(params.cfg, params.accountId);
|
|
106
|
-
const config = account.config.execApprovals;
|
|
107
|
-
if (!config) return;
|
|
108
|
-
return {
|
|
109
|
-
...config,
|
|
110
|
-
enabled: account.enabled && account.secretSource !== "none" ? config.enabled : false
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
function getQQBotExecApprovalApprovers(params) {
|
|
114
|
-
const accountConfig = resolveQQBotAccount(params.cfg, params.accountId).config;
|
|
115
|
-
return resolveApprovalApprovers({
|
|
116
|
-
explicit: resolveQQBotExecApprovalConfig(params)?.approvers,
|
|
117
|
-
allowFrom: accountConfig.allowFrom,
|
|
118
|
-
normalizeApprover: normalizeApproverId
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
function countQQBotExecApprovalEligibleAccounts(params) {
|
|
122
|
-
return listQQBotAccountIds(params.cfg).filter((accountId) => {
|
|
123
|
-
const account = resolveQQBotAccount(params.cfg, accountId);
|
|
124
|
-
if (!account.enabled || account.secretSource === "none") return false;
|
|
125
|
-
const config = resolveQQBotExecApprovalConfig({
|
|
126
|
-
cfg: params.cfg,
|
|
127
|
-
accountId
|
|
128
|
-
});
|
|
129
|
-
return isChannelExecApprovalClientEnabledFromConfig({
|
|
130
|
-
enabled: config?.enabled,
|
|
131
|
-
approverCount: getQQBotExecApprovalApprovers({
|
|
132
|
-
cfg: params.cfg,
|
|
133
|
-
accountId
|
|
134
|
-
}).length
|
|
135
|
-
}) && matchesApprovalRequestFilters({
|
|
136
|
-
request: params.request.request,
|
|
137
|
-
agentFilter: config?.agentFilter,
|
|
138
|
-
sessionFilter: config?.sessionFilter,
|
|
139
|
-
fallbackAgentIdFromSessionKey: true
|
|
140
|
-
});
|
|
141
|
-
}).length;
|
|
142
|
-
}
|
|
143
|
-
function matchesQQBotRequestAccount(params) {
|
|
144
|
-
const turnSourceChannel = normalizeLowercaseStringOrEmpty(params.request.request.turnSourceChannel);
|
|
145
|
-
const boundAccountId = resolveApprovalRequestChannelAccountId({
|
|
146
|
-
cfg: params.cfg,
|
|
147
|
-
request: params.request,
|
|
148
|
-
channel: "qqbot"
|
|
149
|
-
});
|
|
150
|
-
if (turnSourceChannel && turnSourceChannel !== "qqbot" && !boundAccountId) return countQQBotExecApprovalEligibleAccounts({
|
|
151
|
-
cfg: params.cfg,
|
|
152
|
-
request: params.request
|
|
153
|
-
}) <= 1;
|
|
154
|
-
return !boundAccountId || !params.accountId || normalizeAccountId(boundAccountId) === normalizeAccountId(params.accountId);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Count QQBot accounts that could actually deliver a native approval
|
|
158
|
-
* message — i.e. accounts that are enabled and have resolvable secrets.
|
|
159
|
-
* Disabled or unconfigured accounts never spawn a handler, so they
|
|
160
|
-
* must not contribute to the single-account shortcut in the fallback
|
|
161
|
-
* ownership check below.
|
|
162
|
-
*/
|
|
163
|
-
function countQQBotFallbackEligibleAccounts(cfg) {
|
|
164
|
-
return listQQBotAccountIds(cfg).filter((accountId) => {
|
|
165
|
-
const account = resolveQQBotAccount(cfg, accountId);
|
|
166
|
-
return account.enabled && account.secretSource !== "none";
|
|
167
|
-
}).length;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Fallback account-ownership check — applied when `execApprovals` is NOT
|
|
171
|
-
* configured for any QQBot account. In this mode every enabled account
|
|
172
|
-
* handler would otherwise race to deliver the same approval to its own
|
|
173
|
-
* openid namespace, so we must enforce per-account isolation.
|
|
174
|
-
*
|
|
175
|
-
* Rules:
|
|
176
|
-
* - If the request carries a bound account (via `turnSourceAccountId`
|
|
177
|
-
* or session binding), only the handler whose `accountId` matches it
|
|
178
|
-
* delivers the approval. This is strict: a handler with an unknown
|
|
179
|
-
* `accountId` (null/undefined) must not claim a bound request.
|
|
180
|
-
* - If no account is bound, only deliver when there is a single
|
|
181
|
-
* *eligible* QQBot account (enabled + secret resolved). Disabled or
|
|
182
|
-
* unconfigured accounts never deliver anyway, so they shouldn't
|
|
183
|
-
* block the remaining single account from handling the approval.
|
|
184
|
-
* Multiple eligible accounts cannot safely race because openids are
|
|
185
|
-
* account-scoped — cross-account delivery hits the QQ Bot API with
|
|
186
|
-
* a mismatched token and fails.
|
|
187
|
-
*/
|
|
188
|
-
function matchesQQBotFallbackRequestAccount(params) {
|
|
189
|
-
const boundAccountId = resolveApprovalRequestChannelAccountId({
|
|
190
|
-
cfg: params.cfg,
|
|
191
|
-
request: params.request,
|
|
192
|
-
channel: "qqbot"
|
|
193
|
-
});
|
|
194
|
-
if (boundAccountId) {
|
|
195
|
-
if (!params.accountId) return false;
|
|
196
|
-
return normalizeAccountId(boundAccountId) === normalizeAccountId(params.accountId);
|
|
197
|
-
}
|
|
198
|
-
return countQQBotFallbackEligibleAccounts(params.cfg) <= 1;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Unified per-account ownership check used by both the profile and
|
|
202
|
-
* fallback approval paths. Dispatches to the profile rules when the
|
|
203
|
-
* current account has `execApprovals` configured, otherwise uses the
|
|
204
|
-
* fallback rules.
|
|
205
|
-
*
|
|
206
|
-
* This is the single source of truth for "does this QQBot handler own
|
|
207
|
-
* this approval request?" and is consumed by both the capability
|
|
208
|
-
* gate (shouldHandle) and the lazy native runtime adapter.
|
|
209
|
-
*/
|
|
210
|
-
function matchesQQBotApprovalAccount(params) {
|
|
211
|
-
const normalized = {
|
|
212
|
-
cfg: params.cfg,
|
|
213
|
-
accountId: params.accountId,
|
|
214
|
-
request: params.request
|
|
215
|
-
};
|
|
216
|
-
if (resolveQQBotExecApprovalConfig(normalized) !== void 0) return matchesQQBotRequestAccount(normalized);
|
|
217
|
-
return matchesQQBotFallbackRequestAccount(normalized);
|
|
218
|
-
}
|
|
219
|
-
const qqbotExecApprovalProfile = createChannelExecApprovalProfile({
|
|
220
|
-
resolveConfig: resolveQQBotExecApprovalConfig,
|
|
221
|
-
resolveApprovers: getQQBotExecApprovalApprovers,
|
|
222
|
-
matchesRequestAccount: matchesQQBotRequestAccount,
|
|
223
|
-
fallbackAgentIdFromSessionKey: true,
|
|
224
|
-
requireClientEnabledForLocalPromptSuppression: false
|
|
225
|
-
});
|
|
226
|
-
const isQQBotExecApprovalClientEnabled = qqbotExecApprovalProfile.isClientEnabled;
|
|
227
|
-
const isQQBotExecApprovalApprover = qqbotExecApprovalProfile.isApprover;
|
|
228
|
-
const isQQBotExecApprovalAuthorizedSender = qqbotExecApprovalProfile.isAuthorizedSender;
|
|
229
|
-
const shouldHandleQQBotExecApprovalRequest = qqbotExecApprovalProfile.shouldHandleRequest;
|
|
230
|
-
function authorizeQQBotApprovalAction(params) {
|
|
231
|
-
if (resolveQQBotExecApprovalConfig(params) === void 0) return { authorized: true };
|
|
232
|
-
return (params.approvalKind === "plugin" ? isQQBotExecApprovalApprover(params) : isQQBotExecApprovalAuthorizedSender(params)) ? { authorized: true } : {
|
|
233
|
-
authorized: false,
|
|
234
|
-
reason: "You are not authorized to approve this request."
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
//#endregion
|
|
238
|
-
export { shouldHandleQQBotExecApprovalRequest as a, buildPluginApprovalText as c, resolveQQBotExecApprovalConfig as i, parseApprovalButtonData as l, isQQBotExecApprovalClientEnabled as n, buildApprovalKeyboard as o, matchesQQBotApprovalAccount as r, buildExecApprovalText as s, authorizeQQBotApprovalAction as t, resolveApprovalTarget as u };
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
//#region extensions/qqbot/src/bridge/narrowing.ts
|
|
2
|
-
/**
|
|
3
|
-
* Map resolved plugin account to the engine gateway account shape (single assertion on nested config).
|
|
4
|
-
*/
|
|
5
|
-
function toGatewayAccount(account) {
|
|
6
|
-
return {
|
|
7
|
-
accountId: account.accountId,
|
|
8
|
-
appId: account.appId,
|
|
9
|
-
clientSecret: account.clientSecret,
|
|
10
|
-
markdownSupport: account.markdownSupport,
|
|
11
|
-
systemPrompt: account.systemPrompt,
|
|
12
|
-
config: account.config
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Persist OpenClaw config through the injected plugin runtime (typed entry point).
|
|
17
|
-
*/
|
|
18
|
-
async function writeOpenClawConfigThroughRuntime(runtime, cfg) {
|
|
19
|
-
await runtime.config.replaceConfigFile({
|
|
20
|
-
nextConfig: cfg,
|
|
21
|
-
afterWrite: { mode: "auto" }
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
//#endregion
|
|
25
|
-
export { writeOpenClawConfigThroughRuntime as n, toGatewayAccount as t };
|
package/dist/runtime-B-cqLImu.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { _ as setOpenClawVersion } from "./sender-BeE_CBbN.js";
|
|
2
|
-
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
3
|
-
//#region extensions/qqbot/src/bridge/runtime.ts
|
|
4
|
-
const { setRuntime: _setRuntime, getRuntime: getQQBotRuntime } = createPluginRuntimeStore({
|
|
5
|
-
pluginId: "qqbot",
|
|
6
|
-
errorMessage: "QQBot runtime not initialized"
|
|
7
|
-
});
|
|
8
|
-
/** Set the QQBot runtime and inject the framework version into the User-Agent. */
|
|
9
|
-
function setQQBotRuntime(runtime) {
|
|
10
|
-
_setRuntime(runtime);
|
|
11
|
-
setOpenClawVersion(runtime.version);
|
|
12
|
-
}
|
|
13
|
-
/** Type-narrowed getter for engine/ modules that need GatewayPluginRuntime. */
|
|
14
|
-
function getQQBotRuntimeForEngine() {
|
|
15
|
-
return getQQBotRuntime();
|
|
16
|
-
}
|
|
17
|
-
//#endregion
|
|
18
|
-
export { getQQBotRuntimeForEngine as n, setQQBotRuntime as r, getQQBotRuntime as t };
|