@openclaw/qqbot 2026.6.5-beta.2 → 2026.6.5-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +7 -657
- package/dist/{channel-BLMrGYfg.js → channel-CcN43bPL.js} +32 -287
- package/dist/channel-entry-api.js +2 -0
- package/dist/channel-entry-fUBLXKPr.js +143 -0
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-xBlmSU4X.js → channel.setup-CQ_DFfPx.js} +1 -1
- package/dist/config-ZEfgeoL4.js +257 -0
- package/dist/{config-schema-BI7AYP6Q.js → config-schema-BFLNZ8Nf.js} +17 -259
- package/dist/doctor-contract-BYTS8-ia.js +135 -0
- package/dist/doctor-contract-api.js +2 -0
- package/dist/{gateway-iL9SWAED.js → gateway-DdM2k3ss.js} +23 -26
- package/dist/{handler-runtime-CjXwuNlq.js → handler-runtime-JKYDlRdq.js} +4 -3
- package/dist/index.js +8 -1
- package/dist/log-SDfMMBWe.js +107 -0
- package/dist/{outbound-CEqyriPT.js → outbound-C9wV892v.js} +3 -14
- package/dist/request-context-Bm7PTBD1.js +43 -0
- package/dist/runtime-B9UoQ5NI.js +18 -0
- package/dist/runtime-api.js +1 -1
- package/dist/{runtime-BSgtMZ6G.js → sender-DIMG7jHz.js} +36 -123
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{request-context-DsuA124s.js → slash-commands-impl-FRw-Dl44.js} +3 -45
- package/dist/target-parser-BdCUmxK7.js +285 -0
- package/dist/tools-2d9t-N1b.js +518 -0
- package/dist/tools-api.js +2 -0
- package/npm-shrinkwrap.json +3 -3
- package/openclaw.plugin.json +162 -0
- package/package.json +4 -4
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { a as normalizeStringifiedEntries, c as getPlatformAdapter, o as readStringField, r as normalizeOptionalLowercaseString, t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
|
|
2
|
+
import { coerceSecretRef, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
|
|
5
|
+
//#region extensions/qqbot/src/engine/config/resolve.ts
|
|
6
|
+
/**
|
|
7
|
+
* QQBot config resolution (pure logic layer).
|
|
8
|
+
* QQBot 配置解析(纯逻辑层)。
|
|
9
|
+
*
|
|
10
|
+
* Resolves account IDs, default account selection, and base account
|
|
11
|
+
* info from raw config objects. Secret/credential resolution is
|
|
12
|
+
* intentionally left to the outer layer (src/bridge/config.ts) so that
|
|
13
|
+
* this module stays framework-agnostic and self-contained.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Default account ID, used for the unnamed top-level account.
|
|
17
|
+
* 默认账号 ID,用于顶层配置中未命名的账号。
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_ACCOUNT_ID$1 = "default";
|
|
20
|
+
function normalizeAppId(raw) {
|
|
21
|
+
if (typeof raw === "string") return raw.trim();
|
|
22
|
+
if (typeof raw === "number") return String(raw);
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
function normalizeAccountConfig(account) {
|
|
26
|
+
if (!account) return {};
|
|
27
|
+
const audioPolicy = asOptionalObjectRecord(account.audioFormatPolicy);
|
|
28
|
+
return {
|
|
29
|
+
...account,
|
|
30
|
+
...audioPolicy ? { audioFormatPolicy: { ...audioPolicy } } : {}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function readQQBotSection(cfg) {
|
|
34
|
+
return asOptionalObjectRecord(asOptionalObjectRecord(cfg.channels)?.qqbot);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* List all configured QQBot account IDs.
|
|
38
|
+
* 列出所有已配置的 QQBot 账号 ID。
|
|
39
|
+
*/
|
|
40
|
+
function listAccountIds(cfg) {
|
|
41
|
+
const ids = /* @__PURE__ */ new Set();
|
|
42
|
+
const qqbot = readQQBotSection(cfg);
|
|
43
|
+
if (qqbot?.appId || process.env.QQBOT_APP_ID) ids.add(DEFAULT_ACCOUNT_ID$1);
|
|
44
|
+
if (qqbot?.accounts) {
|
|
45
|
+
for (const accountId of Object.keys(qqbot.accounts)) if (qqbot.accounts[accountId]?.appId) ids.add(accountId);
|
|
46
|
+
}
|
|
47
|
+
return Array.from(ids);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the default QQBot account ID.
|
|
51
|
+
* 解析默认 QQBot 账号 ID(优先级:defaultAccount > 顶层 appId > 第一个命名账号)。
|
|
52
|
+
*/
|
|
53
|
+
function resolveDefaultAccountId(cfg) {
|
|
54
|
+
const qqbot = readQQBotSection(cfg);
|
|
55
|
+
const configuredDefaultAccountId = normalizeOptionalLowercaseString(qqbot?.defaultAccount);
|
|
56
|
+
if (configuredDefaultAccountId && (configuredDefaultAccountId === "default" || Boolean(qqbot?.accounts?.[configuredDefaultAccountId]?.appId))) return configuredDefaultAccountId;
|
|
57
|
+
if (qqbot?.appId || process.env.QQBOT_APP_ID) return DEFAULT_ACCOUNT_ID$1;
|
|
58
|
+
if (qqbot?.accounts) {
|
|
59
|
+
const ids = Object.keys(qqbot.accounts);
|
|
60
|
+
if (ids.length > 0) return ids[0];
|
|
61
|
+
}
|
|
62
|
+
return DEFAULT_ACCOUNT_ID$1;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve base account info (without credentials).
|
|
66
|
+
* 解析账号基础信息(不含凭证)。
|
|
67
|
+
*
|
|
68
|
+
* Resolves everything except Secret/credential fields. The outer
|
|
69
|
+
* config.ts layer calls this and adds Secret handling on top.
|
|
70
|
+
*/
|
|
71
|
+
function resolveAccountBase(cfg, accountId) {
|
|
72
|
+
const resolvedAccountId = accountId ?? resolveDefaultAccountId(cfg);
|
|
73
|
+
const qqbot = readQQBotSection(cfg);
|
|
74
|
+
let accountConfig;
|
|
75
|
+
let appId;
|
|
76
|
+
if (resolvedAccountId === "default") {
|
|
77
|
+
accountConfig = normalizeAccountConfig(asOptionalObjectRecord(qqbot));
|
|
78
|
+
appId = normalizeAppId(qqbot?.appId);
|
|
79
|
+
} else {
|
|
80
|
+
const account = qqbot?.accounts?.[resolvedAccountId];
|
|
81
|
+
accountConfig = normalizeAccountConfig(asOptionalObjectRecord(account));
|
|
82
|
+
appId = normalizeAppId(asOptionalObjectRecord(account)?.appId);
|
|
83
|
+
}
|
|
84
|
+
if (!appId && process.env.QQBOT_APP_ID && resolvedAccountId === "default") appId = normalizeAppId(process.env.QQBOT_APP_ID);
|
|
85
|
+
return {
|
|
86
|
+
accountId: resolvedAccountId,
|
|
87
|
+
name: readStringField(accountConfig, "name"),
|
|
88
|
+
enabled: accountConfig.enabled !== false,
|
|
89
|
+
appId,
|
|
90
|
+
systemPrompt: readStringField(accountConfig, "systemPrompt"),
|
|
91
|
+
markdownSupport: accountConfig.markdownSupport !== false,
|
|
92
|
+
config: accountConfig
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** Apply account config updates into a raw config object. */
|
|
96
|
+
function applyAccountConfig(cfg, accountId, input) {
|
|
97
|
+
const next = { ...cfg };
|
|
98
|
+
const channels = asOptionalObjectRecord(cfg.channels) ?? {};
|
|
99
|
+
const existingQQBot = asOptionalObjectRecord(channels.qqbot) ?? {};
|
|
100
|
+
if (accountId === "default") {
|
|
101
|
+
const allowFrom = existingQQBot.allowFrom ?? ["*"];
|
|
102
|
+
next.channels = {
|
|
103
|
+
...channels,
|
|
104
|
+
qqbot: {
|
|
105
|
+
...existingQQBot,
|
|
106
|
+
enabled: true,
|
|
107
|
+
allowFrom,
|
|
108
|
+
...input.appId ? { appId: input.appId } : {},
|
|
109
|
+
...input.clientSecret ? {
|
|
110
|
+
clientSecret: input.clientSecret,
|
|
111
|
+
clientSecretFile: void 0
|
|
112
|
+
} : input.clientSecretFile ? {
|
|
113
|
+
clientSecretFile: input.clientSecretFile,
|
|
114
|
+
clientSecret: void 0
|
|
115
|
+
} : {},
|
|
116
|
+
...input.name ? { name: input.name } : {}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
} else {
|
|
120
|
+
const accounts = existingQQBot.accounts ?? {};
|
|
121
|
+
const existingAccount = accounts[accountId] ?? {};
|
|
122
|
+
const allowFrom = existingAccount.allowFrom ?? ["*"];
|
|
123
|
+
next.channels = {
|
|
124
|
+
...channels,
|
|
125
|
+
qqbot: {
|
|
126
|
+
...existingQQBot,
|
|
127
|
+
enabled: true,
|
|
128
|
+
accounts: {
|
|
129
|
+
...accounts,
|
|
130
|
+
[accountId]: {
|
|
131
|
+
...existingAccount,
|
|
132
|
+
enabled: true,
|
|
133
|
+
allowFrom,
|
|
134
|
+
...input.appId ? { appId: input.appId } : {},
|
|
135
|
+
...input.clientSecret ? {
|
|
136
|
+
clientSecret: input.clientSecret,
|
|
137
|
+
clientSecretFile: void 0
|
|
138
|
+
} : input.clientSecretFile ? {
|
|
139
|
+
clientSecretFile: input.clientSecretFile,
|
|
140
|
+
clientSecret: void 0
|
|
141
|
+
} : {},
|
|
142
|
+
...input.name ? { name: input.name } : {}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return next;
|
|
149
|
+
}
|
|
150
|
+
/** Check whether a QQBot account has been fully configured. */
|
|
151
|
+
function isAccountConfigured(account) {
|
|
152
|
+
return Boolean(account?.appId && (Boolean(account?.clientSecret) || getPlatformAdapter().hasConfiguredSecret(account?.config?.clientSecret) || Boolean(account?.config?.clientSecretFile?.trim())));
|
|
153
|
+
}
|
|
154
|
+
/** Build a summary description of an account. */
|
|
155
|
+
function describeAccount(account) {
|
|
156
|
+
return {
|
|
157
|
+
accountId: account?.accountId ?? "default",
|
|
158
|
+
name: account?.name,
|
|
159
|
+
enabled: account?.enabled ?? false,
|
|
160
|
+
configured: isAccountConfigured(account),
|
|
161
|
+
tokenSource: account?.secretSource
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/** Normalize allowFrom entries into uppercase strings without the qqbot: prefix. */
|
|
165
|
+
function formatAllowFrom(allowFrom) {
|
|
166
|
+
return normalizeStringifiedEntries(allowFrom ?? []).map((entry) => entry.replace(/^qqbot:/i, "")).map((entry) => entry.toUpperCase());
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region extensions/qqbot/src/bridge/config.ts
|
|
170
|
+
const DEFAULT_ACCOUNT_ID = DEFAULT_ACCOUNT_ID$1;
|
|
171
|
+
function assertNotLegacySecretRefMarker(value, path) {
|
|
172
|
+
const normalized = normalizeSecretInputString(value);
|
|
173
|
+
if (!normalized || !/^secretref(?:-env)?:/i.test(normalized)) return;
|
|
174
|
+
throw new Error(`${path}: legacy SecretRef marker strings are not valid QQ Bot clientSecret values; use a structured SecretRef object instead.`);
|
|
175
|
+
}
|
|
176
|
+
function resolveEnvSecretRefValue(params) {
|
|
177
|
+
const ref = coerceSecretRef(params.value, params.cfg.secrets?.defaults);
|
|
178
|
+
if (!ref || ref.source !== "env") return;
|
|
179
|
+
const providerConfig = params.cfg.secrets?.providers?.[ref.provider];
|
|
180
|
+
if (providerConfig) {
|
|
181
|
+
if (providerConfig.source !== "env") throw new Error(`Secret provider "${ref.provider}" has source "${providerConfig.source}" but ref requests "env".`);
|
|
182
|
+
if (providerConfig.allowlist && !providerConfig.allowlist.includes(ref.id)) throw new Error(`Environment variable "${ref.id}" is not allowlisted in secrets.providers.${ref.provider}.allowlist.`);
|
|
183
|
+
} else if (ref.provider !== resolveDefaultSecretProviderAlias(params.cfg, "env")) throw new Error(`Secret provider "${ref.provider}" is not configured (ref: env:${ref.provider}:${ref.id}).`);
|
|
184
|
+
return normalizeSecretInputString((params.env ?? process.env)[ref.id]);
|
|
185
|
+
}
|
|
186
|
+
function resolveQQBotClientSecretInput(params) {
|
|
187
|
+
assertNotLegacySecretRefMarker(params.value, params.path);
|
|
188
|
+
const envSecret = resolveEnvSecretRefValue({
|
|
189
|
+
cfg: params.cfg,
|
|
190
|
+
value: params.value
|
|
191
|
+
});
|
|
192
|
+
if (envSecret) return envSecret;
|
|
193
|
+
return getPlatformAdapter().resolveSecretInputString({
|
|
194
|
+
value: params.value,
|
|
195
|
+
path: params.path
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/** List all configured QQBot account IDs. */
|
|
199
|
+
function listQQBotAccountIds(cfg) {
|
|
200
|
+
return listAccountIds(cfg);
|
|
201
|
+
}
|
|
202
|
+
/** Resolve the default QQBot account ID. */
|
|
203
|
+
function resolveDefaultQQBotAccountId(cfg) {
|
|
204
|
+
return resolveDefaultAccountId(cfg);
|
|
205
|
+
}
|
|
206
|
+
/** Resolve QQBot account config for runtime or setup flows. */
|
|
207
|
+
function resolveQQBotAccount(cfg, accountId, opts) {
|
|
208
|
+
const base = resolveAccountBase(cfg, accountId);
|
|
209
|
+
const qqbot = cfg.channels?.qqbot;
|
|
210
|
+
/**
|
|
211
|
+
* Legacy top-level account uses `channels.qqbot` as the base, but per-account
|
|
212
|
+
* fields (allowFrom, streaming, …) often live under `accounts.default`.
|
|
213
|
+
* Merge that slice so runtime sees `config.streaming` etc.
|
|
214
|
+
*/
|
|
215
|
+
const accountConfig = base.accountId === DEFAULT_ACCOUNT_ID ? {
|
|
216
|
+
...qqbot,
|
|
217
|
+
...qqbot?.accounts?.[DEFAULT_ACCOUNT_ID]
|
|
218
|
+
} : qqbot?.accounts?.[base.accountId] ?? {};
|
|
219
|
+
let clientSecret = "";
|
|
220
|
+
let secretSource = "none";
|
|
221
|
+
const clientSecretPath = base.accountId === DEFAULT_ACCOUNT_ID ? "channels.qqbot.clientSecret" : `channels.qqbot.accounts.${base.accountId}.clientSecret`;
|
|
222
|
+
const adapter = getPlatformAdapter();
|
|
223
|
+
if (adapter.hasConfiguredSecret(accountConfig.clientSecret)) {
|
|
224
|
+
clientSecret = opts?.allowUnresolvedSecretRef ? adapter.normalizeSecretInputString(accountConfig.clientSecret) ?? "" : resolveQQBotClientSecretInput({
|
|
225
|
+
cfg,
|
|
226
|
+
value: accountConfig.clientSecret,
|
|
227
|
+
path: clientSecretPath
|
|
228
|
+
}) ?? "";
|
|
229
|
+
secretSource = "config";
|
|
230
|
+
} else if (accountConfig.clientSecretFile) try {
|
|
231
|
+
clientSecret = fs.readFileSync(accountConfig.clientSecretFile, "utf8").trim();
|
|
232
|
+
secretSource = "file";
|
|
233
|
+
} catch {
|
|
234
|
+
secretSource = "none";
|
|
235
|
+
}
|
|
236
|
+
else if (process.env.QQBOT_CLIENT_SECRET && base.accountId === DEFAULT_ACCOUNT_ID) {
|
|
237
|
+
clientSecret = process.env.QQBOT_CLIENT_SECRET;
|
|
238
|
+
secretSource = "env";
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
accountId: base.accountId,
|
|
242
|
+
name: accountConfig.name,
|
|
243
|
+
enabled: base.enabled,
|
|
244
|
+
appId: base.appId,
|
|
245
|
+
clientSecret,
|
|
246
|
+
secretSource,
|
|
247
|
+
systemPrompt: base.systemPrompt,
|
|
248
|
+
markdownSupport: base.markdownSupport,
|
|
249
|
+
config: accountConfig
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/** Apply account config updates back into the OpenClaw config object. */
|
|
253
|
+
function applyQQBotAccountConfig(cfg, accountId, input) {
|
|
254
|
+
return applyAccountConfig(cfg, accountId, input);
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
export { resolveQQBotAccount as a, describeAccount as c, resolveAccountBase as d, resolveDefaultQQBotAccountId as i, formatAllowFrom as l, applyQQBotAccountConfig as n, DEFAULT_ACCOUNT_ID$1 as o, listQQBotAccountIds as r, applyAccountConfig as s, DEFAULT_ACCOUNT_ID as t, isAccountConfigured as u };
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { d as registerPlatformAdapterFactory, l as hasPlatformAdapter, n as normalizeLowercaseStringOrEmpty, u as registerPlatformAdapter } from "./string-normalize-R_0cKO7Q.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-ZEfgeoL4.js";
|
|
3
|
+
import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
3
4
|
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
|
|
6
5
|
import { applyAccountNameToChannelSection, deleteAccountFromConfigSection, setAccountEnabledInConfigSection } from "openclaw/plugin-sdk/core";
|
|
7
6
|
import { DEFAULT_ACCOUNT_ID, createStandardChannelSetupStatus, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
8
7
|
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
|
9
|
-
import { AllowFromListSchema, buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
8
|
+
import { AllowFromListSchema, ToolPolicySchema, buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
10
9
|
import { z } from "zod";
|
|
11
10
|
//#region extensions/qqbot/src/bridge/logger.ts
|
|
12
11
|
let loggerInstance = null;
|
|
@@ -129,258 +128,6 @@ function ensurePlatformAdapter() {
|
|
|
129
128
|
registerPlatformAdapterFactory(createBuiltinAdapter);
|
|
130
129
|
ensurePlatformAdapter();
|
|
131
130
|
//#endregion
|
|
132
|
-
//#region extensions/qqbot/src/engine/config/resolve.ts
|
|
133
|
-
/**
|
|
134
|
-
* QQBot config resolution (pure logic layer).
|
|
135
|
-
* QQBot 配置解析(纯逻辑层)。
|
|
136
|
-
*
|
|
137
|
-
* Resolves account IDs, default account selection, and base account
|
|
138
|
-
* info from raw config objects. Secret/credential resolution is
|
|
139
|
-
* intentionally left to the outer layer (src/bridge/config.ts) so that
|
|
140
|
-
* this module stays framework-agnostic and self-contained.
|
|
141
|
-
*/
|
|
142
|
-
/**
|
|
143
|
-
* Default account ID, used for the unnamed top-level account.
|
|
144
|
-
* 默认账号 ID,用于顶层配置中未命名的账号。
|
|
145
|
-
*/
|
|
146
|
-
const DEFAULT_ACCOUNT_ID$2 = "default";
|
|
147
|
-
function normalizeAppId(raw) {
|
|
148
|
-
if (typeof raw === "string") return raw.trim();
|
|
149
|
-
if (typeof raw === "number") return String(raw);
|
|
150
|
-
return "";
|
|
151
|
-
}
|
|
152
|
-
function normalizeAccountConfig(account) {
|
|
153
|
-
if (!account) return {};
|
|
154
|
-
const audioPolicy = asOptionalObjectRecord(account.audioFormatPolicy);
|
|
155
|
-
return {
|
|
156
|
-
...account,
|
|
157
|
-
...audioPolicy ? { audioFormatPolicy: { ...audioPolicy } } : {}
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
function readQQBotSection(cfg) {
|
|
161
|
-
return asOptionalObjectRecord(asOptionalObjectRecord(cfg.channels)?.qqbot);
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* List all configured QQBot account IDs.
|
|
165
|
-
* 列出所有已配置的 QQBot 账号 ID。
|
|
166
|
-
*/
|
|
167
|
-
function listAccountIds(cfg) {
|
|
168
|
-
const ids = /* @__PURE__ */ new Set();
|
|
169
|
-
const qqbot = readQQBotSection(cfg);
|
|
170
|
-
if (qqbot?.appId || process.env.QQBOT_APP_ID) ids.add(DEFAULT_ACCOUNT_ID$2);
|
|
171
|
-
if (qqbot?.accounts) {
|
|
172
|
-
for (const accountId of Object.keys(qqbot.accounts)) if (qqbot.accounts[accountId]?.appId) ids.add(accountId);
|
|
173
|
-
}
|
|
174
|
-
return Array.from(ids);
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Resolve the default QQBot account ID.
|
|
178
|
-
* 解析默认 QQBot 账号 ID(优先级:defaultAccount > 顶层 appId > 第一个命名账号)。
|
|
179
|
-
*/
|
|
180
|
-
function resolveDefaultAccountId(cfg) {
|
|
181
|
-
const qqbot = readQQBotSection(cfg);
|
|
182
|
-
const configuredDefaultAccountId = normalizeOptionalLowercaseString(qqbot?.defaultAccount);
|
|
183
|
-
if (configuredDefaultAccountId && (configuredDefaultAccountId === "default" || Boolean(qqbot?.accounts?.[configuredDefaultAccountId]?.appId))) return configuredDefaultAccountId;
|
|
184
|
-
if (qqbot?.appId || process.env.QQBOT_APP_ID) return DEFAULT_ACCOUNT_ID$2;
|
|
185
|
-
if (qqbot?.accounts) {
|
|
186
|
-
const ids = Object.keys(qqbot.accounts);
|
|
187
|
-
if (ids.length > 0) return ids[0];
|
|
188
|
-
}
|
|
189
|
-
return DEFAULT_ACCOUNT_ID$2;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Resolve base account info (without credentials).
|
|
193
|
-
* 解析账号基础信息(不含凭证)。
|
|
194
|
-
*
|
|
195
|
-
* Resolves everything except Secret/credential fields. The outer
|
|
196
|
-
* config.ts layer calls this and adds Secret handling on top.
|
|
197
|
-
*/
|
|
198
|
-
function resolveAccountBase(cfg, accountId) {
|
|
199
|
-
const resolvedAccountId = accountId ?? resolveDefaultAccountId(cfg);
|
|
200
|
-
const qqbot = readQQBotSection(cfg);
|
|
201
|
-
let accountConfig;
|
|
202
|
-
let appId;
|
|
203
|
-
if (resolvedAccountId === "default") {
|
|
204
|
-
accountConfig = normalizeAccountConfig(asOptionalObjectRecord(qqbot));
|
|
205
|
-
appId = normalizeAppId(qqbot?.appId);
|
|
206
|
-
} else {
|
|
207
|
-
const account = qqbot?.accounts?.[resolvedAccountId];
|
|
208
|
-
accountConfig = normalizeAccountConfig(asOptionalObjectRecord(account));
|
|
209
|
-
appId = normalizeAppId(asOptionalObjectRecord(account)?.appId);
|
|
210
|
-
}
|
|
211
|
-
if (!appId && process.env.QQBOT_APP_ID && resolvedAccountId === "default") appId = normalizeAppId(process.env.QQBOT_APP_ID);
|
|
212
|
-
return {
|
|
213
|
-
accountId: resolvedAccountId,
|
|
214
|
-
name: readStringField(accountConfig, "name"),
|
|
215
|
-
enabled: accountConfig.enabled !== false,
|
|
216
|
-
appId,
|
|
217
|
-
systemPrompt: readStringField(accountConfig, "systemPrompt"),
|
|
218
|
-
markdownSupport: accountConfig.markdownSupport !== false,
|
|
219
|
-
config: accountConfig
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
/** Apply account config updates into a raw config object. */
|
|
223
|
-
function applyAccountConfig(cfg, accountId, input) {
|
|
224
|
-
const next = { ...cfg };
|
|
225
|
-
const channels = asOptionalObjectRecord(cfg.channels) ?? {};
|
|
226
|
-
const existingQQBot = asOptionalObjectRecord(channels.qqbot) ?? {};
|
|
227
|
-
if (accountId === "default") {
|
|
228
|
-
const allowFrom = existingQQBot.allowFrom ?? ["*"];
|
|
229
|
-
next.channels = {
|
|
230
|
-
...channels,
|
|
231
|
-
qqbot: {
|
|
232
|
-
...existingQQBot,
|
|
233
|
-
enabled: true,
|
|
234
|
-
allowFrom,
|
|
235
|
-
...input.appId ? { appId: input.appId } : {},
|
|
236
|
-
...input.clientSecret ? {
|
|
237
|
-
clientSecret: input.clientSecret,
|
|
238
|
-
clientSecretFile: void 0
|
|
239
|
-
} : input.clientSecretFile ? {
|
|
240
|
-
clientSecretFile: input.clientSecretFile,
|
|
241
|
-
clientSecret: void 0
|
|
242
|
-
} : {},
|
|
243
|
-
...input.name ? { name: input.name } : {}
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
} else {
|
|
247
|
-
const accounts = existingQQBot.accounts ?? {};
|
|
248
|
-
const existingAccount = accounts[accountId] ?? {};
|
|
249
|
-
const allowFrom = existingAccount.allowFrom ?? ["*"];
|
|
250
|
-
next.channels = {
|
|
251
|
-
...channels,
|
|
252
|
-
qqbot: {
|
|
253
|
-
...existingQQBot,
|
|
254
|
-
enabled: true,
|
|
255
|
-
accounts: {
|
|
256
|
-
...accounts,
|
|
257
|
-
[accountId]: {
|
|
258
|
-
...existingAccount,
|
|
259
|
-
enabled: true,
|
|
260
|
-
allowFrom,
|
|
261
|
-
...input.appId ? { appId: input.appId } : {},
|
|
262
|
-
...input.clientSecret ? {
|
|
263
|
-
clientSecret: input.clientSecret,
|
|
264
|
-
clientSecretFile: void 0
|
|
265
|
-
} : input.clientSecretFile ? {
|
|
266
|
-
clientSecretFile: input.clientSecretFile,
|
|
267
|
-
clientSecret: void 0
|
|
268
|
-
} : {},
|
|
269
|
-
...input.name ? { name: input.name } : {}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
return next;
|
|
276
|
-
}
|
|
277
|
-
/** Check whether a QQBot account has been fully configured. */
|
|
278
|
-
function isAccountConfigured(account) {
|
|
279
|
-
return Boolean(account?.appId && (Boolean(account?.clientSecret) || getPlatformAdapter().hasConfiguredSecret(account?.config?.clientSecret) || Boolean(account?.config?.clientSecretFile?.trim())));
|
|
280
|
-
}
|
|
281
|
-
/** Build a summary description of an account. */
|
|
282
|
-
function describeAccount(account) {
|
|
283
|
-
return {
|
|
284
|
-
accountId: account?.accountId ?? "default",
|
|
285
|
-
name: account?.name,
|
|
286
|
-
enabled: account?.enabled ?? false,
|
|
287
|
-
configured: isAccountConfigured(account),
|
|
288
|
-
tokenSource: account?.secretSource
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
/** Normalize allowFrom entries into uppercase strings without the qqbot: prefix. */
|
|
292
|
-
function formatAllowFrom(allowFrom) {
|
|
293
|
-
return normalizeStringifiedEntries(allowFrom ?? []).map((entry) => entry.replace(/^qqbot:/i, "")).map((entry) => entry.toUpperCase());
|
|
294
|
-
}
|
|
295
|
-
//#endregion
|
|
296
|
-
//#region extensions/qqbot/src/bridge/config.ts
|
|
297
|
-
const DEFAULT_ACCOUNT_ID$1 = DEFAULT_ACCOUNT_ID$2;
|
|
298
|
-
function assertNotLegacySecretRefMarker(value, path) {
|
|
299
|
-
const normalized = normalizeSecretInputString(value);
|
|
300
|
-
if (!normalized || !/^secretref(?:-env)?:/i.test(normalized)) return;
|
|
301
|
-
throw new Error(`${path}: legacy SecretRef marker strings are not valid QQ Bot clientSecret values; use a structured SecretRef object instead.`);
|
|
302
|
-
}
|
|
303
|
-
function resolveEnvSecretRefValue(params) {
|
|
304
|
-
const ref = coerceSecretRef(params.value, params.cfg.secrets?.defaults);
|
|
305
|
-
if (!ref || ref.source !== "env") return;
|
|
306
|
-
const providerConfig = params.cfg.secrets?.providers?.[ref.provider];
|
|
307
|
-
if (providerConfig) {
|
|
308
|
-
if (providerConfig.source !== "env") throw new Error(`Secret provider "${ref.provider}" has source "${providerConfig.source}" but ref requests "env".`);
|
|
309
|
-
if (providerConfig.allowlist && !providerConfig.allowlist.includes(ref.id)) throw new Error(`Environment variable "${ref.id}" is not allowlisted in secrets.providers.${ref.provider}.allowlist.`);
|
|
310
|
-
} else if (ref.provider !== resolveDefaultSecretProviderAlias(params.cfg, "env")) throw new Error(`Secret provider "${ref.provider}" is not configured (ref: env:${ref.provider}:${ref.id}).`);
|
|
311
|
-
return normalizeSecretInputString((params.env ?? process.env)[ref.id]);
|
|
312
|
-
}
|
|
313
|
-
function resolveQQBotClientSecretInput(params) {
|
|
314
|
-
assertNotLegacySecretRefMarker(params.value, params.path);
|
|
315
|
-
const envSecret = resolveEnvSecretRefValue({
|
|
316
|
-
cfg: params.cfg,
|
|
317
|
-
value: params.value
|
|
318
|
-
});
|
|
319
|
-
if (envSecret) return envSecret;
|
|
320
|
-
return getPlatformAdapter().resolveSecretInputString({
|
|
321
|
-
value: params.value,
|
|
322
|
-
path: params.path
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
/** List all configured QQBot account IDs. */
|
|
326
|
-
function listQQBotAccountIds(cfg) {
|
|
327
|
-
return listAccountIds(cfg);
|
|
328
|
-
}
|
|
329
|
-
/** Resolve the default QQBot account ID. */
|
|
330
|
-
function resolveDefaultQQBotAccountId(cfg) {
|
|
331
|
-
return resolveDefaultAccountId(cfg);
|
|
332
|
-
}
|
|
333
|
-
/** Resolve QQBot account config for runtime or setup flows. */
|
|
334
|
-
function resolveQQBotAccount(cfg, accountId, opts) {
|
|
335
|
-
const base = resolveAccountBase(cfg, accountId);
|
|
336
|
-
const qqbot = cfg.channels?.qqbot;
|
|
337
|
-
/**
|
|
338
|
-
* Legacy top-level account uses `channels.qqbot` as the base, but per-account
|
|
339
|
-
* fields (allowFrom, streaming, …) often live under `accounts.default`.
|
|
340
|
-
* Merge that slice so runtime sees `config.streaming` etc.
|
|
341
|
-
*/
|
|
342
|
-
const accountConfig = base.accountId === DEFAULT_ACCOUNT_ID$1 ? {
|
|
343
|
-
...qqbot,
|
|
344
|
-
...qqbot?.accounts?.[DEFAULT_ACCOUNT_ID$1]
|
|
345
|
-
} : qqbot?.accounts?.[base.accountId] ?? {};
|
|
346
|
-
let clientSecret = "";
|
|
347
|
-
let secretSource = "none";
|
|
348
|
-
const clientSecretPath = base.accountId === DEFAULT_ACCOUNT_ID$1 ? "channels.qqbot.clientSecret" : `channels.qqbot.accounts.${base.accountId}.clientSecret`;
|
|
349
|
-
const adapter = getPlatformAdapter();
|
|
350
|
-
if (adapter.hasConfiguredSecret(accountConfig.clientSecret)) {
|
|
351
|
-
clientSecret = opts?.allowUnresolvedSecretRef ? adapter.normalizeSecretInputString(accountConfig.clientSecret) ?? "" : resolveQQBotClientSecretInput({
|
|
352
|
-
cfg,
|
|
353
|
-
value: accountConfig.clientSecret,
|
|
354
|
-
path: clientSecretPath
|
|
355
|
-
}) ?? "";
|
|
356
|
-
secretSource = "config";
|
|
357
|
-
} else if (accountConfig.clientSecretFile) try {
|
|
358
|
-
clientSecret = fs.readFileSync(accountConfig.clientSecretFile, "utf8").trim();
|
|
359
|
-
secretSource = "file";
|
|
360
|
-
} catch {
|
|
361
|
-
secretSource = "none";
|
|
362
|
-
}
|
|
363
|
-
else if (process.env.QQBOT_CLIENT_SECRET && base.accountId === DEFAULT_ACCOUNT_ID$1) {
|
|
364
|
-
clientSecret = process.env.QQBOT_CLIENT_SECRET;
|
|
365
|
-
secretSource = "env";
|
|
366
|
-
}
|
|
367
|
-
return {
|
|
368
|
-
accountId: base.accountId,
|
|
369
|
-
name: accountConfig.name,
|
|
370
|
-
enabled: base.enabled,
|
|
371
|
-
appId: base.appId,
|
|
372
|
-
clientSecret,
|
|
373
|
-
secretSource,
|
|
374
|
-
systemPrompt: base.systemPrompt,
|
|
375
|
-
markdownSupport: base.markdownSupport,
|
|
376
|
-
config: accountConfig
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
/** Apply account config updates back into the OpenClaw config object. */
|
|
380
|
-
function applyQQBotAccountConfig(cfg, accountId, input) {
|
|
381
|
-
return applyAccountConfig(cfg, accountId, input);
|
|
382
|
-
}
|
|
383
|
-
//#endregion
|
|
384
131
|
//#region extensions/qqbot/src/engine/config/setup-logic.ts
|
|
385
132
|
/**
|
|
386
133
|
* QQBot setup business logic (pure layer).
|
|
@@ -654,6 +401,16 @@ const QQBotGroupPolicySchema = z.enum([
|
|
|
654
401
|
"allowlist",
|
|
655
402
|
"disabled"
|
|
656
403
|
]).optional();
|
|
404
|
+
const QQBotGroupSchema = z.object({
|
|
405
|
+
requireMention: z.boolean().optional(),
|
|
406
|
+
ignoreOtherMentions: z.boolean().optional(),
|
|
407
|
+
historyLimit: z.number().optional(),
|
|
408
|
+
name: z.string().optional(),
|
|
409
|
+
prompt: z.string().optional(),
|
|
410
|
+
tools: ToolPolicySchema,
|
|
411
|
+
toolsBySender: z.record(z.string(), ToolPolicySchema).optional()
|
|
412
|
+
}).strict();
|
|
413
|
+
const QQBotGroupsSchema = z.record(z.string(), QQBotGroupSchema).optional();
|
|
657
414
|
const QQBotAccountSchema = z.object({
|
|
658
415
|
enabled: z.boolean().optional(),
|
|
659
416
|
name: z.string().optional(),
|
|
@@ -672,7 +429,8 @@ const QQBotAccountSchema = z.object({
|
|
|
672
429
|
upgradeUrl: z.string().optional(),
|
|
673
430
|
upgradeMode: z.enum(["doc", "hot-reload"]).optional(),
|
|
674
431
|
streaming: QQBotStreamingSchema,
|
|
675
|
-
execApprovals: QQBotExecApprovalsSchema
|
|
432
|
+
execApprovals: QQBotExecApprovalsSchema,
|
|
433
|
+
groups: QQBotGroupsSchema
|
|
676
434
|
}).passthrough();
|
|
677
435
|
const qqbotChannelConfigSchema = buildChannelConfigSchema(QQBotAccountSchema.extend({
|
|
678
436
|
stt: QQBotSttSchema,
|
|
@@ -680,4 +438,4 @@ const qqbotChannelConfigSchema = buildChannelConfigSchema(QQBotAccountSchema.ext
|
|
|
680
438
|
defaultAccount: z.string().optional()
|
|
681
439
|
}).passthrough());
|
|
682
440
|
//#endregion
|
|
683
|
-
export { qqbotSetupAdapterShared as a,
|
|
441
|
+
export { qqbotSetupAdapterShared as a, setBridgeLogger as c, qqbotMeta as i, qqbotSetupWizard as n, ensurePlatformAdapter as o, qqbotConfigAdapter as r, getBridgeLogger as s, qqbotChannelConfigSchema as t };
|