@openclaw/qqbot 2026.5.2-beta.2 → 2026.5.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +646 -0
- package/dist/approval-cg0SVahb.js +94 -0
- package/dist/channel-N6Y_Rcjp.js +551 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.setup-ByCRAe5H.js +25 -0
- package/dist/config-D6545NkC.js +94 -0
- package/dist/config-schema-DFcjQw73.js +309 -0
- package/dist/exec-approvals-COUsM6wZ.js +138 -0
- package/dist/gateway-CmSUJKSt.js +6726 -0
- package/dist/handler-runtime-Bqm6N0WG.js +120 -0
- package/dist/index.js +29 -0
- package/dist/narrowing-BoieBTIU.js +25 -0
- package/dist/outbound-BJfhwrPg.js +1357 -0
- package/dist/request-context-DXtpwWui.js +921 -0
- package/dist/resolve-D_06fV6-.js +284 -0
- package/dist/runtime-BJAS3eXW.js +18 -0
- package/dist/runtime-api.js +2 -0
- package/dist/secret-contract-api.js +56 -0
- package/dist/sender-p-B14eLG.js +1987 -0
- package/dist/setup-entry.js +15 -0
- package/dist/setup-plugin-api.js +2 -0
- package/dist/string-normalize-Ci6NM5DE.js +98 -0
- package/dist/target-parser-Y0prnrXD.js +245 -0
- package/package.json +15 -6
- package/api.ts +0 -56
- package/channel-plugin-api.ts +0 -1
- package/index.ts +0 -29
- package/runtime-api.ts +0 -9
- package/setup-entry.ts +0 -9
- package/setup-plugin-api.ts +0 -3
- package/src/bridge/approval/capability.ts +0 -237
- package/src/bridge/approval/handler-runtime.ts +0 -204
- package/src/bridge/bootstrap.ts +0 -135
- package/src/bridge/channel-entry.ts +0 -18
- package/src/bridge/commands/framework-context-adapter.ts +0 -60
- package/src/bridge/commands/framework-registration.ts +0 -47
- package/src/bridge/commands/from-parser.test.ts +0 -86
- package/src/bridge/commands/from-parser.ts +0 -60
- package/src/bridge/commands/result-dispatcher.ts +0 -76
- package/src/bridge/config-shared.ts +0 -132
- package/src/bridge/config.ts +0 -111
- package/src/bridge/gateway.ts +0 -174
- package/src/bridge/logger.ts +0 -31
- package/src/bridge/narrowing.ts +0 -31
- package/src/bridge/plugin-version.test.ts +0 -146
- package/src/bridge/plugin-version.ts +0 -102
- package/src/bridge/runtime.ts +0 -25
- package/src/bridge/sdk-adapter.ts +0 -131
- package/src/bridge/setup/finalize.ts +0 -144
- package/src/bridge/setup/surface.ts +0 -34
- package/src/bridge/tools/channel.ts +0 -58
- package/src/bridge/tools/index.ts +0 -15
- package/src/bridge/tools/remind.test.ts +0 -124
- package/src/bridge/tools/remind.ts +0 -91
- package/src/channel.setup.ts +0 -33
- package/src/channel.ts +0 -288
- package/src/command-auth.test.ts +0 -62
- package/src/config-schema.ts +0 -84
- package/src/config.test.ts +0 -364
- package/src/engine/access/access-control.test.ts +0 -198
- package/src/engine/access/access-control.ts +0 -226
- package/src/engine/access/index.ts +0 -16
- package/src/engine/access/resolve-policy.test.ts +0 -59
- package/src/engine/access/resolve-policy.ts +0 -57
- package/src/engine/access/sender-match.test.ts +0 -60
- package/src/engine/access/sender-match.ts +0 -55
- package/src/engine/access/types.ts +0 -53
- package/src/engine/adapter/audio.port.ts +0 -27
- package/src/engine/adapter/commands.port.ts +0 -22
- package/src/engine/adapter/history.port.ts +0 -52
- package/src/engine/adapter/index.ts +0 -139
- package/src/engine/adapter/mention-gate.port.ts +0 -50
- package/src/engine/adapter/types.ts +0 -38
- package/src/engine/api/api-client.ts +0 -212
- package/src/engine/api/media-chunked.test.ts +0 -336
- package/src/engine/api/media-chunked.ts +0 -622
- package/src/engine/api/media.ts +0 -218
- package/src/engine/api/messages.ts +0 -293
- package/src/engine/api/retry.ts +0 -217
- package/src/engine/api/routes.ts +0 -95
- package/src/engine/api/token.ts +0 -271
- package/src/engine/approval/index.test.ts +0 -22
- package/src/engine/approval/index.ts +0 -224
- package/src/engine/commands/builtin/log-helpers.ts +0 -319
- package/src/engine/commands/builtin/register-all.ts +0 -17
- package/src/engine/commands/builtin/register-approve.ts +0 -201
- package/src/engine/commands/builtin/register-basic.ts +0 -95
- package/src/engine/commands/builtin/register-clear-storage.ts +0 -187
- package/src/engine/commands/builtin/register-logs.ts +0 -20
- package/src/engine/commands/builtin/register-streaming.ts +0 -137
- package/src/engine/commands/builtin/state.ts +0 -31
- package/src/engine/commands/slash-command-auth.ts +0 -48
- package/src/engine/commands/slash-command-handler.ts +0 -146
- package/src/engine/commands/slash-commands-impl.test.ts +0 -8
- package/src/engine/commands/slash-commands-impl.ts +0 -61
- package/src/engine/commands/slash-commands.ts +0 -199
- package/src/engine/config/credential-backup.test.ts +0 -88
- package/src/engine/config/credential-backup.ts +0 -107
- package/src/engine/config/credentials.ts +0 -76
- package/src/engine/config/group.test.ts +0 -234
- package/src/engine/config/group.ts +0 -299
- package/src/engine/config/resolve.test.ts +0 -152
- package/src/engine/config/resolve.ts +0 -283
- package/src/engine/config/setup-logic.ts +0 -84
- package/src/engine/engine-import-boundary.test.ts +0 -73
- package/src/engine/gateway/codec.ts +0 -47
- package/src/engine/gateway/constants.ts +0 -117
- package/src/engine/gateway/event-dispatcher.ts +0 -177
- package/src/engine/gateway/gateway-connection.ts +0 -371
- package/src/engine/gateway/gateway.ts +0 -291
- package/src/engine/gateway/inbound-attachments.test.ts +0 -126
- package/src/engine/gateway/inbound-attachments.ts +0 -360
- package/src/engine/gateway/inbound-context.ts +0 -195
- package/src/engine/gateway/inbound-pipeline.self-echo.test.ts +0 -218
- package/src/engine/gateway/inbound-pipeline.ts +0 -235
- package/src/engine/gateway/interaction-handler.ts +0 -220
- package/src/engine/gateway/message-queue.test.ts +0 -282
- package/src/engine/gateway/message-queue.ts +0 -499
- package/src/engine/gateway/outbound-dispatch.test.ts +0 -231
- package/src/engine/gateway/outbound-dispatch.ts +0 -575
- package/src/engine/gateway/reconnect.ts +0 -199
- package/src/engine/gateway/stages/access-stage.ts +0 -132
- package/src/engine/gateway/stages/assembly-stage.ts +0 -156
- package/src/engine/gateway/stages/content-stage.test.ts +0 -77
- package/src/engine/gateway/stages/content-stage.ts +0 -77
- package/src/engine/gateway/stages/envelope-stage.test.ts +0 -152
- package/src/engine/gateway/stages/envelope-stage.ts +0 -144
- package/src/engine/gateway/stages/group-gate-stage.ts +0 -292
- package/src/engine/gateway/stages/index.ts +0 -18
- package/src/engine/gateway/stages/quote-stage.ts +0 -113
- package/src/engine/gateway/stages/refidx-stage.ts +0 -62
- package/src/engine/gateway/stages/stub-contexts.ts +0 -116
- package/src/engine/gateway/types.ts +0 -264
- package/src/engine/gateway/typing-keepalive.ts +0 -79
- package/src/engine/group/activation.test.ts +0 -114
- package/src/engine/group/activation.ts +0 -147
- package/src/engine/group/history.test.ts +0 -314
- package/src/engine/group/history.ts +0 -321
- package/src/engine/group/mention.test.ts +0 -141
- package/src/engine/group/mention.ts +0 -197
- package/src/engine/group/message-gating.test.ts +0 -188
- package/src/engine/group/message-gating.ts +0 -216
- package/src/engine/messaging/decode-media-path.ts +0 -82
- package/src/engine/messaging/media-source.ts +0 -215
- package/src/engine/messaging/media-type-detect.ts +0 -37
- package/src/engine/messaging/outbound-audio-port.ts +0 -38
- package/src/engine/messaging/outbound-deliver.ts +0 -810
- package/src/engine/messaging/outbound-media-send.ts +0 -702
- package/src/engine/messaging/outbound-reply.ts +0 -27
- package/src/engine/messaging/outbound-result-helpers.ts +0 -54
- package/src/engine/messaging/outbound-types.ts +0 -45
- package/src/engine/messaging/outbound.ts +0 -485
- package/src/engine/messaging/reply-dispatcher.ts +0 -597
- package/src/engine/messaging/reply-limiter.ts +0 -164
- package/src/engine/messaging/sender.ts +0 -729
- package/src/engine/messaging/streaming-c2c.ts +0 -1192
- package/src/engine/messaging/streaming-media-send.ts +0 -544
- package/src/engine/messaging/target-parser.ts +0 -104
- package/src/engine/ref/format-message-ref.ts +0 -142
- package/src/engine/ref/format-ref-entry.test.ts +0 -60
- package/src/engine/ref/format-ref-entry.ts +0 -27
- package/src/engine/ref/store.ts +0 -208
- package/src/engine/ref/types.ts +0 -27
- package/src/engine/session/known-users.ts +0 -137
- package/src/engine/session/session-store.ts +0 -204
- package/src/engine/tools/channel-api.ts +0 -244
- package/src/engine/tools/remind-logic.test.ts +0 -280
- package/src/engine/tools/remind-logic.ts +0 -377
- package/src/engine/types.ts +0 -313
- package/src/engine/utils/attachment-tags.test.ts +0 -186
- package/src/engine/utils/attachment-tags.ts +0 -174
- package/src/engine/utils/audio.test.ts +0 -250
- package/src/engine/utils/audio.ts +0 -585
- package/src/engine/utils/data-paths.ts +0 -38
- package/src/engine/utils/diagnostics.ts +0 -109
- package/src/engine/utils/file-utils.test.ts +0 -72
- package/src/engine/utils/file-utils.ts +0 -225
- package/src/engine/utils/format.test.ts +0 -68
- package/src/engine/utils/format.ts +0 -70
- package/src/engine/utils/image-size.test.ts +0 -158
- package/src/engine/utils/image-size.ts +0 -249
- package/src/engine/utils/log.test.ts +0 -28
- package/src/engine/utils/log.ts +0 -61
- package/src/engine/utils/media-tags.test.ts +0 -32
- package/src/engine/utils/media-tags.ts +0 -177
- package/src/engine/utils/payload.test.ts +0 -68
- package/src/engine/utils/payload.ts +0 -145
- package/src/engine/utils/platform-storage-laziness.test.ts +0 -63
- package/src/engine/utils/platform.test.ts +0 -148
- package/src/engine/utils/platform.ts +0 -343
- package/src/engine/utils/request-context.ts +0 -60
- package/src/engine/utils/string-normalize.ts +0 -91
- package/src/engine/utils/stt.test.ts +0 -104
- package/src/engine/utils/stt.ts +0 -100
- package/src/engine/utils/text-parsing.test.ts +0 -29
- package/src/engine/utils/text-parsing.ts +0 -155
- package/src/engine/utils/upload-cache.ts +0 -96
- package/src/engine/utils/voice-text.ts +0 -15
- package/src/exec-approvals.ts +0 -218
- package/src/manifest-schema.test.ts +0 -56
- package/src/qqbot-test-support.ts +0 -29
- package/src/types.ts +0 -210
- package/tsconfig.json +0 -16
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QQBot credential management (pure logic layer).
|
|
3
|
-
* QQBot 凭证管理(纯逻辑层)。
|
|
4
|
-
*
|
|
5
|
-
* Credential clearing and field-level cleanup for logout and setup
|
|
6
|
-
* flows. All functions operate on plain objects (Record<string, unknown>)
|
|
7
|
-
* and stay framework-agnostic.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { asOptionalObjectRecord as asRecord } from "../utils/string-normalize.js";
|
|
11
|
-
import { DEFAULT_ACCOUNT_ID } from "./resolve.js";
|
|
12
|
-
|
|
13
|
-
// ---- Logout: clear all credential fields for an account ----
|
|
14
|
-
|
|
15
|
-
interface ClearCredentialsResult {
|
|
16
|
-
nextCfg: Record<string, unknown>;
|
|
17
|
-
cleared: boolean;
|
|
18
|
-
changed: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Remove clientSecret / clientSecretFile from a QQBot account config.
|
|
23
|
-
*
|
|
24
|
-
* Returns a shallow-cloned config with credentials removed, plus flags
|
|
25
|
-
* indicating whether anything actually changed.
|
|
26
|
-
*/
|
|
27
|
-
export function clearAccountCredentials(
|
|
28
|
-
cfg: Record<string, unknown>,
|
|
29
|
-
accountId: string,
|
|
30
|
-
): ClearCredentialsResult {
|
|
31
|
-
const nextCfg = { ...cfg };
|
|
32
|
-
const channels = asRecord(cfg.channels);
|
|
33
|
-
const nextQQBot = channels?.qqbot ? { ...asRecord(channels.qqbot) } : undefined;
|
|
34
|
-
let cleared = false;
|
|
35
|
-
let changed = false;
|
|
36
|
-
|
|
37
|
-
if (nextQQBot) {
|
|
38
|
-
const qqbot = nextQQBot as Record<string, unknown>;
|
|
39
|
-
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
40
|
-
if (qqbot.clientSecret) {
|
|
41
|
-
delete qqbot.clientSecret;
|
|
42
|
-
cleared = true;
|
|
43
|
-
changed = true;
|
|
44
|
-
}
|
|
45
|
-
if (qqbot.clientSecretFile) {
|
|
46
|
-
delete qqbot.clientSecretFile;
|
|
47
|
-
cleared = true;
|
|
48
|
-
changed = true;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const accounts = qqbot.accounts as Record<string, Record<string, unknown>> | undefined;
|
|
52
|
-
if (accounts && accountId in accounts) {
|
|
53
|
-
const entry = accounts[accountId] as Record<string, unknown> | undefined;
|
|
54
|
-
if (entry && "clientSecret" in entry) {
|
|
55
|
-
delete entry.clientSecret;
|
|
56
|
-
cleared = true;
|
|
57
|
-
changed = true;
|
|
58
|
-
}
|
|
59
|
-
if (entry && "clientSecretFile" in entry) {
|
|
60
|
-
delete entry.clientSecretFile;
|
|
61
|
-
cleared = true;
|
|
62
|
-
changed = true;
|
|
63
|
-
}
|
|
64
|
-
if (entry && Object.keys(entry).length === 0) {
|
|
65
|
-
delete accounts[accountId];
|
|
66
|
-
changed = true;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (changed && nextQQBot) {
|
|
72
|
-
nextCfg.channels = { ...channels, qqbot: nextQQBot };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return { nextCfg, cleared, changed };
|
|
76
|
-
}
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_GROUP_HISTORY_LIMIT,
|
|
4
|
-
resolveGroupConfig,
|
|
5
|
-
resolveGroupName,
|
|
6
|
-
resolveGroupPrompt,
|
|
7
|
-
resolveGroupSettings,
|
|
8
|
-
resolveGroupToolPolicy,
|
|
9
|
-
resolveHistoryLimit,
|
|
10
|
-
resolveIgnoreOtherMentions,
|
|
11
|
-
resolveMentionPatterns,
|
|
12
|
-
resolveRequireMention,
|
|
13
|
-
} from "./group.js";
|
|
14
|
-
|
|
15
|
-
describe("engine/config/group", () => {
|
|
16
|
-
describe("resolveGroupConfig precedence", () => {
|
|
17
|
-
it("returns defaults when no config exists", () => {
|
|
18
|
-
const cfg = resolveGroupConfig({}, "G1");
|
|
19
|
-
expect(cfg).toMatchObject({
|
|
20
|
-
requireMention: true,
|
|
21
|
-
ignoreOtherMentions: false,
|
|
22
|
-
toolPolicy: "restricted",
|
|
23
|
-
name: "",
|
|
24
|
-
historyLimit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
25
|
-
});
|
|
26
|
-
expect(cfg.prompt).toBeUndefined();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("falls back to wildcard when specific is missing", () => {
|
|
30
|
-
const cfg = {
|
|
31
|
-
channels: {
|
|
32
|
-
qqbot: {
|
|
33
|
-
appId: "1",
|
|
34
|
-
groups: {
|
|
35
|
-
"*": {
|
|
36
|
-
requireMention: false,
|
|
37
|
-
toolPolicy: "full",
|
|
38
|
-
historyLimit: 20,
|
|
39
|
-
name: "wild",
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
const resolved = resolveGroupConfig(cfg, "G1");
|
|
46
|
-
expect(resolved.requireMention).toBe(false);
|
|
47
|
-
expect(resolved.toolPolicy).toBe("full");
|
|
48
|
-
expect(resolved.historyLimit).toBe(20);
|
|
49
|
-
expect(resolved.name).toBe("wild");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("specific overrides wildcard and defaults", () => {
|
|
53
|
-
const cfg = {
|
|
54
|
-
channels: {
|
|
55
|
-
qqbot: {
|
|
56
|
-
appId: "1",
|
|
57
|
-
groups: {
|
|
58
|
-
"*": { requireMention: true, toolPolicy: "restricted", historyLimit: 20 },
|
|
59
|
-
GROUPA: { requireMention: false, toolPolicy: "none", historyLimit: 5, name: "A" },
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
const resolved = resolveGroupConfig(cfg, "GROUPA");
|
|
65
|
-
expect(resolved.requireMention).toBe(false);
|
|
66
|
-
expect(resolved.toolPolicy).toBe("none");
|
|
67
|
-
expect(resolved.historyLimit).toBe(5);
|
|
68
|
-
expect(resolved.name).toBe("A");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("historyLimit is clamped to >= 0 and floored", () => {
|
|
72
|
-
const cfg = {
|
|
73
|
-
channels: {
|
|
74
|
-
qqbot: { appId: "1", groups: { "*": { historyLimit: -3.7 } } },
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
expect(resolveHistoryLimit(cfg, "G")).toBe(0);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("non-finite historyLimit falls back to default", () => {
|
|
81
|
-
const cfg = {
|
|
82
|
-
channels: {
|
|
83
|
-
qqbot: { appId: "1", groups: { "*": { historyLimit: "not a number" } } },
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
expect(resolveHistoryLimit(cfg, "G")).toBe(DEFAULT_GROUP_HISTORY_LIMIT);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("invalid toolPolicy values are ignored", () => {
|
|
90
|
-
const cfg = {
|
|
91
|
-
channels: {
|
|
92
|
-
qqbot: { appId: "1", groups: { "*": { toolPolicy: "invalid" } } },
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
expect(resolveGroupToolPolicy(cfg, "G")).toBe("restricted");
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe("named accounts", () => {
|
|
100
|
-
it("reads groups from the named-account scope", () => {
|
|
101
|
-
const cfg = {
|
|
102
|
-
channels: {
|
|
103
|
-
qqbot: {
|
|
104
|
-
accounts: {
|
|
105
|
-
bot2: {
|
|
106
|
-
appId: "9",
|
|
107
|
-
groups: { "*": { requireMention: false, historyLimit: 7 } },
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
expect(resolveRequireMention(cfg, "G", "bot2")).toBe(false);
|
|
114
|
-
expect(resolveHistoryLimit(cfg, "G", "bot2")).toBe(7);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe("resolveGroupName", () => {
|
|
119
|
-
it("uses the first 8 chars of openid when name is unset", () => {
|
|
120
|
-
expect(resolveGroupName({}, "ABCDEFGH1234")).toBe("ABCDEFGH");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("prefers the configured name", () => {
|
|
124
|
-
const cfg = {
|
|
125
|
-
channels: { qqbot: { appId: "1", groups: { ABCDEFGH1234: { name: "Foo" } } } },
|
|
126
|
-
};
|
|
127
|
-
expect(resolveGroupName(cfg, "ABCDEFGH1234")).toBe("Foo");
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe("resolveGroupPrompt", () => {
|
|
132
|
-
it("returns the default prompt when nothing configured", () => {
|
|
133
|
-
expect(resolveGroupPrompt({}, "G")).toContain("bot");
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("prefers specific over wildcard", () => {
|
|
137
|
-
const cfg = {
|
|
138
|
-
channels: {
|
|
139
|
-
qqbot: {
|
|
140
|
-
appId: "1",
|
|
141
|
-
groups: { "*": { prompt: "WILD" }, G1: { prompt: "SPEC" } },
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
expect(resolveGroupPrompt(cfg, "G1")).toBe("SPEC");
|
|
146
|
-
expect(resolveGroupPrompt(cfg, "G2")).toBe("WILD");
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
describe("resolveIgnoreOtherMentions", () => {
|
|
151
|
-
it("defaults to false", () => {
|
|
152
|
-
expect(resolveIgnoreOtherMentions({}, "G")).toBe(false);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("honours wildcard override", () => {
|
|
156
|
-
const cfg = {
|
|
157
|
-
channels: { qqbot: { appId: "1", groups: { "*": { ignoreOtherMentions: true } } } },
|
|
158
|
-
};
|
|
159
|
-
expect(resolveIgnoreOtherMentions(cfg, "G")).toBe(true);
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
describe("resolveMentionPatterns", () => {
|
|
164
|
-
it("returns [] when nothing configured", () => {
|
|
165
|
-
expect(resolveMentionPatterns({})).toEqual([]);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("reads global patterns", () => {
|
|
169
|
-
const cfg = { messages: { groupChat: { mentionPatterns: ["/^hey/"] } } };
|
|
170
|
-
expect(resolveMentionPatterns(cfg)).toEqual(["/^hey/"]);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("agent-level overrides global", () => {
|
|
174
|
-
const cfg = {
|
|
175
|
-
messages: { groupChat: { mentionPatterns: ["g"] } },
|
|
176
|
-
agents: {
|
|
177
|
-
list: [{ id: "main", groupChat: { mentionPatterns: ["a", "b"] } }],
|
|
178
|
-
},
|
|
179
|
-
};
|
|
180
|
-
expect(resolveMentionPatterns(cfg, "main")).toEqual(["a", "b"]);
|
|
181
|
-
expect(resolveMentionPatterns(cfg, "OTHER")).toEqual(["g"]);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("filters non-string entries", () => {
|
|
185
|
-
const cfg = { messages: { groupChat: { mentionPatterns: ["ok", 42, null] } } };
|
|
186
|
-
expect(resolveMentionPatterns(cfg)).toEqual(["ok"]);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
describe("resolveGroupSettings (aggregate)", () => {
|
|
191
|
-
it("returns merged config + name + mentionPatterns in one call", () => {
|
|
192
|
-
const cfg = {
|
|
193
|
-
channels: {
|
|
194
|
-
qqbot: {
|
|
195
|
-
appId: "1",
|
|
196
|
-
groups: {
|
|
197
|
-
G1: { requireMention: false, name: "Dev" },
|
|
198
|
-
"*": { historyLimit: 10 },
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
messages: { groupChat: { mentionPatterns: ["@bot"] } },
|
|
203
|
-
};
|
|
204
|
-
const settings = resolveGroupSettings({ cfg, groupOpenid: "G1" });
|
|
205
|
-
expect(settings.config.requireMention).toBe(false);
|
|
206
|
-
expect(settings.config.historyLimit).toBe(10);
|
|
207
|
-
expect(settings.name).toBe("Dev");
|
|
208
|
-
expect(settings.mentionPatterns).toEqual(["@bot"]);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it("falls back to the first 8 chars of the openid for name", () => {
|
|
212
|
-
const settings = resolveGroupSettings({
|
|
213
|
-
cfg: {},
|
|
214
|
-
groupOpenid: "ABCDEFGHIJKLMNOP",
|
|
215
|
-
});
|
|
216
|
-
expect(settings.name).toBe("ABCDEFGH");
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it("applies agent-level mentionPatterns over global", () => {
|
|
220
|
-
const cfg = {
|
|
221
|
-
agents: {
|
|
222
|
-
list: [{ id: "custom", groupChat: { mentionPatterns: ["@agent"] } }],
|
|
223
|
-
},
|
|
224
|
-
messages: { groupChat: { mentionPatterns: ["@global"] } },
|
|
225
|
-
};
|
|
226
|
-
const settings = resolveGroupSettings({
|
|
227
|
-
cfg,
|
|
228
|
-
groupOpenid: "G1",
|
|
229
|
-
agentId: "custom",
|
|
230
|
-
});
|
|
231
|
-
expect(settings.mentionPatterns).toEqual(["@agent"]);
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
});
|
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QQBot group configuration resolution (pure logic).
|
|
3
|
-
* QQBot 群配置解析(纯逻辑层)。
|
|
4
|
-
*
|
|
5
|
-
* Resolves per-group settings that the inbound pipeline needs to decide how
|
|
6
|
-
* to gate and contextualize group messages. Reads from a raw config object
|
|
7
|
-
* produced by the framework's config loader, with a `specific > wildcard
|
|
8
|
-
* ("*") > default` precedence chain.
|
|
9
|
-
*
|
|
10
|
-
* All functions are **pure** (no I/O, no external state) — making them
|
|
11
|
-
* portable to the standalone plugin build and trivially unit-testable.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { asOptionalObjectRecord as asRecord } from "../utils/string-normalize.js";
|
|
15
|
-
import { resolveAccountBase } from "./resolve.js";
|
|
16
|
-
|
|
17
|
-
// ============ Types ============
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Tool policy — which tool palette an agent should use in a given group.
|
|
21
|
-
*
|
|
22
|
-
* - `full`: default allow everything (no engine-side restriction).
|
|
23
|
-
* - `restricted`: engine returns an empty allowlist so the framework falls
|
|
24
|
-
* back to its built-in restricted palette.
|
|
25
|
-
* - `none`: deny all tools.
|
|
26
|
-
*/
|
|
27
|
-
type GroupToolPolicy = "full" | "restricted" | "none";
|
|
28
|
-
|
|
29
|
-
/** Per-group configuration — everything that may be overridden per group. */
|
|
30
|
-
interface GroupConfig {
|
|
31
|
-
/** Whether the bot requires @mention to respond. Defaults to true. */
|
|
32
|
-
requireMention: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* When true, group messages that @other users (but not the bot) are
|
|
35
|
-
* dropped silently without reaching the AI pipeline.
|
|
36
|
-
*/
|
|
37
|
-
ignoreOtherMentions: boolean;
|
|
38
|
-
/** Tool palette policy. Defaults to "restricted". */
|
|
39
|
-
toolPolicy: GroupToolPolicy;
|
|
40
|
-
/** Human-readable group name. Empty string if not configured. */
|
|
41
|
-
name: string;
|
|
42
|
-
/** Per-group behaviour prompt appended to the system prompt. */
|
|
43
|
-
prompt?: string;
|
|
44
|
-
/**
|
|
45
|
-
* Number of non-@ history messages buffered per group. Clamped to 0 when
|
|
46
|
-
* disabled. The default matches the standalone build's `50`.
|
|
47
|
-
*/
|
|
48
|
-
historyLimit: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ============ Defaults ============
|
|
52
|
-
|
|
53
|
-
/** Default history limit — matches the standalone build. */
|
|
54
|
-
export const DEFAULT_GROUP_HISTORY_LIMIT = 50;
|
|
55
|
-
|
|
56
|
-
/** Default group behaviour prompt. Exported so the gating stage can use
|
|
57
|
-
* the same fallback when no per-group `prompt` is configured. */
|
|
58
|
-
export const DEFAULT_GROUP_PROMPT =
|
|
59
|
-
"If the sender is a bot, respond only when they explicitly @mention you to ask a question or request assistance with a specific task; keep your replies concise and clear, avoiding the urge to race other bots to answer or engage in lengthy, unproductive exchanges. In group chats, prioritize responding to messages from human users; bots should maintain a collaborative rather than competitive dynamic to ensure the conversation remains orderly and does not result in message flooding.";
|
|
60
|
-
|
|
61
|
-
const DEFAULT_GROUP_CONFIG: Readonly<Omit<GroupConfig, "prompt">> = {
|
|
62
|
-
requireMention: true,
|
|
63
|
-
ignoreOtherMentions: false,
|
|
64
|
-
toolPolicy: "restricted",
|
|
65
|
-
name: "",
|
|
66
|
-
historyLimit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// ============ Helpers ============
|
|
70
|
-
|
|
71
|
-
/** Read a named account's raw `groups` map from an OpenClawConfig. */
|
|
72
|
-
function readGroupsMap(
|
|
73
|
-
cfg: Record<string, unknown>,
|
|
74
|
-
accountId?: string | null,
|
|
75
|
-
): Record<string, Record<string, unknown>> {
|
|
76
|
-
const account = resolveAccountBase(cfg, accountId);
|
|
77
|
-
const groups = asRecord(account.config.groups);
|
|
78
|
-
if (!groups) {
|
|
79
|
-
return {};
|
|
80
|
-
}
|
|
81
|
-
// Only keep sub-objects; skip scalars produced by user mistakes.
|
|
82
|
-
const normalized: Record<string, Record<string, unknown>> = {};
|
|
83
|
-
for (const [key, value] of Object.entries(groups)) {
|
|
84
|
-
const sub = asRecord(value);
|
|
85
|
-
if (sub) {
|
|
86
|
-
normalized[key] = sub;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return normalized;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function readBoolean(obj: Record<string, unknown>, key: string): boolean | undefined {
|
|
93
|
-
const v = obj[key];
|
|
94
|
-
return typeof v === "boolean" ? v : undefined;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function readString(obj: Record<string, unknown>, key: string): string | undefined {
|
|
98
|
-
const v = obj[key];
|
|
99
|
-
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function readToolPolicy(obj: Record<string, unknown>, key: string): GroupToolPolicy | undefined {
|
|
103
|
-
const v = obj[key];
|
|
104
|
-
return v === "full" || v === "restricted" || v === "none" ? v : undefined;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function readHistoryLimit(obj: Record<string, unknown>, key: string): number | undefined {
|
|
108
|
-
const v = obj[key];
|
|
109
|
-
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
112
|
-
return Math.max(0, Math.floor(v));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// ============ Public API ============
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Resolve per-group configuration with `specific > "*" > default` precedence.
|
|
119
|
-
*
|
|
120
|
-
* When `groupOpenid` is not provided, only the wildcard/default values are
|
|
121
|
-
* returned. This lets callers query the "default" behaviour for new groups.
|
|
122
|
-
*/
|
|
123
|
-
export function resolveGroupConfig(
|
|
124
|
-
cfg: Record<string, unknown>,
|
|
125
|
-
groupOpenid?: string | null,
|
|
126
|
-
accountId?: string | null,
|
|
127
|
-
): GroupConfig {
|
|
128
|
-
const groups = readGroupsMap(cfg, accountId);
|
|
129
|
-
const wildcard = groups["*"] ?? {};
|
|
130
|
-
const specific = groupOpenid ? (groups[groupOpenid] ?? {}) : {};
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
requireMention:
|
|
134
|
-
readBoolean(specific, "requireMention") ??
|
|
135
|
-
readBoolean(wildcard, "requireMention") ??
|
|
136
|
-
DEFAULT_GROUP_CONFIG.requireMention,
|
|
137
|
-
ignoreOtherMentions:
|
|
138
|
-
readBoolean(specific, "ignoreOtherMentions") ??
|
|
139
|
-
readBoolean(wildcard, "ignoreOtherMentions") ??
|
|
140
|
-
DEFAULT_GROUP_CONFIG.ignoreOtherMentions,
|
|
141
|
-
toolPolicy:
|
|
142
|
-
readToolPolicy(specific, "toolPolicy") ??
|
|
143
|
-
readToolPolicy(wildcard, "toolPolicy") ??
|
|
144
|
-
DEFAULT_GROUP_CONFIG.toolPolicy,
|
|
145
|
-
name: readString(specific, "name") ?? readString(wildcard, "name") ?? DEFAULT_GROUP_CONFIG.name,
|
|
146
|
-
prompt: readString(specific, "prompt") ?? readString(wildcard, "prompt"),
|
|
147
|
-
historyLimit:
|
|
148
|
-
readHistoryLimit(specific, "historyLimit") ??
|
|
149
|
-
readHistoryLimit(wildcard, "historyLimit") ??
|
|
150
|
-
DEFAULT_GROUP_CONFIG.historyLimit,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Resolve the effective `historyLimit` (>= 0) for a given group. */
|
|
155
|
-
export function resolveHistoryLimit(
|
|
156
|
-
cfg: Record<string, unknown>,
|
|
157
|
-
groupOpenid?: string | null,
|
|
158
|
-
accountId?: string | null,
|
|
159
|
-
): number {
|
|
160
|
-
return resolveGroupConfig(cfg, groupOpenid, accountId).historyLimit;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** Resolve `requireMention` for a given group. */
|
|
164
|
-
export function resolveRequireMention(
|
|
165
|
-
cfg: Record<string, unknown>,
|
|
166
|
-
groupOpenid?: string | null,
|
|
167
|
-
accountId?: string | null,
|
|
168
|
-
): boolean {
|
|
169
|
-
return resolveGroupConfig(cfg, groupOpenid, accountId).requireMention;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** Resolve `ignoreOtherMentions` for a given group. */
|
|
173
|
-
export function resolveIgnoreOtherMentions(
|
|
174
|
-
cfg: Record<string, unknown>,
|
|
175
|
-
groupOpenid?: string | null,
|
|
176
|
-
accountId?: string | null,
|
|
177
|
-
): boolean {
|
|
178
|
-
return resolveGroupConfig(cfg, groupOpenid, accountId).ignoreOtherMentions;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** Resolve tool policy for a given group. */
|
|
182
|
-
export function resolveGroupToolPolicy(
|
|
183
|
-
cfg: Record<string, unknown>,
|
|
184
|
-
groupOpenid?: string | null,
|
|
185
|
-
accountId?: string | null,
|
|
186
|
-
): GroupToolPolicy {
|
|
187
|
-
return resolveGroupConfig(cfg, groupOpenid, accountId).toolPolicy;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Resolve the behaviour prompt (PE) for a group. Falls back to the built-in
|
|
192
|
-
* default when neither specific nor wildcard configuration provides one.
|
|
193
|
-
*/
|
|
194
|
-
export function resolveGroupPrompt(
|
|
195
|
-
cfg: Record<string, unknown>,
|
|
196
|
-
groupOpenid?: string | null,
|
|
197
|
-
accountId?: string | null,
|
|
198
|
-
): string {
|
|
199
|
-
return resolveGroupConfig(cfg, groupOpenid, accountId).prompt ?? DEFAULT_GROUP_PROMPT;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Resolve the display name for a group.
|
|
204
|
-
*
|
|
205
|
-
* When no name is configured, the first 8 characters of the openid are used
|
|
206
|
-
* as a short identifier so log lines stay compact.
|
|
207
|
-
*/
|
|
208
|
-
export function resolveGroupName(
|
|
209
|
-
cfg: Record<string, unknown>,
|
|
210
|
-
groupOpenid: string,
|
|
211
|
-
accountId?: string | null,
|
|
212
|
-
): string {
|
|
213
|
-
const name = resolveGroupConfig(cfg, groupOpenid, accountId).name;
|
|
214
|
-
return name || groupOpenid.slice(0, 8);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// ============ GroupSettings (aggregate) ============
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Per-inbound aggregate of everything the pipeline needs about a group.
|
|
221
|
-
*
|
|
222
|
-
* Built once at the top of the group-gate stage so downstream consumers
|
|
223
|
-
* don't repeatedly re-parse the same `cfg` tree. Superset of
|
|
224
|
-
* {@link GroupConfig}: also includes the effective `mentionPatterns`
|
|
225
|
-
* (which depend on `agentId`, not on the group itself) and a
|
|
226
|
-
* pre-computed display name for logging.
|
|
227
|
-
*/
|
|
228
|
-
interface GroupSettings {
|
|
229
|
-
/** Merged group config (specific > wildcard > defaults). */
|
|
230
|
-
config: GroupConfig;
|
|
231
|
-
/** Display name — `config.name` or the first 8 chars of the openid. */
|
|
232
|
-
name: string;
|
|
233
|
-
/** Raw mentionPatterns (agent > global > []). */
|
|
234
|
-
mentionPatterns: string[];
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Resolve all per-inbound group-related settings in one pass.
|
|
239
|
-
*
|
|
240
|
-
* Prefer this over calling `resolveHistoryLimit` / `resolveRequireMention`
|
|
241
|
-
* / etc. individually in hot paths — each of those currently re-walks
|
|
242
|
-
* the config tree on its own.
|
|
243
|
-
*/
|
|
244
|
-
export function resolveGroupSettings(params: {
|
|
245
|
-
cfg: Record<string, unknown>;
|
|
246
|
-
groupOpenid: string;
|
|
247
|
-
accountId?: string | null;
|
|
248
|
-
agentId?: string | null;
|
|
249
|
-
}): GroupSettings {
|
|
250
|
-
const config = resolveGroupConfig(params.cfg, params.groupOpenid, params.accountId);
|
|
251
|
-
const name = config.name || params.groupOpenid.slice(0, 8);
|
|
252
|
-
const mentionPatterns = resolveMentionPatterns(params.cfg, params.agentId);
|
|
253
|
-
return { config, name, mentionPatterns };
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
interface AgentEntry {
|
|
257
|
-
id?: unknown;
|
|
258
|
-
groupChat?: { mentionPatterns?: unknown };
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Resolve mentionPatterns with `agent > global > []` precedence.
|
|
263
|
-
*
|
|
264
|
-
* Mirrors the framework's `messages.groupChat.mentionPatterns` / per-agent
|
|
265
|
-
* `agents.list[].groupChat.mentionPatterns` chain.
|
|
266
|
-
*/
|
|
267
|
-
export function resolveMentionPatterns(
|
|
268
|
-
cfg: Record<string, unknown>,
|
|
269
|
-
agentId?: string | null,
|
|
270
|
-
): string[] {
|
|
271
|
-
// ---- 1. Agent-level ----
|
|
272
|
-
if (agentId) {
|
|
273
|
-
const agents = asRecord(cfg.agents);
|
|
274
|
-
const list = Array.isArray(agents?.list) ? (agents?.list as AgentEntry[]) : [];
|
|
275
|
-
const entry = list.find(
|
|
276
|
-
(a) => typeof a.id === "string" && a.id.trim().toLowerCase() === agentId.trim().toLowerCase(),
|
|
277
|
-
);
|
|
278
|
-
const agentGroupChat = entry?.groupChat;
|
|
279
|
-
if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) {
|
|
280
|
-
const patterns = agentGroupChat.mentionPatterns;
|
|
281
|
-
return Array.isArray(patterns)
|
|
282
|
-
? patterns.filter((p): p is string => typeof p === "string")
|
|
283
|
-
: [];
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// ---- 2. Global level ----
|
|
288
|
-
const messages = asRecord(cfg.messages);
|
|
289
|
-
const globalGroupChat = asRecord(messages?.groupChat);
|
|
290
|
-
if (globalGroupChat && Object.hasOwn(globalGroupChat, "mentionPatterns")) {
|
|
291
|
-
const patterns = globalGroupChat.mentionPatterns;
|
|
292
|
-
return Array.isArray(patterns)
|
|
293
|
-
? patterns.filter((p): p is string => typeof p === "string")
|
|
294
|
-
: [];
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// ---- 3. Default ----
|
|
298
|
-
return [];
|
|
299
|
-
}
|