@cxyhhhhh/openclaw-qqbot 2.0.0-dev.202607081621 → 2.0.0-dev.202607082155

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 (36) hide show
  1. package/dist/index.cjs +606 -427
  2. package/dist/index.cjs.map +1 -1
  3. package/index.ts +1 -1
  4. package/package.json +2 -2
  5. package/skills/qqbot-channel/SKILL.md +33 -11
  6. package/src/{runtime-adapter/contract-check.ts → adapter/contract.ts} +4 -7
  7. package/src/{runtime-adapter/gateway-runtime.ts → adapter/gateway.ts} +2 -3
  8. package/src/{runtime-adapter → adapter}/index.ts +3 -3
  9. package/src/{runtime-adapter/lint-runtime-access.ts → adapter/lint.ts} +7 -7
  10. package/src/adapter/media.ts +112 -0
  11. package/src/adapter/setup.ts +55 -0
  12. package/src/adapter/workspace.ts +29 -0
  13. package/src/bot-instance.ts +1 -1
  14. package/src/channel.ts +1 -1
  15. package/src/commands/bot-approve.ts +1 -1
  16. package/src/commands/bot-logs.ts +1 -1
  17. package/src/commands/bot-pairing.ts +1 -1
  18. package/src/commands/config-util.ts +1 -1
  19. package/src/dispatch/body-assembler.ts +1 -1
  20. package/src/dispatch/dispatch.ts +75 -45
  21. package/src/features/approval-handler.ts +1 -1
  22. package/src/gateway/event-handlers.ts +1 -1
  23. package/src/gateway/lifecycle.ts +1 -1
  24. package/src/gateway/qqbot-gateway.ts +1 -1
  25. package/src/middleware/access-control.ts +1 -1
  26. package/src/middleware/attachment.ts +4 -4
  27. package/src/openclaw-plugin-sdk.d.ts +41 -0
  28. package/src/outbound/media-send.ts +4 -6
  29. package/src/runtime.ts +3 -1
  30. package/src/setup/finalize.ts +1 -2
  31. package/src/setup/surface.ts +5 -6
  32. package/src/utils/pkg-version.ts +100 -49
  33. package/tsup.config.ts +1 -1
  34. /package/src/{runtime-adapter/pairing-runtime.ts → adapter/pairing.ts} +0 -0
  35. /package/src/{runtime-adapter → adapter}/resolve.ts +0 -0
  36. /package/src/{runtime-adapter/webhook-adapter.ts → adapter/webhook.ts} +0 -0
@@ -22,7 +22,7 @@ import { setupMiddlewares } from './middleware-setup.js';
22
22
  import { handleMessage, handleInteraction } from './event-handlers.js';
23
23
  import { getQQBotDataDir } from '../utils/platform.js';
24
24
  import { buildUserAgent } from '../bot-instance.js';
25
- import { createPluginWebhookAdapter } from '../runtime-adapter/webhook-adapter.js';
25
+ import { createPluginWebhookAdapter } from '../adapter/webhook.js';
26
26
  import { getPersistedRefIndexStore } from '../features/ref-index-store.js';
27
27
  import { getCachedMsgId } from '../features/msgid-cache.js';
28
28
 
@@ -5,7 +5,7 @@
5
5
  * 模式:disabled | open | allowlist | pairing(仅 c2c)
6
6
  */
7
7
  import type { Middleware } from '@tencent-connect/qqbot-nodejs';
8
- import { getPairingApi } from '../runtime-adapter/pairing-runtime.js';
8
+ import { getPairingApi } from '../adapter/pairing.js';
9
9
 
10
10
 
