@openclaw/qqbot 2026.5.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.
Files changed (203) hide show
  1. package/dist/api.js +646 -0
  2. package/dist/approval-cg0SVahb.js +94 -0
  3. package/dist/channel-N6Y_Rcjp.js +551 -0
  4. package/dist/channel-plugin-api.js +2 -0
  5. package/dist/channel.setup-ByCRAe5H.js +25 -0
  6. package/dist/config-D6545NkC.js +94 -0
  7. package/dist/config-schema-DFcjQw73.js +309 -0
  8. package/dist/exec-approvals-COUsM6wZ.js +138 -0
  9. package/dist/gateway-CmSUJKSt.js +6726 -0
  10. package/dist/handler-runtime-Bqm6N0WG.js +120 -0
  11. package/dist/index.js +29 -0
  12. package/dist/narrowing-BoieBTIU.js +25 -0
  13. package/dist/outbound-BJfhwrPg.js +1357 -0
  14. package/dist/request-context-DXtpwWui.js +921 -0
  15. package/dist/resolve-D_06fV6-.js +284 -0
  16. package/dist/runtime-BJAS3eXW.js +18 -0
  17. package/dist/runtime-api.js +2 -0
  18. package/dist/secret-contract-api.js +56 -0
  19. package/dist/sender-p-B14eLG.js +1987 -0
  20. package/dist/setup-entry.js +15 -0
  21. package/dist/setup-plugin-api.js +2 -0
  22. package/dist/string-normalize-Ci6NM5DE.js +98 -0
  23. package/dist/target-parser-Y0prnrXD.js +245 -0
  24. package/package.json +15 -6
  25. package/api.ts +0 -56
  26. package/channel-plugin-api.ts +0 -1
  27. package/index.ts +0 -29
  28. package/runtime-api.ts +0 -9
  29. package/setup-entry.ts +0 -9
  30. package/setup-plugin-api.ts +0 -3
  31. package/src/bridge/approval/capability.ts +0 -237
  32. package/src/bridge/approval/handler-runtime.ts +0 -204
  33. package/src/bridge/bootstrap.ts +0 -135
  34. package/src/bridge/channel-entry.ts +0 -18
  35. package/src/bridge/commands/framework-context-adapter.ts +0 -60
  36. package/src/bridge/commands/framework-registration.ts +0 -47
  37. package/src/bridge/commands/from-parser.test.ts +0 -86
  38. package/src/bridge/commands/from-parser.ts +0 -60
  39. package/src/bridge/commands/result-dispatcher.ts +0 -76
  40. package/src/bridge/config-shared.ts +0 -132
  41. package/src/bridge/config.ts +0 -111
  42. package/src/bridge/gateway.ts +0 -174
  43. package/src/bridge/logger.ts +0 -31
  44. package/src/bridge/narrowing.ts +0 -31
  45. package/src/bridge/plugin-version.test.ts +0 -146
  46. package/src/bridge/plugin-version.ts +0 -102
  47. package/src/bridge/runtime.ts +0 -25
  48. package/src/bridge/sdk-adapter.ts +0 -131
  49. package/src/bridge/setup/finalize.ts +0 -144
  50. package/src/bridge/setup/surface.ts +0 -34
  51. package/src/bridge/tools/channel.ts +0 -58
  52. package/src/bridge/tools/index.ts +0 -15
  53. package/src/bridge/tools/remind.test.ts +0 -124
  54. package/src/bridge/tools/remind.ts +0 -91
  55. package/src/channel.setup.ts +0 -33
  56. package/src/channel.ts +0 -288
  57. package/src/command-auth.test.ts +0 -62
  58. package/src/config-schema.ts +0 -84
  59. package/src/config.test.ts +0 -364
  60. package/src/engine/access/access-control.test.ts +0 -198
  61. package/src/engine/access/access-control.ts +0 -226
  62. package/src/engine/access/index.ts +0 -16
  63. package/src/engine/access/resolve-policy.test.ts +0 -59
  64. package/src/engine/access/resolve-policy.ts +0 -57
  65. package/src/engine/access/sender-match.test.ts +0 -60
  66. package/src/engine/access/sender-match.ts +0 -55
  67. package/src/engine/access/types.ts +0 -53
  68. package/src/engine/adapter/audio.port.ts +0 -27
  69. package/src/engine/adapter/commands.port.ts +0 -22
  70. package/src/engine/adapter/history.port.ts +0 -52
  71. package/src/engine/adapter/index.ts +0 -139
  72. package/src/engine/adapter/mention-gate.port.ts +0 -50
  73. package/src/engine/adapter/types.ts +0 -38
  74. package/src/engine/api/api-client.ts +0 -212
  75. package/src/engine/api/media-chunked.test.ts +0 -336
  76. package/src/engine/api/media-chunked.ts +0 -622
  77. package/src/engine/api/media.ts +0 -218
  78. package/src/engine/api/messages.ts +0 -293
  79. package/src/engine/api/retry.ts +0 -217
  80. package/src/engine/api/routes.ts +0 -95
  81. package/src/engine/api/token.ts +0 -271
  82. package/src/engine/approval/index.test.ts +0 -22
  83. package/src/engine/approval/index.ts +0 -224
  84. package/src/engine/commands/builtin/log-helpers.ts +0 -319
  85. package/src/engine/commands/builtin/register-all.ts +0 -17
  86. package/src/engine/commands/builtin/register-approve.ts +0 -201
  87. package/src/engine/commands/builtin/register-basic.ts +0 -95
  88. package/src/engine/commands/builtin/register-clear-storage.ts +0 -187
  89. package/src/engine/commands/builtin/register-logs.ts +0 -20
  90. package/src/engine/commands/builtin/register-streaming.ts +0 -137
  91. package/src/engine/commands/builtin/state.ts +0 -31
  92. package/src/engine/commands/slash-command-auth.ts +0 -48
  93. package/src/engine/commands/slash-command-handler.ts +0 -146
  94. package/src/engine/commands/slash-commands-impl.test.ts +0 -8
  95. package/src/engine/commands/slash-commands-impl.ts +0 -61
  96. package/src/engine/commands/slash-commands.ts +0 -199
  97. package/src/engine/config/credential-backup.test.ts +0 -88
  98. package/src/engine/config/credential-backup.ts +0 -107
  99. package/src/engine/config/credentials.ts +0 -76
  100. package/src/engine/config/group.test.ts +0 -234
  101. package/src/engine/config/group.ts +0 -299
  102. package/src/engine/config/resolve.test.ts +0 -152
  103. package/src/engine/config/resolve.ts +0 -283
  104. package/src/engine/config/setup-logic.ts +0 -84
  105. package/src/engine/engine-import-boundary.test.ts +0 -73
  106. package/src/engine/gateway/codec.ts +0 -47
  107. package/src/engine/gateway/constants.ts +0 -117
  108. package/src/engine/gateway/event-dispatcher.ts +0 -177
  109. package/src/engine/gateway/gateway-connection.ts +0 -371
  110. package/src/engine/gateway/gateway.ts +0 -291
  111. package/src/engine/gateway/inbound-attachments.test.ts +0 -126
  112. package/src/engine/gateway/inbound-attachments.ts +0 -360
  113. package/src/engine/gateway/inbound-context.ts +0 -195
  114. package/src/engine/gateway/inbound-pipeline.self-echo.test.ts +0 -218
  115. package/src/engine/gateway/inbound-pipeline.ts +0 -235
  116. package/src/engine/gateway/interaction-handler.ts +0 -220
  117. package/src/engine/gateway/message-queue.test.ts +0 -282
  118. package/src/engine/gateway/message-queue.ts +0 -499
  119. package/src/engine/gateway/outbound-dispatch.test.ts +0 -231
  120. package/src/engine/gateway/outbound-dispatch.ts +0 -575
  121. package/src/engine/gateway/reconnect.ts +0 -199
  122. package/src/engine/gateway/stages/access-stage.ts +0 -132
  123. package/src/engine/gateway/stages/assembly-stage.ts +0 -156
  124. package/src/engine/gateway/stages/content-stage.test.ts +0 -77
  125. package/src/engine/gateway/stages/content-stage.ts +0 -77
  126. package/src/engine/gateway/stages/envelope-stage.test.ts +0 -152
  127. package/src/engine/gateway/stages/envelope-stage.ts +0 -144
  128. package/src/engine/gateway/stages/group-gate-stage.ts +0 -292
  129. package/src/engine/gateway/stages/index.ts +0 -18
  130. package/src/engine/gateway/stages/quote-stage.ts +0 -113
  131. package/src/engine/gateway/stages/refidx-stage.ts +0 -62
  132. package/src/engine/gateway/stages/stub-contexts.ts +0 -116
  133. package/src/engine/gateway/types.ts +0 -264
  134. package/src/engine/gateway/typing-keepalive.ts +0 -79
  135. package/src/engine/group/activation.test.ts +0 -114
  136. package/src/engine/group/activation.ts +0 -147
  137. package/src/engine/group/history.test.ts +0 -314
  138. package/src/engine/group/history.ts +0 -321
  139. package/src/engine/group/mention.test.ts +0 -141
  140. package/src/engine/group/mention.ts +0 -197
  141. package/src/engine/group/message-gating.test.ts +0 -188
  142. package/src/engine/group/message-gating.ts +0 -216
  143. package/src/engine/messaging/decode-media-path.ts +0 -82
  144. package/src/engine/messaging/media-source.ts +0 -215
  145. package/src/engine/messaging/media-type-detect.ts +0 -37
  146. package/src/engine/messaging/outbound-audio-port.ts +0 -38
  147. package/src/engine/messaging/outbound-deliver.ts +0 -810
  148. package/src/engine/messaging/outbound-media-send.ts +0 -702
  149. package/src/engine/messaging/outbound-reply.ts +0 -27
  150. package/src/engine/messaging/outbound-result-helpers.ts +0 -54
  151. package/src/engine/messaging/outbound-types.ts +0 -45
  152. package/src/engine/messaging/outbound.ts +0 -485
  153. package/src/engine/messaging/reply-dispatcher.ts +0 -597
  154. package/src/engine/messaging/reply-limiter.ts +0 -164
  155. package/src/engine/messaging/sender.ts +0 -729
  156. package/src/engine/messaging/streaming-c2c.ts +0 -1192
  157. package/src/engine/messaging/streaming-media-send.ts +0 -544
  158. package/src/engine/messaging/target-parser.ts +0 -104
  159. package/src/engine/ref/format-message-ref.ts +0 -142
  160. package/src/engine/ref/format-ref-entry.test.ts +0 -60
  161. package/src/engine/ref/format-ref-entry.ts +0 -27
  162. package/src/engine/ref/store.ts +0 -208
  163. package/src/engine/ref/types.ts +0 -27
  164. package/src/engine/session/known-users.ts +0 -137
  165. package/src/engine/session/session-store.ts +0 -204
  166. package/src/engine/tools/channel-api.ts +0 -244
  167. package/src/engine/tools/remind-logic.test.ts +0 -280
  168. package/src/engine/tools/remind-logic.ts +0 -377
  169. package/src/engine/types.ts +0 -313
  170. package/src/engine/utils/attachment-tags.test.ts +0 -186
  171. package/src/engine/utils/attachment-tags.ts +0 -174
  172. package/src/engine/utils/audio.test.ts +0 -250
  173. package/src/engine/utils/audio.ts +0 -585
  174. package/src/engine/utils/data-paths.ts +0 -38
  175. package/src/engine/utils/diagnostics.ts +0 -109
  176. package/src/engine/utils/file-utils.test.ts +0 -72
  177. package/src/engine/utils/file-utils.ts +0 -225
  178. package/src/engine/utils/format.test.ts +0 -68
  179. package/src/engine/utils/format.ts +0 -70
  180. package/src/engine/utils/image-size.test.ts +0 -158
  181. package/src/engine/utils/image-size.ts +0 -249
  182. package/src/engine/utils/log.test.ts +0 -28
  183. package/src/engine/utils/log.ts +0 -61
  184. package/src/engine/utils/media-tags.test.ts +0 -32
  185. package/src/engine/utils/media-tags.ts +0 -177
  186. package/src/engine/utils/payload.test.ts +0 -68
  187. package/src/engine/utils/payload.ts +0 -145
  188. package/src/engine/utils/platform-storage-laziness.test.ts +0 -63
  189. package/src/engine/utils/platform.test.ts +0 -148
  190. package/src/engine/utils/platform.ts +0 -343
  191. package/src/engine/utils/request-context.ts +0 -60
  192. package/src/engine/utils/string-normalize.ts +0 -91
  193. package/src/engine/utils/stt.test.ts +0 -104
  194. package/src/engine/utils/stt.ts +0 -100
  195. package/src/engine/utils/text-parsing.test.ts +0 -29
  196. package/src/engine/utils/text-parsing.ts +0 -155
  197. package/src/engine/utils/upload-cache.ts +0 -96
  198. package/src/engine/utils/voice-text.ts +0 -15
  199. package/src/exec-approvals.ts +0 -218
  200. package/src/manifest-schema.test.ts +0 -56
  201. package/src/qqbot-test-support.ts +0 -29
  202. package/src/types.ts +0 -210
  203. package/tsconfig.json +0 -16
