@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,31 +0,0 @@
1
- /**
2
- * Bridge-layer logger — holds the framework logger injected at gateway startup.
3
- *
4
- * Bridge modules (approval, tools, etc.) use this instead of `console.log` or
5
- * engine's `debugLog` so that all logs flow through the OpenClaw log system.
6
- */
7
-
8
- interface BridgeLogger {
9
- info: (msg: string) => void;
10
- error: (msg: string) => void;
11
- warn?: (msg: string) => void;
12
- debug?: (msg: string) => void;
13
- }
14
-
15
- let _logger: BridgeLogger | null = null;
16
-
17
- /** Register the framework logger. Called once in startGateway(). */
18
- export function setBridgeLogger(logger: BridgeLogger): void {
19
- _logger = logger;
20
- }
21
-
22
- /** Get the bridge logger. Falls back to console if not yet registered. */
23
- export function getBridgeLogger(): BridgeLogger {
24
- return (
25
- _logger ?? {
26
- info: (msg) => console.log(msg),
27
- error: (msg) => console.error(msg),
28
- debug: (msg) => console.log(msg),
29
- }
30
- );
31
- }
@@ -1,31 +0,0 @@
1
- import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
2
- import type { PluginRuntime } from "openclaw/plugin-sdk/core";
3
- import type { GatewayAccount } from "../engine/types.js";
4
- import type { ResolvedQQBotAccount } from "../types.js";
5
-
6
- /**
7
- * Map resolved plugin account to the engine gateway account shape (single assertion on nested config).
8
- */
9
- export function toGatewayAccount(account: ResolvedQQBotAccount): GatewayAccount {
10
- return {
11
- accountId: account.accountId,
12
- appId: account.appId,
13
- clientSecret: account.clientSecret,
14
- markdownSupport: account.markdownSupport,
15
- systemPrompt: account.systemPrompt,
16
- config: account.config as GatewayAccount["config"],
17
- };
18
- }
19
-
20
- /**
21
- * Persist OpenClaw config through the injected plugin runtime (typed entry point).
22
- */
23
- export async function writeOpenClawConfigThroughRuntime(
24
- runtime: PluginRuntime,
25
- cfg: OpenClawConfig,
26
- ): Promise<void> {
27
- await runtime.config.replaceConfigFile({
28
- nextConfig: cfg,
29
- afterWrite: { mode: "auto" },
30
- });
31
- }
@@ -1,146 +0,0 @@
1
- /**
2
- * Tests for `resolveQQBotPluginVersion`.
3
- *
4
- * These exercise the directory-walk lookup against controlled fixture
5
- * trees rather than the repo's real `package.json`, so the behaviour
6
- * is deterministic regardless of where the test runs.
7
- */
8
-
9
- import fs from "node:fs";
10
- import os from "node:os";
11
- import path from "node:path";
12
- import { pathToFileURL } from "node:url";
13
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
14
- import { QQBOT_PLUGIN_VERSION_UNKNOWN, resolveQQBotPluginVersion } from "./plugin-version.js";
15
-
16
- /** Create a temp directory tree for an individual test and return its root. */
17
- function createTempTree(): string {
18
- return fs.mkdtempSync(path.join(os.tmpdir(), "qqbot-pkg-version-"));
19
- }
20
-
21
- function writeJson(file: string, data: unknown): void {
22
- fs.mkdirSync(path.dirname(file), { recursive: true });
23
- fs.writeFileSync(file, JSON.stringify(data), "utf8");
24
- }
25
-
26
- function fakeEntryFileUrl(dir: string): string {
27
- const entryPath = path.join(dir, "gateway.ts");
28
- // File need not exist for `fileURLToPath` to work; the resolver
29
- // only uses its *parent directory* as the walk start point.
30
- return pathToFileURL(entryPath).href;
31
- }
32
-
33
- describe("resolveQQBotPluginVersion", () => {
34
- let tempRoots: string[] = [];
35
-
36
- beforeEach(() => {
37
- tempRoots = [];
38
- });
39
-
40
- afterEach(() => {
41
- for (const root of tempRoots) {
42
- fs.rmSync(root, { recursive: true, force: true });
43
- }
44
- });
45
-
46
- function newTree(): string {
47
- const root = createTempTree();
48
- tempRoots.push(root);
49
- return root;
50
- }
51
-
52
- it("returns the version from the nearest matching package.json", () => {
53
- const root = newTree();
54
- const pluginDir = path.join(root, "extensions", "qqbot");
55
- const bridgeDir = path.join(pluginDir, "src", "bridge");
56
- writeJson(path.join(pluginDir, "package.json"), {
57
- name: "@openclaw/qqbot",
58
- version: "2026.4.16",
59
- });
60
- fs.mkdirSync(bridgeDir, { recursive: true });
61
-
62
- const version = resolveQQBotPluginVersion(fakeEntryFileUrl(bridgeDir));
63
-
64
- expect(version).toBe("2026.4.16");
65
- });
66
-
67
- it("skips package.json files whose name field does not match", () => {
68
- const root = newTree();
69
- // Parent package.json belongs to the framework, not the plugin.
70
- writeJson(path.join(root, "package.json"), {
71
- name: "openclaw",
72
- version: "9.9.9",
73
- });
74
- const pluginDir = path.join(root, "extensions", "qqbot");
75
- const bridgeDir = path.join(pluginDir, "src", "bridge");
76
- writeJson(path.join(pluginDir, "package.json"), {
77
- name: "@openclaw/qqbot",
78
- version: "2026.4.16",
79
- });
80
- fs.mkdirSync(bridgeDir, { recursive: true });
81
-
82
- const version = resolveQQBotPluginVersion(fakeEntryFileUrl(bridgeDir));
83
-
84
- // Must stop at the plugin manifest, never bubble up to the framework one.
85
- expect(version).toBe("2026.4.16");
86
- });
87
-
88
- it("ignores manifests with unrelated name and returns unknown when no match is found", () => {
89
- const root = newTree();
90
- // Only an unrelated manifest exists up the tree.
91
- writeJson(path.join(root, "package.json"), {
92
- name: "some-other-package",
93
- version: "1.0.0",
94
- });
95
- const startDir = path.join(root, "extensions", "qqbot", "src", "bridge");
96
- fs.mkdirSync(startDir, { recursive: true });
97
-
98
- const version = resolveQQBotPluginVersion(fakeEntryFileUrl(startDir));
99
-
100
- expect(version).toBe(QQBOT_PLUGIN_VERSION_UNKNOWN);
101
- });
102
-
103
- it("returns unknown when no package.json exists above the start directory", () => {
104
- const root = newTree();
105
- const startDir = path.join(root, "extensions", "qqbot", "src", "bridge");
106
- fs.mkdirSync(startDir, { recursive: true });
107
-
108
- const version = resolveQQBotPluginVersion(fakeEntryFileUrl(startDir));
109
-
110
- expect(version).toBe(QQBOT_PLUGIN_VERSION_UNKNOWN);
111
- });
112
-
113
- it("returns unknown when the matching manifest lacks a version field", () => {
114
- const root = newTree();
115
- const pluginDir = path.join(root, "extensions", "qqbot");
116
- const bridgeDir = path.join(pluginDir, "src", "bridge");
117
- writeJson(path.join(pluginDir, "package.json"), {
118
- name: "@openclaw/qqbot",
119
- // version intentionally missing
120
- });
121
- fs.mkdirSync(bridgeDir, { recursive: true });
122
-
123
- const version = resolveQQBotPluginVersion(fakeEntryFileUrl(bridgeDir));
124
-
125
- expect(version).toBe(QQBOT_PLUGIN_VERSION_UNKNOWN);
126
- });
127
-
128
- it("tolerates a malformed package.json and keeps walking", () => {
129
- const root = newTree();
130
- const pluginDir = path.join(root, "extensions", "qqbot");
131
- const bridgeDir = path.join(pluginDir, "src", "bridge");
132
- // Broken manifest at the expected plugin location.
133
- fs.mkdirSync(pluginDir, { recursive: true });
134
- fs.writeFileSync(path.join(pluginDir, "package.json"), "{ not valid json", "utf8");
135
- // Valid matching manifest higher up (unusual layout but still resolvable).
136
- writeJson(path.join(root, "package.json"), {
137
- name: "@openclaw/qqbot",
138
- version: "2026.9.9",
139
- });
140
- fs.mkdirSync(bridgeDir, { recursive: true });
141
-
142
- const version = resolveQQBotPluginVersion(fakeEntryFileUrl(bridgeDir));
143
-
144
- expect(version).toBe("2026.9.9");
145
- });
146
- });
@@ -1,102 +0,0 @@
1
- /**
2
- * QQBot plugin version resolver.
3
- *
4
- * Reads the version field from this plugin's own `package.json` by
5
- * walking up the directory tree starting from `import.meta.url` of the
6
- * caller until a `package.json` whose `name` field matches the plugin
7
- * package id is located.
8
- *
9
- * Why not a hardcoded relative path?
10
- * - The source file can live at different depths depending on whether
11
- * we run from raw sources (`src/bridge/gateway.ts`) or a future
12
- * compiled output. Hardcoding `"../../package.json"` breaks as soon
13
- * as the source layout changes, which is what caused the previous
14
- * `vunknown` regression.
15
- * - A `name` guard prevents accidentally reading the parent
16
- * `openclaw/package.json` (the framework root) when the plugin
17
- * lives inside the monorepo.
18
- *
19
- * The lookup is performed only once per process at startup, so the
20
- * synchronous file I/O is negligible.
21
- */
22
-
23
- import fs from "node:fs";
24
- import path from "node:path";
25
- import { fileURLToPath } from "node:url";
26
-
27
- /** `name` field in this plugin's `package.json`. */
28
- const QQBOT_PLUGIN_PKG_NAME = "@openclaw/qqbot";
29
-
30
- /** Sentinel used when the version cannot be resolved. */
31
- export const QQBOT_PLUGIN_VERSION_UNKNOWN = "unknown";
32
-
33
- /**
34
- * Resolve the QQBot plugin version from `package.json`.
35
- *
36
- * @param startUrl — pass `import.meta.url` from the call site so the
37
- * lookup begins at the caller's file regardless of where this helper
38
- * itself lives. Falls back to this module's own location when omitted.
39
- */
40
- export function resolveQQBotPluginVersion(startUrl?: string): string {
41
- const entryUrl = startUrl ?? import.meta.url;
42
- let dir: string;
43
- try {
44
- dir = path.dirname(fileURLToPath(entryUrl));
45
- } catch {
46
- return QQBOT_PLUGIN_VERSION_UNKNOWN;
47
- }
48
-
49
- const root = path.parse(dir).root;
50
- while (dir && dir !== root) {
51
- const candidate = path.join(dir, "package.json");
52
- if (fs.existsSync(candidate)) {
53
- const version = readQQBotVersionFromManifest(candidate);
54
- if (version) {
55
- return version;
56
- }
57
- }
58
- const parent = path.dirname(dir);
59
- if (parent === dir) {
60
- break;
61
- }
62
- dir = parent;
63
- }
64
-
65
- return QQBOT_PLUGIN_VERSION_UNKNOWN;
66
- }
67
-
68
- /**
69
- * Read the `version` field from a `package.json` file and return it
70
- * only when the manifest describes the QQBot plugin itself.
71
- *
72
- * Returning `null` for mismatched or malformed manifests lets the
73
- * caller keep walking up the directory tree until the correct package
74
- * boundary is located.
75
- */
76
- function readQQBotVersionFromManifest(manifestPath: string): string | null {
77
- let raw: string;
78
- try {
79
- raw = fs.readFileSync(manifestPath, "utf8");
80
- } catch {
81
- return null;
82
- }
83
-
84
- let parsed: unknown;
85
- try {
86
- parsed = JSON.parse(raw);
87
- } catch {
88
- return null;
89
- }
90
-
91
- if (!parsed || typeof parsed !== "object") {
92
- return null;
93
- }
94
- const manifest = parsed as { name?: unknown; version?: unknown };
95
- if (manifest.name !== QQBOT_PLUGIN_PKG_NAME) {
96
- return null;
97
- }
98
- if (typeof manifest.version !== "string" || manifest.version.length === 0) {
99
- return null;
100
- }
101
- return manifest.version;
102
- }
@@ -1,25 +0,0 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk/core";
2
- import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
3
- import type { GatewayPluginRuntime } from "../engine/gateway/types.js";
4
- import { setOpenClawVersion } from "../engine/messaging/sender.js";
5
-
6
- // Single plugin runtime per process — concurrent multi-tenant qqbot runtimes are not supported.
7
- const { setRuntime: _setRuntime, getRuntime: getQQBotRuntime } =
8
- createPluginRuntimeStore<PluginRuntime>({
9
- pluginId: "qqbot",
10
- errorMessage: "QQBot runtime not initialized",
11
- });
12
-
13
- /** Set the QQBot runtime and inject the framework version into the User-Agent. */
14
- function setQQBotRuntime(runtime: PluginRuntime): void {
15
- _setRuntime(runtime);
16
- // Inject the framework version into the User-Agent string (same as standalone).
17
- setOpenClawVersion(runtime.version);
18
- }
19
-
20
- export { getQQBotRuntime, setQQBotRuntime };
21
-
22
- /** Type-narrowed getter for engine/ modules that need GatewayPluginRuntime. */
23
- export function getQQBotRuntimeForEngine(): GatewayPluginRuntime {
24
- return getQQBotRuntime() as GatewayPluginRuntime;
25
- }
@@ -1,131 +0,0 @@
1
- /**
2
- * SDK adapter — binds engine port interfaces to the framework's shared
3
- * SDK implementations.
4
- *
5
- * This file lives in bridge/ (not engine/) because it imports from
6
- * `openclaw/plugin-sdk/*`. The engine layer stays zero-SDK-dependency;
7
- * only the bridge layer couples to the framework.
8
- */
9
-
10
- import {
11
- implicitMentionKindWhen,
12
- resolveInboundMentionDecision,
13
- } from "openclaw/plugin-sdk/channel-mention-gating";
14
- import {
15
- buildPendingHistoryContextFromMap,
16
- clearHistoryEntriesIfEnabled,
17
- recordPendingHistoryEntryIfEnabled,
18
- type HistoryEntry as SdkHistoryEntry,
19
- } from "openclaw/plugin-sdk/reply-history";
20
- import type { HistoryPort, HistoryEntryLike } from "../engine/adapter/history.port.js";
21
- import type {
22
- MentionGatePort,
23
- MentionGateDecision,
24
- MentionFacts,
25
- MentionPolicy,
26
- } from "../engine/adapter/mention-gate.port.js";
27
-
28
- // ============ History Adapter ============
29
-
30
- // Helper: cast engine Map to SDK Map. TypeScript Map is invariant on its
31
- // value type, but the shapes are structurally identical (HistoryEntryLike
32
- // ⊇ SdkHistoryEntry). The `as unknown as` double-cast is safe here.
33
- function asSdkMap<T>(map: Map<string, T[]>): Map<string, SdkHistoryEntry[]> {
34
- return map as unknown as Map<string, SdkHistoryEntry[]>;
35
- }
36
-
37
- /**
38
- * History adapter backed by SDK `reply-history`.
39
- *
40
- * Delegates record/build/clear to the SDK's shared implementation so
41
- * the engine benefits from SDK improvements (e.g. future visibility
42
- * filtering) without code duplication.
43
- */
44
- export function createSdkHistoryAdapter(): HistoryPort {
45
- return {
46
- recordPendingHistoryEntry<T extends HistoryEntryLike>(params: {
47
- historyMap: Map<string, T[]>;
48
- historyKey: string;
49
- entry?: T | null;
50
- limit: number;
51
- }): T[] {
52
- return recordPendingHistoryEntryIfEnabled({
53
- historyMap: asSdkMap(params.historyMap),
54
- historyKey: params.historyKey,
55
- entry: params.entry as SdkHistoryEntry | undefined,
56
- limit: params.limit,
57
- }) as T[];
58
- },
59
-
60
- buildPendingHistoryContext(params: {
61
- historyMap: Map<string, HistoryEntryLike[]>;
62
- historyKey: string;
63
- limit: number;
64
- currentMessage: string;
65
- formatEntry: (entry: HistoryEntryLike) => string;
66
- lineBreak?: string;
67
- }): string {
68
- return buildPendingHistoryContextFromMap({
69
- historyMap: asSdkMap(params.historyMap),
70
- historyKey: params.historyKey,
71
- limit: params.limit,
72
- currentMessage: params.currentMessage,
73
- formatEntry: params.formatEntry as (entry: SdkHistoryEntry) => string,
74
- lineBreak: params.lineBreak,
75
- });
76
- },
77
-
78
- clearPendingHistory(params: {
79
- historyMap: Map<string, HistoryEntryLike[]>;
80
- historyKey: string;
81
- limit: number;
82
- }): void {
83
- clearHistoryEntriesIfEnabled({
84
- historyMap: asSdkMap(params.historyMap),
85
- historyKey: params.historyKey,
86
- limit: params.limit,
87
- });
88
- },
89
- };
90
- }
91
-
92
- // ============ MentionGate Adapter ============
93
-
94
- /**
95
- * MentionGate adapter backed by SDK `channel-mention-gating`.
96
- *
97
- * Maps the engine's mention facts/policy to the SDK's
98
- * `resolveInboundMentionDecision` call, normalizing the implicit
99
- * mention boolean into the SDK's typed `ImplicitMentionKind[]`.
100
- */
101
- export function createSdkMentionGateAdapter(): MentionGatePort {
102
- return {
103
- resolveInboundMentionDecision(params: {
104
- facts: MentionFacts;
105
- policy: MentionPolicy;
106
- }): MentionGateDecision {
107
- const result = resolveInboundMentionDecision({
108
- facts: {
109
- canDetectMention: params.facts.canDetectMention,
110
- wasMentioned: params.facts.wasMentioned,
111
- hasAnyMention: params.facts.hasAnyMention,
112
- implicitMentionKinds:
113
- params.facts.implicitMentionKinds ?? implicitMentionKindWhen("reply_to_bot", false),
114
- },
115
- policy: {
116
- isGroup: params.policy.isGroup,
117
- requireMention: params.policy.requireMention,
118
- allowTextCommands: params.policy.allowTextCommands,
119
- hasControlCommand: params.policy.hasControlCommand,
120
- commandAuthorized: params.policy.commandAuthorized,
121
- },
122
- });
123
- return {
124
- effectiveWasMentioned: result.effectiveWasMentioned,
125
- shouldSkip: result.shouldSkip,
126
- shouldBypassMention: result.shouldBypassMention,
127
- implicitMention: result.implicitMention,
128
- };
129
- },
130
- };
131
- }
@@ -1,144 +0,0 @@
1
- import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
2
- import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
3
- import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
4
- import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
5
- import { applyQQBotAccountConfig, resolveQQBotAccount } from "../config.js";
6
-
7
- type SetupPrompter = Parameters<NonNullable<ChannelSetupWizard["finalize"]>>[0]["prompter"];
8
- type SetupRuntime = Parameters<NonNullable<ChannelSetupWizard["finalize"]>>[0]["runtime"];
9
-
10
- function isQQBotAccountConfigured(cfg: OpenClawConfig, accountId: string): boolean {
11
- const account = resolveQQBotAccount(cfg, accountId, { allowUnresolvedSecretRef: true });
12
- return Boolean(account.appId && account.clientSecret);
13
- }
14
-
15
- async function linkViaQrCode(params: {
16
- cfg: OpenClawConfig;
17
- accountId: string;
18
- prompter: SetupPrompter;
19
- runtime: SetupRuntime;
20
- }): Promise<OpenClawConfig> {
21
- try {
22
- const { qrConnect } = await import("@tencent-connect/qqbot-connector");
23
-
24
- const accounts: { appId: string; appSecret: string }[] = await qrConnect({
25
- source: "openclaw",
26
- });
27
-
28
- if (accounts.length === 0) {
29
- await params.prompter.note("未获取到任何 QQ Bot 账号信息。", "QQ Bot");
30
- return params.cfg;
31
- }
32
-
33
- let next = params.cfg;
34
-
35
- for (let i = 0; i < accounts.length; i++) {
36
- const { appId, appSecret } = accounts[i];
37
- // use current account id for first account, and use app id for subsequent accounts
38
- const targetAccountId = i === 0 ? params.accountId : appId;
39
-
40
- next = applyQQBotAccountConfig(next, targetAccountId, {
41
- appId,
42
- clientSecret: appSecret,
43
- });
44
- }
45
-
46
- if (accounts.length === 1) {
47
- params.runtime.log(`✔ QQ Bot 绑定成功!(AppID: ${accounts[0].appId})`);
48
- } else {
49
- const idList = accounts.map((a) => a.appId).join(", ");
50
- params.runtime.log(`✔ ${accounts.length} 个 QQ Bot 绑定成功!(AppID: ${idList})`);
51
- }
52
-
53
- return next;
54
- } catch (error) {
55
- params.runtime.error(`QQ Bot 绑定失败: ${String(error)}`);
56
- await params.prompter.note(
57
- [
58
- "绑定失败,您可以稍后手动配置。",
59
- `文档: ${formatDocsLink("/channels/qqbot", "qqbot")}`,
60
- ].join("\n"),
61
- "QQ Bot",
62
- );
63
- return params.cfg;
64
- }
65
- }
66
-
67
- async function linkViaManualInput(params: {
68
- cfg: OpenClawConfig;
69
- accountId: string;
70
- prompter: SetupPrompter;
71
- }): Promise<OpenClawConfig> {
72
- const appId = await params.prompter.text({
73
- message: "请输入 QQ Bot AppID",
74
- validate: (value: string) => (value.trim() ? undefined : "AppID 不能为空"),
75
- });
76
-
77
- const appSecret = await params.prompter.text({
78
- message: "请输入 QQ Bot AppSecret",
79
- validate: (value: string) => (value.trim() ? undefined : "AppSecret 不能为空"),
80
- });
81
-
82
- const next = applyQQBotAccountConfig(params.cfg, params.accountId, {
83
- appId: appId.trim(),
84
- clientSecret: appSecret.trim(),
85
- });
86
-
87
- await params.prompter.note("✔ QQ Bot 配置完成!", "QQ Bot");
88
- return next;
89
- }
90
-
91
- export async function finalizeQQBotSetup(params: {
92
- cfg: OpenClawConfig;
93
- accountId: string;
94
- forceAllowFrom: boolean;
95
- prompter: SetupPrompter;
96
- runtime: SetupRuntime;
97
- }): Promise<{ cfg: OpenClawConfig }> {
98
- const accountId = params.accountId.trim() || DEFAULT_ACCOUNT_ID;
99
- let next = params.cfg;
100
-
101
- const configured = isQQBotAccountConfigured(next, accountId);
102
-
103
- const mode = await params.prompter.select({
104
- message: configured ? "QQ 已绑定,选择操作" : "选择 QQ 绑定方式",
105
- options: [
106
- {
107
- value: "qr",
108
- label: "扫码绑定(推荐)",
109
- hint: "使用 QQ 扫描二维码自动完成绑定",
110
- },
111
- {
112
- value: "manual",
113
- label: "手动输入 QQ Bot AppID 和 AppSecret",
114
- hint: "需到 QQ 开放平台 q.qq.com 查看",
115
- },
116
- {
117
- value: "skip",
118
- label: configured ? "保持当前配置" : "稍后配置",
119
- },
120
- ],
121
- });
122
-
123
- if (mode === "qr") {
124
- next = await linkViaQrCode({
125
- cfg: next,
126
- accountId,
127
- prompter: params.prompter,
128
- runtime: params.runtime,
129
- });
130
- } else if (mode === "manual") {
131
- next = await linkViaManualInput({
132
- cfg: next,
133
- accountId,
134
- prompter: params.prompter,
135
- });
136
- } else if (!configured) {
137
- await params.prompter.note(
138
- ["您可以稍后运行以下命令重新选择 QQ Bot 进行配置:", " openclaw channels add"].join("\n"),
139
- "QQ Bot",
140
- );
141
- }
142
-
143
- return { cfg: next };
144
- }
@@ -1,34 +0,0 @@
1
- import {
2
- createStandardChannelSetupStatus,
3
- setSetupChannelEnabled,
4
- } from "openclaw/plugin-sdk/setup";
5
- import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
6
- import { isAccountConfigured } from "../../engine/config/resolve.js";
7
- import { listQQBotAccountIds, resolveQQBotAccount } from "../config.js";
8
- import { finalizeQQBotSetup } from "./finalize.js";
9
-
10
- const channel = "qqbot" as const;
11
-
12
- export const qqbotSetupWizard: ChannelSetupWizard = {
13
- channel,
14
- status: createStandardChannelSetupStatus({
15
- channelLabel: "QQ Bot",
16
- configuredLabel: "configured",
17
- unconfiguredLabel: "needs AppID + AppSercet",
18
- configuredHint: "configured",
19
- unconfiguredHint: "needs AppID + AppSercet",
20
- configuredScore: 1,
21
- unconfiguredScore: 6,
22
- resolveConfigured: ({ cfg, accountId }) =>
23
- (accountId ? [accountId] : listQQBotAccountIds(cfg)).some((resolvedAccountId) => {
24
- const account = resolveQQBotAccount(cfg, resolvedAccountId, {
25
- allowUnresolvedSecretRef: true,
26
- });
27
- return isAccountConfigured(account as never);
28
- }),
29
- }),
30
- credentials: [],
31
- finalize: async ({ cfg, accountId, forceAllowFrom, prompter, runtime }) =>
32
- await finalizeQQBotSetup({ cfg, accountId, forceAllowFrom, prompter, runtime }),
33
- disable: (cfg) => setSetupChannelEnabled(cfg, channel, false),
34
- };