11
11
  /**
@@ -14,11 +14,11 @@ import {
14
14
  convertSilkToWav,
15
15
  isVoiceAttachment,
16
16
  } from '@tencent-connect/qqbot-nodejs/protocol';
17
- import { saveRemoteMedia } from 'openclaw/plugin-sdk/media-runtime';
18
17
  import type { MessageAttachment } from '../types.js';
19
18
  import { transcribeAudio, resolveSTTConfig } from '../utils/stt.js';
20
19
  import { formatVoiceText, formatDuration, type VoiceTranscript, type TranscriptSource } from '../utils/voice-text.js';
21
- import { getAdapters } from '../runtime-adapter/resolve.js';
20
+ import { downloadRemoteMedia } from '../adapter/media.js';
21
+ import { getAdapters } from '../adapter/resolve.js';
22
22
 
23
23
  export { formatVoiceText, formatDuration };
24
24
  export type { VoiceTranscript, TranscriptSource };
@@ -298,12 +298,12 @@ async function downloadMediaFile(
298
298
  }
299
299
 
300
300
  try {
301
- const result = await saveRemoteMedia({
301
+ const result = await downloadRemoteMedia({
302
302
  url,
303
303
  subdir: 'qqbot/downloads',
304
304
  originalFilename: filename,
305
305
  maxBytes: 500 * 1024 * 1024,
306
- timeoutMs: 60_000,
306
+ timeoutMs: 120_000,
307
307
  });
308
308
  log?.debug?.(`Downloaded: ${result.path}`);
309
309
  return result.path;
@@ -675,6 +675,47 @@ declare module "openclaw/plugin-sdk" {
675
675
  export function normalizeAccountId(accountId: string | undefined | null): string;
676
676
  }
677
677
 
678
+ declare module "openclaw/plugin-sdk/setup" {
679
+ export interface ChannelSetupWizardStatus {
680
+ channelLabel: string;
681
+ configuredLabel: string;
682
+ unconfiguredLabel: string;
683
+ configuredHint?: string;
684
+ unconfiguredHint?: string;
685
+ configuredScore?: number;
686
+ unconfiguredScore?: number;
687
+ includeStatusLine?: boolean;
688
+ resolveConfigured: (params: { cfg: unknown; accountId: string }) => boolean;
689
+ }
690
+
691
+ export interface ChannelSetupWizard {
692
+ channel: string;
693
+ status: ChannelSetupWizardStatus;
694
+ credentials?: Array<{ id: string; label: string }>;
695
+ onStatusChange?: (params: { cfg: unknown }) => void | Promise<void>;
696
+ configure?: (params: { cfg: unknown; accountId: string; prompter: unknown; runtime: unknown }) => Promise<unknown>;
697
+ finalize: (params: { cfg: unknown; accountId: string; prompter: unknown; runtime: unknown }) => Promise<unknown>;
698
+ enable?: (cfg: unknown) => unknown;
699
+ disable?: (cfg: unknown) => unknown;
700
+ }
701
+
702
+ export function createStandardChannelSetupStatus(
703
+ opts: ChannelSetupWizardStatus,
704
+ ): ChannelSetupWizardStatus;
705
+
706
+ export function setSetupChannelEnabled(
707
+ cfg: unknown,
708
+ channel: string,
709
+ enabled: boolean,
710
+ ): void;
711
+
712
+ export const DEFAULT_ACCOUNT_ID: string;
713
+ }
714
+
715
+ declare module "openclaw/plugin-sdk/setup-tools" {
716
+ export function formatDocsLink(href: string, text?: string): string;
717
+ }
718
+
678
719
  declare module "openclaw/plugin-sdk/approval-runtime" {
679
720
  export interface ExecApprovalReplyMetadata {
680
721
  approvalId: string;
@@ -13,11 +13,11 @@
13
13
  import * as path from 'node:path';
14
14
  import * as fs from 'node:fs';
15
15
  import * as os from 'node:os';
16
- import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from 'openclaw/plugin-sdk/health';
16
+ import { resolveAgentWorkspace } from '../adapter/workspace.js';
17
17
  import type { ReplyTarget } from '@tencent-connect/qqbot-nodejs';
18
18
  import type { QQBotGateway } from '../gateway/index.js';
19
19
  import { tryGetQQBotRuntime } from '../runtime.js';
20
- import { getAdapters } from '../runtime-adapter/resolve.js';
20
+ import { getAdapters } from '../adapter/resolve.js';
21
21
  import type { PluginLogger } from '../utils/plugin-logger.js';
22
22
  import { getGateway } from './outbound-service.js';
23
23
  import { parseTarget } from './target.js';
@@ -174,13 +174,11 @@ function resolveMediaPath(source: string, log?: SendMediaParams['log'], workspac
174
174
  return { ok: true, path: real, isLocal: true };
175
175
  }
176
176
 
177
- /** Agent ID → workspaceDir(出站时解析,无需透传) */
177
+ /** Agent ID → workspaceDir(动态加载 plugin-sdk/health) */
178
178
  function resolveWorkspaceFromAgent(agentId?: string): string | undefined {
179
179
  const cfg = resolveConfigViaAdapter();
180
180
  if (!cfg) return undefined;
181
- try {
182
- return resolveAgentWorkspaceDir(cfg, agentId ?? resolveDefaultAgentId(cfg));
183
- } catch { return undefined; }
181
+ return resolveAgentWorkspace(cfg, agentId);
184
182
  }
