@jerryliang122/openclaw-qqbot 1.0.5 → 1.0.7

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.5" : "unknown";
8262
+ return true ? "1.0.7" : "unknown";
8505
8263
  }
8506
8264
  function getOpenclawVersion(runtimeVersion) {
8507
8265
  return runtimeVersion && runtimeVersion !== "unknown" ? runtimeVersion : "unknown";
@@ -8772,86 +8530,460 @@ function getQQBotRuntime() {
8772
8530
  if (!runtime) throw new Error("QQBot runtime not initialized");
8773
8531
  return runtime;
8774
8532
  }
8775
- function tryGetQQBotRuntime() {
8776
- return runtime;
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
+ }
8777
8838
  }
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;
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;
8869
+ }
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();
@@ -14410,6 +14492,7 @@ function mapQuote(quote) {
14410
14492
  return {
14411
14493
  content: quote.text,
14412
14494
  senderId: quote.entry?.senderId ?? "",
14495
+ messageId: quote.entry?.messageId,
14413
14496
  attachments: quote.attachments
14414
14497
  };
14415
14498
  }
@@ -14626,7 +14709,24 @@ function buildCtxPayload(params) {
14626
14709
  },
14627
14710
  reply: {
14628
14711
  to: envelope.targetId,
14629
- replyToId: envelope.messageId,
14712
+ /**
14713
+ * ReplyToId 契约语义 = 当前消息回复(引用)的那条消息的 ID —— 对齐
14714
+ * telegram 原生通道(replyHead?.messageId ?? visibleReplyTarget?.id),
14715
+ * 无引用时必须为 undefined。框架将其渲染为模型可见 Conversation info
14716
+ * 的 reply_to_id 字段。
14717
+ *
14718
+ * 历史版本误填 envelope.messageId(消息自身 ID),导致每条消息都呈现
14719
+ * message_id == reply_to_id —— 被上层 agent 的 AGENTS 规则判为「内部
14720
+ * 回灌、非新指令」而拒绝响应(2026-09-22 事故)。
14721
+ *
14722
+ * QQ 被动回复锚点不依赖此字段:deliverCtx.replyToId / msgid-cache 兜底
14723
+ * (resolvePassiveFirstReplyToId)/ attachMsgIdWithQuota 均独立取值,
14724
+ * threading resolveReplyToMode='off' 阻断框架隐式回复线程。
14725
+ *
14726
+ * 防御守卫:即使上游异常传入当前消息 ID(自引用),也强制置空——
14727
+ * 「reply_to_id == message_id」这一假回灌签名在任何路径都不允许再现。
14728
+ */
14729
+ replyToId: envelope.quote?.messageId && envelope.quote.messageId !== envelope.messageId ? envelope.quote.messageId : void 0,
14630
14730
  originatingTo: envelope.targetId
14631
14731
  },
14632
14732
  access: {
@@ -14635,7 +14735,14 @@ function buildCtxPayload(params) {
14635
14735
  command: isSlashCommand ? { kind: "text-slash", body: assembled.rawBody, authorized: true } : void 0,
14636
14736
  media: mediaFacts.length > 0 ? mediaFacts : void 0,
14637
14737
  supplemental: {
14638
- quote: envelope.quote ? { id: envelope.messageId, body: envelope.quote.content, sender: envelope.quote.senderId } : void 0,
14738
+ // quote.id 同样必须是「被引用消息」的真实 ID(ref-index 命中时),
14739
+ // 而非当前消息 ID——框架经 applySupplementalContext 映射为
14740
+ // ctx.ReplyToId / "Reply target of current user message"。
14741
+ quote: envelope.quote ? {
14742
+ ...envelope.quote.messageId && envelope.quote.messageId !== envelope.messageId ? { id: envelope.quote.messageId } : {},
14743
+ body: envelope.quote.content,
14744
+ sender: envelope.quote.senderId
14745
+ } : void 0,
14639
14746
  groupSystemPrompt: envelope.systemPrompt
14640
14747
  },
14641
14748
  extra: {
@@ -16761,6 +16868,7 @@ function resolveMediaSource(source) {
16761
16868
  // src/gateway/lifecycle.ts
16762
16869
  init_plugin_logger();
16763
16870
  init_outbound_service();
16871
+ init_credential_backup();
16764
16872
 
16765
16873
  // src/features/command-panel.ts
16766
16874
  var import_native_command_registry = require("openclaw/plugin-sdk/native-command-registry");
@@ -17125,7 +17233,7 @@ async function stopAccountGracefully(params) {
17125
17233
  log5?.error(`[qqbot:${accountId}] gateway stop error: ${err instanceof Error ? err.message : String(err)}`);
17126
17234
  }
17127
17235
  }
17128
- unregisterGateway(accountId);
17236
+ unregisterGateway(accountId, gw);
17129
17237
  }
17130
17238
  async function logoutAndClearCredentials(params) {
17131
17239
  const { accountId, cfg } = params;