@openclaw/qqbot 2026.5.9-beta.1 → 2026.5.10-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +6 -6
- package/dist/{channel-nsJeL4qp.js → channel-Cl-NJpPD.js} +11 -11
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-DS8qSqfn.js → channel.setup-BDM8y3aD.js} +2 -2
- package/dist/{config-D6545NkC.js → config-BNAanXEH.js} +2 -2
- package/dist/{config-schema-BUrvFkQH.js → config-schema-BTV9NZbt.js} +3 -3
- package/dist/{exec-approvals-COUsM6wZ.js → exec-approvals-DgO1msIB.js} +1 -1
- package/dist/{gateway-Dcs6Ip8b.js → gateway-OhbnW8Uq.js} +157 -555
- package/dist/{handler-runtime-DraqODp_.js → handler-runtime-C9SrM2gR.js} +4 -4
- package/dist/{outbound-CqRmSFJZ.js → outbound-DL154TS6.js} +3 -3
- package/dist/{request-context-DVsN666m.js → request-context-Bn63CUkr.js} +2 -2
- package/dist/{resolve-D_06fV6-.js → resolve-Cm33TWZp.js} +1 -1
- package/dist/{runtime-C3Qpr4Ph.js → runtime-CHGblcVc.js} +1 -1
- package/dist/runtime-api.js +1 -1
- package/dist/{sender-CgdXGY2H.js → sender-C-TmIFow.js} +1 -1
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{string-normalize-Ci6NM5DE.js → string-normalize-BzzzPOeb.js} +0 -16
- package/dist/{target-parser-DQ6J3Qe-.js → target-parser-CTNFSKb1.js} +2 -2
- package/package.json +4 -4
- package/skills/qqbot-channel/SKILL.md +1 -1
- package/skills/qqbot-media/SKILL.md +1 -1
- package/skills/qqbot-remind/SKILL.md +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { E as setOutboundAudioPort, _ as sendVoice, c as isMediaPayload, g as sendVideoMsg, h as sendPhoto, l as parseQQBotPayload, m as sendDocument, o as encodePayloadForCron, r as sendMedia, s as isCronReminderPayload, u as normalizeMediaTags, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR } from "./outbound-
|
|
2
|
-
import { c as getPlatformAdapter, i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, o as readStringField, s as sanitizeFileName, t as asOptionalObjectRecord } from "./string-normalize-
|
|
3
|
-
import { d as setBridgeLogger, l as ensurePlatformAdapter, s as resolveAccountBase } from "./resolve-
|
|
1
|
+
import { E as setOutboundAudioPort, _ as sendVoice, c as isMediaPayload, g as sendVideoMsg, h as sendPhoto, l as parseQQBotPayload, m as sendDocument, o as encodePayloadForCron, r as sendMedia, s as isCronReminderPayload, u as normalizeMediaTags, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR } from "./outbound-DL154TS6.js";
|
|
2
|
+
import { c as getPlatformAdapter, i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, o as readStringField, s as sanitizeFileName, t as asOptionalObjectRecord } from "./string-normalize-BzzzPOeb.js";
|
|
3
|
+
import { d as setBridgeLogger, l as ensurePlatformAdapter, s as resolveAccountBase } from "./resolve-Cm33TWZp.js";
|
|
4
4
|
import { i as parseApprovalButtonData } from "./approval-cg0SVahb.js";
|
|
5
5
|
import { t as toGatewayAccount } from "./narrowing-BoieBTIU.js";
|
|
6
|
-
import { B as StreamInputState, C as debugWarn, D as openLocalFile, I as formatDuration, L as formatErrorMessage, N as getImageMimeType, P as getMaxUploadSize, R as StreamContentType, S as debugLog, T as getNextMsgSeq, a as createRawInputNotifyFn, b as withTokenRetry, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia$1, i as clearTokenCache, j as formatFileSize, k as downloadFile, l as getPluginUserAgent, m as sendInputNotify, n as acknowledgeInteraction, o as getAccessToken, p as registerAccount, r as buildDeliveryTarget, s as getGatewayUrl, t as accountToCreds, u as initApiConfig, v as startBackgroundTokenRefresh, x as debugError, y as stopBackgroundTokenRefresh, z as StreamInputMode } from "./sender-
|
|
7
|
-
import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-
|
|
8
|
-
import { a as getHomeDir, c as getQQBotMediaDir, d as isLocalPath, f as isWindows, i as checkSilkWasmAvailable, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath$1, s as getQQBotDataPath, u as getTempDir } from "./target-parser-
|
|
9
|
-
import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-
|
|
6
|
+
import { B as StreamInputState, C as debugWarn, D as openLocalFile, I as formatDuration, L as formatErrorMessage, N as getImageMimeType, P as getMaxUploadSize, R as StreamContentType, S as debugLog, T as getNextMsgSeq, a as createRawInputNotifyFn, b as withTokenRetry, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia$1, i as clearTokenCache, j as formatFileSize, k as downloadFile, l as getPluginUserAgent, m as sendInputNotify, n as acknowledgeInteraction, o as getAccessToken, p as registerAccount, r as buildDeliveryTarget, s as getGatewayUrl, t as accountToCreds, u as initApiConfig, v as startBackgroundTokenRefresh, x as debugError, y as stopBackgroundTokenRefresh, z as StreamInputMode } from "./sender-C-TmIFow.js";
|
|
7
|
+
import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-CHGblcVc.js";
|
|
8
|
+
import { a as getHomeDir, c as getQQBotMediaDir, d as isLocalPath, f as isWindows, i as checkSilkWasmAvailable, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath$1, s as getQQBotDataPath, u as getTempDir } from "./target-parser-CTNFSKb1.js";
|
|
9
|
+
import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-Bn63CUkr.js";
|
|
10
10
|
import * as fs$1 from "node:fs";
|
|
11
11
|
import fs from "node:fs";
|
|
12
12
|
import * as os$1 from "node:os";
|
|
@@ -21,33 +21,10 @@ import WebSocket from "ws";
|
|
|
21
21
|
import { resolveAmbientNodeProxyAgent } from "openclaw/plugin-sdk/extension-shared";
|
|
22
22
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
|
-
import {
|
|
24
|
+
import { createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
25
|
+
import { resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-mention-gating";
|
|
25
26
|
import { buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, recordPendingHistoryEntryIfEnabled } from "openclaw/plugin-sdk/reply-history";
|
|
26
27
|
//#region extensions/qqbot/src/engine/group/activation.ts
|
|
27
|
-
/**
|
|
28
|
-
* Group activation mode — how the bot decides whether to respond in a group.
|
|
29
|
-
*
|
|
30
|
-
* Resolution chain:
|
|
31
|
-
* 1. session store override (`/activation` command writes per-session
|
|
32
|
-
* `groupActivation` value) — highest priority
|
|
33
|
-
* 2. per-group `requireMention` config
|
|
34
|
-
* 3. `"mention"` default (require @-bot to respond)
|
|
35
|
-
*
|
|
36
|
-
* File I/O is isolated in the default node-based reader so the gating
|
|
37
|
-
* logic itself stays a pure function, testable without touching disk.
|
|
38
|
-
*
|
|
39
|
-
* Note: the implicit-mention predicate (quoting a bot message counts as
|
|
40
|
-
* @-ing the bot) lives in `./mention.ts` alongside the other mention
|
|
41
|
-
* helpers — see `resolveImplicitMention` there.
|
|
42
|
-
*/
|
|
43
|
-
/**
|
|
44
|
-
* Resolve the effective activation mode for one inbound message.
|
|
45
|
-
*
|
|
46
|
-
* Order of precedence:
|
|
47
|
-
* 1. `store[sessionKey].groupActivation` (read via the injected reader)
|
|
48
|
-
* 2. config-level `requireMention` (maps to `"mention"` / `"always"`)
|
|
49
|
-
* 3. `"mention"` (safe default)
|
|
50
|
-
*/
|
|
51
28
|
function resolveGroupActivation(params) {
|
|
52
29
|
const fallback = params.configRequireMention ? "mention" : "always";
|
|
53
30
|
const store = params.sessionStoreReader?.read({
|
|
@@ -61,14 +38,6 @@ function resolveGroupActivation(params) {
|
|
|
61
38
|
if (normalized === "mention" || normalized === "always") return normalized;
|
|
62
39
|
return fallback;
|
|
63
40
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Resolve the on-disk path to the agent-sessions file.
|
|
66
|
-
*
|
|
67
|
-
* Priority:
|
|
68
|
-
* 1. `cfg.session.store` (supports `{agentId}` placeholder and `~` expansion)
|
|
69
|
-
* 2. `$OPENCLAW_STATE_DIR` / `$CLAWDBOT_STATE_DIR`
|
|
70
|
-
* 3. `~/.openclaw/agents/{agentId}/sessions/sessions.json`
|
|
71
|
-
*/
|
|
72
41
|
function resolveSessionStorePath(cfg, agentId) {
|
|
73
42
|
const resolvedAgentId = agentId || "default";
|
|
74
43
|
const session = typeof cfg.session === "object" && cfg.session !== null ? cfg.session : void 0;
|
|
@@ -85,16 +54,6 @@ function resolveSessionStorePath(cfg, agentId) {
|
|
|
85
54
|
const stateDir = process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim() || path.join(process.env.HOME || process.env.USERPROFILE || "", ".openclaw");
|
|
86
55
|
return path.join(stateDir, "agents", resolvedAgentId, "sessions", "sessions.json");
|
|
87
56
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Create the default, production-ready session-store reader.
|
|
90
|
-
*
|
|
91
|
-
* Reads the file synchronously on every call. The overhead is acceptable
|
|
92
|
-
* because activation mode is only resolved once per group message and
|
|
93
|
-
* the sessions file is typically a handful of kilobytes.
|
|
94
|
-
*
|
|
95
|
-
* Any I/O or JSON error is swallowed and returned as `null` so the
|
|
96
|
-
* gating pipeline falls back to the config default.
|
|
97
|
-
*/
|
|
98
57
|
function createNodeSessionStoreReader() {
|
|
99
58
|
return { read: ({ cfg, agentId }) => {
|
|
100
59
|
try {
|
|
@@ -414,27 +373,6 @@ async function runDiagnostics() {
|
|
|
414
373
|
return report;
|
|
415
374
|
}
|
|
416
375
|
//#endregion
|
|
417
|
-
//#region extensions/qqbot/src/engine/access/resolve-policy.ts
|
|
418
|
-
function hasRealRestriction(list) {
|
|
419
|
-
if (!list || list.length === 0) return false;
|
|
420
|
-
return !list.every((entry) => String(entry).trim() === "*");
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* Derive the effective dmPolicy and groupPolicy applied at runtime.
|
|
424
|
-
*
|
|
425
|
-
* Caller should pass the raw `QQBotAccountConfig`. The resolver does
|
|
426
|
-
* not look at `groups[id]` overrides — per-group overrides are layered
|
|
427
|
-
* on top elsewhere (see `inbound-pipeline` mention gating).
|
|
428
|
-
*/
|
|
429
|
-
function resolveQQBotEffectivePolicies(input) {
|
|
430
|
-
const allowFromRestricted = hasRealRestriction(input.allowFrom);
|
|
431
|
-
const groupAllowFromRestricted = hasRealRestriction(input.groupAllowFrom);
|
|
432
|
-
return {
|
|
433
|
-
dmPolicy: input.dmPolicy ?? (allowFromRestricted ? "allowlist" : "open"),
|
|
434
|
-
groupPolicy: input.groupPolicy ?? (groupAllowFromRestricted || allowFromRestricted ? "allowlist" : "open")
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
//#endregion
|
|
438
376
|
//#region extensions/qqbot/src/engine/access/sender-match.ts
|
|
439
377
|
/**
|
|
440
378
|
* QQBot sender normalization and allowlist matching.
|
|
@@ -474,177 +412,13 @@ function createQQBotSenderMatcher(senderId) {
|
|
|
474
412
|
};
|
|
475
413
|
}
|
|
476
414
|
//#endregion
|
|
477
|
-
//#region extensions/qqbot/src/engine/access/types.ts
|
|
478
|
-
/** Structured reason codes used in logs and metrics. */
|
|
479
|
-
const QQBOT_ACCESS_REASON = {
|
|
480
|
-
DM_POLICY_OPEN: "dm_policy_open",
|
|
481
|
-
DM_POLICY_DISABLED: "dm_policy_disabled",
|
|
482
|
-
DM_POLICY_ALLOWLISTED: "dm_policy_allowlisted",
|
|
483
|
-
DM_POLICY_NOT_ALLOWLISTED: "dm_policy_not_allowlisted",
|
|
484
|
-
DM_POLICY_EMPTY_ALLOWLIST: "dm_policy_empty_allowlist",
|
|
485
|
-
GROUP_POLICY_ALLOWED: "group_policy_allowed",
|
|
486
|
-
GROUP_POLICY_DISABLED: "group_policy_disabled",
|
|
487
|
-
GROUP_POLICY_EMPTY_ALLOWLIST: "group_policy_empty_allowlist",
|
|
488
|
-
GROUP_POLICY_NOT_ALLOWLISTED: "group_policy_not_allowlisted",
|
|
489
|
-
BOT_SELF_ECHO: "bot_self_echo"
|
|
490
|
-
};
|
|
491
|
-
//#endregion
|
|
492
|
-
//#region extensions/qqbot/src/engine/access/access-control.ts
|
|
493
|
-
/**
|
|
494
|
-
* QQBot inbound access decision.
|
|
495
|
-
*
|
|
496
|
-
* This module is the single place where the QQBot engine decides
|
|
497
|
-
* whether an inbound message from a given sender is allowed to
|
|
498
|
-
* proceed into the outbound pipeline. The implementation mirrors the
|
|
499
|
-
* semantics of the framework-wide `resolveDmGroupAccessDecision`
|
|
500
|
-
* (`src/security/dm-policy-shared.ts`) but is kept standalone so the
|
|
501
|
-
* `engine/` layer does not pull in `openclaw/plugin-sdk/*` modules —
|
|
502
|
-
* a hard constraint shared with the standalone `openclaw-qqbot` build.
|
|
503
|
-
*
|
|
504
|
-
* If in the future we lift the zero-dependency rule in the engine
|
|
505
|
-
* layer, this file can be replaced by a thin adapter around the
|
|
506
|
-
* framework API with identical semantics.
|
|
507
|
-
*/
|
|
508
|
-
/**
|
|
509
|
-
* Evaluate the inbound access policy.
|
|
510
|
-
*
|
|
511
|
-
* Semantics (aligned with `resolveDmGroupAccessDecision`):
|
|
512
|
-
* - Group message:
|
|
513
|
-
* - `groupPolicy=disabled` → block
|
|
514
|
-
* - `groupPolicy=open` → allow
|
|
515
|
-
* - `groupPolicy=allowlist`:
|
|
516
|
-
* - empty effectiveGroupAllowFrom → block (empty_allowlist)
|
|
517
|
-
* - sender not in list → block (not_allowlisted)
|
|
518
|
-
* - otherwise → allow
|
|
519
|
-
* - Direct message:
|
|
520
|
-
* - `dmPolicy=disabled` → block
|
|
521
|
-
* - `dmPolicy=open` → allow wildcard, legacy empty allowFrom, or matching allowFrom
|
|
522
|
-
* - `dmPolicy=allowlist`:
|
|
523
|
-
* - empty effectiveAllowFrom → block (empty_allowlist)
|
|
524
|
-
* - sender not in list → block (not_allowlisted)
|
|
525
|
-
* - otherwise → allow
|
|
526
|
-
*
|
|
527
|
-
* The function never throws; callers can rely on the returned
|
|
528
|
-
* `decision`/`reasonCode` pair for branching.
|
|
529
|
-
*/
|
|
530
|
-
function resolveQQBotAccess(input) {
|
|
531
|
-
const { dmPolicy, groupPolicy } = resolveQQBotEffectivePolicies(input);
|
|
532
|
-
const rawGroupAllowFrom = input.groupAllowFrom && input.groupAllowFrom.length > 0 ? input.groupAllowFrom : input.allowFrom ?? [];
|
|
533
|
-
const normalizedAllowFrom = normalizeQQBotAllowFrom(input.allowFrom);
|
|
534
|
-
const effectiveAllowFrom = dmPolicy === "open" && normalizedAllowFrom.length === 0 ? ["*"] : normalizedAllowFrom;
|
|
535
|
-
const effectiveGroupAllowFrom = normalizeQQBotAllowFrom(rawGroupAllowFrom);
|
|
536
|
-
const isSenderAllowed = createQQBotSenderMatcher(input.senderId);
|
|
537
|
-
if (input.isGroup) return evaluateGroupDecision({
|
|
538
|
-
groupPolicy,
|
|
539
|
-
dmPolicy,
|
|
540
|
-
effectiveAllowFrom,
|
|
541
|
-
effectiveGroupAllowFrom,
|
|
542
|
-
isSenderAllowed
|
|
543
|
-
});
|
|
544
|
-
return evaluateDmDecision({
|
|
545
|
-
groupPolicy,
|
|
546
|
-
dmPolicy,
|
|
547
|
-
effectiveAllowFrom,
|
|
548
|
-
effectiveGroupAllowFrom,
|
|
549
|
-
isSenderAllowed
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
function evaluateGroupDecision(ctx) {
|
|
553
|
-
const base = buildResultBase(ctx);
|
|
554
|
-
if (ctx.groupPolicy === "disabled") return {
|
|
555
|
-
...base,
|
|
556
|
-
decision: "block",
|
|
557
|
-
reasonCode: QQBOT_ACCESS_REASON.GROUP_POLICY_DISABLED,
|
|
558
|
-
reason: "groupPolicy=disabled"
|
|
559
|
-
};
|
|
560
|
-
if (ctx.groupPolicy === "open") return {
|
|
561
|
-
...base,
|
|
562
|
-
decision: "allow",
|
|
563
|
-
reasonCode: QQBOT_ACCESS_REASON.GROUP_POLICY_ALLOWED,
|
|
564
|
-
reason: "groupPolicy=open"
|
|
565
|
-
};
|
|
566
|
-
if (ctx.effectiveGroupAllowFrom.length === 0) return {
|
|
567
|
-
...base,
|
|
568
|
-
decision: "block",
|
|
569
|
-
reasonCode: QQBOT_ACCESS_REASON.GROUP_POLICY_EMPTY_ALLOWLIST,
|
|
570
|
-
reason: "groupPolicy=allowlist (empty allowlist)"
|
|
571
|
-
};
|
|
572
|
-
if (!ctx.isSenderAllowed(ctx.effectiveGroupAllowFrom)) return {
|
|
573
|
-
...base,
|
|
574
|
-
decision: "block",
|
|
575
|
-
reasonCode: QQBOT_ACCESS_REASON.GROUP_POLICY_NOT_ALLOWLISTED,
|
|
576
|
-
reason: "groupPolicy=allowlist (not allowlisted)"
|
|
577
|
-
};
|
|
578
|
-
return {
|
|
579
|
-
...base,
|
|
580
|
-
decision: "allow",
|
|
581
|
-
reasonCode: QQBOT_ACCESS_REASON.GROUP_POLICY_ALLOWED,
|
|
582
|
-
reason: "groupPolicy=allowlist (allowlisted)"
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
function evaluateDmDecision(ctx) {
|
|
586
|
-
const base = buildResultBase(ctx);
|
|
587
|
-
if (ctx.dmPolicy === "disabled") return {
|
|
588
|
-
...base,
|
|
589
|
-
decision: "block",
|
|
590
|
-
reasonCode: QQBOT_ACCESS_REASON.DM_POLICY_DISABLED,
|
|
591
|
-
reason: "dmPolicy=disabled"
|
|
592
|
-
};
|
|
593
|
-
if (ctx.dmPolicy === "open") {
|
|
594
|
-
if (ctx.effectiveAllowFrom.includes("*")) return {
|
|
595
|
-
...base,
|
|
596
|
-
decision: "allow",
|
|
597
|
-
reasonCode: QQBOT_ACCESS_REASON.DM_POLICY_OPEN,
|
|
598
|
-
reason: "dmPolicy=open"
|
|
599
|
-
};
|
|
600
|
-
if (ctx.isSenderAllowed(ctx.effectiveAllowFrom)) return {
|
|
601
|
-
...base,
|
|
602
|
-
decision: "allow",
|
|
603
|
-
reasonCode: QQBOT_ACCESS_REASON.DM_POLICY_ALLOWLISTED,
|
|
604
|
-
reason: "dmPolicy=open (allowlisted)"
|
|
605
|
-
};
|
|
606
|
-
return {
|
|
607
|
-
...base,
|
|
608
|
-
decision: "block",
|
|
609
|
-
reasonCode: QQBOT_ACCESS_REASON.DM_POLICY_NOT_ALLOWLISTED,
|
|
610
|
-
reason: "dmPolicy=open (not allowlisted)"
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
if (ctx.effectiveAllowFrom.length === 0) return {
|
|
614
|
-
...base,
|
|
615
|
-
decision: "block",
|
|
616
|
-
reasonCode: QQBOT_ACCESS_REASON.DM_POLICY_EMPTY_ALLOWLIST,
|
|
617
|
-
reason: "dmPolicy=allowlist (empty allowlist)"
|
|
618
|
-
};
|
|
619
|
-
if (!ctx.isSenderAllowed(ctx.effectiveAllowFrom)) return {
|
|
620
|
-
...base,
|
|
621
|
-
decision: "block",
|
|
622
|
-
reasonCode: QQBOT_ACCESS_REASON.DM_POLICY_NOT_ALLOWLISTED,
|
|
623
|
-
reason: "dmPolicy=allowlist (not allowlisted)"
|
|
624
|
-
};
|
|
625
|
-
return {
|
|
626
|
-
...base,
|
|
627
|
-
decision: "allow",
|
|
628
|
-
reasonCode: QQBOT_ACCESS_REASON.DM_POLICY_ALLOWLISTED,
|
|
629
|
-
reason: "dmPolicy=allowlist (allowlisted)"
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
function buildResultBase(ctx) {
|
|
633
|
-
return {
|
|
634
|
-
effectiveAllowFrom: ctx.effectiveAllowFrom,
|
|
635
|
-
effectiveGroupAllowFrom: ctx.effectiveGroupAllowFrom,
|
|
636
|
-
dmPolicy: ctx.dmPolicy,
|
|
637
|
-
groupPolicy: ctx.groupPolicy
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
//#endregion
|
|
641
415
|
//#region extensions/qqbot/src/engine/commands/slash-command-auth.ts
|
|
642
416
|
/**
|
|
643
417
|
* Pre-dispatch authorization for requireAuth slash commands.
|
|
644
418
|
*
|
|
645
|
-
* Unlike the
|
|
646
|
-
*
|
|
647
|
-
*
|
|
419
|
+
* Unlike the inbound message ingress command projection (which permits
|
|
420
|
+
* open-policy chat senders), this function requires the sender to appear in an
|
|
421
|
+
* **explicit non-wildcard** allowFrom list.
|
|
648
422
|
*
|
|
649
423
|
* Rationale: sensitive operations (log export, file deletion, approval
|
|
650
424
|
* config changes) must be gated behind a deliberate operator decision.
|
|
@@ -713,6 +487,22 @@ async function trySlashCommand(msg, ctx) {
|
|
|
713
487
|
}
|
|
714
488
|
const receivedAt = Date.now();
|
|
715
489
|
const peerId = ctx.getMessagePeerId(msg);
|
|
490
|
+
const isGroup = msg.type === "group" || msg.type === "guild";
|
|
491
|
+
const commandsAllowFrom = resolveQQBotCommandsAllowFrom(ctx.cfg);
|
|
492
|
+
const commandAuthorized = ctx.resolveCommandAuthorized ? await ctx.resolveCommandAuthorized({
|
|
493
|
+
isGroup,
|
|
494
|
+
senderId: msg.senderId,
|
|
495
|
+
conversationId: msg.groupOpenid ?? msg.channelId ?? msg.senderId,
|
|
496
|
+
allowFrom: account.config?.allowFrom,
|
|
497
|
+
groupAllowFrom: account.config?.groupAllowFrom,
|
|
498
|
+
commandsAllowFrom
|
|
499
|
+
}) : resolveSlashCommandAuth({
|
|
500
|
+
senderId: msg.senderId,
|
|
501
|
+
isGroup,
|
|
502
|
+
allowFrom: account.config?.allowFrom,
|
|
503
|
+
groupAllowFrom: account.config?.groupAllowFrom,
|
|
504
|
+
commandsAllowFrom
|
|
505
|
+
});
|
|
716
506
|
const cmdCtx = {
|
|
717
507
|
type: msg.type,
|
|
718
508
|
senderId: msg.senderId,
|
|
@@ -727,13 +517,7 @@ async function trySlashCommand(msg, ctx) {
|
|
|
727
517
|
accountId: account.accountId,
|
|
728
518
|
appId: account.appId,
|
|
729
519
|
accountConfig: account.config,
|
|
730
|
-
commandAuthorized
|
|
731
|
-
senderId: msg.senderId,
|
|
732
|
-
isGroup: msg.type === "group" || msg.type === "guild",
|
|
733
|
-
allowFrom: account.config?.allowFrom,
|
|
734
|
-
groupAllowFrom: account.config?.groupAllowFrom,
|
|
735
|
-
commandsAllowFrom: resolveQQBotCommandsAllowFrom(ctx.cfg)
|
|
736
|
-
}),
|
|
520
|
+
commandAuthorized,
|
|
737
521
|
queueSnapshot: ctx.getQueueSnapshot(peerId)
|
|
738
522
|
};
|
|
739
523
|
try {
|
|
@@ -1345,43 +1129,16 @@ function buildGroupQueuedMessage(data, accountId, eventType) {
|
|
|
1345
1129
|
}
|
|
1346
1130
|
//#endregion
|
|
1347
1131
|
//#region extensions/qqbot/src/engine/gateway/message-queue.ts
|
|
1348
|
-
/**
|
|
1349
|
-
* Per-user concurrent message queue.
|
|
1350
|
-
*
|
|
1351
|
-
* Messages are serialized per **peer** (one DM user, one group, one guild
|
|
1352
|
-
* channel) and processed in parallel across peers up to
|
|
1353
|
-
* {@link DEFAULT_MAX_CONCURRENT_USERS}.
|
|
1354
|
-
*
|
|
1355
|
-
* Group-specific enhancements (added when merging from the standalone build):
|
|
1356
|
-
* - Group peers have a larger queue cap ({@link DEFAULT_GROUP_QUEUE_SIZE})
|
|
1357
|
-
* because groups can burst more chatter than a single DM.
|
|
1358
|
-
* - When a group's queue overflows, bot-authored messages are evicted
|
|
1359
|
-
* preferentially so human messages don't get dropped.
|
|
1360
|
-
* - When draining a group peer with more than one queued message, the
|
|
1361
|
-
* non-command messages are **merged** into one logical turn (see
|
|
1362
|
-
* {@link mergeGroupMessages}). Slash commands are always processed
|
|
1363
|
-
* individually to avoid conflating a "/stop" with surrounding chatter.
|
|
1364
|
-
*
|
|
1365
|
-
* The module is self-contained: the only injected dependency is the
|
|
1366
|
-
* logger / abort probe supplied via {@link MessageQueueContext}.
|
|
1367
|
-
*/
|
|
1368
|
-
/** Global cap across all peers. */
|
|
1369
1132
|
const DEFAULT_GLOBAL_QUEUE_SIZE = 1e3;
|
|
1370
|
-
/** Per-DM / per-channel cap. */
|
|
1371
1133
|
const DEFAULT_PER_PEER_QUEUE_SIZE = 20;
|
|
1372
|
-
/** Per-group cap — larger because groups burst more. */
|
|
1373
1134
|
const DEFAULT_GROUP_QUEUE_SIZE = 50;
|
|
1374
|
-
/** Parallel fanout across peers. */
|
|
1375
1135
|
const DEFAULT_MAX_CONCURRENT_USERS = 10;
|
|
1376
|
-
/** Convenience predicate: is this a merged multi-message turn? */
|
|
1377
1136
|
function isMergedTurn(msg) {
|
|
1378
1137
|
return (msg.merge?.count ?? 0) > 1;
|
|
1379
1138
|
}
|
|
1380
|
-
/** Return true when the peer id refers to a group-like conversation. */
|
|
1381
1139
|
function isGroupPeer(peerId) {
|
|
1382
1140
|
return peerId.startsWith("group:") || peerId.startsWith("guild:");
|
|
1383
1141
|
}
|
|
1384
|
-
/** Slash-command test used by {@link drainGroupBatch}. */
|
|
1385
1142
|
function isSlashCommand(msg) {
|
|
1386
1143
|
return (msg.content ?? "").trim().startsWith("/");
|
|
1387
1144
|
}
|
|
@@ -1448,9 +1205,6 @@ function mergeGroupMessages(batch) {
|
|
|
1448
1205
|
}
|
|
1449
1206
|
};
|
|
1450
1207
|
}
|
|
1451
|
-
/**
|
|
1452
|
-
* Create a per-user concurrent queue with built-in group enhancements.
|
|
1453
|
-
*/
|
|
1454
1208
|
function createMessageQueue(ctx) {
|
|
1455
1209
|
const { accountId: _accountId, log } = ctx;
|
|
1456
1210
|
const globalQueueSize = ctx.globalQueueSize ?? DEFAULT_GLOBAL_QUEUE_SIZE;
|
|
@@ -1466,13 +1220,6 @@ function createMessageQueue(ctx) {
|
|
|
1466
1220
|
if (msg.type === "group") return `group:${msg.groupOpenid ?? "unknown"}`;
|
|
1467
1221
|
return `dm:${msg.senderId}`;
|
|
1468
1222
|
};
|
|
1469
|
-
/**
|
|
1470
|
-
* Evict one message from an over-full queue.
|
|
1471
|
-
*
|
|
1472
|
-
* For group peers we prefer to drop a bot-authored message so human
|
|
1473
|
-
* input never gets lost. Falling back to dropping the oldest keeps the
|
|
1474
|
-
* queue bounded when all members are bots.
|
|
1475
|
-
*/
|
|
1476
1223
|
const evictOne = (queue, isGroup) => {
|
|
1477
1224
|
if (isGroup) {
|
|
1478
1225
|
const botIdx = queue.findIndex((m) => m.senderIsBot);
|
|
@@ -1480,7 +1227,6 @@ function createMessageQueue(ctx) {
|
|
|
1480
1227
|
}
|
|
1481
1228
|
return queue.shift();
|
|
1482
1229
|
};
|
|
1483
|
-
/** Run a single message, capturing errors in the log. */
|
|
1484
1230
|
const processOne = async (msg, peerId, label) => {
|
|
1485
1231
|
try {
|
|
1486
1232
|
await handleMessageFnRef(msg);
|
|
@@ -1488,11 +1234,6 @@ function createMessageQueue(ctx) {
|
|
|
1488
1234
|
log?.error(`${label} error for ${peerId}: ${formatErrorMessage(err)}`);
|
|
1489
1235
|
}
|
|
1490
1236
|
};
|
|
1491
|
-
/**
|
|
1492
|
-
* Drain a group's batch:
|
|
1493
|
-
* - slash commands are processed one by one (order preserved);
|
|
1494
|
-
* - the remaining messages are merged into a single turn.
|
|
1495
|
-
*/
|
|
1496
1237
|
const drainGroupBatch = async (batch, peerId) => {
|
|
1497
1238
|
const commands = [];
|
|
1498
1239
|
const normal = [];
|
|
@@ -1508,7 +1249,6 @@ function createMessageQueue(ctx) {
|
|
|
1508
1249
|
await processOne(merged, peerId, `Message processor (merged batch of ${normal.length})`);
|
|
1509
1250
|
}
|
|
1510
1251
|
};
|
|
1511
|
-
/** Process one peer's queue serially. */
|
|
1512
1252
|
const drainUserQueue = async (peerId) => {
|
|
1513
1253
|
if (activeUsers.has(peerId)) return;
|
|
1514
1254
|
if (activeUsers.size >= maxConcurrentUsers) {
|
|
@@ -1761,12 +1501,6 @@ async function createQQWSClient(options) {
|
|
|
1761
1501
|
}
|
|
1762
1502
|
//#endregion
|
|
1763
1503
|
//#region extensions/qqbot/src/engine/gateway/gateway-connection.ts
|
|
1764
|
-
/**
|
|
1765
|
-
* GatewayConnection — WebSocket lifecycle, heartbeat, reconnect, and session persistence.
|
|
1766
|
-
*
|
|
1767
|
-
* Encapsulates all connection state as class fields (replaces 11 closure variables).
|
|
1768
|
-
* Event handling and message processing are delegated to injected handlers.
|
|
1769
|
-
*/
|
|
1770
1504
|
var GatewayConnection = class {
|
|
1771
1505
|
constructor(ctx) {
|
|
1772
1506
|
this.isAborted = false;
|
|
@@ -1785,7 +1519,6 @@ var GatewayConnection = class {
|
|
|
1785
1519
|
isAborted: () => this.isAborted
|
|
1786
1520
|
});
|
|
1787
1521
|
}
|
|
1788
|
-
/** Start the connection loop. Resolves when abortSignal fires. */
|
|
1789
1522
|
async start() {
|
|
1790
1523
|
this.restoreSession();
|
|
1791
1524
|
this.registerAbortHandler();
|
|
@@ -1882,7 +1615,12 @@ var GatewayConnection = class {
|
|
|
1882
1615
|
cfg: this.ctx.cfg,
|
|
1883
1616
|
log,
|
|
1884
1617
|
getMessagePeerId: (msg) => this.msgQueue.getMessagePeerId(msg),
|
|
1885
|
-
getQueueSnapshot: (peerId) => this.msgQueue.getSnapshot(peerId)
|
|
1618
|
+
getQueueSnapshot: (peerId) => this.msgQueue.getSnapshot(peerId),
|
|
1619
|
+
resolveCommandAuthorized: (params) => this.ctx.adapters.access.resolveSlashCommandAuthorization({
|
|
1620
|
+
cfg: this.ctx.cfg,
|
|
1621
|
+
accountId: account.accountId,
|
|
1622
|
+
...params
|
|
1623
|
+
})
|
|
1886
1624
|
};
|
|
1887
1625
|
const trySlashCommandOrEnqueue = async (msg) => {
|
|
1888
1626
|
const result = await trySlashCommand(msg, slashCtx);
|
|
@@ -2319,7 +2057,6 @@ async function processVoiceAttachment(localPath, audioPath, att, asrReferText, c
|
|
|
2319
2057
|
}
|
|
2320
2058
|
//#endregion
|
|
2321
2059
|
//#region extensions/qqbot/src/engine/gateway/stages/stub-contexts.ts
|
|
2322
|
-
/** Build an {@link InboundContext} with all non-routing fields cleared. */
|
|
2323
2060
|
function emptyInboundContext(fields) {
|
|
2324
2061
|
return {
|
|
2325
2062
|
event: fields.event,
|
|
@@ -2328,30 +2065,12 @@ function emptyInboundContext(fields) {
|
|
|
2328
2065
|
peerId: fields.peerId,
|
|
2329
2066
|
qualifiedTarget: fields.qualifiedTarget,
|
|
2330
2067
|
fromAddress: fields.fromAddress,
|
|
2331
|
-
parsedContent: "",
|
|
2332
|
-
userContent: "",
|
|
2333
|
-
quotePart: "",
|
|
2334
|
-
dynamicCtx: "",
|
|
2335
|
-
userMessage: "",
|
|
2336
2068
|
agentBody: "",
|
|
2337
2069
|
body: "",
|
|
2338
|
-
systemPrompts: [],
|
|
2339
2070
|
groupSystemPrompt: void 0,
|
|
2340
|
-
attachments: {
|
|
2341
|
-
attachmentInfo: "",
|
|
2342
|
-
imageUrls: [],
|
|
2343
|
-
imageMediaTypes: [],
|
|
2344
|
-
voiceAttachmentPaths: [],
|
|
2345
|
-
voiceAttachmentUrls: [],
|
|
2346
|
-
voiceAsrReferTexts: [],
|
|
2347
|
-
voiceTranscripts: [],
|
|
2348
|
-
voiceTranscriptSources: [],
|
|
2349
|
-
attachmentLocalPaths: []
|
|
2350
|
-
},
|
|
2351
2071
|
localMediaPaths: [],
|
|
2352
2072
|
localMediaTypes: [],
|
|
2353
2073
|
remoteMediaUrls: [],
|
|
2354
|
-
remoteMediaTypes: [],
|
|
2355
2074
|
uniqueVoicePaths: [],
|
|
2356
2075
|
uniqueVoiceUrls: [],
|
|
2357
2076
|
uniqueVoiceAsrReferTexts: [],
|
|
@@ -2367,52 +2086,29 @@ function emptyInboundContext(fields) {
|
|
|
2367
2086
|
inputNotifyRefIdx: void 0
|
|
2368
2087
|
};
|
|
2369
2088
|
}
|
|
2370
|
-
/**
|
|
2371
|
-
* Build an {@link InboundContext} that represents a message blocked by
|
|
2372
|
-
* access control (policy denial, allowlist mismatch, etc.).
|
|
2373
|
-
*/
|
|
2374
2089
|
function buildBlockedInboundContext(params) {
|
|
2375
2090
|
return {
|
|
2376
2091
|
...emptyInboundContext(params),
|
|
2377
2092
|
blocked: true,
|
|
2378
|
-
blockReason: params.access.
|
|
2379
|
-
blockReasonCode: params.access.reasonCode,
|
|
2380
|
-
accessDecision: params.access.decision
|
|
2093
|
+
blockReason: params.access.senderAccess.reasonCode,
|
|
2094
|
+
blockReasonCode: params.access.senderAccess.reasonCode,
|
|
2095
|
+
accessDecision: params.access.senderAccess.decision
|
|
2381
2096
|
};
|
|
2382
2097
|
}
|
|
2383
|
-
/**
|
|
2384
|
-
* Build an {@link InboundContext} that represents a message stopped by
|
|
2385
|
-
* the group gate (drop_other_mention, block_unauthorized_command,
|
|
2386
|
-
* skip_no_mention). Any history side-effects have already been applied
|
|
2387
|
-
* by the gate stage.
|
|
2388
|
-
*/
|
|
2389
2098
|
function buildSkippedInboundContext(params) {
|
|
2390
2099
|
return {
|
|
2391
2100
|
...emptyInboundContext(params),
|
|
2392
2101
|
group: params.group,
|
|
2393
2102
|
skipped: true,
|
|
2394
2103
|
skipReason: params.skipReason,
|
|
2395
|
-
accessDecision: params.access.decision,
|
|
2104
|
+
accessDecision: params.access.senderAccess.decision,
|
|
2396
2105
|
typing: params.typing,
|
|
2397
2106
|
inputNotifyRefIdx: params.inputNotifyRefIdx
|
|
2398
2107
|
};
|
|
2399
2108
|
}
|
|
2400
2109
|
//#endregion
|
|
2401
2110
|
//#region extensions/qqbot/src/engine/gateway/stages/access-stage.ts
|
|
2402
|
-
|
|
2403
|
-
* Access stage — resolves routing target + runs access control.
|
|
2404
|
-
*
|
|
2405
|
-
* Split from the pipeline so it is trivially unit-testable: given a raw
|
|
2406
|
-
* event and the runtime's routing info, the stage returns either:
|
|
2407
|
-
* - `{ kind: "allow", ... }` — proceed through the rest of the pipeline
|
|
2408
|
-
* - `{ kind: "block", context }` — short-circuit; the caller returns
|
|
2409
|
-
* `context` directly to its own caller.
|
|
2410
|
-
*/
|
|
2411
|
-
/**
|
|
2412
|
-
* Resolve the routing target, walk the access policy, and decide whether
|
|
2413
|
-
* the inbound message should proceed to the rest of the pipeline.
|
|
2414
|
-
*/
|
|
2415
|
-
function runAccessStage(event, deps) {
|
|
2111
|
+
async function runAccessStage(event, deps) {
|
|
2416
2112
|
const { account, cfg, runtime, log } = deps;
|
|
2417
2113
|
const isGroupChat = event.type === "guild" || event.type === "group";
|
|
2418
2114
|
const peerId = resolvePeerId(event, isGroupChat);
|
|
@@ -2426,16 +2122,19 @@ function runAccessStage(event, deps) {
|
|
|
2426
2122
|
id: peerId
|
|
2427
2123
|
}
|
|
2428
2124
|
});
|
|
2429
|
-
const access =
|
|
2125
|
+
const access = await deps.adapters.access.resolveInboundAccess({
|
|
2126
|
+
cfg,
|
|
2127
|
+
accountId: account.accountId,
|
|
2430
2128
|
isGroup: isGroupChat,
|
|
2431
2129
|
senderId: event.senderId,
|
|
2130
|
+
conversationId: peerId,
|
|
2432
2131
|
allowFrom: account.config?.allowFrom,
|
|
2433
2132
|
groupAllowFrom: account.config?.groupAllowFrom,
|
|
2434
2133
|
dmPolicy: account.config?.dmPolicy,
|
|
2435
2134
|
groupPolicy: account.config?.groupPolicy
|
|
2436
2135
|
});
|
|
2437
|
-
if (access.decision !== "allow") {
|
|
2438
|
-
log?.info(`Blocked qqbot inbound: decision=${access.decision} reasonCode=${access.reasonCode}
|
|
2136
|
+
if (access.senderAccess.decision !== "allow") {
|
|
2137
|
+
log?.info(`Blocked qqbot inbound: decision=${access.senderAccess.decision} reasonCode=${access.senderAccess.reasonCode} senderId=${event.senderId} accountId=${account.accountId} isGroup=${isGroupChat}`);
|
|
2439
2138
|
return {
|
|
2440
2139
|
kind: "block",
|
|
2441
2140
|
context: buildBlockedInboundContext({
|
|
@@ -2469,28 +2168,9 @@ function buildQualifiedTarget(event, isGroupChat) {
|
|
|
2469
2168
|
if (isGroupChat) return event.type === "guild" ? `qqbot:channel:${event.channelId}` : `qqbot:group:${event.groupOpenid}`;
|
|
2470
2169
|
return event.type === "dm" ? `qqbot:dm:${event.guildId}` : `qqbot:c2c:${event.senderId}`;
|
|
2471
2170
|
}
|
|
2472
|
-
/**
|
|
2473
|
-
* Decide whether the access decision permits running text-based control
|
|
2474
|
-
* commands. Placed in the access stage because the rule is an
|
|
2475
|
-
* access-policy derivative, not a gate derivative.
|
|
2476
|
-
*/
|
|
2477
|
-
function resolveCommandAuthorized(access) {
|
|
2478
|
-
return access.reasonCode === "dm_policy_open" || access.reasonCode === "dm_policy_allowlisted" || access.reasonCode === "group_policy_allowed" && access.effectiveGroupAllowFrom.length > 0 && access.groupPolicy === "allowlist";
|
|
2479
|
-
}
|
|
2480
2171
|
//#endregion
|
|
2481
2172
|
//#region extensions/qqbot/src/engine/group/mention.ts
|
|
2482
|
-
/** Regex detecting `<@openid>` / `<@!openid>` mention tags in raw content. */
|
|
2483
2173
|
const MENTION_TAG_RE = /<@!?\w+>/;
|
|
2484
|
-
/**
|
|
2485
|
-
* Detect whether the inbound message explicitly targets the bot.
|
|
2486
|
-
*
|
|
2487
|
-
* Priority order:
|
|
2488
|
-
* 1. `mentions[].is_you === true` (most reliable)
|
|
2489
|
-
* 2. `eventType === "GROUP_AT_MESSAGE_CREATE"` (QQ-level @bot event)
|
|
2490
|
-
* 3. regex match on any of `mentionPatterns` (fallback, e.g. "@bot-name")
|
|
2491
|
-
*
|
|
2492
|
-
* Returns `false` for direct messages or when no signal is found.
|
|
2493
|
-
*/
|
|
2494
2174
|
function detectWasMentioned(input) {
|
|
2495
2175
|
const { eventType, mentions, content, mentionPatterns } = input;
|
|
2496
2176
|
if (mentions?.some((m) => m.is_you)) return true;
|
|
@@ -2503,30 +2183,11 @@ function detectWasMentioned(input) {
|
|
|
2503
2183
|
}
|
|
2504
2184
|
return false;
|
|
2505
2185
|
}
|
|
2506
|
-
/**
|
|
2507
|
-
* Report whether the message contains **any** @mention (not necessarily @bot).
|
|
2508
|
-
*
|
|
2509
|
-
* Used by the gating layer to decide whether to bypass mention requirements
|
|
2510
|
-
* for control commands. A control command like `/stop` that also @-s another
|
|
2511
|
-
* user should NOT bypass the mention gate — the `@other-user` prefix is a
|
|
2512
|
-
* strong signal that the command wasn't addressed to the bot.
|
|
2513
|
-
*/
|
|
2514
2186
|
function hasAnyMention(input) {
|
|
2515
2187
|
if (input.mentions && input.mentions.length > 0) return true;
|
|
2516
2188
|
if (input.content && MENTION_TAG_RE.test(input.content)) return true;
|
|
2517
2189
|
return false;
|
|
2518
2190
|
}
|
|
2519
|
-
/**
|
|
2520
|
-
* Clean up `<@openid>` mention tags in raw QQ group content.
|
|
2521
|
-
*
|
|
2522
|
-
* - For the bot's own mention (`is_you === true`): the tag is removed
|
|
2523
|
-
* outright so prompts don't contain visible `<@BOTID>` garbage.
|
|
2524
|
-
* - For other mentioned users: the tag is replaced with `@nickname` (or
|
|
2525
|
-
* `@username`) for readability. Entries without a display name are left
|
|
2526
|
-
* as-is (rare in practice).
|
|
2527
|
-
*
|
|
2528
|
-
* Returns the original text unchanged when `text` or `mentions` is empty.
|
|
2529
|
-
*/
|
|
2530
2191
|
function stripMentionText(text, mentions) {
|
|
2531
2192
|
if (!text || !mentions?.length) return text;
|
|
2532
2193
|
let cleaned = text;
|
|
@@ -2542,7 +2203,6 @@ function stripMentionText(text, mentions) {
|
|
|
2542
2203
|
}
|
|
2543
2204
|
return cleaned;
|
|
2544
2205
|
}
|
|
2545
|
-
/** Escape characters that carry regex meaning. */
|
|
2546
2206
|
function escapeRegex$1(str) {
|
|
2547
2207
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2548
2208
|
}
|
|
@@ -2852,7 +2512,6 @@ const DEFAULT_GROUP_CONFIG = {
|
|
|
2852
2512
|
name: "",
|
|
2853
2513
|
historyLimit: 50
|
|
2854
2514
|
};
|
|
2855
|
-
/** Read a named account's raw `groups` map from an OpenClawConfig. */
|
|
2856
2515
|
function readGroupsMap(cfg, accountId) {
|
|
2857
2516
|
const groups = asOptionalObjectRecord(resolveAccountBase(cfg, accountId).config.groups);
|
|
2858
2517
|
if (!groups) return {};
|
|
@@ -2880,12 +2539,6 @@ function readHistoryLimit(obj, key) {
|
|
|
2880
2539
|
if (typeof v !== "number" || !Number.isFinite(v)) return;
|
|
2881
2540
|
return Math.max(0, Math.floor(v));
|
|
2882
2541
|
}
|
|
2883
|
-
/**
|
|
2884
|
-
* Resolve per-group configuration with `specific > "*" > default` precedence.
|
|
2885
|
-
*
|
|
2886
|
-
* When `groupOpenid` is not provided, only the wildcard/default values are
|
|
2887
|
-
* returned. This lets callers query the "default" behaviour for new groups.
|
|
2888
|
-
*/
|
|
2889
2542
|
function resolveGroupConfig(cfg, groupOpenid, accountId) {
|
|
2890
2543
|
const groups = readGroupsMap(cfg, accountId);
|
|
2891
2544
|
const wildcard = groups["*"] ?? {};
|
|
@@ -2899,13 +2552,6 @@ function resolveGroupConfig(cfg, groupOpenid, accountId) {
|
|
|
2899
2552
|
historyLimit: readHistoryLimit(specific, "historyLimit") ?? readHistoryLimit(wildcard, "historyLimit") ?? DEFAULT_GROUP_CONFIG.historyLimit
|
|
2900
2553
|
};
|
|
2901
2554
|
}
|
|
2902
|
-
/**
|
|
2903
|
-
* Resolve all per-inbound group-related settings in one pass.
|
|
2904
|
-
*
|
|
2905
|
-
* Prefer this over calling `resolveHistoryLimit` / `resolveRequireMention`
|
|
2906
|
-
* / etc. individually in hot paths — each of those currently re-walks
|
|
2907
|
-
* the config tree on its own.
|
|
2908
|
-
*/
|
|
2909
2555
|
function resolveGroupSettings(params) {
|
|
2910
2556
|
const config = resolveGroupConfig(params.cfg, params.groupOpenid, params.accountId);
|
|
2911
2557
|
return {
|
|
@@ -2914,12 +2560,6 @@ function resolveGroupSettings(params) {
|
|
|
2914
2560
|
mentionPatterns: resolveMentionPatterns(params.cfg, params.agentId)
|
|
2915
2561
|
};
|
|
2916
2562
|
}
|
|
2917
|
-
/**
|
|
2918
|
-
* Resolve mentionPatterns with `agent > global > []` precedence.
|
|
2919
|
-
*
|
|
2920
|
-
* Mirrors the framework's `messages.groupChat.mentionPatterns` / per-agent
|
|
2921
|
-
* `agents.list[].groupChat.mentionPatterns` chain.
|
|
2922
|
-
*/
|
|
2923
2563
|
function resolveMentionPatterns(cfg, agentId) {
|
|
2924
2564
|
if (agentId) {
|
|
2925
2565
|
const agents = asOptionalObjectRecord(cfg.agents);
|
|
@@ -2938,34 +2578,6 @@ function resolveMentionPatterns(cfg, agentId) {
|
|
|
2938
2578
|
}
|
|
2939
2579
|
//#endregion
|
|
2940
2580
|
//#region extensions/qqbot/src/engine/gateway/stages/group-gate-stage.ts
|
|
2941
|
-
/**
|
|
2942
|
-
* Group-gate stage — for `type === "group"` inbound events, decide
|
|
2943
|
-
* whether the message should pass to AI dispatch or be intercepted.
|
|
2944
|
-
*
|
|
2945
|
-
* Three possible outcomes:
|
|
2946
|
-
* - `{ kind: "pass", groupInfo }` — continue the pipeline
|
|
2947
|
-
* - `{ kind: "skip", groupInfo, skipReason }` — buffered to history
|
|
2948
|
-
* (if applicable) and short-circuit
|
|
2949
|
-
* - No group info at all — returned when the event isn't a group event
|
|
2950
|
-
* (caller should treat as a straight pass-through)
|
|
2951
|
-
*
|
|
2952
|
-
* Consolidates the control-command auth check, session-store
|
|
2953
|
-
* activation override, mention detection, and the unified
|
|
2954
|
-
* {@link resolveGroupMessageGate} call. Delegates all pure logic to
|
|
2955
|
-
* existing `engine/group/*` modules so this stage remains a thin
|
|
2956
|
-
* orchestrator.
|
|
2957
|
-
*/
|
|
2958
|
-
/**
|
|
2959
|
-
* Run the group-gate stage.
|
|
2960
|
-
*
|
|
2961
|
-
* Precondition: `event.type === "group"` && `event.groupOpenid` is set.
|
|
2962
|
-
* The caller (pipeline) enforces this; the stage doesn't re-check.
|
|
2963
|
-
*
|
|
2964
|
-
* On `skip` outcomes the stage records the message into the group's
|
|
2965
|
-
* history buffer when the skip reason is one that should preserve
|
|
2966
|
-
* context (drop / skip_no_mention), then returns. `block` skip
|
|
2967
|
-
* reasons do NOT write history — they are silent rejects.
|
|
2968
|
-
*/
|
|
2969
2581
|
function runGroupGateStage(input) {
|
|
2970
2582
|
const { event, deps, accountId, agentId, sessionKey, userContent, processedAttachments } = input;
|
|
2971
2583
|
const groupOpenid = event.groupOpenid;
|
|
@@ -3002,7 +2614,7 @@ function runGroupGateStage(input) {
|
|
|
3002
2614
|
});
|
|
3003
2615
|
const content = (event.content ?? "").trim();
|
|
3004
2616
|
const isControlCommand = Boolean(deps.isControlCommand?.(content));
|
|
3005
|
-
const commandAuthorized = deps.allowTextCommands !== false &&
|
|
2617
|
+
const commandAuthorized = deps.allowTextCommands !== false && input.access.commandAccess.authorized;
|
|
3006
2618
|
const gate = resolveGateWithPort({
|
|
3007
2619
|
mentionGatePort: deps.adapters.mentionGate,
|
|
3008
2620
|
ignoreOtherMentions,
|
|
@@ -3052,12 +2664,6 @@ function runGroupGateStage(input) {
|
|
|
3052
2664
|
skipReason: gate.action
|
|
3053
2665
|
};
|
|
3054
2666
|
}
|
|
3055
|
-
/**
|
|
3056
|
-
* Resolve the gate using the SDK MentionGatePort adapter.
|
|
3057
|
-
*
|
|
3058
|
-
* Layer 1 (ignoreOtherMentions) is QQ-specific and handled here.
|
|
3059
|
-
* Layers 2+3 delegate to the SDK's `resolveInboundMentionDecision`.
|
|
3060
|
-
*/
|
|
3061
2667
|
function resolveGateWithPort(params) {
|
|
3062
2668
|
if (params.ignoreOtherMentions && params.hasAnyMention && !params.wasMentioned && !params.implicitMention) return {
|
|
3063
2669
|
action: "drop_other_mention",
|
|
@@ -3095,15 +2701,6 @@ function resolveGateWithPort(params) {
|
|
|
3095
2701
|
shouldBypassMention: decision.shouldBypassMention
|
|
3096
2702
|
};
|
|
3097
2703
|
}
|
|
3098
|
-
/**
|
|
3099
|
-
* Test whether the sender is on the DM `allowFrom` list.
|
|
3100
|
-
*/
|
|
3101
|
-
function isSenderAllowedForCommands(senderId, deps) {
|
|
3102
|
-
const raw = deps.account.config?.allowFrom;
|
|
3103
|
-
if (!Array.isArray(raw) || raw.length === 0) return true;
|
|
3104
|
-
const normalized = normalizeQQBotAllowFrom(raw);
|
|
3105
|
-
return createQQBotSenderMatcher(senderId)(normalized);
|
|
3106
|
-
}
|
|
3107
2704
|
function recordGroupHistory(params) {
|
|
3108
2705
|
const { historyMap, groupOpenid, historyLimit, event, userContent, historyPort, localPaths } = params;
|
|
3109
2706
|
if (!historyMap || historyLimit <= 0) return;
|
|
@@ -3271,15 +2868,9 @@ function writeRefIndex(params) {
|
|
|
3271
2868
|
}
|
|
3272
2869
|
//#endregion
|
|
3273
2870
|
//#region extensions/qqbot/src/engine/gateway/inbound-pipeline.ts
|
|
3274
|
-
/**
|
|
3275
|
-
* Process a raw queued message through the full inbound pipeline.
|
|
3276
|
-
*
|
|
3277
|
-
* Returns an {@link InboundContext} with `blocked` / `skipped` set when
|
|
3278
|
-
* the message should not reach the AI dispatcher.
|
|
3279
|
-
*/
|
|
3280
2871
|
async function buildInboundContext(event, deps) {
|
|
3281
2872
|
const { account, log } = deps;
|
|
3282
|
-
const accessResult = runAccessStage(event, deps);
|
|
2873
|
+
const accessResult = await runAccessStage(event, deps);
|
|
3283
2874
|
if (accessResult.kind === "block") return accessResult.context;
|
|
3284
2875
|
const { isGroupChat, peerId, qualifiedTarget, fromAddress, route, access } = accessResult;
|
|
3285
2876
|
const typingPromise = deps.startTyping(event);
|
|
@@ -3311,7 +2902,8 @@ async function buildInboundContext(event, deps) {
|
|
|
3311
2902
|
agentId: route.agentId,
|
|
3312
2903
|
sessionKey: route.sessionKey,
|
|
3313
2904
|
userContent,
|
|
3314
|
-
processedAttachments: processed
|
|
2905
|
+
processedAttachments: processed,
|
|
2906
|
+
access
|
|
3315
2907
|
});
|
|
3316
2908
|
if (gateOutcome.kind === "skip") {
|
|
3317
2909
|
typingResult.keepAlive?.stop();
|
|
@@ -3346,26 +2938,6 @@ async function buildInboundContext(event, deps) {
|
|
|
3346
2938
|
uniqueVoiceUrls: media.uniqueVoiceUrls,
|
|
3347
2939
|
uniqueVoiceAsrReferTexts: media.uniqueVoiceAsrReferTexts
|
|
3348
2940
|
});
|
|
3349
|
-
const userMessage = buildUserMessage({
|
|
3350
|
-
event,
|
|
3351
|
-
userContent,
|
|
3352
|
-
quotePart,
|
|
3353
|
-
isGroupChat,
|
|
3354
|
-
groupInfo
|
|
3355
|
-
});
|
|
3356
|
-
const agentBody = buildAgentBody({
|
|
3357
|
-
event,
|
|
3358
|
-
userContent,
|
|
3359
|
-
userMessage,
|
|
3360
|
-
dynamicCtx,
|
|
3361
|
-
isGroupChat,
|
|
3362
|
-
groupInfo,
|
|
3363
|
-
deps
|
|
3364
|
-
});
|
|
3365
|
-
const systemPrompts = [];
|
|
3366
|
-
if (account.systemPrompt) systemPrompts.push(account.systemPrompt);
|
|
3367
|
-
const groupSystemPrompt = buildGroupSystemPrompt(systemPrompts.length > 0 ? systemPrompts.join("\n") : "", groupInfo);
|
|
3368
|
-
const commandAuthorized = resolveCommandAuthorized(access);
|
|
3369
2941
|
return {
|
|
3370
2942
|
event,
|
|
3371
2943
|
route,
|
|
@@ -3373,20 +2945,26 @@ async function buildInboundContext(event, deps) {
|
|
|
3373
2945
|
peerId,
|
|
3374
2946
|
qualifiedTarget,
|
|
3375
2947
|
fromAddress,
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
2948
|
+
agentBody: buildAgentBody({
|
|
2949
|
+
event,
|
|
2950
|
+
userContent,
|
|
2951
|
+
userMessage: buildUserMessage({
|
|
2952
|
+
event,
|
|
2953
|
+
userContent,
|
|
2954
|
+
quotePart,
|
|
2955
|
+
isGroupChat,
|
|
2956
|
+
groupInfo
|
|
2957
|
+
}),
|
|
2958
|
+
dynamicCtx,
|
|
2959
|
+
isGroupChat,
|
|
2960
|
+
groupInfo,
|
|
2961
|
+
deps
|
|
2962
|
+
}),
|
|
3382
2963
|
body,
|
|
3383
|
-
|
|
3384
|
-
groupSystemPrompt,
|
|
3385
|
-
attachments: processed,
|
|
2964
|
+
groupSystemPrompt: buildGroupSystemPrompt(account.systemPrompt ?? "", groupInfo),
|
|
3386
2965
|
localMediaPaths: media.localMediaPaths,
|
|
3387
2966
|
localMediaTypes: media.localMediaTypes,
|
|
3388
2967
|
remoteMediaUrls: media.remoteMediaUrls,
|
|
3389
|
-
remoteMediaTypes: media.remoteMediaTypes,
|
|
3390
2968
|
uniqueVoicePaths: media.uniqueVoicePaths,
|
|
3391
2969
|
uniqueVoiceUrls: media.uniqueVoiceUrls,
|
|
3392
2970
|
uniqueVoiceAsrReferTexts: media.uniqueVoiceAsrReferTexts,
|
|
@@ -3394,19 +2972,15 @@ async function buildInboundContext(event, deps) {
|
|
|
3394
2972
|
hasAsrReferFallback: media.hasAsrReferFallback,
|
|
3395
2973
|
voiceTranscriptSources: media.voiceTranscriptSources,
|
|
3396
2974
|
replyTo,
|
|
3397
|
-
commandAuthorized,
|
|
2975
|
+
commandAuthorized: access.commandAccess.authorized,
|
|
3398
2976
|
group: groupInfo,
|
|
3399
2977
|
blocked: false,
|
|
3400
2978
|
skipped: false,
|
|
3401
|
-
accessDecision: access.decision,
|
|
2979
|
+
accessDecision: access.senderAccess.decision,
|
|
3402
2980
|
typing: { keepAlive: typingResult.keepAlive },
|
|
3403
2981
|
inputNotifyRefIdx: typingResult.refIdx
|
|
3404
2982
|
};
|
|
3405
2983
|
}
|
|
3406
|
-
/**
|
|
3407
|
-
* Clear a group's pending history buffer. Exposed so the gateway can
|
|
3408
|
-
* call it in its `finally` block after a reply attempt.
|
|
3409
|
-
*/
|
|
3410
2984
|
function clearGroupPendingHistory(params) {
|
|
3411
2985
|
if (!params.historyMap || !params.groupOpenid) return;
|
|
3412
2986
|
params.historyPort.clearPendingHistory({
|
|
@@ -3416,6 +2990,20 @@ function clearGroupPendingHistory(params) {
|
|
|
3416
2990
|
});
|
|
3417
2991
|
}
|
|
3418
2992
|
//#endregion
|
|
2993
|
+
//#region extensions/qqbot/src/engine/access/resolve-policy.ts
|
|
2994
|
+
function hasRealRestriction(list) {
|
|
2995
|
+
if (!list || list.length === 0) return false;
|
|
2996
|
+
return !list.every((entry) => String(entry).trim() === "*");
|
|
2997
|
+
}
|
|
2998
|
+
function resolveQQBotEffectivePolicies(input) {
|
|
2999
|
+
const allowFromRestricted = hasRealRestriction(input.allowFrom);
|
|
3000
|
+
const groupAllowFromRestricted = hasRealRestriction(input.groupAllowFrom);
|
|
3001
|
+
return {
|
|
3002
|
+
dmPolicy: input.dmPolicy ?? (allowFromRestricted ? "allowlist" : "open"),
|
|
3003
|
+
groupPolicy: input.groupPolicy ?? (groupAllowFromRestricted || allowFromRestricted ? "allowlist" : "open")
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
//#endregion
|
|
3419
3007
|
//#region extensions/qqbot/src/engine/gateway/interaction-handler.ts
|
|
3420
3008
|
/**
|
|
3421
3009
|
* INTERACTION_CREATE event handler.
|
|
@@ -5942,7 +5530,7 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5942
5530
|
};
|
|
5943
5531
|
const sendWithRetry = (sendFn) => sendWithTokenRetry(account.appId, account.clientSecret, sendFn, log, account.accountId);
|
|
5944
5532
|
const sendErrorMessage = (errorText) => sendErrorToTarget(replyCtx, errorText);
|
|
5945
|
-
const ctxPayload = buildCtxPayload(inbound, runtime);
|
|
5533
|
+
const ctxPayload = buildCtxPayload(inbound, runtime, cfg);
|
|
5946
5534
|
let hasResponse = false;
|
|
5947
5535
|
let hasBlockResponse = false;
|
|
5948
5536
|
let toolDeliverCount = 0;
|
|
@@ -6226,8 +5814,14 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
6226
5814
|
}
|
|
6227
5815
|
}
|
|
6228
5816
|
}
|
|
6229
|
-
function
|
|
5817
|
+
function resolveCommandSource(inbound, runtime, cfg) {
|
|
5818
|
+
const commandBody = inbound.event.content;
|
|
5819
|
+
if (!runtime.channel.commands?.isControlCommandMessage?.(commandBody, cfg)) return;
|
|
5820
|
+
return "text";
|
|
5821
|
+
}
|
|
5822
|
+
function buildCtxPayload(inbound, runtime, cfg) {
|
|
6230
5823
|
const { event } = inbound;
|
|
5824
|
+
const commandSource = resolveCommandSource(inbound, runtime, cfg);
|
|
6231
5825
|
return runtime.channel.reply.finalizeInboundContext({
|
|
6232
5826
|
Body: inbound.body,
|
|
6233
5827
|
BodyForAgent: inbound.agentBody,
|
|
@@ -6257,6 +5851,7 @@ function buildCtxPayload(inbound, runtime) {
|
|
|
6257
5851
|
QQVoiceAsrReferTexts: inbound.uniqueVoiceAsrReferTexts,
|
|
6258
5852
|
QQVoiceInputStrategy: "prefer_audio_stt_then_asr_fallback",
|
|
6259
5853
|
CommandAuthorized: inbound.commandAuthorized,
|
|
5854
|
+
...commandSource ? { CommandSource: commandSource } : {},
|
|
6260
5855
|
...inbound.voiceMediaTypes.length > 0 ? {
|
|
6261
5856
|
MediaTypes: inbound.voiceMediaTypes,
|
|
6262
5857
|
MediaType: inbound.voiceMediaTypes[0]
|
|
@@ -6337,22 +5932,6 @@ var TypingKeepAlive = class {
|
|
|
6337
5932
|
};
|
|
6338
5933
|
//#endregion
|
|
6339
5934
|
//#region extensions/qqbot/src/engine/gateway/gateway.ts
|
|
6340
|
-
/**
|
|
6341
|
-
* Core gateway entry point — thin shell that wires together:
|
|
6342
|
-
*
|
|
6343
|
-
* - GatewayConnection: WebSocket lifecycle, heartbeat, reconnect
|
|
6344
|
-
* - buildInboundContext: content building, attachments, quote resolution
|
|
6345
|
-
* - dispatchOutbound: AI dispatch, deliver callbacks, timeouts
|
|
6346
|
-
*
|
|
6347
|
-
* The only responsibilities of this file are:
|
|
6348
|
-
* 1. Initialize adapters from EngineAdapters
|
|
6349
|
-
* 2. Initialize API config + refIdx cache hook
|
|
6350
|
-
* 3. Create the message handler (inbound → outbound pipeline)
|
|
6351
|
-
* 4. Start GatewayConnection
|
|
6352
|
-
*/
|
|
6353
|
-
/**
|
|
6354
|
-
* Start the Gateway WebSocket connection with automatic reconnect support.
|
|
6355
|
-
*/
|
|
6356
5935
|
async function startGateway$1(ctx) {
|
|
6357
5936
|
const { account, log, runtime, adapters } = ctx;
|
|
6358
5937
|
setOutboundAudioPort(adapters.outboundAudio);
|
|
@@ -6475,6 +6054,7 @@ async function startGateway$1(ctx) {
|
|
|
6475
6054
|
cfg: ctx.cfg,
|
|
6476
6055
|
log,
|
|
6477
6056
|
runtime,
|
|
6057
|
+
adapters,
|
|
6478
6058
|
onReady: ctx.onReady,
|
|
6479
6059
|
onResumed: ctx.onResumed,
|
|
6480
6060
|
onError: ctx.onError,
|
|
@@ -6612,24 +6192,14 @@ function readQQBotVersionFromManifest(manifestPath) {
|
|
|
6612
6192
|
}
|
|
6613
6193
|
//#endregion
|
|
6614
6194
|
//#region extensions/qqbot/src/bridge/sdk-adapter.ts
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
*
|
|
6619
|
-
|
|
6620
|
-
* `openclaw/plugin-sdk/*`. The engine layer stays zero-SDK-dependency;
|
|
6621
|
-
* only the bridge layer couples to the framework.
|
|
6622
|
-
*/
|
|
6195
|
+
const qqbotIngressIdentity = defineStableChannelIngressIdentity({
|
|
6196
|
+
key: "sender-id",
|
|
6197
|
+
normalize: normalizeQQBotSenderId,
|
|
6198
|
+
isWildcardEntry: (entry) => normalizeQQBotSenderId(entry) === "*"
|
|
6199
|
+
});
|
|
6623
6200
|
function asSdkMap(map) {
|
|
6624
6201
|
return map;
|
|
6625
6202
|
}
|
|
6626
|
-
/**
|
|
6627
|
-
* History adapter backed by SDK `reply-history`.
|
|
6628
|
-
*
|
|
6629
|
-
* Delegates record/build/clear to the SDK's shared implementation so
|
|
6630
|
-
* the engine benefits from SDK improvements (e.g. future visibility
|
|
6631
|
-
* filtering) without code duplication.
|
|
6632
|
-
*/
|
|
6633
6203
|
function createSdkHistoryAdapter() {
|
|
6634
6204
|
return {
|
|
6635
6205
|
recordPendingHistoryEntry(params) {
|
|
@@ -6659,38 +6229,69 @@ function createSdkHistoryAdapter() {
|
|
|
6659
6229
|
}
|
|
6660
6230
|
};
|
|
6661
6231
|
}
|
|
6662
|
-
/**
|
|
6663
|
-
* MentionGate adapter backed by SDK `channel-mention-gating`.
|
|
6664
|
-
*
|
|
6665
|
-
* Maps the engine's mention facts/policy to the SDK's
|
|
6666
|
-
* `resolveInboundMentionDecision` call, normalizing the implicit
|
|
6667
|
-
* mention boolean into the SDK's typed `ImplicitMentionKind[]`.
|
|
6668
|
-
*/
|
|
6669
6232
|
function createSdkMentionGateAdapter() {
|
|
6670
6233
|
return { resolveInboundMentionDecision(params) {
|
|
6671
|
-
|
|
6672
|
-
facts: {
|
|
6673
|
-
canDetectMention: params.facts.canDetectMention,
|
|
6674
|
-
wasMentioned: params.facts.wasMentioned,
|
|
6675
|
-
hasAnyMention: params.facts.hasAnyMention,
|
|
6676
|
-
implicitMentionKinds: params.facts.implicitMentionKinds ?? implicitMentionKindWhen("reply_to_bot", false)
|
|
6677
|
-
},
|
|
6678
|
-
policy: {
|
|
6679
|
-
isGroup: params.policy.isGroup,
|
|
6680
|
-
requireMention: params.policy.requireMention,
|
|
6681
|
-
allowTextCommands: params.policy.allowTextCommands,
|
|
6682
|
-
hasControlCommand: params.policy.hasControlCommand,
|
|
6683
|
-
commandAuthorized: params.policy.commandAuthorized
|
|
6684
|
-
}
|
|
6685
|
-
});
|
|
6686
|
-
return {
|
|
6687
|
-
effectiveWasMentioned: result.effectiveWasMentioned,
|
|
6688
|
-
shouldSkip: result.shouldSkip,
|
|
6689
|
-
shouldBypassMention: result.shouldBypassMention,
|
|
6690
|
-
implicitMention: result.implicitMention
|
|
6691
|
-
};
|
|
6234
|
+
return resolveInboundMentionDecision(params);
|
|
6692
6235
|
} };
|
|
6693
6236
|
}
|
|
6237
|
+
function createSdkAccessAdapter() {
|
|
6238
|
+
return {
|
|
6239
|
+
async resolveInboundAccess(input) {
|
|
6240
|
+
const { dmPolicy, groupPolicy } = resolveQQBotEffectivePolicies(input);
|
|
6241
|
+
const rawGroupAllowFrom = input.groupAllowFrom && input.groupAllowFrom.length > 0 ? input.groupAllowFrom : input.allowFrom ?? [];
|
|
6242
|
+
const normalizedAllowFrom = normalizeQQBotAllowFrom(input.allowFrom);
|
|
6243
|
+
const dmAllowFromForIngress = dmPolicy === "open" && normalizedAllowFrom.length === 0 ? ["*"] : input.allowFrom ?? [];
|
|
6244
|
+
const commandOwnerAllowFrom = input.isGroup ? [] : input.allowFrom && input.allowFrom.length > 0 ? input.allowFrom : ["*"];
|
|
6245
|
+
return await createChannelIngressResolver({
|
|
6246
|
+
channelId: "qqbot",
|
|
6247
|
+
accountId: input.accountId,
|
|
6248
|
+
identity: qqbotIngressIdentity,
|
|
6249
|
+
cfg: input.cfg
|
|
6250
|
+
}).message({
|
|
6251
|
+
subject: { stableId: input.senderId },
|
|
6252
|
+
conversation: {
|
|
6253
|
+
kind: input.isGroup ? "group" : "direct",
|
|
6254
|
+
id: input.conversationId
|
|
6255
|
+
},
|
|
6256
|
+
event: { mayPair: false },
|
|
6257
|
+
dmPolicy,
|
|
6258
|
+
groupPolicy,
|
|
6259
|
+
policy: { groupAllowFromFallbackToAllowFrom: false },
|
|
6260
|
+
allowFrom: dmAllowFromForIngress,
|
|
6261
|
+
groupAllowFrom: rawGroupAllowFrom,
|
|
6262
|
+
command: { commandOwnerAllowFrom }
|
|
6263
|
+
});
|
|
6264
|
+
},
|
|
6265
|
+
async resolveSlashCommandAuthorization(input) {
|
|
6266
|
+
return await resolveQQBotSlashCommandAuthorized(input);
|
|
6267
|
+
}
|
|
6268
|
+
};
|
|
6269
|
+
}
|
|
6270
|
+
async function resolveQQBotSlashCommandAuthorized(params) {
|
|
6271
|
+
const explicitAllowFrom = normalizeQQBotAllowFrom(params.commandsAllowFrom ?? (params.isGroup && params.groupAllowFrom && params.groupAllowFrom.length > 0 ? params.groupAllowFrom : params.allowFrom)).filter((entry) => entry !== "*");
|
|
6272
|
+
if (explicitAllowFrom.length === 0) return false;
|
|
6273
|
+
return (await createChannelIngressResolver({
|
|
6274
|
+
channelId: "qqbot",
|
|
6275
|
+
accountId: params.accountId,
|
|
6276
|
+
identity: qqbotIngressIdentity,
|
|
6277
|
+
cfg: params.cfg
|
|
6278
|
+
}).message({
|
|
6279
|
+
subject: { stableId: params.senderId },
|
|
6280
|
+
conversation: {
|
|
6281
|
+
kind: params.isGroup ? "group" : "direct",
|
|
6282
|
+
id: params.conversationId
|
|
6283
|
+
},
|
|
6284
|
+
event: {
|
|
6285
|
+
kind: "slash-command",
|
|
6286
|
+
authMode: "none",
|
|
6287
|
+
mayPair: false
|
|
6288
|
+
},
|
|
6289
|
+
dmPolicy: "allowlist",
|
|
6290
|
+
groupPolicy: "open",
|
|
6291
|
+
allowFrom: explicitAllowFrom,
|
|
6292
|
+
command: { modeWhenAccessGroupsOff: "configured" }
|
|
6293
|
+
})).commandAccess.authorized;
|
|
6294
|
+
}
|
|
6694
6295
|
//#endregion
|
|
6695
6296
|
//#region extensions/qqbot/src/bridge/gateway.ts
|
|
6696
6297
|
/**
|
|
@@ -6716,6 +6317,7 @@ function createEngineAdapters(_runtime) {
|
|
|
6716
6317
|
return {
|
|
6717
6318
|
history: createSdkHistoryAdapter(),
|
|
6718
6319
|
mentionGate: createSdkMentionGateAdapter(),
|
|
6320
|
+
access: createSdkAccessAdapter(),
|
|
6719
6321
|
audioConvert: {
|
|
6720
6322
|
convertSilkToWav,
|
|
6721
6323
|
isVoiceAttachment,
|