185
183
 
186
184
  /** 通过 adapter 获取配置 */
package/src/runtime.ts CHANGED
@@ -7,13 +7,15 @@
7
7
  import type { PluginRuntime } from "openclaw/plugin-sdk";
8
8
  import { setOpenClawVersion } from "./bot-instance.js";
9
9
  import { flushAllRefIndexStores } from "./features/ref-index-store.js";
10
+ import { getOpenclawVersion } from "./utils/pkg-version.js";
10
11
 
11
12
  let runtime: PluginRuntime | null = null;
12
13
  let exitHooksInstalled = false;
13
14
 
14
15
  export function setQQBotRuntime(next: PluginRuntime) {
15
16
  runtime = next;
16
- setOpenClawVersion(next.version);
17
+ const version = getOpenclawVersion(next.version);
18
+ setOpenClawVersion(version);
17
19
  installExitHooksOnce();
18
20
  }
19
21
 
@@ -2,8 +2,7 @@
2
2
  * openclaw setup 引导 — QQ Bot 扫码/手动绑定
3
3
  */
4
4
  import type { OpenClawConfig } from 'openclaw/plugin-sdk';
5
- import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk/setup';
6
- import { formatDocsLink } from 'openclaw/plugin-sdk/setup-tools';
5
+ import { DEFAULT_ACCOUNT_ID, formatDocsLink } from '../adapter/setup.js';
7
6
  import { qrConnect } from '@tencent-connect/qqbot-connector';
8
7
  import { applyQQBotAccountConfig, resolveQQBotAccount } from '../config.js';
9
8
 
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * OpenClaw setup 向导 — QQ Bot 配置界面
3
3
  */
4
- import type { ChannelSetupWizard } from 'openclaw/plugin-sdk/setup';
5
- import { createStandardChannelSetupStatus } from 'openclaw/plugin-sdk/setup';
4
+ import type { ChannelSetupWizard } from '../adapter/setup.js';
5
+ import { createStandardChannelSetupStatus, setSetupChannelEnabled } from '../adapter/setup.js';
6
6
  import { listQQBotAccountIds, resolveQQBotAccount } from '../config.js';
7
7
  import { finalizeQQBotSetup } from './finalize.js';
8
- import { setSetupChannelEnabled } from 'openclaw/plugin-sdk/setup';
9
8
 
10
9
  const CHANNEL = 'qqbot' as const;
11
10
 