@@ -1,47 +0,0 @@
1
- /**
2
- * Register all `requireAuth: true` slash commands with the framework via
3
- * `api.registerCommand`.
4
- *
5
- * Routing through the framework lets `resolveCommandAuthorization()` apply
6
- * `commands.allowFrom.qqbot` precedence and the `qqbot:` prefix normalization
7
- * before any QQBot command handler runs.
8
- *
9
- * This module is intentionally thin: it wires the engine-side command
10
- * registry (`getFrameworkCommands`) to the framework registration surface via
11
- * the three single-responsibility helpers in this directory.
12
- */
13
-
14
- import type { OpenClawPluginApi, PluginCommandContext } from "openclaw/plugin-sdk/plugin-entry";
15
- import { getFrameworkCommands } from "../../engine/commands/slash-commands-impl.js";
16
- import { resolveQQBotAccount } from "../config.js";
17
- import { buildFrameworkSlashContext } from "./framework-context-adapter.js";
18
- import { parseQQBotFrom } from "./from-parser.js";
19
- import { dispatchFrameworkSlashResult } from "./result-dispatcher.js";
20
-
21
- export function registerQQBotFrameworkCommands(api: OpenClawPluginApi): void {
22
- for (const cmd of getFrameworkCommands()) {
23
- api.registerCommand({
24
- name: cmd.name,
25
- description: cmd.description,
26
- requireAuth: true,
27
- acceptsArgs: true,
28
- handler: async (ctx: PluginCommandContext) => {
29
- const from = parseQQBotFrom(ctx.from);
30
- const account = resolveQQBotAccount(ctx.config, ctx.accountId ?? undefined);
31
- const slashCtx = buildFrameworkSlashContext({
32
- ctx,
33
- account,
34
- from,
35
- commandName: cmd.name,
36
- });
37
- const result = await cmd.handler(slashCtx);
38
- return await dispatchFrameworkSlashResult({
39
- result,
40
- account,
41
- from,
42
- logger: api.logger,
43
- });
44
- },
45
- });
46
- }
47
- }
@@ -1,86 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { parseQQBotFrom } from "./from-parser.js";
3
-
4
- describe("parseQQBotFrom", () => {
5
- it("parses a group from string", () => {
6
- expect(parseQQBotFrom("qqbot:group:ABCDEF")).toEqual({
7
- msgType: "group",
8
- targetType: "group",
9
- targetId: "ABCDEF",
10
- });
11
- });
12
-
13
- it("parses a channel prefix into the guild msgType", () => {
14
- expect(parseQQBotFrom("qqbot:channel:123")).toEqual({
15
- msgType: "guild",
16
- targetType: "channel",
17
- targetId: "123",
18
- });
19
- });
20
-
21
- it("parses a dm prefix", () => {
22
- expect(parseQQBotFrom("qqbot:dm:456")).toEqual({
23
- msgType: "dm",
24
- targetType: "dm",
25
- targetId: "456",
26
- });
27
- });
28
-
29
- it("parses a c2c prefix", () => {
30
- expect(parseQQBotFrom("qqbot:c2c:user-1")).toEqual({
31
- msgType: "c2c",
32
- targetType: "c2c",
33
- targetId: "user-1",
34
- });
35
- });
36
-
37
- it("is case-insensitive on the qqbot: prefix", () => {
38
- expect(parseQQBotFrom("QQBOT:group:gid")).toEqual({
39
- msgType: "group",
40
- targetType: "group",
41
- targetId: "gid",
42
- });
43
- });
44
-
45
- it("handles target ids that contain a colon", () => {
46
- expect(parseQQBotFrom("qqbot:group:GROUP:ID")).toEqual({
47
- msgType: "group",
48
- targetType: "group",
49
- targetId: "GROUP:ID",
50
- });
51
- });
52
-
53
- it("falls back to c2c for unknown prefixes", () => {
54
- expect(parseQQBotFrom("qqbot:unknown:abc")).toEqual({
55
- msgType: "c2c",
56
- targetType: "c2c",
57
- targetId: "abc",
58
- });
59
- });
60
-
61
- it("falls back to c2c for missing from", () => {
62
- expect(parseQQBotFrom(undefined)).toEqual({
63
- msgType: "c2c",
64
- targetType: "c2c",
65
- targetId: "",
66
- });
67
- expect(parseQQBotFrom(null)).toEqual({
68
- msgType: "c2c",
69
- targetType: "c2c",
70
- targetId: "",
71
- });
72
- expect(parseQQBotFrom("")).toEqual({
73
- msgType: "c2c",
74
- targetType: "c2c",
75
- targetId: "",
76
- });
77
- });
78
-
79
- it("treats a bare prefix (no colon) as c2c with that id", () => {
80
- expect(parseQQBotFrom("qqbot:c2c")).toEqual({
81
- msgType: "c2c",
82
- targetType: "c2c",
83
- targetId: "c2c",
84
- });
85
- });
86
- });
@@ -1,60 +0,0 @@
1
- /**
2
- * Parse the framework `PluginCommandContext.from` string into the QQBot
3
- * message type and send target.
4
- *
5
- * The framework passes `from` in the form `qqbot:<kind>:<id>` (case-insensitive
6
- * prefix). We split that string once and map `<kind>` into the engine-side
7
- * `SlashCommandContext.type` enum and the outbound `MediaTargetContext.targetType`
8
- * enum. Both enums diverge only for guild/channel, so we keep two lookup
9
- * tables to avoid the nested ternary chain the previous implementation used.
10
- */
11
-
12
- export interface QQBotFromParseResult {
13
- /** Message type consumed by SlashCommandContext.type. */
14
- msgType: "c2c" | "guild" | "dm" | "group";
15
- /** Target type consumed by MediaTargetContext.targetType. */
16
- targetType: "c2c" | "group" | "channel" | "dm";
17
- /** Raw target id (everything after the first `:`). */
18
- targetId: string;
19
- }
20
-
21
- type FromKind = "c2c" | "group" | "channel" | "dm";
22
-
23
- const MSG_TYPE_MAP: Record<FromKind, QQBotFromParseResult["msgType"]> = {
24
- c2c: "c2c",
25
- dm: "dm",
26
- group: "group",
27
- channel: "guild",
28
- };
29
-
30
- const TARGET_TYPE_MAP: Record<FromKind, QQBotFromParseResult["targetType"]> = {
31
- c2c: "c2c",
32
- dm: "dm",
33
- group: "group",
34
- channel: "channel",
35
- };
36
-
37
- function isFromKind(value: string): value is FromKind {
38
- return value === "c2c" || value === "dm" || value === "group" || value === "channel";
39
- }
40
-
41
- /**
42
- * Parse `ctx.from` into the structured fields the QQBot bridge expects.
43
- *
44
- * Unknown or missing prefixes fall back to c2c. The remainder after the first
45
- * `:` is returned verbatim as the target id, matching what the previous inline
46
- * implementation did.
47
- */
48
- export function parseQQBotFrom(from: string | undefined | null): QQBotFromParseResult {
49
- const stripped = (from ?? "").replace(/^qqbot:/iu, "");
50
- const colonIdx = stripped.indexOf(":");
51
- const rawPrefix = colonIdx === -1 ? stripped : stripped.slice(0, colonIdx);
52
- const targetId = colonIdx === -1 ? stripped : stripped.slice(colonIdx + 1);
53
- const kind: FromKind = isFromKind(rawPrefix) ? rawPrefix : "c2c";
54
-
55
- return {
56
- msgType: MSG_TYPE_MAP[kind],
57
- targetType: TARGET_TYPE_MAP[kind],
58
- targetId,
59
- };
60
- }
@@ -1,76 +0,0 @@
1
- /**
2
- * Dispatch a slash command result produced on the framework command surface.
3
- *
4
- * Slash command handlers return one of:
5
- * 1. a plain string (text reply),
6
- * 2. a `SlashCommandFileResult` (text plus a local file to upload), or
7
- * 3. null / unexpected value (we surface a generic warning).
8
- *
9
- * This module isolates the text/file branching so the framework registration
10
- * layer stays declarative and so the file-send side effect has a single
11
- * location where logging and error handling live.
12
- */
13
-
14
- import type { PluginLogger } from "openclaw/plugin-sdk/plugin-entry";
15
- import type { SlashCommandResult } from "../../engine/commands/slash-commands.js";
16
- import { sendDocument, type MediaTargetContext } from "../../engine/messaging/outbound.js";
17
- import type { ResolvedQQBotAccount } from "../../types.js";
18
- import type { QQBotFromParseResult } from "./from-parser.js";
19
-
20
- const UNEXPECTED_RESULT_TEXT = "⚠️ 命令返回了意外结果。";
21
-
22
- interface FrameworkSlashReply {
23
- text: string;
24
- }
25
-
26
- interface DispatchFrameworkSlashResultInput {
27
- result: SlashCommandResult;
28
- account: ResolvedQQBotAccount;
29
- from: QQBotFromParseResult;
30
- logger?: PluginLogger;
31
- }
32
-
33
- function hasFilePath(value: unknown): value is { text: string; filePath: string } {
34
- return (
35
- typeof value === "object" &&
36
- value !== null &&
37
- "filePath" in value &&
38
- typeof (value as { filePath: unknown }).filePath === "string"
39
- );
40
- }
41
-
42
- function buildMediaTarget(
43
- account: ResolvedQQBotAccount,
44
- from: QQBotFromParseResult,
45
- ): MediaTargetContext {
46
- return {
47
- targetType: from.targetType,
48
- targetId: from.targetId,
49
- account: account as unknown as MediaTargetContext["account"],
50
- };
51
- }
52
-
53
- export async function dispatchFrameworkSlashResult({
54
- result,
55
- account,
56
- from,
57
- logger,
58
- }: DispatchFrameworkSlashResultInput): Promise<FrameworkSlashReply> {
59
- if (typeof result === "string") {
60
- return { text: result };
61
- }
62
-
63
- if (hasFilePath(result)) {
64
- const mediaCtx = buildMediaTarget(account, from);
65
- try {
66
- await sendDocument(mediaCtx, result.filePath, {
67
- allowQQBotDataDownloads: true,
68
- });
69
- } catch (err) {
70
- logger?.warn(`framework slash file send failed: ${String(err)}`);
71
- }
72
- return { text: result.text };
73
- }
74
-
75
- return { text: UNEXPECTED_RESULT_TEXT };
76
- }
@@ -1,132 +0,0 @@
1
- import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
2
- import {
3
- applyAccountNameToChannelSection,
4
- deleteAccountFromConfigSection,
5
- setAccountEnabledInConfigSection,
6
- } from "openclaw/plugin-sdk/core";
7
- import type { ChannelSetupInput } from "openclaw/plugin-sdk/setup";
8
- import {
9
- describeAccount as engineDescribeAccount,
10
- formatAllowFrom as engineFormatAllowFrom,
11
- isAccountConfigured as engineIsAccountConfigured,
12
- } from "../engine/config/resolve.js";
13
- import {
14
- applySetupAccountConfig as engineApplySetupAccountConfig,
15
- validateSetupInput as engineValidateSetupInput,
16
- } from "../engine/config/setup-logic.js";
17
- import { normalizeLowercaseStringOrEmpty } from "../engine/utils/string-normalize.js";
18
- import type { ResolvedQQBotAccount } from "../types.js";
19
- import {
20
- listQQBotAccountIds,
21
- resolveDefaultQQBotAccountId,
22
- resolveQQBotAccount,
23
- } from "./config.js";
24
-
25
- export const qqbotMeta = {
26
- id: "qqbot",
27
- label: "QQ Bot",
28
- selectionLabel: "QQ Bot (Bot API)",
29
- docsPath: "/channels/qqbot",
30
- blurb: "Connect to QQ via official QQ Bot API",
31
- order: 50,
32
- } as const;
33
-
34
- function validateQQBotSetupInput(params: {
35
- accountId: string;
36
- input: ChannelSetupInput;
37
- }): string | null {
38
- return engineValidateSetupInput(params.accountId, params.input);
39
- }
40
-
41
- function applyQQBotSetupAccountConfig(params: {
42
- cfg: OpenClawConfig;
43
- accountId: string;
44
- input: ChannelSetupInput;
45
- }): OpenClawConfig {
46
- return engineApplySetupAccountConfig(
47
- params.cfg as unknown as Record<string, unknown>,
48
- params.accountId,
49
- params.input,
50
- ) as OpenClawConfig;
51
- }
52
-
53
- function isQQBotConfigured(account: ResolvedQQBotAccount | undefined): boolean {
54
- return engineIsAccountConfigured(account as never);
55
- }
56
-
57
- function describeQQBotAccount(account: ResolvedQQBotAccount | undefined) {
58
- return engineDescribeAccount(account as never);
59
- }
60
-
61
- function formatQQBotAllowFrom(params: {
62
- allowFrom: Array<string | number> | undefined | null;
63
- }): string[] {
64
- return engineFormatAllowFrom(params.allowFrom);
65
- }
66
-
67
- export const qqbotConfigAdapter = {
68
- listAccountIds: (cfg: OpenClawConfig) => listQQBotAccountIds(cfg),
69
- resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>
70
- resolveQQBotAccount(cfg, accountId, { allowUnresolvedSecretRef: true }),
71
- defaultAccountId: (cfg: OpenClawConfig) => resolveDefaultQQBotAccountId(cfg),
72
- setAccountEnabled: ({
73
- cfg,
74
- accountId,
75
- enabled,
76
- }: {
77
- cfg: OpenClawConfig;
78
- accountId: string;
79
- enabled: boolean;
80
- }) =>
81
- setAccountEnabledInConfigSection({
82
- cfg,
83
- sectionKey: "qqbot",
84
- accountId,
85
- enabled,
86
- allowTopLevel: true,
87
- }),
88
- deleteAccount: ({ cfg, accountId }: { cfg: OpenClawConfig; accountId: string }) =>
89
- deleteAccountFromConfigSection({
90
- cfg,
91
- sectionKey: "qqbot",
92
- accountId,
93
- clearBaseFields: ["appId", "clientSecret", "clientSecretFile", "name"],
94
- }),
95
- isConfigured: isQQBotConfigured,
96
- describeAccount: describeQQBotAccount,
97
- resolveAllowFrom: ({ cfg, accountId }: { cfg: OpenClawConfig; accountId?: string | null }) =>
98
- resolveQQBotAccount(cfg, accountId, { allowUnresolvedSecretRef: true }).config?.allowFrom,
99
- formatAllowFrom: ({ allowFrom }: { allowFrom: Array<string | number> | undefined | null }) =>
100
- formatQQBotAllowFrom({ allowFrom }),
101
- };
102
-
103
- export const qqbotSetupAdapterShared = {
104
- resolveAccountId: ({ cfg, accountId }: { cfg: OpenClawConfig; accountId?: string | null }) =>
105
- normalizeLowercaseStringOrEmpty(accountId) || resolveDefaultQQBotAccountId(cfg),
106
- applyAccountName: ({
107
- cfg,
108
- accountId,
109
- name,
110
- }: {
111
- cfg: OpenClawConfig;
112
- accountId: string;
113
- name?: string;
114
- }) =>
115
- applyAccountNameToChannelSection({
116
- cfg,
117
- channelKey: "qqbot",
118
- accountId,
119
- name,
120
- }),
121
- validateInput: ({ accountId, input }: { accountId: string; input: ChannelSetupInput }) =>
122
- validateQQBotSetupInput({ accountId, input }),
123
- applyAccountConfig: ({
124
- cfg,
125
- accountId,
126
- input,
127
- }: {
128
- cfg: OpenClawConfig;
129
- accountId: string;
130
- input: ChannelSetupInput;
131
- }) => applyQQBotSetupAccountConfig({ cfg, accountId, input }),
132
- };
@@ -1,111 +0,0 @@
1
- import fs from "node:fs";
2
- import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
3
- import { getPlatformAdapter } from "../engine/adapter/index.js";
4
- import {
5
- DEFAULT_ACCOUNT_ID as ENGINE_DEFAULT_ACCOUNT_ID,
6
- applyAccountConfig,
7
- listAccountIds,
8
- resolveAccountBase,
9
- resolveDefaultAccountId,
10
- } from "../engine/config/resolve.js";
11
- import type { ResolvedQQBotAccount, QQBotAccountConfig } from "../types.js";
12
-
13
- export const DEFAULT_ACCOUNT_ID = ENGINE_DEFAULT_ACCOUNT_ID;
14
-
15
- interface QQBotChannelConfig extends QQBotAccountConfig {
16
- accounts?: Record<string, QQBotAccountConfig>;
17
- defaultAccount?: string;
18
- }
19
-
20
- /** List all configured QQBot account IDs. */
21
- export function listQQBotAccountIds(cfg: OpenClawConfig): string[] {
22
- return listAccountIds(cfg as unknown as Record<string, unknown>);
23
- }
24
-
25
- /** Resolve the default QQBot account ID. */
26
- export function resolveDefaultQQBotAccountId(cfg: OpenClawConfig): string {
27
- return resolveDefaultAccountId(cfg as unknown as Record<string, unknown>);
28
- }
29
-
30
- /** Resolve QQBot account config for runtime or setup flows. */
31
- export function resolveQQBotAccount(
32
- cfg: OpenClawConfig,
33
- accountId?: string | null,
34
- opts?: { allowUnresolvedSecretRef?: boolean },
35
- ): ResolvedQQBotAccount {
36
- const raw = cfg as unknown as Record<string, unknown>;
37
- const base = resolveAccountBase(raw, accountId);
38
-
39
- const qqbot = cfg.channels?.qqbot as QQBotChannelConfig | undefined;
40
- /**
41
- * Legacy top-level account uses `channels.qqbot` as the base, but per-account
42
- * fields (allowFrom, streaming, …) often live under `accounts.default`.
43
- * Merge that slice so runtime sees `config.streaming` etc.
44
- */
45
- const accountConfig: QQBotAccountConfig =
46
- base.accountId === DEFAULT_ACCOUNT_ID
47
- ? {
48
- ...qqbot,
49
- ...qqbot?.accounts?.[DEFAULT_ACCOUNT_ID],
50
- }
51
- : (qqbot?.accounts?.[base.accountId] ?? {});
52
-
53
- let clientSecret = "";
54
- let secretSource: "config" | "file" | "env" | "none" = "none";
55
-
56
- const clientSecretPath =
57
- base.accountId === DEFAULT_ACCOUNT_ID
58
- ? "channels.qqbot.clientSecret"
59
- : `channels.qqbot.accounts.${base.accountId}.clientSecret`;
60
-
61
- const adapter = getPlatformAdapter();
62
- if (adapter.hasConfiguredSecret(accountConfig.clientSecret)) {
63
- clientSecret = opts?.allowUnresolvedSecretRef
64
- ? (adapter.normalizeSecretInputString(accountConfig.clientSecret) ?? "")
65
- : (adapter.resolveSecretInputString({
66
- value: accountConfig.clientSecret,
67
- path: clientSecretPath,
68
- }) ?? "");
69
- secretSource = "config";
70
- } else if (accountConfig.clientSecretFile) {
71
- try {
72
- clientSecret = fs.readFileSync(accountConfig.clientSecretFile, "utf8").trim();
73
- secretSource = "file";
74
- } catch {
75
- secretSource = "none";
76
- }
77
- } else if (process.env.QQBOT_CLIENT_SECRET && base.accountId === DEFAULT_ACCOUNT_ID) {
78
- clientSecret = process.env.QQBOT_CLIENT_SECRET;
79
- secretSource = "env";
80
- }
81
-
82
- return {
83
- accountId: base.accountId,
84
- name: accountConfig.name,
85
- enabled: base.enabled,
86
- appId: base.appId,
87
- clientSecret,
88
- secretSource,
89
- systemPrompt: base.systemPrompt,
90
- markdownSupport: base.markdownSupport,
91
- config: accountConfig,
92
- };
93
- }
94
-
95
- /** Apply account config updates back into the OpenClaw config object. */
96
- export function applyQQBotAccountConfig(
97
- cfg: OpenClawConfig,
98
- accountId: string,
99
- input: {
100
- appId?: string;
101
- clientSecret?: string;
102
- clientSecretFile?: string;
103
- name?: string;
104
- },
105
- ): OpenClawConfig {
106
- return applyAccountConfig(
107
- cfg as unknown as Record<string, unknown>,
108
- accountId,
109
- input,
110
- ) as OpenClawConfig;
111
- }
@@ -1,174 +0,0 @@
1
- /**
2
- * Gateway entry point — thin bridge shell that constructs
3
- * {@link EngineAdapters} and passes them to the engine's
4
- * `startGateway`.
5
- *
6
- * All adapter dependencies are assembled here in one place.
7
- */
8
-
9
- import { resolveRuntimeServiceVersion } from "openclaw/plugin-sdk/cli-runtime";
10
- import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
11
- import type { EngineAdapters } from "../engine/adapter/index.js";
12
- import {
13
- startGateway as coreStartGateway,
14
- type CoreGatewayContext,
15
- } from "../engine/gateway/gateway.js";
16
- import type { GatewayPluginRuntime } from "../engine/gateway/types.js";
17
- import { initSender, registerAccount } from "../engine/messaging/sender.js";
18
- import type { EngineLogger } from "../engine/types.js";
19
- import * as _audioModule from "../engine/utils/audio.js";
20
- import { formatDuration } from "../engine/utils/format.js";
21
- import { debugLog, debugError } from "../engine/utils/log.js";
22
- import type { ResolvedQQBotAccount } from "../types.js";
23
- import { ensurePlatformAdapter } from "./bootstrap.js";
24
- import { setBridgeLogger } from "./logger.js";
25
- import { toGatewayAccount } from "./narrowing.js";
26
- import { resolveQQBotPluginVersion } from "./plugin-version.js";
27
- import { getQQBotRuntime, getQQBotRuntimeForEngine } from "./runtime.js";
28
- import { createSdkHistoryAdapter, createSdkMentionGateAdapter } from "./sdk-adapter.js";
29
-
30
- // ---- One-time startup initialization (module-level) ----
31
-
32
- const _pluginVersion = resolveQQBotPluginVersion(import.meta.url);
33
- initSender({
34
- pluginVersion: _pluginVersion,
35
- openclawVersion: resolveRuntimeServiceVersion(),
36
- });
37
-
38
- // ============ Public types ============
39
-
40
- export interface GatewayContext {
41
- account: ResolvedQQBotAccount;
42
- abortSignal: AbortSignal;
43
- cfg: OpenClawConfig;
44
- onReady?: (data: unknown) => void;
45
- onResumed?: (data: unknown) => void;
46
- onError?: (error: Error) => void;
47
- log?: {
48
- info: (msg: string) => void;
49
- error: (msg: string) => void;
50
- debug?: (msg: string) => void;
51
- };
52
- channelRuntime?: {
53
- runtimeContexts: {
54
- register: (params: {
55
- channelId: string;
56
- accountId: string;
57
- capability: string;
58
- context: unknown;
59
- abortSignal?: AbortSignal;
60
- }) => { dispose: () => void };
61
- };
62
- };
63
- }
64
-
65
- // ============ Adapter factory ============
66
-
67
- /**
68
- * Create the full set of engine adapters from the bridge layer.
69
- *
70
- * This is the **single assembly point** — all SDK → engine binding
71
- * happens here. The engine receives a fully-populated
72
- * {@link EngineAdapters} object with zero global singletons.
73
- */
74
- function createEngineAdapters(_runtime: GatewayPluginRuntime): EngineAdapters {
75
- return {
76
- history: createSdkHistoryAdapter(),
77
- mentionGate: createSdkMentionGateAdapter(),
78
- audioConvert: {
79
- convertSilkToWav: _audioModule.convertSilkToWav,
80
- isVoiceAttachment: _audioModule.isVoiceAttachment,
81
- formatDuration,
82
- },
83
- outboundAudio: {
84
- audioFileToSilkBase64: async (p: string, f?: string[]) =>
85
- (await _audioModule.audioFileToSilkBase64(p, f)) ?? undefined,
86
- isAudioFile: (p: string, m?: string) => _audioModule.isAudioFile(p, m),
87
- shouldTranscodeVoice: (p: string) => _audioModule.shouldTranscodeVoice(p),
88
- waitForFile: (p: string, ms?: number) => _audioModule.waitForFile(p, ms),
89
- },
90
- commands: {
91
- resolveVersion: resolveRuntimeServiceVersion,
92
- pluginVersion: _pluginVersion,
93
- approveRuntimeGetter: () => {
94
- const rt = getQQBotRuntime();
95
- return { config: rt.config };
96
- },
97
- },
98
- };
99
- }
100
-
101
- // ============ startGateway ============
102
-
103
- /**
104
- * Start the Gateway WebSocket connection.
105
- *
106
- * Assembles all adapters and passes them to the engine's core gateway.
107
- */
108
- export async function startGateway(ctx: GatewayContext): Promise<void> {
109
- ensurePlatformAdapter();
110
-
111
- const runtime = getQQBotRuntimeForEngine();
112
- const accountLogger = createAccountLogger(ctx.log, ctx.account.accountId);
113
-
114
- // Per-account registration (still global — sender is a leaf utility).
115
- registerAccount(ctx.account.appId, {
116
- logger: accountLogger,
117
- markdownSupport: ctx.account.markdownSupport,
118
- });
119
- setBridgeLogger(accountLogger);
120
-
121
- if (ctx.channelRuntime) {
122
- accountLogger.info("Registering approval.native runtime context");
123
- const lease = ctx.channelRuntime.runtimeContexts.register({
124
- channelId: "qqbot",
125
- accountId: ctx.account.accountId,
126
- capability: "approval.native",
127
- context: { account: ctx.account },
128
- abortSignal: ctx.abortSignal,
129
- });
130
- accountLogger.info(`approval.native context registered (lease=${!!lease})`);
131
- } else {
132
- accountLogger.info("No channelRuntime — skipping approval.native registration");
133
- }
134
-
135
- const coreCtx: CoreGatewayContext = {
136
- account: toGatewayAccount(ctx.account),
137
- abortSignal: ctx.abortSignal,
138
- cfg: ctx.cfg,
139
- onReady: ctx.onReady,
140
- onResumed: ctx.onResumed,
141
- onError: ctx.onError,
142
- log: accountLogger,
143
- runtime,
144
- adapters: createEngineAdapters(runtime),
145
- };
146
-
147
- return coreStartGateway(coreCtx);
148
- }
149
-
150
- // ============ Per-account logger factory ============
151
-
152
- function createAccountLogger(
153
- raw: GatewayContext["log"] | undefined,
154
- accountId: string,
155
- ): EngineLogger {
156
- const prefix = `[${accountId}]`;
157
- const withMeta = (msg: string, meta?: Record<string, unknown>) =>
158
- meta && Object.keys(meta).length > 0 ? `${msg} ${JSON.stringify(meta)}` : msg;
159
-
160
- if (!raw) {
161
- return {
162
- info: (msg, meta) => debugLog(`${prefix} ${withMeta(msg, meta)}`),
163
- error: (msg, meta) => debugError(`${prefix} ${withMeta(msg, meta)}`),
164
- warn: (msg, meta) => debugError(`${prefix} ${withMeta(msg, meta)}`),
165
- debug: (msg, meta) => debugLog(`${prefix} ${withMeta(msg, meta)}`),
166
- };
167
- }
168
- return {
169
- info: (msg, meta) => raw.info(`${prefix} ${withMeta(msg, meta)}`),
170
- error: (msg, meta) => raw.error(`${prefix} ${withMeta(msg, meta)}`),
171
- warn: (msg, meta) => raw.error(`${prefix} ${withMeta(msg, meta)}`),
172
- debug: (msg, meta) => raw.debug?.(`${prefix} ${withMeta(msg, meta)}`),
173
- };
174
- }