@jerryliang122/openclaw-qqbot 1.0.4 → 1.0.6

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/index.cjs CHANGED
@@ -34,367 +34,79 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  ));
35
35
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
36
 
37
- // src/config.ts
38
- var config_exports = {};
39
- __export(config_exports, {
40
- DEFAULT_ACCOUNT_ID: () => DEFAULT_ACCOUNT_ID,
41
- applyQQBotAccountConfig: () => applyQQBotAccountConfig,
42
- isGroupAllowed: () => isGroupAllowed,
43
- listQQBotAccountIds: () => listQQBotAccountIds,
44
- resolveDefaultQQBotAccountId: () => resolveDefaultQQBotAccountId,
45
- resolveGroupAllowFrom: () => resolveGroupAllowFrom,
46
- resolveGroupCoalesceConfig: () => resolveGroupCoalesceConfig,
47
- resolveGroupCoalesceEnabled: () => resolveGroupCoalesceEnabled,
48
- resolveGroupConfig: () => resolveGroupConfig,
49
- resolveGroupConfigFromAccount: () => resolveGroupConfigFromAccount,
50
- resolveGroupName: () => resolveGroupName,
51
- resolveGroupPolicy: () => resolveGroupPolicy,
52
- resolveGroupPrompt: () => resolveGroupPrompt,
53
- resolveHistoryLimit: () => resolveHistoryLimit,
54
- resolveIgnoreOtherMentions: () => resolveIgnoreOtherMentions,
55
- resolveMentionPatterns: () => resolveMentionPatterns,
56
- resolveQQBotAccount: () => resolveQQBotAccount,
57
- resolveQQBotEnvAppId: () => resolveQQBotEnvAppId,
58
- resolveQQBotEnvClientSecret: () => resolveQQBotEnvClientSecret,
59
- resolveRequireMention: () => resolveRequireMention,
60
- resolveToolPolicy: () => resolveToolPolicy,
61
- resolveUserAgentSuffix: () => resolveUserAgentSuffix
62
- });
63
- function resolveMentionPatterns(cfg, agentId) {
64
- if (agentId) {
65
- const agents = cfg.agents;
66
- const entry = agents?.list?.find((a) => a.id?.trim().toLowerCase() === agentId.trim().toLowerCase());
67
- const agentGroupChat = entry?.groupChat;
68
- if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) {
69
- return agentGroupChat.mentionPatterns ?? [];
70
- }
71
- }
72
- const globalGroupChat = cfg?.messages?.groupChat;
73
- if (globalGroupChat && typeof globalGroupChat === "object" && Object.hasOwn(globalGroupChat, "mentionPatterns")) {
74
- return globalGroupChat.mentionPatterns ?? [];
37
+ // src/utils/platform.ts
38
+ function getHomeDir() {
39
+ try {
40
+ const home = os.homedir();
41
+ if (home && fs.existsSync(home)) return home;
42
+ } catch {
75
43
  }
76
- return [];
44
+ const envHome = process.env.HOME || process.env.USERPROFILE;
45
+ if (envHome && fs.existsSync(envHome)) return envHome;
46
+ return os.tmpdir();
77
47
  }
78
- function evaluateMatchedGroupAccessForPolicy(params) {
79
- if (params.groupPolicy === "disabled") {
80
- return { allowed: false, groupPolicy: params.groupPolicy, reason: "disabled" };
81
- }
82
- if (params.groupPolicy === "allowlist") {
83
- if (params.requireMatchInput && !params.hasMatchInput) {
84
- return { allowed: false, groupPolicy: params.groupPolicy, reason: "missing_match_input" };
85
- }
86
- if (!params.allowlistConfigured) {
87
- return { allowed: false, groupPolicy: params.groupPolicy, reason: "empty_allowlist" };
88
- }
89
- if (!params.allowlistMatched) {
90
- return { allowed: false, groupPolicy: params.groupPolicy, reason: "not_allowlisted" };
91
- }
48
+ function getQQBotDataDir(...subPaths) {
49
+ const dir = path.join(getHomeDir(), ".openclaw", "qqbot", ...subPaths);
50
+ if (!fs.existsSync(dir)) {
51
+ fs.mkdirSync(dir, { recursive: true });
92
52
  }
93
- return { allowed: true, groupPolicy: params.groupPolicy, reason: "allowed" };
94
- }
95
- function resolveGroupPolicy(cfg, accountId) {
96
- const account = resolveQQBotAccount(cfg, accountId);
97
- return account.config?.groupPolicy ?? DEFAULT_GROUP_POLICY;
98
- }
99
- function resolveGroupAllowFrom(cfg, accountId) {
100
- const account = resolveQQBotAccount(cfg, accountId);
101
- return (account.config?.groupAllowFrom ?? []).map((id) => String(id).trim().toUpperCase());
102
- }
103
- function isGroupAllowed(cfg, groupOpenid, accountId) {
104
- const account = resolveQQBotAccount(cfg, accountId);
105
- const policy = account.config?.groupPolicy ?? DEFAULT_GROUP_POLICY;
106
- const allowList = (account.config?.groupAllowFrom ?? []).map((id) => String(id).trim().toUpperCase());
107
- const allowlistConfigured = allowList.length > 0;
108
- const allowlistMatched = allowList.some((id) => id === "*" || id === groupOpenid.toUpperCase());
109
- return evaluateMatchedGroupAccessForPolicy({
110
- groupPolicy: policy,
111
- allowlistConfigured,
112
- allowlistMatched
113
- }).allowed;
114
- }
115
- function resolveGroupConfigFromAccount(account, groupOpenid) {
116
- const groups = account.config?.groups ?? {};
117
- const wildcardCfg = groups["*"] ?? {};
118
- const specificCfg = groups[groupOpenid] ?? {};
119
- const accountDefaultRequireMention = account.config?.defaultRequireMention ?? DEFAULT_GROUP_CONFIG.requireMention;
120
- const accountDefaultCoalesce = account.config?.groupCoalesce ?? DEFAULT_GROUP_COALESCE_CONFIG;
121
- const coalesce = {
122
- enabled: specificCfg.coalesce?.enabled ?? wildcardCfg.coalesce?.enabled ?? accountDefaultCoalesce.enabled ?? DEFAULT_GROUP_COALESCE_CONFIG.enabled
123
- };
124
- return {
125
- requireMention: specificCfg.requireMention ?? wildcardCfg.requireMention ?? accountDefaultRequireMention,
126
- ignoreOtherMentions: specificCfg.ignoreOtherMentions ?? wildcardCfg.ignoreOtherMentions ?? DEFAULT_GROUP_CONFIG.ignoreOtherMentions,
127
- toolPolicy: specificCfg.toolPolicy ?? wildcardCfg.toolPolicy ?? DEFAULT_GROUP_CONFIG.toolPolicy,
128
- name: specificCfg.name ?? wildcardCfg.name ?? DEFAULT_GROUP_CONFIG.name,
129
- prompt: specificCfg.prompt ?? wildcardCfg.prompt ?? DEFAULT_GROUP_PROMPT,
130
- historyLimit: specificCfg.historyLimit ?? wildcardCfg.historyLimit ?? DEFAULT_GROUP_CONFIG.historyLimit,
131
- historyMode: specificCfg.historyMode ?? wildcardCfg.historyMode ?? DEFAULT_GROUP_CONFIG.historyMode,
132
- unmentionedInbound: specificCfg.unmentionedInbound ?? wildcardCfg.unmentionedInbound ?? DEFAULT_GROUP_CONFIG.unmentionedInbound,
133
- coalesce
134
- };
135
- }
136
- function resolveGroupConfig(cfg, groupOpenid, accountId) {
137
- return resolveGroupConfigFromAccount(resolveQQBotAccount(cfg, accountId), groupOpenid);
138
- }
139
- function resolveHistoryLimit(cfg, groupOpenid, accountId) {
140
- return Math.max(0, resolveGroupConfig(cfg, groupOpenid, accountId).historyLimit);
141
- }
142
- function resolveGroupPrompt(cfg, groupOpenid, accountId) {
143
- return resolveGroupConfig(cfg, groupOpenid, accountId).prompt;
144
- }
145
- function resolveRequireMention(cfg, groupOpenid, accountId) {
146
- return resolveGroupConfig(cfg, groupOpenid, accountId).requireMention;
147
- }
148
- function resolveIgnoreOtherMentions(cfg, groupOpenid, accountId) {
149
- return resolveGroupConfig(cfg, groupOpenid, accountId).ignoreOtherMentions;
150
- }
151
- function resolveToolPolicy(cfg, groupOpenid, accountId) {
152
- return resolveGroupConfig(cfg, groupOpenid, accountId).toolPolicy;
153
- }
154
- function resolveGroupName(cfg, groupOpenid, accountId) {
155
- const name = resolveGroupConfig(cfg, groupOpenid, accountId).name;
156
- return name || groupOpenid.slice(0, 8);
157
- }
158
- function resolveUserAgentSuffix(cfg) {
159
- const qqbot = cfg.channels?.qqbot;
160
- return qqbot?.userAgentSuffix ? String(qqbot.userAgentSuffix).trim() : "";
53
+ return dir;
161
54
  }
162
- function normalizeAppId(raw) {
163
- if (raw === null || raw === void 0) return "";
164
- return String(raw).trim();
55
+ function getQQBotMediaDir(...subPaths) {
56
+ const dir = path.join(getHomeDir(), ".openclaw", "media", "qqbot", ...subPaths);
57
+ if (!fs.existsSync(dir)) {
58
+ fs.mkdirSync(dir, { recursive: true });
59
+ }
60
+ return dir;
165
61
  }
166
- function firstNonEmptyEnv(...values) {
167
- for (const value of values) {
168
- if (value?.trim()) return value.trim();
62
+ var os, path, fs;
63
+ var init_platform = __esm({
64
+ "src/utils/platform.ts"() {
65
+ "use strict";
66
+ os = __toESM(require("os"), 1);
67
+ path = __toESM(require("path"), 1);
68
+ fs = __toESM(require("fs"), 1);
169
69
  }
170
- return "";
70
+ });
71
+
72
+ // src/request-context.ts
73
+ function runWithRequestContext(ctx, fn) {
74
+ return asyncLocalStorage.run(ctx, fn);
171
75
  }
172
- function resolveQQBotEnvAppId() {
173
- return firstNonEmptyEnv(process.env.QQBOT_APPID);
76
+ function getRequestContext() {
77
+ return asyncLocalStorage.getStore();
174
78
  }
175
- function resolveQQBotEnvClientSecret() {
176
- return firstNonEmptyEnv(process.env.QQBOT_SECRET);
79
+ function getRequestTarget() {
80
+ return asyncLocalStorage.getStore()?.target;
177
81
  }
178
- function listQQBotAccountIds(cfg) {
179
- const ids = /* @__PURE__ */ new Set();
180
- const qqbot = cfg.channels?.qqbot;
181
- if (qqbot?.appId || resolveQQBotEnvAppId()) {
182
- ids.add(DEFAULT_ACCOUNT_ID);
183
- }
184
- if (qqbot?.accounts) {
185
- for (const accountId of Object.keys(qqbot.accounts)) {
186
- if (qqbot.accounts[accountId]?.appId) {
187
- ids.add(accountId);
188
- }
189
- }
190
- }
191
- return Array.from(ids);
82
+ function getRequestAccountId() {
83
+ return asyncLocalStorage.getStore()?.accountId;
192
84
  }
193
- function resolveDefaultQQBotAccountId(cfg) {
194
- const qqbot = cfg.channels?.qqbot;
195
- if (qqbot?.appId || resolveQQBotEnvAppId()) {
196
- return DEFAULT_ACCOUNT_ID;
197
- }
198
- if (qqbot?.accounts) {
199
- const ids = Object.keys(qqbot.accounts);
200
- if (ids.length > 0) {
201
- return ids[0];
202
- }
85
+ var import_node_async_hooks, asyncLocalStorage;
86
+ var init_request_context = __esm({
87
+ "src/request-context.ts"() {
88
+ "use strict";
89
+ import_node_async_hooks = require("async_hooks");
90
+ asyncLocalStorage = new import_node_async_hooks.AsyncLocalStorage();
203
91
  }
204
- return DEFAULT_ACCOUNT_ID;
205
- }
206
- function resolveQQBotAccount(cfg, accountId) {
207
- const resolvedAccountId = accountId ?? resolveDefaultQQBotAccountId(cfg);
208
- const qqbot = cfg.channels?.qqbot;
209
- let accountConfig = {};
210
- let appId = "";
211
- let clientSecret = "";
212
- let secretSource = "none";
213
- if (resolvedAccountId === DEFAULT_ACCOUNT_ID) {
214
- const { accounts: _accounts, ...topLevelConfig } = qqbot ?? {};
215
- accountConfig = {
216
- ...topLevelConfig,
217
- markdownSupport: qqbot?.markdownSupport ?? true
218
- };
219
- appId = normalizeAppId(qqbot?.appId);
220
- } else {
221
- const account = qqbot?.accounts?.[resolvedAccountId];
222
- accountConfig = account ?? {};
223
- appId = normalizeAppId(account?.appId);
92
+ });
93
+
94
+ // node_modules/@tencent-connect/qqbot-nodejs/dist/middleware/types.js
95
+ function resolvePolicy(ctx, path21, explicit, defaultValue) {
96
+ if (explicit !== void 0 && explicit !== null) {
97
+ return explicit;
224
98
  }
225
- if (accountConfig.clientSecret) {
226
- clientSecret = accountConfig.clientSecret;
227
- secretSource = "config";
228
- } else if (accountConfig.clientSecretFile) {
229
- secretSource = "file";
230
- } else if (resolvedAccountId === DEFAULT_ACCOUNT_ID && resolveQQBotEnvClientSecret()) {
231
- clientSecret = resolveQQBotEnvClientSecret();
232
- secretSource = "env";
99
+ const keys = path21.split(".");
100
+ let value = ctx.state.policy;
101
+ for (const key2 of keys) {
102
+ if (value === null || value === void 0)
103
+ break;
104
+ value = value[key2];
233
105
  }
234
- if (!appId && resolvedAccountId === DEFAULT_ACCOUNT_ID) {
235
- appId = normalizeAppId(resolveQQBotEnvAppId());
106
+ if (value !== void 0 && value !== null) {
107
+ return value;
236
108
  }
237
- return {
238
- accountId: resolvedAccountId,
239
- name: accountConfig.name,
240
- enabled: accountConfig.enabled !== false,
241
- appId,
242
- clientSecret,
243
- secretSource,
244
- systemPrompt: accountConfig.systemPrompt,
245
- markdownSupport: accountConfig.markdownSupport !== false,
246
- commandPanelNative: accountConfig.commands?.native !== false,
247
- userAgentSuffix: resolveUserAgentSuffix(cfg),
248
- config: accountConfig
249
- };
250
- }
251
- function applyQQBotAccountConfig(cfg, accountId, input) {
252
- const next = { ...cfg };
253
- if (accountId === DEFAULT_ACCOUNT_ID) {
254
- const existingConfig = next.channels?.qqbot || {};
255
- const allowFrom = existingConfig.allowFrom ?? ["*"];
256
- next.channels = {
257
- ...next.channels,
258
- qqbot: {
259
- ...next.channels?.qqbot || {},
260
- enabled: true,
261
- allowFrom,
262
- ...input.appId ? { appId: input.appId } : {},
263
- ...input.clientSecret ? { clientSecret: input.clientSecret } : input.clientSecretFile ? { clientSecretFile: input.clientSecretFile } : {},
264
- ...input.name ? { name: input.name } : {}
265
- }
266
- };
267
- } else {
268
- const existingAccountConfig = next.channels?.qqbot?.accounts?.[accountId] || {};
269
- const allowFrom = existingAccountConfig.allowFrom ?? ["*"];
270
- next.channels = {
271
- ...next.channels,
272
- qqbot: {
273
- ...next.channels?.qqbot || {},
274
- enabled: true,
275
- accounts: {
276
- ...next.channels?.qqbot?.accounts || {},
277
- [accountId]: {
278
- ...next.channels?.qqbot?.accounts?.[accountId] || {},
279
- enabled: true,
280
- allowFrom,
281
- ...input.appId ? { appId: input.appId } : {},
282
- ...input.clientSecret ? { clientSecret: input.clientSecret } : input.clientSecretFile ? { clientSecretFile: input.clientSecretFile } : {},
283
- ...input.name ? { name: input.name } : {}
284
- }
285
- }
286
- }
287
- };
288
- }
289
- return next;
290
- }
291
- function resolveGroupCoalesceConfig(cfg, groupOpenid, accountId) {
292
- return resolveGroupConfig(cfg, groupOpenid, accountId).coalesce;
293
- }
294
- function resolveGroupCoalesceEnabled(cfg, groupOpenid, accountId) {
295
- return resolveGroupCoalesceConfig(cfg, groupOpenid, accountId).enabled;
296
- }
297
- var DEFAULT_ACCOUNT_ID, DEFAULT_GROUP_POLICY, DEFAULT_GROUP_HISTORY_LIMIT, DEFAULT_GROUP_COALESCE_CONFIG, DEFAULT_GROUP_CONFIG, DEFAULT_GROUP_PROMPT;
298
- var init_config = __esm({
299
- "src/config.ts"() {
300
- "use strict";
301
- DEFAULT_ACCOUNT_ID = "default";
302
- DEFAULT_GROUP_POLICY = "open";
303
- DEFAULT_GROUP_HISTORY_LIMIT = 20;
304
- DEFAULT_GROUP_COALESCE_CONFIG = {
305
- enabled: true
306
- };
307
- DEFAULT_GROUP_CONFIG = {
308
- requireMention: true,
309
- ignoreOtherMentions: false,
310
- toolPolicy: "restricted",
311
- name: "",
312
- historyLimit: DEFAULT_GROUP_HISTORY_LIMIT,
313
- historyMode: "clear",
314
- unmentionedInbound: "user_request",
315
- coalesce: DEFAULT_GROUP_COALESCE_CONFIG
316
- };
317
- DEFAULT_GROUP_PROMPT = [
318
- "\u82E5\u53D1\u9001\u8005\u4E3A\u673A\u5668\u4EBA\uFF0C\u4EC5\u5728\u5BF9\u65B9\u660E\u786E@\u4F60\u63D0\u95EE\u6216\u8BF7\u6C42\u534F\u52A9\u5177\u4F53\u4EFB\u52A1\u65F6\uFF0C\u4EE5\u7B80\u6D01\u660E\u4E86\u7684\u5185\u5BB9\u56DE\u590D\uFF0C",
319
- "\u907F\u514D\u4E0E\u5176\u4ED6\u673A\u5668\u4EBA\u4EA7\u751F\u62A2\u7B54\u6216\u591A\u8F6E\u65E0\u610F\u4E49\u5BF9\u8BDD\u3002",
320
- "\u5728\u7FA4\u804A\u4E2D\u4F18\u5148\u8BA9\u4EBA\u7C7B\u7528\u6237\u7684\u6D88\u606F\u5F97\u5230\u54CD\u5E94\uFF0C\u673A\u5668\u4EBA\u4E4B\u95F4\u4FDD\u6301\u534F\u4F5C\u800C\u975E\u7ADE\u4E89\uFF0C\u786E\u4FDD\u5BF9\u8BDD\u6709\u5E8F\u4E0D\u5237\u5C4F\u3002"
321
- ].join("");
322
- }
323
- });
324
-
325
- // src/utils/platform.ts
326
- function getHomeDir() {
327
- try {
328
- const home = os.homedir();
329
- if (home && fs.existsSync(home)) return home;
330
- } catch {
331
- }
332
- const envHome = process.env.HOME || process.env.USERPROFILE;
333
- if (envHome && fs.existsSync(envHome)) return envHome;
334
- return os.tmpdir();
335
- }
336
- function getQQBotDataDir(...subPaths) {
337
- const dir = path.join(getHomeDir(), ".openclaw", "qqbot", ...subPaths);
338
- if (!fs.existsSync(dir)) {
339
- fs.mkdirSync(dir, { recursive: true });
340
- }
341
- return dir;
342
- }
343
- function getQQBotMediaDir(...subPaths) {
344
- const dir = path.join(getHomeDir(), ".openclaw", "media", "qqbot", ...subPaths);
345
- if (!fs.existsSync(dir)) {
346
- fs.mkdirSync(dir, { recursive: true });
347
- }
348
- return dir;
349
- }
350
- var os, path, fs;
351
- var init_platform = __esm({
352
- "src/utils/platform.ts"() {
353
- "use strict";
354
- os = __toESM(require("os"), 1);
355
- path = __toESM(require("path"), 1);
356
- fs = __toESM(require("fs"), 1);
357
- }
358
- });
359
-
360
- // src/request-context.ts
361
- function runWithRequestContext(ctx, fn) {
362
- return asyncLocalStorage.run(ctx, fn);
363
- }
364
- function getRequestContext() {
365
- return asyncLocalStorage.getStore();
366
- }
367
- function getRequestTarget() {
368
- return asyncLocalStorage.getStore()?.target;
369
- }
370
- function getRequestAccountId() {
371
- return asyncLocalStorage.getStore()?.accountId;
372
- }
373
- var import_node_async_hooks, asyncLocalStorage;
374
- var init_request_context = __esm({
375
- "src/request-context.ts"() {
376
- "use strict";
377
- import_node_async_hooks = require("async_hooks");
378
- asyncLocalStorage = new import_node_async_hooks.AsyncLocalStorage();
379
- }
380
- });
381
-
382
- // node_modules/@tencent-connect/qqbot-nodejs/dist/middleware/types.js
383
- function resolvePolicy(ctx, path21, explicit, defaultValue) {
384
- if (explicit !== void 0 && explicit !== null) {
385
- return explicit;
386
- }
387
- const keys = path21.split(".");
388
- let value = ctx.state.policy;
389
- for (const key2 of keys) {
390
- if (value === null || value === void 0)
391
- break;
392
- value = value[key2];
393
- }
394
- if (value !== void 0 && value !== null) {
395
- return value;
396
- }
397
- return defaultValue;
109
+ return defaultValue;
398
110
  }
399
111
  async function runMiddlewareChain(middlewares, ctx) {
400
112
  let index = -1;
@@ -8272,7 +7984,10 @@ var outbound_service_exports = {};
8272
7984
  __export(outbound_service_exports, {
8273
7985
  OutboundService: () => OutboundService,
8274
7986
  getGateway: () => getGateway,
7987
+ getRegisteredAccountIds: () => getRegisteredAccountIds,
7988
+ notRunningError: () => notRunningError,
8275
7989
  registerGateway: () => registerGateway,
7990
+ resolveGatewayForSend: () => resolveGatewayForSend,
8276
7991
  rollbackPassiveSlot: () => rollbackPassiveSlot,
8277
7992
  sendMedia: () => sendMedia,
8278
7993
  sendText: () => sendText,
@@ -8281,6 +7996,13 @@ __export(outbound_service_exports, {
8281
7996
  tryAcquirePassiveSlot: () => tryAcquirePassiveSlot,
8282
7997
  unregisterGateway: () => unregisterGateway
8283
7998
  });
7999
+ function logFallbackInfo(msg) {
8000
+ try {
8001
+ svcLog ??= createPluginLogger({ prefix: "[outbound]" });
8002
+ svcLog.info(msg);
8003
+ } catch {
8004
+ }
8005
+ }
8284
8006
  function reservePassiveReply(params) {
8285
8007
  if (!params.replyToId) return { rollback: () => {
8286
8008
  } };
@@ -8310,26 +8032,57 @@ function rollbackPassiveSlot(accountId, msgId) {
8310
8032
  function registerGateway(accountId, gw) {
8311
8033
  gateways.set(accountId, gw);
8312
8034
  }
8313
- function unregisterGateway(accountId) {
8035
+ function unregisterGateway(accountId, expectGw) {
8036
+ if (expectGw) {
8037
+ if (gateways.get(accountId) !== expectGw) return;
8038
+ gateways.delete(accountId);
8039
+ clearQuotaCacheForAccount(accountId);
8040
+ return;
8041
+ }
8314
8042
  gateways.delete(accountId);
8315
8043
  clearQuotaCacheForAccount(accountId);
8316
8044
  }
8317
8045
  function getGateway(accountId) {
8318
8046
  return gateways.get(accountId);
8319
8047
  }
8048
+ function getRegisteredAccountIds() {
8049
+ return [...gateways.keys()];
8050
+ }
8051
+ function resolveGatewayForSend(accountId) {
8052
+ const gw = gateways.get(accountId);
8053
+ if (gw) return { gw, accountId };
8054
+ const running = getRegisteredAccountIds();
8055
+ if (running.length === 1) {
8056
+ const soleAccountId = running[0];
8057
+ const soleGw = gateways.get(soleAccountId);
8058
+ if (!soleGw) return void 0;
8059
+ if (!gatewayFallbackLogged.has(accountId)) {
8060
+ gatewayFallbackLogged.add(accountId);
8061
+ logFallbackInfo(
8062
+ `account "${accountId}" not running; falling back to sole running account "${soleAccountId}"`
8063
+ );
8064
+ }
8065
+ return { gw: soleGw, accountId: soleAccountId, fallbackFrom: accountId };
8066
+ }
8067
+ return void 0;
8068
+ }
8069
+ function notRunningError(accountId) {
8070
+ const running = getRegisteredAccountIds();
8071
+ return `Bot "${accountId}" not running${running.length ? ` (running accounts: ${running.join(", ")})` : ""}`;
8072
+ }
8320
8073
  async function sendText(params) {
8321
8074
  const accountId = params.account.accountId;
8322
- const gw = gateways.get(accountId);
8323
- if (!gw) return { error: `Bot "${accountId}" not running` };
8075
+ const resolved = resolveGatewayForSend(accountId);
8076
+ if (!resolved) return { error: notRunningError(accountId) };
8324
8077
  const target = parseTarget(params.to);
8325
8078
  const reservation = reservePassiveReply({
8326
8079
  replyToId: params.replyToId,
8327
- accountId,
8080
+ accountId: resolved.accountId,
8328
8081
  scope: target.scope,
8329
8082
  quotaReserved: params.quotaReserved
8330
8083
  });
8331
8084
  try {
8332
- const result = await gw.sendText(target, params.text, { msgId: reservation.msgId });
8085
+ const result = await resolved.gw.sendText(target, params.text, { msgId: reservation.msgId });
8333
8086
  return { messageId: result.id };
8334
8087
  } catch (err) {
8335
8088
  reservation.rollback();
@@ -8338,12 +8091,12 @@ async function sendText(params) {
8338
8091
  }
8339
8092
  async function sendMedia(params) {
8340
8093
  const accountId = params.account.accountId;
8341
- const gw = gateways.get(accountId);
8342
- if (!gw) return { error: `Bot "${accountId}" not running` };
8094
+ const resolved = resolveGatewayForSend(accountId);
8095
+ if (!resolved) return { error: notRunningError(accountId) };
8343
8096
  const target = parseTarget(params.to);
8344
8097
  const reservation = reservePassiveReply({
8345
8098
  replyToId: params.replyToId,
8346
- accountId,
8099
+ accountId: resolved.accountId,
8347
8100
  scope: target.scope,
8348
8101
  quotaReserved: params.quotaReserved
8349
8102
  });
@@ -8352,19 +8105,19 @@ async function sendMedia(params) {
8352
8105
  const msgId = reservation.msgId;
8353
8106
  if (kind === "voice") {
8354
8107
  const source = resolveVoiceSource(params.mediaUrl);
8355
- const result2 = await gw.sendVoice(target, source, { text: params.text, msgId });
8108
+ const result2 = await resolved.gw.sendVoice(target, source, { text: params.text, msgId });
8356
8109
  return { messageId: result2.id };
8357
8110
  }
8358
8111
  if (kind === "video") {
8359
- const result2 = await gw.sendVideo(target, params.mediaUrl, { text: params.text, msgId });
8112
+ const result2 = await resolved.gw.sendVideo(target, params.mediaUrl, { text: params.text, msgId });
8360
8113
  return { messageId: result2.id };
8361
8114
  }
8362
8115
  if (kind === "file") {
8363
- const result2 = await gw.sendFile(target, params.mediaUrl, { text: params.text, msgId });
8116
+ const result2 = await resolved.gw.sendFile(target, params.mediaUrl, { text: params.text, msgId });
8364
8117
  return { messageId: result2.id };
8365
8118
  }
8366
8119
  const fileType = MEDIA_KIND_TO_FILE_TYPE[kind];
8367
- const result = await gw.sendMedia(target, params.mediaUrl, { text: params.text, msgId, fileType });
8120
+ const result = await resolved.gw.sendMedia(target, params.mediaUrl, { text: params.text, msgId, fileType });
8368
8121
  return { messageId: result.id };
8369
8122
  } catch (err) {
8370
8123
  reservation.rollback();
@@ -8373,17 +8126,17 @@ async function sendMedia(params) {
8373
8126
  }
8374
8127
  async function sendVoice(params) {
8375
8128
  const accountId = params.account.accountId;
8376
- const gw = gateways.get(accountId);
8377
- if (!gw) return { error: `Bot "${accountId}" not running` };
8129
+ const resolved = resolveGatewayForSend(accountId);
8130
+ if (!resolved) return { error: notRunningError(accountId) };
8378
8131
  const target = parseTarget(params.to);
8379
8132
  const reservation = reservePassiveReply({
8380
8133
  replyToId: params.replyToId,
8381
- accountId,
8134
+ accountId: resolved.accountId,
8382
8135
  scope: target.scope,
8383
8136
  quotaReserved: params.quotaReserved
8384
8137
  });
8385
8138
  try {
8386
- const result = await gw.sendVoice(target, params.source, { msgId: reservation.msgId });
8139
+ const result = await resolved.gw.sendVoice(target, params.source, { msgId: reservation.msgId });
8387
8140
  return { messageId: result.id };
8388
8141
  } catch (err) {
8389
8142
  reservation.rollback();
@@ -8392,17 +8145,17 @@ async function sendVoice(params) {
8392
8145
  }
8393
8146
  async function sendVideo(params) {
8394
8147
  const accountId = params.account.accountId;
8395
- const gw = gateways.get(accountId);
8396
- if (!gw) return { error: `Bot "${accountId}" not running` };
8148
+ const resolved = resolveGatewayForSend(accountId);
8149
+ if (!resolved) return { error: notRunningError(accountId) };
8397
8150
  const target = parseTarget(params.to);
8398
8151
  const reservation = reservePassiveReply({
8399
8152
  replyToId: params.replyToId,
8400
- accountId,
8153
+ accountId: resolved.accountId,
8401
8154
  scope: target.scope,
8402
8155
  quotaReserved: params.quotaReserved
8403
8156
  });
8404
8157
  try {
8405
- const result = await gw.sendVideo(target, params.videoUrl, { msgId: reservation.msgId });
8158
+ const result = await resolved.gw.sendVideo(target, params.videoUrl, { msgId: reservation.msgId });
8406
8159
  return { messageId: result.id };
8407
8160
  } catch (err) {
8408
8161
  reservation.rollback();
@@ -8427,15 +8180,20 @@ function formatError(err) {
8427
8180
  }
8428
8181
  return { error: String(err) };
8429
8182
  }
8430
- var path4, gateways, MEDIA_KIND_TO_FILE_TYPE, OutboundService;
8183
+ var path4, GATEWAYS_REGISTRY_KEY, globalRegistry, gateways, svcLog, gatewayFallbackLogged, MEDIA_KIND_TO_FILE_TYPE, OutboundService;
8431
8184
  var init_outbound_service = __esm({
8432
8185
  "src/outbound/outbound-service.ts"() {
8433
8186
  "use strict";
8434
8187
  path4 = __toESM(require("path"), 1);
8435
8188
  init_dist();
8189
+ init_plugin_logger();
8436
8190
  init_target();
8437
8191
  init_quota_manager();
8438
- gateways = /* @__PURE__ */ new Map();
8192
+ GATEWAYS_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("openclaw-qqbot.gateways");
8193
+ globalRegistry = globalThis;
8194
+ gateways = globalRegistry[GATEWAYS_REGISTRY_KEY] ?? /* @__PURE__ */ new Map();
8195
+ globalRegistry[GATEWAYS_REGISTRY_KEY] = gateways;
8196
+ gatewayFallbackLogged = /* @__PURE__ */ new Set();
8439
8197
  MEDIA_KIND_TO_FILE_TYPE = {
8440
8198
  image: MediaFileType.IMAGE,
8441
8199
  voice: MediaFileType.VOICE,
@@ -8501,7 +8259,7 @@ var init_outbound_service = __esm({
8501
8259
 
8502
8260
  // src/utils/pkg-version.ts
8503
8261
  function getPackageVersion() {
8504
- return true ? "1.0.4" : "unknown";
8262
+ return true ? "1.0.6" : "unknown";
8505
8263
  }
8506
8264
  function getOpenclawVersion(runtimeVersion) {
8507
8265
  return runtimeVersion && runtimeVersion !== "unknown" ? runtimeVersion : "unknown";
@@ -8768,90 +8526,464 @@ function installExitHooksOnce() {
8768
8526
  process.exit(0);
8769
8527
  });
8770
8528
  }
8771
- function getQQBotRuntime() {
8772
- if (!runtime) throw new Error("QQBot runtime not initialized");
8773
- return runtime;
8529
+ function getQQBotRuntime() {
8530
+ if (!runtime) throw new Error("QQBot runtime not initialized");
8531
+ return runtime;
8532
+ }
8533
+ function tryGetQQBotRuntime() {
8534
+ return runtime;
8535
+ }
8536
+ var runtime, exitHooksInstalled;
8537
+ var init_runtime = __esm({
8538
+ "src/runtime.ts"() {
8539
+ "use strict";
8540
+ init_bot_instance();
8541
+ init_ref_index_store();
8542
+ init_pkg_version();
8543
+ runtime = null;
8544
+ exitHooksInstalled = false;
8545
+ }
8546
+ });
8547
+
8548
+ // src/utils/plugin-logger.ts
8549
+ function consoleSink() {
8550
+ const C2 = "\x1B[36m", Y = "\x1B[33m", R = "\x1B[31m", G = "\x1B[90m", X = "\x1B[0m";
8551
+ return {
8552
+ debug: (m3) => console.debug(`${G}[qqbot]${X}`, m3),
8553
+ info: (m3) => console.log(`${C2}[qqbot]${X}`, m3),
8554
+ warn: (m3) => console.warn(`${Y}[qqbot]${X}`, m3),
8555
+ error: (m3) => console.error(`${R}[qqbot]${X}`, m3)
8556
+ };
8557
+ }
8558
+ function enrichMeta(meta) {
8559
+ const ctx = getRequestContext();
8560
+ if (!ctx) return meta;
8561
+ const trace = {};
8562
+ if (ctx.accountId) trace.accountId = ctx.accountId;
8563
+ if (ctx.messageId) trace.messageId = ctx.messageId;
8564
+ if (ctx.openId) trace.openId = ctx.openId;
8565
+ if (Object.keys(trace).length === 0) return meta;
8566
+ return meta ? { ...trace, ...meta } : trace;
8567
+ }
8568
+ function frameworkSink() {
8569
+ const resolve2 = () => {
8570
+ try {
8571
+ const r = tryGetQQBotRuntime();
8572
+ if (r?.logging) {
8573
+ const child = r.logging.getChildLogger({ subsystem: "qqbot/core" });
8574
+ return {
8575
+ debug: child.debug?.bind(child),
8576
+ info: child.info.bind(child),
8577
+ warn: child.warn.bind(child),
8578
+ error: child.error.bind(child)
8579
+ };
8580
+ }
8581
+ } catch {
8582
+ }
8583
+ return consoleSink();
8584
+ };
8585
+ return {
8586
+ debug: (msg, meta) => resolve2().debug?.(msg, meta),
8587
+ info: (msg, meta) => resolve2().info(msg, meta),
8588
+ warn: (msg, meta) => resolve2().warn(msg, meta),
8589
+ error: (msg, meta) => resolve2().error(msg, meta)
8590
+ };
8591
+ }
8592
+ function createPluginLogger(opts = {}) {
8593
+ const output = opts.output ?? (opts.forceConsole ? consoleSink() : frameworkSink());
8594
+ const prefix = opts.prefix ?? "";
8595
+ const fmt = (msg) => prefix ? `${prefix} ${msg}` : msg;
8596
+ const buildChild = (parentPrefix, tag) => createPluginLogger({
8597
+ output,
8598
+ prefix: parentPrefix ? `${parentPrefix}[${tag}]` : `[${tag}]`
8599
+ });
8600
+ return {
8601
+ info: (msg, meta) => output.info(fmt(msg), enrichMeta(meta)),
8602
+ warn: (msg, meta) => output.warn(fmt(msg), enrichMeta(meta)),
8603
+ error: (msg, meta) => output.error(fmt(msg), enrichMeta(meta)),
8604
+ debug: (msg, meta) => output.debug(fmt(msg), enrichMeta(meta)),
8605
+ child: (tag) => buildChild(prefix, tag)
8606
+ };
8607
+ }
8608
+ var init_plugin_logger = __esm({
8609
+ "src/utils/plugin-logger.ts"() {
8610
+ "use strict";
8611
+ init_request_context();
8612
+ init_runtime();
8613
+ }
8614
+ });
8615
+
8616
+ // src/features/credential-backup.ts
8617
+ function getBackupPath() {
8618
+ return import_node_path3.default.join(getQQBotDataDir("data"), BACKUP_DIR, BACKUP_FILENAME);
8619
+ }
8620
+ function saveCredentialBackup(accountId, appId, clientSecret) {
8621
+ if (!appId || !clientSecret) return;
8622
+ try {
8623
+ const backupPath = getBackupPath();
8624
+ const dir = import_node_path3.default.dirname(backupPath);
8625
+ if (!import_node_fs3.default.existsSync(dir)) {
8626
+ import_node_fs3.default.mkdirSync(dir, { recursive: true });
8627
+ }
8628
+ const data = {
8629
+ accountId,
8630
+ appId,
8631
+ clientSecret,
8632
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
8633
+ };
8634
+ const tmpPath = backupPath + ".tmp";
8635
+ import_node_fs3.default.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf8");
8636
+ import_node_fs3.default.renameSync(tmpPath, backupPath);
8637
+ } catch (err) {
8638
+ backupLog.debug(`save failed: ${err instanceof Error ? err.message : String(err)}`, { accountId });
8639
+ }
8640
+ }
8641
+ function loadCredentialBackup(accountId) {
8642
+ try {
8643
+ const backupPath = getBackupPath();
8644
+ if (import_node_fs3.default.existsSync(backupPath)) {
8645
+ return readBackupFile(backupPath, accountId);
8646
+ }
8647
+ return null;
8648
+ } catch (err) {
8649
+ backupLog.debug(`load failed: ${err instanceof Error ? err.message : String(err)}`);
8650
+ return null;
8651
+ }
8652
+ }
8653
+ function readBackupFile(filePath, accountId) {
8654
+ const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
8655
+ const data = JSON.parse(raw);
8656
+ if (!data.appId || !data.clientSecret) return null;
8657
+ if (accountId && data.accountId !== accountId) return null;
8658
+ return data;
8659
+ }
8660
+ var import_node_fs3, import_node_path3, backupLog, BACKUP_DIR, BACKUP_FILENAME;
8661
+ var init_credential_backup = __esm({
8662
+ "src/features/credential-backup.ts"() {
8663
+ "use strict";
8664
+ import_node_fs3 = __toESM(require("fs"), 1);
8665
+ import_node_path3 = __toESM(require("path"), 1);
8666
+ init_platform();
8667
+ init_plugin_logger();
8668
+ backupLog = createPluginLogger({ prefix: "[credential-backup]" });
8669
+ BACKUP_DIR = "credential-backup";
8670
+ BACKUP_FILENAME = "current.json";
8671
+ }
8672
+ });
8673
+
8674
+ // src/config.ts
8675
+ var config_exports = {};
8676
+ __export(config_exports, {
8677
+ DEFAULT_ACCOUNT_ID: () => DEFAULT_ACCOUNT_ID,
8678
+ applyQQBotAccountConfig: () => applyQQBotAccountConfig,
8679
+ isGroupAllowed: () => isGroupAllowed,
8680
+ listQQBotAccountIds: () => listQQBotAccountIds,
8681
+ resolveDefaultQQBotAccountId: () => resolveDefaultQQBotAccountId,
8682
+ resolveGroupAllowFrom: () => resolveGroupAllowFrom,
8683
+ resolveGroupCoalesceConfig: () => resolveGroupCoalesceConfig,
8684
+ resolveGroupCoalesceEnabled: () => resolveGroupCoalesceEnabled,
8685
+ resolveGroupConfig: () => resolveGroupConfig,
8686
+ resolveGroupConfigFromAccount: () => resolveGroupConfigFromAccount,
8687
+ resolveGroupName: () => resolveGroupName,
8688
+ resolveGroupPolicy: () => resolveGroupPolicy,
8689
+ resolveGroupPrompt: () => resolveGroupPrompt,
8690
+ resolveHistoryLimit: () => resolveHistoryLimit,
8691
+ resolveIgnoreOtherMentions: () => resolveIgnoreOtherMentions,
8692
+ resolveMentionPatterns: () => resolveMentionPatterns,
8693
+ resolveQQBotAccount: () => resolveQQBotAccount,
8694
+ resolveQQBotEnvAppId: () => resolveQQBotEnvAppId,
8695
+ resolveQQBotEnvClientSecret: () => resolveQQBotEnvClientSecret,
8696
+ resolveRequireMention: () => resolveRequireMention,
8697
+ resolveToolPolicy: () => resolveToolPolicy,
8698
+ resolveUserAgentSuffix: () => resolveUserAgentSuffix
8699
+ });
8700
+ function resolveMentionPatterns(cfg, agentId) {
8701
+ if (agentId) {
8702
+ const agents = cfg.agents;
8703
+ const entry = agents?.list?.find((a) => a.id?.trim().toLowerCase() === agentId.trim().toLowerCase());
8704
+ const agentGroupChat = entry?.groupChat;
8705
+ if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) {
8706
+ return agentGroupChat.mentionPatterns ?? [];
8707
+ }
8708
+ }
8709
+ const globalGroupChat = cfg?.messages?.groupChat;
8710
+ if (globalGroupChat && typeof globalGroupChat === "object" && Object.hasOwn(globalGroupChat, "mentionPatterns")) {
8711
+ return globalGroupChat.mentionPatterns ?? [];
8712
+ }
8713
+ return [];
8714
+ }
8715
+ function evaluateMatchedGroupAccessForPolicy(params) {
8716
+ if (params.groupPolicy === "disabled") {
8717
+ return { allowed: false, groupPolicy: params.groupPolicy, reason: "disabled" };
8718
+ }
8719
+ if (params.groupPolicy === "allowlist") {
8720
+ if (params.requireMatchInput && !params.hasMatchInput) {
8721
+ return { allowed: false, groupPolicy: params.groupPolicy, reason: "missing_match_input" };
8722
+ }
8723
+ if (!params.allowlistConfigured) {
8724
+ return { allowed: false, groupPolicy: params.groupPolicy, reason: "empty_allowlist" };
8725
+ }
8726
+ if (!params.allowlistMatched) {
8727
+ return { allowed: false, groupPolicy: params.groupPolicy, reason: "not_allowlisted" };
8728
+ }
8729
+ }
8730
+ return { allowed: true, groupPolicy: params.groupPolicy, reason: "allowed" };
8731
+ }
8732
+ function resolveGroupPolicy(cfg, accountId) {
8733
+ const account = resolveQQBotAccount(cfg, accountId);
8734
+ return account.config?.groupPolicy ?? DEFAULT_GROUP_POLICY;
8735
+ }
8736
+ function resolveGroupAllowFrom(cfg, accountId) {
8737
+ const account = resolveQQBotAccount(cfg, accountId);
8738
+ return (account.config?.groupAllowFrom ?? []).map((id) => String(id).trim().toUpperCase());
8739
+ }
8740
+ function isGroupAllowed(cfg, groupOpenid, accountId) {
8741
+ const account = resolveQQBotAccount(cfg, accountId);
8742
+ const policy = account.config?.groupPolicy ?? DEFAULT_GROUP_POLICY;
8743
+ const allowList = (account.config?.groupAllowFrom ?? []).map((id) => String(id).trim().toUpperCase());
8744
+ const allowlistConfigured = allowList.length > 0;
8745
+ const allowlistMatched = allowList.some((id) => id === "*" || id === groupOpenid.toUpperCase());
8746
+ return evaluateMatchedGroupAccessForPolicy({
8747
+ groupPolicy: policy,
8748
+ allowlistConfigured,
8749
+ allowlistMatched
8750
+ }).allowed;
8751
+ }
8752
+ function resolveGroupConfigFromAccount(account, groupOpenid) {
8753
+ const groups = account.config?.groups ?? {};
8754
+ const wildcardCfg = groups["*"] ?? {};
8755
+ const specificCfg = groups[groupOpenid] ?? {};
8756
+ const accountDefaultRequireMention = account.config?.defaultRequireMention ?? DEFAULT_GROUP_CONFIG.requireMention;
8757
+ const accountDefaultCoalesce = account.config?.groupCoalesce ?? DEFAULT_GROUP_COALESCE_CONFIG;
8758
+ const coalesce = {
8759
+ enabled: specificCfg.coalesce?.enabled ?? wildcardCfg.coalesce?.enabled ?? accountDefaultCoalesce.enabled ?? DEFAULT_GROUP_COALESCE_CONFIG.enabled
8760
+ };
8761
+ return {
8762
+ requireMention: specificCfg.requireMention ?? wildcardCfg.requireMention ?? accountDefaultRequireMention,
8763
+ ignoreOtherMentions: specificCfg.ignoreOtherMentions ?? wildcardCfg.ignoreOtherMentions ?? DEFAULT_GROUP_CONFIG.ignoreOtherMentions,
8764
+ toolPolicy: specificCfg.toolPolicy ?? wildcardCfg.toolPolicy ?? DEFAULT_GROUP_CONFIG.toolPolicy,
8765
+ name: specificCfg.name ?? wildcardCfg.name ?? DEFAULT_GROUP_CONFIG.name,
8766
+ prompt: specificCfg.prompt ?? wildcardCfg.prompt ?? DEFAULT_GROUP_PROMPT,
8767
+ historyLimit: specificCfg.historyLimit ?? wildcardCfg.historyLimit ?? DEFAULT_GROUP_CONFIG.historyLimit,
8768
+ historyMode: specificCfg.historyMode ?? wildcardCfg.historyMode ?? DEFAULT_GROUP_CONFIG.historyMode,
8769
+ unmentionedInbound: specificCfg.unmentionedInbound ?? wildcardCfg.unmentionedInbound ?? DEFAULT_GROUP_CONFIG.unmentionedInbound,
8770
+ coalesce
8771
+ };
8772
+ }
8773
+ function resolveGroupConfig(cfg, groupOpenid, accountId) {
8774
+ return resolveGroupConfigFromAccount(resolveQQBotAccount(cfg, accountId), groupOpenid);
8775
+ }
8776
+ function resolveHistoryLimit(cfg, groupOpenid, accountId) {
8777
+ return Math.max(0, resolveGroupConfig(cfg, groupOpenid, accountId).historyLimit);
8778
+ }
8779
+ function resolveGroupPrompt(cfg, groupOpenid, accountId) {
8780
+ return resolveGroupConfig(cfg, groupOpenid, accountId).prompt;
8781
+ }
8782
+ function resolveRequireMention(cfg, groupOpenid, accountId) {
8783
+ return resolveGroupConfig(cfg, groupOpenid, accountId).requireMention;
8784
+ }
8785
+ function resolveIgnoreOtherMentions(cfg, groupOpenid, accountId) {
8786
+ return resolveGroupConfig(cfg, groupOpenid, accountId).ignoreOtherMentions;
8787
+ }
8788
+ function resolveToolPolicy(cfg, groupOpenid, accountId) {
8789
+ return resolveGroupConfig(cfg, groupOpenid, accountId).toolPolicy;
8790
+ }
8791
+ function resolveGroupName(cfg, groupOpenid, accountId) {
8792
+ const name = resolveGroupConfig(cfg, groupOpenid, accountId).name;
8793
+ return name || groupOpenid.slice(0, 8);
8794
+ }
8795
+ function resolveUserAgentSuffix(cfg) {
8796
+ const qqbot = cfg.channels?.qqbot;
8797
+ return qqbot?.userAgentSuffix ? String(qqbot.userAgentSuffix).trim() : "";
8798
+ }
8799
+ function normalizeAppId(raw) {
8800
+ if (raw === null || raw === void 0) return "";
8801
+ return String(raw).trim();
8802
+ }
8803
+ function firstNonEmptyEnv(...values) {
8804
+ for (const value of values) {
8805
+ if (value?.trim()) return value.trim();
8806
+ }
8807
+ return "";
8808
+ }
8809
+ function resolveQQBotEnvAppId() {
8810
+ return firstNonEmptyEnv(process.env.QQBOT_APPID);
8811
+ }
8812
+ function resolveQQBotEnvClientSecret() {
8813
+ return firstNonEmptyEnv(process.env.QQBOT_SECRET);
8814
+ }
8815
+ function listQQBotAccountIds(cfg) {
8816
+ const ids = /* @__PURE__ */ new Set();
8817
+ const qqbot = cfg.channels?.qqbot;
8818
+ if (qqbot?.appId || resolveQQBotEnvAppId()) {
8819
+ ids.add(DEFAULT_ACCOUNT_ID);
8820
+ }
8821
+ if (qqbot?.accounts) {
8822
+ for (const accountId of Object.keys(qqbot.accounts)) {
8823
+ if (qqbot.accounts[accountId]?.appId) {
8824
+ ids.add(accountId);
8825
+ }
8826
+ }
8827
+ }
8828
+ return Array.from(ids);
8829
+ }
8830
+ function accountShapeRunnable(params) {
8831
+ if (!params.appId || !params.enabled) return false;
8832
+ if (params.hasSecretSource) return true;
8833
+ try {
8834
+ return loadCredentialBackup(params.accountId) !== null;
8835
+ } catch {
8836
+ return false;
8837
+ }
8774
8838
  }
8775
- function tryGetQQBotRuntime() {
8776
- return runtime;
8839
+ function resolveDefaultQQBotAccountId(cfg) {
8840
+ const qqbot = cfg.channels?.qqbot;
8841
+ if (qqbot?.appId || resolveQQBotEnvAppId()) {
8842
+ const runnable = accountShapeRunnable({
8843
+ appId: normalizeAppId(qqbot?.appId) || normalizeAppId(resolveQQBotEnvAppId()),
8844
+ enabled: qqbot?.enabled !== false,
8845
+ hasSecretSource: Boolean(
8846
+ qqbot?.clientSecret || qqbot?.clientSecretFile || resolveQQBotEnvClientSecret()
8847
+ ),
8848
+ accountId: DEFAULT_ACCOUNT_ID
8849
+ });
8850
+ if (runnable) return DEFAULT_ACCOUNT_ID;
8851
+ }
8852
+ if (qqbot?.accounts) {
8853
+ for (const [accountId, account] of Object.entries(qqbot.accounts)) {
8854
+ if (!account) continue;
8855
+ const runnable = accountShapeRunnable({
8856
+ appId: normalizeAppId(account.appId),
8857
+ enabled: account.enabled !== false,
8858
+ hasSecretSource: Boolean(account.clientSecret || account.clientSecretFile),
8859
+ accountId
8860
+ });
8861
+ if (runnable) return accountId;
8862
+ }
8863
+ const ids = Object.keys(qqbot.accounts);
8864
+ if (ids.length > 0 && ids.every((id) => !qqbot.accounts?.[id]?.appId)) {
8865
+ return ids[0];
8866
+ }
8867
+ }
8868
+ return DEFAULT_ACCOUNT_ID;
8777
8869
  }
8778
- var runtime, exitHooksInstalled;
8779
- var init_runtime = __esm({
8780
- "src/runtime.ts"() {
8781
- "use strict";
8782
- init_bot_instance();
8783
- init_ref_index_store();
8784
- init_pkg_version();
8785
- runtime = null;
8786
- exitHooksInstalled = false;
8870
+ function resolveQQBotAccount(cfg, accountId) {
8871
+ const resolvedAccountId = accountId ?? resolveDefaultQQBotAccountId(cfg);
8872
+ const qqbot = cfg.channels?.qqbot;
8873
+ let accountConfig = {};
8874
+ let appId = "";
8875
+ let clientSecret = "";
8876
+ let secretSource = "none";
8877
+ if (resolvedAccountId === DEFAULT_ACCOUNT_ID) {
8878
+ const { accounts: _accounts, ...topLevelConfig } = qqbot ?? {};
8879
+ accountConfig = {
8880
+ ...topLevelConfig,
8881
+ markdownSupport: qqbot?.markdownSupport ?? true
8882
+ };
8883
+ appId = normalizeAppId(qqbot?.appId);
8884
+ } else {
8885
+ const account = qqbot?.accounts?.[resolvedAccountId];
8886
+ accountConfig = account ?? {};
8887
+ appId = normalizeAppId(account?.appId);
8888
+ }
8889
+ if (accountConfig.clientSecret) {
8890
+ clientSecret = accountConfig.clientSecret;
8891
+ secretSource = "config";
8892
+ } else if (accountConfig.clientSecretFile) {
8893
+ secretSource = "file";
8894
+ } else if (resolvedAccountId === DEFAULT_ACCOUNT_ID && resolveQQBotEnvClientSecret()) {
8895
+ clientSecret = resolveQQBotEnvClientSecret();
8896
+ secretSource = "env";
8897
+ }
8898
+ if (!appId && resolvedAccountId === DEFAULT_ACCOUNT_ID) {
8899
+ appId = normalizeAppId(resolveQQBotEnvAppId());
8787
8900
  }
8788
- });
8789
-
8790
- // src/utils/plugin-logger.ts
8791
- function consoleSink() {
8792
- const C2 = "\x1B[36m", Y = "\x1B[33m", R = "\x1B[31m", G = "\x1B[90m", X = "\x1B[0m";
8793
8901
  return {
8794
- debug: (m3) => console.debug(`${G}[qqbot]${X}`, m3),
8795
- info: (m3) => console.log(`${C2}[qqbot]${X}`, m3),
8796
- warn: (m3) => console.warn(`${Y}[qqbot]${X}`, m3),
8797
- error: (m3) => console.error(`${R}[qqbot]${X}`, m3)
8902
+ accountId: resolvedAccountId,
8903
+ name: accountConfig.name,
8904
+ enabled: accountConfig.enabled !== false,
8905
+ appId,
8906
+ clientSecret,
8907
+ secretSource,
8908
+ systemPrompt: accountConfig.systemPrompt,
8909
+ markdownSupport: accountConfig.markdownSupport !== false,
8910
+ commandPanelNative: accountConfig.commands?.native !== false,
8911
+ userAgentSuffix: resolveUserAgentSuffix(cfg),
8912
+ config: accountConfig
8798
8913
  };
8799
8914
  }
8800
- function enrichMeta(meta) {
8801
- const ctx = getRequestContext();
8802
- if (!ctx) return meta;
8803
- const trace = {};
8804
- if (ctx.accountId) trace.accountId = ctx.accountId;
8805
- if (ctx.messageId) trace.messageId = ctx.messageId;
8806
- if (ctx.openId) trace.openId = ctx.openId;
8807
- if (Object.keys(trace).length === 0) return meta;
8808
- return meta ? { ...trace, ...meta } : trace;
8809
- }
8810
- function frameworkSink() {
8811
- const resolve2 = () => {
8812
- try {
8813
- const r = tryGetQQBotRuntime();
8814
- if (r?.logging) {
8815
- const child = r.logging.getChildLogger({ subsystem: "qqbot/core" });
8816
- return {
8817
- debug: child.debug?.bind(child),
8818
- info: child.info.bind(child),
8819
- warn: child.warn.bind(child),
8820
- error: child.error.bind(child)
8821
- };
8915
+ function applyQQBotAccountConfig(cfg, accountId, input) {
8916
+ const next = { ...cfg };
8917
+ if (accountId === DEFAULT_ACCOUNT_ID) {
8918
+ const existingConfig = next.channels?.qqbot || {};
8919
+ const allowFrom = existingConfig.allowFrom ?? ["*"];
8920
+ next.channels = {
8921
+ ...next.channels,
8922
+ qqbot: {
8923
+ ...next.channels?.qqbot || {},
8924
+ enabled: true,
8925
+ allowFrom,
8926
+ ...input.appId ? { appId: input.appId } : {},
8927
+ ...input.clientSecret ? { clientSecret: input.clientSecret } : input.clientSecretFile ? { clientSecretFile: input.clientSecretFile } : {},
8928
+ ...input.name ? { name: input.name } : {}
8822
8929
  }
8823
- } catch {
8824
- }
8825
- return consoleSink();
8826
- };
8827
- return {
8828
- debug: (msg, meta) => resolve2().debug?.(msg, meta),
8829
- info: (msg, meta) => resolve2().info(msg, meta),
8830
- warn: (msg, meta) => resolve2().warn(msg, meta),
8831
- error: (msg, meta) => resolve2().error(msg, meta)
8832
- };
8930
+ };
8931
+ } else {
8932
+ const existingAccountConfig = next.channels?.qqbot?.accounts?.[accountId] || {};
8933
+ const allowFrom = existingAccountConfig.allowFrom ?? ["*"];
8934
+ next.channels = {
8935
+ ...next.channels,
8936
+ qqbot: {
8937
+ ...next.channels?.qqbot || {},
8938
+ enabled: true,
8939
+ accounts: {
8940
+ ...next.channels?.qqbot?.accounts || {},
8941
+ [accountId]: {
8942
+ ...next.channels?.qqbot?.accounts?.[accountId] || {},
8943
+ enabled: true,
8944
+ allowFrom,
8945
+ ...input.appId ? { appId: input.appId } : {},
8946
+ ...input.clientSecret ? { clientSecret: input.clientSecret } : input.clientSecretFile ? { clientSecretFile: input.clientSecretFile } : {},
8947
+ ...input.name ? { name: input.name } : {}
8948
+ }
8949
+ }
8950
+ }
8951
+ };
8952
+ }
8953
+ return next;
8833
8954
  }
8834
- function createPluginLogger(opts = {}) {
8835
- const output = opts.output ?? (opts.forceConsole ? consoleSink() : frameworkSink());
8836
- const prefix = opts.prefix ?? "";
8837
- const fmt = (msg) => prefix ? `${prefix} ${msg}` : msg;
8838
- const buildChild = (parentPrefix, tag) => createPluginLogger({
8839
- output,
8840
- prefix: parentPrefix ? `${parentPrefix}[${tag}]` : `[${tag}]`
8841
- });
8842
- return {
8843
- info: (msg, meta) => output.info(fmt(msg), enrichMeta(meta)),
8844
- warn: (msg, meta) => output.warn(fmt(msg), enrichMeta(meta)),
8845
- error: (msg, meta) => output.error(fmt(msg), enrichMeta(meta)),
8846
- debug: (msg, meta) => output.debug(fmt(msg), enrichMeta(meta)),
8847
- child: (tag) => buildChild(prefix, tag)
8848
- };
8955
+ function resolveGroupCoalesceConfig(cfg, groupOpenid, accountId) {
8956
+ return resolveGroupConfig(cfg, groupOpenid, accountId).coalesce;
8849
8957
  }
8850
- var init_plugin_logger = __esm({
8851
- "src/utils/plugin-logger.ts"() {
8958
+ function resolveGroupCoalesceEnabled(cfg, groupOpenid, accountId) {
8959
+ return resolveGroupCoalesceConfig(cfg, groupOpenid, accountId).enabled;
8960
+ }
8961
+ var DEFAULT_ACCOUNT_ID, DEFAULT_GROUP_POLICY, DEFAULT_GROUP_HISTORY_LIMIT, DEFAULT_GROUP_COALESCE_CONFIG, DEFAULT_GROUP_CONFIG, DEFAULT_GROUP_PROMPT;
8962
+ var init_config = __esm({
8963
+ "src/config.ts"() {
8852
8964
  "use strict";
8853
- init_request_context();
8854
- init_runtime();
8965
+ init_credential_backup();
8966
+ DEFAULT_ACCOUNT_ID = "default";
8967
+ DEFAULT_GROUP_POLICY = "open";
8968
+ DEFAULT_GROUP_HISTORY_LIMIT = 20;
8969
+ DEFAULT_GROUP_COALESCE_CONFIG = {
8970
+ enabled: true
8971
+ };
8972
+ DEFAULT_GROUP_CONFIG = {
8973
+ requireMention: true,
8974
+ ignoreOtherMentions: false,
8975
+ toolPolicy: "restricted",
8976
+ name: "",
8977
+ historyLimit: DEFAULT_GROUP_HISTORY_LIMIT,
8978
+ historyMode: "clear",
8979
+ unmentionedInbound: "user_request",
8980
+ coalesce: DEFAULT_GROUP_COALESCE_CONFIG
8981
+ };
8982
+ DEFAULT_GROUP_PROMPT = [
8983
+ "\u82E5\u53D1\u9001\u8005\u4E3A\u673A\u5668\u4EBA\uFF0C\u4EC5\u5728\u5BF9\u65B9\u660E\u786E@\u4F60\u63D0\u95EE\u6216\u8BF7\u6C42\u534F\u52A9\u5177\u4F53\u4EFB\u52A1\u65F6\uFF0C\u4EE5\u7B80\u6D01\u660E\u4E86\u7684\u5185\u5BB9\u56DE\u590D\uFF0C",
8984
+ "\u907F\u514D\u4E0E\u5176\u4ED6\u673A\u5668\u4EBA\u4EA7\u751F\u62A2\u7B54\u6216\u591A\u8F6E\u65E0\u610F\u4E49\u5BF9\u8BDD\u3002",
8985
+ "\u5728\u7FA4\u804A\u4E2D\u4F18\u5148\u8BA9\u4EBA\u7C7B\u7528\u6237\u7684\u6D88\u606F\u5F97\u5230\u54CD\u5E94\uFF0C\u673A\u5668\u4EBA\u4E4B\u95F4\u4FDD\u6301\u534F\u4F5C\u800C\u975E\u7ADE\u4E89\uFF0C\u786E\u4FDD\u5BF9\u8BDD\u6709\u5E8F\u4E0D\u5237\u5C4F\u3002"
8986
+ ].join("");
8855
8987
  }
8856
8988
  });
8857
8989
 
@@ -10586,14 +10718,15 @@ async function sendMedia2(params) {
10586
10718
  return { error: resolved.error };
10587
10719
  }
10588
10720
  const kind = params.mediaKind ?? (params.mimeType ? inferMediaKindFromMime(params.mimeType) : void 0) ?? inferMediaKind(resolved.path);
10589
- const gw = getGateway(accountId);
10590
- if (!gw) {
10591
- return { error: `Bot "${accountId}" not running` };
10721
+ const sendGateway = resolveGatewayForSend(accountId);
10722
+ if (!sendGateway) {
10723
+ return { error: notRunningError(accountId) };
10592
10724
  }
10725
+ const gw = sendGateway.gw;
10593
10726
  const target = parseTarget(params.to);
10594
10727
  const reservation = params.quotaReserved || !params.replyToId ? { canReply: Boolean(params.replyToId), rollback: () => {
10595
10728
  } } : checkAndConsumePassiveReplyQuota({
10596
- accountId,
10729
+ accountId: sendGateway.accountId,
10597
10730
  msgId: params.replyToId,
10598
10731
  scope: target.scope,
10599
10732
  log: log5
@@ -10845,58 +10978,7 @@ var import_channel_core = require("openclaw/plugin-sdk/channel-core");
10845
10978
  var import_account_id = require("openclaw/plugin-sdk/account-id");
10846
10979
  var import_core = require("openclaw/plugin-sdk/core");
10847
10980
  init_config();
10848
-
10849
- // src/features/credential-backup.ts
10850
- var import_node_fs3 = __toESM(require("fs"), 1);
10851
- var import_node_path3 = __toESM(require("path"), 1);
10852
- init_platform();
10853
- init_plugin_logger();
10854
- var backupLog = createPluginLogger({ prefix: "[credential-backup]" });
10855
- var BACKUP_DIR = "credential-backup";
10856
- var BACKUP_FILENAME = "current.json";
10857
- function getBackupPath() {
10858
- return import_node_path3.default.join(getQQBotDataDir("data"), BACKUP_DIR, BACKUP_FILENAME);
10859
- }
10860
- function saveCredentialBackup(accountId, appId, clientSecret) {
10861
- if (!appId || !clientSecret) return;
10862
- try {
10863
- const backupPath = getBackupPath();
10864
- const dir = import_node_path3.default.dirname(backupPath);
10865
- if (!import_node_fs3.default.existsSync(dir)) {
10866
- import_node_fs3.default.mkdirSync(dir, { recursive: true });
10867
- }
10868
- const data = {
10869
- accountId,
10870
- appId,
10871
- clientSecret,
10872
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
10873
- };
10874
- const tmpPath = backupPath + ".tmp";
10875
- import_node_fs3.default.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf8");
10876
- import_node_fs3.default.renameSync(tmpPath, backupPath);
10877
- } catch (err) {
10878
- backupLog.debug(`save failed: ${err instanceof Error ? err.message : String(err)}`, { accountId });
10879
- }
10880
- }
10881
- function loadCredentialBackup(accountId) {
10882
- try {
10883
- const backupPath = getBackupPath();
10884
- if (import_node_fs3.default.existsSync(backupPath)) {
10885
- return readBackupFile(backupPath, accountId);
10886
- }
10887
- return null;
10888
- } catch (err) {
10889
- backupLog.debug(`load failed: ${err instanceof Error ? err.message : String(err)}`);
10890
- return null;
10891
- }
10892
- }
10893
- function readBackupFile(filePath, accountId) {
10894
- const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
10895
- const data = JSON.parse(raw);
10896
- if (!data.appId || !data.clientSecret) return null;
10897
- if (accountId && data.accountId !== accountId) return null;
10898
- return data;
10899
- }
10981
+ init_credential_backup();
10900
10982
 
10901
10983
  // src/setup/surface.ts
10902
10984
  init_setup();
@@ -14747,8 +14829,13 @@ var StreamingController = class {
14747
14829
  session = null;
14748
14830
  /** QQ 已接受的最新文本 — 单源真理 */
14749
14831
  lastAcceptedFull = "";
14750
- /** 已成功发送的分片数(降级:=0 则走静态消息兜底) */
14832
+ /** 已成功发送的分片数(降级:=0 且无外部投递则走静态消息兜底) */
14751
14833
  sentChunkCount = 0;
14834
+ /**
14835
+ * 标记:内容已通过非流式通道成功投递(如 ask_user 按钮卡片)。
14836
+ * 置 true 后 handleFinalize 不再走 fallback 路径,避免框架误报 outcome=error。
14837
+ */
14838
+ _deliveredExternally = false;
14752
14839
  /** 同步标志:收到第一个 onPartialReply 即置 true(不等 async 完成) */
14753
14840
  _hasStarted = false;
14754
14841
  /** 串行队列 */
@@ -14781,7 +14868,14 @@ var StreamingController = class {
14781
14868
  return this.phase === "done" || this.phase === "failed";
14782
14869
  }
14783
14870
  get shouldFallbackToStatic() {
14784
- return this.isTerminal && this.sentChunkCount === 0;
14871
+ return this.isTerminal && this.sentChunkCount === 0 && !this._deliveredExternally;
14872
+ }
14873
+ /**
14874
+ * 告知控制器内容已通过外部通道投递(如 ask_user 卡片)。
14875
+ * 后续 finalize() 不会走 fallback 路径,框架不会误报 outcome=error。
14876
+ */
14877
+ markDeliveredExternally() {
14878
+ this._deliveredExternally = true;
14785
14879
  }
14786
14880
  // ── 入口 ──
14787
14881
  onPartialReply(text) {
@@ -14883,6 +14977,8 @@ var StreamingController = class {
14883
14977
  }
14884
14978
  } else if (this.sentChunkCount > 0 && this.lastAcceptedFull) {
14885
14979
  this.transition("failed", "finalize:no_sendstatic");
14980
+ } else if (this._deliveredExternally) {
14981
+ this.transition("done", "finalize");
14886
14982
  } else {
14887
14983
  this.transition("done", "finalize:fallback");
14888
14984
  }
@@ -14894,8 +14990,8 @@ var StreamingController = class {
14894
14990
  this.deps.log?.info(`stream done chunks=${this.sentChunkCount} chars=${this.lastAcceptedFull.length}`);
14895
14991
  return;
14896
14992
  }
14897
- if (this.sentChunkCount > 0) {
14898
- this.transition("done", "finalize:no_session");
14993
+ if (this.sentChunkCount > 0 || this._deliveredExternally) {
14994
+ this.transition("done", this.sentChunkCount > 0 ? "finalize:no_session" : "finalize");
14899
14995
  } else {
14900
14996
  this.transition("done", "finalize:fallback");
14901
14997
  }
@@ -15129,6 +15225,7 @@ async function dispatchToOpenClaw(ctx, msg, account, runtime2, log5) {
15129
15225
  try {
15130
15226
  await bot.sendTextWithKeyboard(replyTarget, text, keyboard);
15131
15227
  outboundSendOk++;
15228
+ streamingController?.markDeliveredExternally();
15132
15229
  dlog?.debug(`[question] sent ask_user with keyboard questionId=${questionId} options=${optionValues.length}`);
15133
15230
  return;
15134
15231
  } catch (err) {
@@ -15154,6 +15251,7 @@ async function dispatchToOpenClaw(ctx, msg, account, runtime2, log5) {
15154
15251
  await bot.sendTextWithKeyboard(replyTarget, cardText, keyboard);
15155
15252
  outboundSendOk++;
15156
15253
  }
15254
+ streamingController?.markDeliveredExternally();
15157
15255
  dlog?.debug(`[question] sent multi-question ask_user questionId=${questionId} questions=${questions.length}`);
15158
15256
  return;
15159
15257
  } catch (err) {
@@ -16745,6 +16843,7 @@ function resolveMediaSource(source) {
16745
16843
  // src/gateway/lifecycle.ts
16746
16844
  init_plugin_logger();
16747
16845
  init_outbound_service();
16846
+ init_credential_backup();
16748
16847
 
16749
16848
  // src/features/command-panel.ts
16750
16849
  var import_native_command_registry = require("openclaw/plugin-sdk/native-command-registry");
@@ -17109,7 +17208,7 @@ async function stopAccountGracefully(params) {
17109
17208
  log5?.error(`[qqbot:${accountId}] gateway stop error: ${err instanceof Error ? err.message : String(err)}`);
17110
17209
  }
17111
17210
  }
17112
- unregisterGateway(accountId);
17211
+ unregisterGateway(accountId, gw);
17113
17212
  }
17114
17213
  async function logoutAndClearCredentials(params) {
17115
17214
  const { accountId, cfg } = params;