@@ -20,8 +19,8 @@ export const qqbotSetupWizard: ChannelSetupWizard = {
20
19
  configuredScore: 1,
21
20
  unconfiguredScore: 6,
22
21
  resolveConfigured: ({ cfg, accountId }) =>
23
- (accountId ? [accountId] : listQQBotAccountIds(cfg)).some((id) => {
24
- const account = resolveQQBotAccount(cfg, id);
22
+ (accountId ? [accountId] : listQQBotAccountIds(cfg as any)).some((id) => {
23
+ const account = resolveQQBotAccount(cfg as any, id);
25
24
  return Boolean(account.appId && account.clientSecret);
26
25
  }),
27
26
  }),
@@ -29,5 +28,5 @@ export const qqbotSetupWizard: ChannelSetupWizard = {
29
28
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
29
  finalize: (async ({ cfg, accountId, prompter, runtime }: any) =>
31
30
  finalizeQQBotSetup({ cfg, accountId, prompter: prompter as any, runtime: runtime as any })) as any,
32
- disable: (cfg) => setSetupChannelEnabled(cfg, CHANNEL, false),
31
+ disable: (cfg) => { setSetupChannelEnabled(cfg, CHANNEL, false); return cfg; },
33
32
  };
@@ -1,35 +1,26 @@
1
1
  /**
2
- * 从当前文件位置向上遍历目录树查找 package.json 并读取 version。
3
- * 不依赖硬编码的 "../" 层级,无论编译输出结构如何变化都能可靠找到。
2
+ * 版本号获取工具。
4
3
  *
5
- * 兼容 CJS(tsup bundle)和 ESM 两种输出格式。
4
+ * - getPackageVersion(): 插件自身版本(@tencent-connect/openclaw-qqbot)
5
+ * - getOpenclawVersion(): OpenClaw 框架版本降级链
6
+ *
7
+ * 兼容 CJS(tsup bundle)环境,不依赖 require.resolve。
6
8
  */
7
9
 
8
- import { fileURLToPath } from "node:url";
9
- import { createRequire } from "node:module";
10
10
  import path from "node:path";
11
11
  import fs from "node:fs";
12
12
 
13
- /** 已定位到的 package.json 路径,避免重复遍历目录树 */
13
+ /** 已定位到的插件 package.json 路径,避免重复遍历目录树 */
14
14
  let _resolvedPkgPath: string | null = null;
15
15
 
16
+ /** OpenClaw 框架版本缓存(一次查完不再重复 io) */
17
+ let _cachedOpenclawVersion: string | undefined;
18
+
16
19
  /**
17
- * 获取当前文件路径(兼容 CJS/ESM)。
18
- * tsup CJS bundle 中 __filename 由构建工具注入。
20
+ * 获取插件自身版本号(@tencent-connect/openclaw-qqbot)。
21
+ * __filename 向上遍历目录树找同名 package.json。
19
22
  */
20
- function getCurrentFilename(metaUrl?: string): string {
21
- if (metaUrl) {
22
- return fileURLToPath(metaUrl);
23
- }
24
- // CJS 环境:__filename 由 tsup 注入
25
- if (typeof __filename !== "undefined") {
26
- return __filename;
27
- }
28
- return process.cwd();
29
- }
30
-
31
- export function getPackageVersion(metaUrl?: string): string {
32
- // 如果之前已定位到 package.json 路径,直接重新读取(快速路径)
23
+ export function getPackageVersion(): string {
33
24
  if (_resolvedPkgPath) {
34
25
  try {
35
26
  const pkg = JSON.parse(fs.readFileSync(_resolvedPkgPath, "utf8"));
@@ -37,45 +28,105 @@ export function getPackageVersion(metaUrl?: string): string {
37
28
  return pkg.version as string;
38
29
  }
39
30
  } catch {
40
- // 文件可能已被删除(升级过程中),清除路径缓存,走完整查找
41
31
  _resolvedPkgPath = null;
42
32
  }
43
33
  }
44
34
 
45
- // Strategy 1: 从调用者位置向上遍历找 package.json
46
- const startFile = getCurrentFilename(metaUrl);
47
- let dir = path.dirname(startFile);
48
- const root = path.parse(dir).root;
49
-
50
- while (dir !== root) {
51
- const candidate = path.join(dir, "package.json");
52
- try {
53
- if (fs.existsSync(candidate)) {
54
- const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
55
- // 确认是我们自己的包(避免找到其他 package.json)
56
- if (pkg.name === "@tencent-connect/openclaw-qqbot" && pkg.version) {
57
- _resolvedPkgPath = candidate;
58
- return pkg.version as string;
35
+ // __filename 向上遍历目录树
36
+ if (typeof __filename === "string") {
37
+ let dir = path.dirname(__filename);
38
+ const root = path.parse(dir).root;
39
+ while (dir !== root) {
40
+ const candidate = path.join(dir, "package.json");
41
+ try {
42
+ if (fs.existsSync(candidate)) {
43
+ const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
44
+ if (pkg.name === "@tencent-connect/openclaw-qqbot" && pkg.version) {
45
+ _resolvedPkgPath = candidate;
46
+ return pkg.version as string;
47
+ }
59
48
  }
60
- }
61
- } catch {
62
- // ignore and try parent
49
+ } catch { /* next */ }
50
+ dir = path.dirname(dir);
63
51
  }
64
- dir = path.dirname(dir);
65
52
  }
66
53
 
67
- // Strategy 2: fallback 用 createRequire 尝试常见相对路径
54
+ return "unknown";
55
+ }
56
+
57
+ // ── OpenClaw 框架版本 ──
58
+
59
+ /**
60
+ * OpenClaw 框架版本降级链(优先后)。
61
+ * 1. PluginRuntime.version(3.31+,非 "unknown")
62
+ * 2. OPENCLAW_VERSION / OPENCLAW_SERVICE_VERSION 环境变量
63
+ * 3. 文件系统搜索 openclaw/package.json
64
+ * 4. 兜底 "unknown"
65
+ */
66
+ export function getOpenclawVersion(runtimeVersion?: string): string {
67
+ if (_cachedOpenclawVersion) return _cachedOpenclawVersion;
68
+
69
+ // 1. runtime.version(排除兜底值)
70
+ if (runtimeVersion && runtimeVersion !== "unknown") {
71
+ return _cachedOpenclawVersion = runtimeVersion;
72
+ }
73
+ // 2. 环境变量
74
+ if (process.env.OPENCLAW_VERSION) {
75
+ return _cachedOpenclawVersion = process.env.OPENCLAW_VERSION;
76
+ }
77
+ if (process.env.OPENCLAW_SERVICE_VERSION) {
78
+ return _cachedOpenclawVersion = process.env.OPENCLAW_SERVICE_VERSION;
79
+ }
80
+ // 3. 文件系统
81
+ const pkgVer = readOpenclawPackageVersion();
82
+ if (pkgVer) return _cachedOpenclawVersion = pkgVer;
83
+
84
+ return "unknown";
85
+ }
86
+
87
+ function readOpenclawPackageVersion(): string | undefined {
68
88
  try {
69
- const req = createRequire(__filename);
70
- for (const rel of ["../../package.json", "../package.json", "./package.json"]) {
89
+ const dirs = searchRoots();
90
+ for (const dir of dirs) {
91
+ const pkgPath = path.join(dir, "package.json");
71
92
  try {
72
- const pkg = req(rel);
73
- if (pkg?.version) {
74
- return pkg.version as string;
75
- }
93
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")) as { name?: string; version?: string };
94
+ if (pkg.name === "openclaw" && pkg.version) return pkg.version;
76
95
  } catch { /* next */ }
77
96
  }
78
- } catch { /* fallback */ }
97
+ } catch { /* fallthrough */ }
98
+ return undefined;
99
+ }
79
100
 
80
- return "unknown";
101
+ function searchRoots(): string[] {
102
+ const roots: string[] = [];
103
+
104
+ // 从插件自身路径向上查找
105
+ if (typeof __filename === "string") {
106
+ let dir = path.dirname(__filename);
107
+ for (let i = 0; i < 10; i++) {
108
+ roots.push(dir);
109
+ const parent = path.dirname(dir);
110
+ if (parent === dir) break;
111
+ dir = parent;
112
+ }
113
+ }
114
+
115
+ // STATE_DIR 指向的 openclaw 安装目录附近
116
+ for (const key of ["OPENCLAW_STATE_DIR", "CLAWDBOT_STATE_DIR", "MOLTBOT_STATE_DIR"]) {
117
+ const v = process.env[key];
118
+ if (v) {
119
+ roots.push(path.dirname(v));
120
+ roots.push(path.resolve(v, ".."));
121
+ }
122
+ }
123
+
124
+ // 常见安装路径
125
+ const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
126
+ for (const name of ["openclaw", "clawdbot", "moltbot"]) {
127
+ roots.push(path.join(home, `.${name}`));
128
+ }
129
+ roots.push(process.cwd());
130
+
131
+ return [...new Set(roots.filter((r) => typeof r === "string" && r.length > 0))];
81
132
  }
package/tsup.config.ts CHANGED
@@ -22,7 +22,7 @@ export default defineConfig({
22
22
  clean: true,
23
23
  external: [
24
24
  'openclaw',
25
- 'openclaw/plugin-sdk',
25
+ /^openclaw\/plugin-sdk(\/.+)?$/,
26
26
  ],
27
27
  noExternal: [
28
28
  '@tencent-connect/qqbot-nodejs',
File without changes