agentdev 0.1.8 → 0.1.10

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 (187) hide show
  1. package/dist/BasicAgent-R7DYGTHF.js +13 -0
  2. package/dist/ExplorerAgent-DXY3OQ5U.js +13 -0
  3. package/dist/{chunk-OBOU27DM.js → chunk-3AR3JBW6.js} +6701 -1730
  4. package/dist/chunk-3AR3JBW6.js.map +1 -0
  5. package/dist/{chunk-F3PR7UTL.js → chunk-72H6A6NB.js} +5 -3
  6. package/dist/{chunk-F3PR7UTL.js.map → chunk-72H6A6NB.js.map} +1 -1
  7. package/dist/{chunk-TSASFMRF.js → chunk-BAP2GCYH.js} +1 -1
  8. package/dist/chunk-BAP2GCYH.js.map +1 -0
  9. package/dist/{chunk-3BPSNNK3.js → chunk-EECW6PYP.js} +11 -9
  10. package/dist/chunk-EECW6PYP.js.map +1 -0
  11. package/dist/chunk-G5ECPY4K.js +551 -0
  12. package/dist/chunk-G5ECPY4K.js.map +1 -0
  13. package/dist/{chunk-LLV3W326.js → chunk-NORTAQIL.js} +67 -20
  14. package/dist/chunk-NORTAQIL.js.map +1 -0
  15. package/dist/{chunk-LQTEETML.js → chunk-REOJZCSZ.js} +12 -7
  16. package/dist/chunk-REOJZCSZ.js.map +1 -0
  17. package/dist/cli/server.js +2 -2
  18. package/dist/cli/viewer.js +2 -2
  19. package/dist/create-feature-cli.js +26 -7
  20. package/dist/features/shell/templates/bash.render.d.ts +1 -1
  21. package/dist/features/websearch/templates/web-fetch.render.d.ts +1 -1
  22. package/dist/index.d.ts +1755 -497
  23. package/dist/index.js +30 -8
  24. package/dist/index.js.map +1 -1
  25. package/dist/{notification-3VEAP7YF.js → notification-NWVOS2WR.js} +3 -3
  26. package/dist/tools-LDR3LIJP.js +14 -0
  27. package/dist/tools-LDR3LIJP.js.map +1 -0
  28. package/dist/{types-DUKIIntb.d.ts → types-CF5UsxD9.d.ts} +3 -0
  29. package/node_modules/@sliverp/qqbot/LICENSE +21 -0
  30. package/node_modules/@sliverp/qqbot/README.md +427 -0
  31. package/node_modules/@sliverp/qqbot/README.standalone.zh.md +77 -0
  32. package/node_modules/@sliverp/qqbot/README.zh.md +423 -0
  33. package/node_modules/@sliverp/qqbot/bin/qqbot-cli.js +227 -0
  34. package/node_modules/@sliverp/qqbot/clawdbot.plugin.json +16 -0
  35. package/node_modules/@sliverp/qqbot/dist/index.d.ts +20 -0
  36. package/node_modules/@sliverp/qqbot/dist/index.js +25 -0
  37. package/node_modules/@sliverp/qqbot/dist/src/agent.d.ts +76 -0
  38. package/node_modules/@sliverp/qqbot/dist/src/agent.js +36 -0
  39. package/node_modules/@sliverp/qqbot/dist/src/api.d.ts +138 -0
  40. package/node_modules/@sliverp/qqbot/dist/src/api.js +523 -0
  41. package/node_modules/@sliverp/qqbot/dist/src/channel.d.ts +3 -0
  42. package/node_modules/@sliverp/qqbot/dist/src/channel.js +349 -0
  43. package/node_modules/@sliverp/qqbot/dist/src/config.d.ts +25 -0
  44. package/node_modules/@sliverp/qqbot/dist/src/config.js +156 -0
  45. package/node_modules/@sliverp/qqbot/dist/src/demo-standalone.d.ts +1 -0
  46. package/node_modules/@sliverp/qqbot/dist/src/demo-standalone.js +125 -0
  47. package/node_modules/@sliverp/qqbot/dist/src/gateway.d.ts +20 -0
  48. package/node_modules/@sliverp/qqbot/dist/src/gateway.js +2156 -0
  49. package/node_modules/@sliverp/qqbot/dist/src/image-server.d.ts +62 -0
  50. package/node_modules/@sliverp/qqbot/dist/src/image-server.js +401 -0
  51. package/node_modules/@sliverp/qqbot/dist/src/known-users.d.ts +100 -0
  52. package/node_modules/@sliverp/qqbot/dist/src/known-users.js +263 -0
  53. package/node_modules/@sliverp/qqbot/dist/src/onboarding.d.ts +10 -0
  54. package/node_modules/@sliverp/qqbot/dist/src/onboarding.js +203 -0
  55. package/node_modules/@sliverp/qqbot/dist/src/openclaw-agent-adapter.d.ts +2 -0
  56. package/node_modules/@sliverp/qqbot/dist/src/openclaw-agent-adapter.js +155 -0
  57. package/node_modules/@sliverp/qqbot/dist/src/outbound.d.ts +150 -0
  58. package/node_modules/@sliverp/qqbot/dist/src/outbound.js +1175 -0
  59. package/node_modules/@sliverp/qqbot/dist/src/proactive.d.ts +170 -0
  60. package/node_modules/@sliverp/qqbot/dist/src/proactive.js +399 -0
  61. package/node_modules/@sliverp/qqbot/dist/src/runtime.d.ts +5 -0
  62. package/node_modules/@sliverp/qqbot/dist/src/runtime.js +16 -0
  63. package/node_modules/@sliverp/qqbot/dist/src/session-store.d.ts +52 -0
  64. package/node_modules/@sliverp/qqbot/dist/src/session-store.js +254 -0
  65. package/node_modules/@sliverp/qqbot/dist/src/types.d.ts +145 -0
  66. package/node_modules/@sliverp/qqbot/dist/src/types.js +1 -0
  67. package/node_modules/@sliverp/qqbot/dist/src/utils/audio-convert.d.ts +73 -0
  68. package/node_modules/@sliverp/qqbot/dist/src/utils/audio-convert.js +645 -0
  69. package/node_modules/@sliverp/qqbot/dist/src/utils/file-utils.d.ts +46 -0
  70. package/node_modules/@sliverp/qqbot/dist/src/utils/file-utils.js +107 -0
  71. package/node_modules/@sliverp/qqbot/dist/src/utils/image-size.d.ts +51 -0
  72. package/node_modules/@sliverp/qqbot/dist/src/utils/image-size.js +234 -0
  73. package/node_modules/@sliverp/qqbot/dist/src/utils/media-tags.d.ts +14 -0
  74. package/node_modules/@sliverp/qqbot/dist/src/utils/media-tags.js +120 -0
  75. package/node_modules/@sliverp/qqbot/dist/src/utils/payload.d.ts +112 -0
  76. package/node_modules/@sliverp/qqbot/dist/src/utils/payload.js +186 -0
  77. package/node_modules/@sliverp/qqbot/dist/src/utils/platform.d.ts +126 -0
  78. package/node_modules/@sliverp/qqbot/dist/src/utils/platform.js +358 -0
  79. package/node_modules/@sliverp/qqbot/dist/src/utils/upload-cache.d.ts +34 -0
  80. package/node_modules/@sliverp/qqbot/dist/src/utils/upload-cache.js +93 -0
  81. package/node_modules/@sliverp/qqbot/dist/standalone.d.ts +6 -0
  82. package/node_modules/@sliverp/qqbot/dist/standalone.js +6 -0
  83. package/node_modules/@sliverp/qqbot/index.ts +30 -0
  84. package/node_modules/@sliverp/qqbot/moltbot.plugin.json +16 -0
  85. package/node_modules/@sliverp/qqbot/node_modules/@eshaz/web-worker/LICENSE +201 -0
  86. package/node_modules/@sliverp/qqbot/node_modules/@eshaz/web-worker/README.md +134 -0
  87. package/node_modules/@sliverp/qqbot/node_modules/@eshaz/web-worker/browser.js +17 -0
  88. package/node_modules/@sliverp/qqbot/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
  89. package/node_modules/@sliverp/qqbot/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
  90. package/node_modules/@sliverp/qqbot/node_modules/@eshaz/web-worker/index.d.ts +4 -0
  91. package/node_modules/@sliverp/qqbot/node_modules/@eshaz/web-worker/node.js +223 -0
  92. package/node_modules/@sliverp/qqbot/node_modules/@eshaz/web-worker/package.json +54 -0
  93. package/node_modules/@sliverp/qqbot/node_modules/@wasm-audio-decoders/common/index.js +5 -0
  94. package/node_modules/@sliverp/qqbot/node_modules/@wasm-audio-decoders/common/package.json +36 -0
  95. package/node_modules/@sliverp/qqbot/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
  96. package/node_modules/@sliverp/qqbot/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
  97. package/node_modules/@sliverp/qqbot/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
  98. package/node_modules/@sliverp/qqbot/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
  99. package/node_modules/@sliverp/qqbot/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
  100. package/node_modules/@sliverp/qqbot/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
  101. package/node_modules/@sliverp/qqbot/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
  102. package/node_modules/@sliverp/qqbot/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
  103. package/node_modules/@sliverp/qqbot/node_modules/mpg123-decoder/README.md +265 -0
  104. package/node_modules/@sliverp/qqbot/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
  105. package/node_modules/@sliverp/qqbot/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
  106. package/node_modules/@sliverp/qqbot/node_modules/mpg123-decoder/index.js +8 -0
  107. package/node_modules/@sliverp/qqbot/node_modules/mpg123-decoder/package.json +58 -0
  108. package/node_modules/@sliverp/qqbot/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
  109. package/node_modules/@sliverp/qqbot/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
  110. package/node_modules/@sliverp/qqbot/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
  111. package/node_modules/@sliverp/qqbot/node_modules/mpg123-decoder/types.d.ts +30 -0
  112. package/node_modules/@sliverp/qqbot/node_modules/silk-wasm/LICENSE +21 -0
  113. package/node_modules/@sliverp/qqbot/node_modules/silk-wasm/README.md +85 -0
  114. package/node_modules/@sliverp/qqbot/node_modules/silk-wasm/lib/index.cjs +16 -0
  115. package/node_modules/@sliverp/qqbot/node_modules/silk-wasm/lib/index.d.ts +70 -0
  116. package/node_modules/@sliverp/qqbot/node_modules/silk-wasm/lib/index.mjs +16 -0
  117. package/node_modules/@sliverp/qqbot/node_modules/silk-wasm/lib/silk.wasm +0 -0
  118. package/node_modules/@sliverp/qqbot/node_modules/silk-wasm/lib/utils.d.ts +4 -0
  119. package/node_modules/@sliverp/qqbot/node_modules/silk-wasm/package.json +39 -0
  120. package/node_modules/@sliverp/qqbot/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
  121. package/node_modules/@sliverp/qqbot/node_modules/simple-yenc/.prettierignore +1 -0
  122. package/node_modules/@sliverp/qqbot/node_modules/simple-yenc/LICENSE +7 -0
  123. package/node_modules/@sliverp/qqbot/node_modules/simple-yenc/README.md +163 -0
  124. package/node_modules/@sliverp/qqbot/node_modules/simple-yenc/dist/esm.js +1 -0
  125. package/node_modules/@sliverp/qqbot/node_modules/simple-yenc/dist/index.js +1 -0
  126. package/node_modules/@sliverp/qqbot/node_modules/simple-yenc/package.json +50 -0
  127. package/node_modules/@sliverp/qqbot/node_modules/simple-yenc/rollup.config.js +27 -0
  128. package/node_modules/@sliverp/qqbot/node_modules/simple-yenc/src/simple-yenc.js +302 -0
  129. package/node_modules/@sliverp/qqbot/node_modules/ws/LICENSE +20 -0
  130. package/node_modules/@sliverp/qqbot/node_modules/ws/README.md +548 -0
  131. package/node_modules/@sliverp/qqbot/node_modules/ws/browser.js +8 -0
  132. package/node_modules/@sliverp/qqbot/node_modules/ws/index.js +13 -0
  133. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/buffer-util.js +131 -0
  134. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/constants.js +19 -0
  135. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/event-target.js +292 -0
  136. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/extension.js +203 -0
  137. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/limiter.js +55 -0
  138. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/permessage-deflate.js +528 -0
  139. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/receiver.js +706 -0
  140. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/sender.js +602 -0
  141. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/stream.js +161 -0
  142. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/subprotocol.js +62 -0
  143. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/validation.js +152 -0
  144. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/websocket-server.js +554 -0
  145. package/node_modules/@sliverp/qqbot/node_modules/ws/lib/websocket.js +1393 -0
  146. package/node_modules/@sliverp/qqbot/node_modules/ws/package.json +69 -0
  147. package/node_modules/@sliverp/qqbot/node_modules/ws/wrapper.mjs +8 -0
  148. package/node_modules/@sliverp/qqbot/openclaw.plugin.json +16 -0
  149. package/node_modules/@sliverp/qqbot/package.json +81 -0
  150. package/node_modules/@sliverp/qqbot/skills/qqbot-cron/SKILL.md +513 -0
  151. package/node_modules/@sliverp/qqbot/skills/qqbot-media/SKILL.md +194 -0
  152. package/node_modules/@sliverp/qqbot/src/agent.ts +133 -0
  153. package/node_modules/@sliverp/qqbot/src/api.ts +704 -0
  154. package/node_modules/@sliverp/qqbot/src/channel.ts +380 -0
  155. package/node_modules/@sliverp/qqbot/src/config.ts +182 -0
  156. package/node_modules/@sliverp/qqbot/src/demo-standalone.ts +144 -0
  157. package/node_modules/@sliverp/qqbot/src/gateway.ts +2285 -0
  158. package/node_modules/@sliverp/qqbot/src/image-server.ts +474 -0
  159. package/node_modules/@sliverp/qqbot/src/known-users.ts +353 -0
  160. package/node_modules/@sliverp/qqbot/src/onboarding.ts +274 -0
  161. package/node_modules/@sliverp/qqbot/src/openclaw-agent-adapter.ts +168 -0
  162. package/node_modules/@sliverp/qqbot/src/openclaw-plugin-sdk.d.ts +483 -0
  163. package/node_modules/@sliverp/qqbot/src/outbound.ts +1301 -0
  164. package/node_modules/@sliverp/qqbot/src/proactive.ts +530 -0
  165. package/node_modules/@sliverp/qqbot/src/runtime.ts +22 -0
  166. package/node_modules/@sliverp/qqbot/src/session-store.ts +303 -0
  167. package/node_modules/@sliverp/qqbot/src/types.ts +153 -0
  168. package/node_modules/@sliverp/qqbot/src/utils/audio-convert.ts +738 -0
  169. package/node_modules/@sliverp/qqbot/src/utils/file-utils.ts +122 -0
  170. package/node_modules/@sliverp/qqbot/src/utils/image-size.ts +266 -0
  171. package/node_modules/@sliverp/qqbot/src/utils/media-tags.ts +134 -0
  172. package/node_modules/@sliverp/qqbot/src/utils/payload.ts +265 -0
  173. package/node_modules/@sliverp/qqbot/src/utils/platform.ts +404 -0
  174. package/node_modules/@sliverp/qqbot/src/utils/upload-cache.ts +128 -0
  175. package/node_modules/@sliverp/qqbot/standalone.ts +6 -0
  176. package/node_modules/@sliverp/qqbot/tsconfig.json +16 -0
  177. package/package.json +10 -2
  178. package/dist/BasicAgent-QWEYCLV5.js +0 -12
  179. package/dist/ExplorerAgent-4IT22VB7.js +0 -12
  180. package/dist/chunk-3BPSNNK3.js.map +0 -1
  181. package/dist/chunk-LLV3W326.js.map +0 -1
  182. package/dist/chunk-LQTEETML.js.map +0 -1
  183. package/dist/chunk-OBOU27DM.js.map +0 -1
  184. package/dist/chunk-TSASFMRF.js.map +0 -1
  185. /package/dist/{BasicAgent-QWEYCLV5.js.map → BasicAgent-R7DYGTHF.js.map} +0 -0
  186. /package/dist/{ExplorerAgent-4IT22VB7.js.map → ExplorerAgent-DXY3OQ5U.js.map} +0 -0
  187. /package/dist/{notification-3VEAP7YF.js.map → notification-NWVOS2WR.js.map} +0 -0
@@ -0,0 +1,263 @@
1
+ /**
2
+ * 已知用户存储
3
+ * 记录与机器人交互过的所有用户
4
+ * 支持主动消息和批量通知功能
5
+ */
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { getQQBotDataDir } from "./utils/platform.js";
9
+ // 存储文件路径
10
+ const KNOWN_USERS_DIR = getQQBotDataDir("data");
11
+ const KNOWN_USERS_FILE = path.join(KNOWN_USERS_DIR, "known-users.json");
12
+ // 内存缓存
13
+ let usersCache = null;
14
+ // 写入节流配置
15
+ const SAVE_THROTTLE_MS = 5000; // 5秒写入一次
16
+ let saveTimer = null;
17
+ let isDirty = false;
18
+ /**
19
+ * 确保目录存在
20
+ */
21
+ function ensureDir() {
22
+ if (!fs.existsSync(KNOWN_USERS_DIR)) {
23
+ fs.mkdirSync(KNOWN_USERS_DIR, { recursive: true });
24
+ }
25
+ }
26
+ /**
27
+ * 从文件加载用户数据到缓存
28
+ */
29
+ function loadUsersFromFile() {
30
+ if (usersCache !== null) {
31
+ return usersCache;
32
+ }
33
+ usersCache = new Map();
34
+ try {
35
+ if (fs.existsSync(KNOWN_USERS_FILE)) {
36
+ const data = fs.readFileSync(KNOWN_USERS_FILE, "utf-8");
37
+ const users = JSON.parse(data);
38
+ for (const user of users) {
39
+ // 使用复合键:accountId + type + openid(群组还要加 groupOpenid)
40
+ const key = makeUserKey(user);
41
+ usersCache.set(key, user);
42
+ }
43
+ console.log(`[known-users] Loaded ${usersCache.size} users`);
44
+ }
45
+ }
46
+ catch (err) {
47
+ console.error(`[known-users] Failed to load users: ${err}`);
48
+ usersCache = new Map();
49
+ }
50
+ return usersCache;
51
+ }
52
+ /**
53
+ * 保存用户数据到文件(节流版本)
54
+ */
55
+ function saveUsersToFile() {
56
+ if (!isDirty)
57
+ return;
58
+ if (saveTimer) {
59
+ return; // 已有定时器在等待
60
+ }
61
+ saveTimer = setTimeout(() => {
62
+ saveTimer = null;
63
+ doSaveUsersToFile();
64
+ }, SAVE_THROTTLE_MS);
65
+ }
66
+ /**
67
+ * 实际执行保存
68
+ */
69
+ function doSaveUsersToFile() {
70
+ if (!usersCache || !isDirty)
71
+ return;
72
+ try {
73
+ ensureDir();
74
+ const users = Array.from(usersCache.values());
75
+ fs.writeFileSync(KNOWN_USERS_FILE, JSON.stringify(users, null, 2), "utf-8");
76
+ isDirty = false;
77
+ }
78
+ catch (err) {
79
+ console.error(`[known-users] Failed to save users: ${err}`);
80
+ }
81
+ }
82
+ /**
83
+ * 强制立即保存(用于进程退出前)
84
+ */
85
+ export function flushKnownUsers() {
86
+ if (saveTimer) {
87
+ clearTimeout(saveTimer);
88
+ saveTimer = null;
89
+ }
90
+ doSaveUsersToFile();
91
+ }
92
+ /**
93
+ * 生成用户唯一键
94
+ */
95
+ function makeUserKey(user) {
96
+ const base = `${user.accountId}:${user.type}:${user.openid}`;
97
+ if (user.type === "group" && user.groupOpenid) {
98
+ return `${base}:${user.groupOpenid}`;
99
+ }
100
+ return base;
101
+ }
102
+ /**
103
+ * 记录已知用户(收到消息时调用)
104
+ * @param user 用户信息(部分字段)
105
+ */
106
+ export function recordKnownUser(user) {
107
+ const cache = loadUsersFromFile();
108
+ const key = makeUserKey(user);
109
+ const now = Date.now();
110
+ const existing = cache.get(key);
111
+ if (existing) {
112
+ // 更新已存在的用户
113
+ existing.lastSeenAt = now;
114
+ existing.interactionCount++;
115
+ if (user.nickname && user.nickname !== existing.nickname) {
116
+ existing.nickname = user.nickname;
117
+ }
118
+ }
119
+ else {
120
+ // 新用户
121
+ const newUser = {
122
+ openid: user.openid,
123
+ type: user.type,
124
+ nickname: user.nickname,
125
+ groupOpenid: user.groupOpenid,
126
+ accountId: user.accountId,
127
+ firstSeenAt: now,
128
+ lastSeenAt: now,
129
+ interactionCount: 1,
130
+ };
131
+ cache.set(key, newUser);
132
+ console.log(`[known-users] New user: ${user.openid} (${user.type})`);
133
+ }
134
+ isDirty = true;
135
+ saveUsersToFile();
136
+ }
137
+ /**
138
+ * 获取单个用户信息
139
+ * @param accountId 机器人账户 ID
140
+ * @param openid 用户 openid
141
+ * @param type 消息类型
142
+ * @param groupOpenid 群组 openid(可选)
143
+ */
144
+ export function getKnownUser(accountId, openid, type = "c2c", groupOpenid) {
145
+ const cache = loadUsersFromFile();
146
+ const key = makeUserKey({ accountId, openid, type, groupOpenid });
147
+ return cache.get(key);
148
+ }
149
+ /**
150
+ * 列出所有已知用户
151
+ * @param options 筛选选项
152
+ */
153
+ export function listKnownUsers(options) {
154
+ const cache = loadUsersFromFile();
155
+ let users = Array.from(cache.values());
156
+ // 筛选
157
+ if (options?.accountId) {
158
+ users = users.filter(u => u.accountId === options.accountId);
159
+ }
160
+ if (options?.type) {
161
+ users = users.filter(u => u.type === options.type);
162
+ }
163
+ if (options?.activeWithin) {
164
+ const cutoff = Date.now() - options.activeWithin;
165
+ users = users.filter(u => u.lastSeenAt >= cutoff);
166
+ }
167
+ // 排序
168
+ const sortBy = options?.sortBy ?? "lastSeenAt";
169
+ const sortOrder = options?.sortOrder ?? "desc";
170
+ users.sort((a, b) => {
171
+ const aVal = a[sortBy] ?? 0;
172
+ const bVal = b[sortBy] ?? 0;
173
+ return sortOrder === "asc" ? aVal - bVal : bVal - aVal;
174
+ });
175
+ // 限制数量
176
+ if (options?.limit && options.limit > 0) {
177
+ users = users.slice(0, options.limit);
178
+ }
179
+ return users;
180
+ }
181
+ /**
182
+ * 获取用户统计信息
183
+ * @param accountId 机器人账户 ID(可选,不传则返回所有账户的统计)
184
+ */
185
+ export function getKnownUsersStats(accountId) {
186
+ let users = listKnownUsers({ accountId });
187
+ const now = Date.now();
188
+ const day = 24 * 60 * 60 * 1000;
189
+ return {
190
+ totalUsers: users.length,
191
+ c2cUsers: users.filter(u => u.type === "c2c").length,
192
+ groupUsers: users.filter(u => u.type === "group").length,
193
+ activeIn24h: users.filter(u => now - u.lastSeenAt < day).length,
194
+ activeIn7d: users.filter(u => now - u.lastSeenAt < 7 * day).length,
195
+ };
196
+ }
197
+ /**
198
+ * 删除用户记录
199
+ * @param accountId 机器人账户 ID
200
+ * @param openid 用户 openid
201
+ * @param type 消息类型
202
+ * @param groupOpenid 群组 openid(可选)
203
+ */
204
+ export function removeKnownUser(accountId, openid, type = "c2c", groupOpenid) {
205
+ const cache = loadUsersFromFile();
206
+ const key = makeUserKey({ accountId, openid, type, groupOpenid });
207
+ if (cache.has(key)) {
208
+ cache.delete(key);
209
+ isDirty = true;
210
+ saveUsersToFile();
211
+ console.log(`[known-users] Removed user ${openid}`);
212
+ return true;
213
+ }
214
+ return false;
215
+ }
216
+ /**
217
+ * 清除所有用户记录
218
+ * @param accountId 机器人账户 ID(可选,不传则清除所有)
219
+ */
220
+ export function clearKnownUsers(accountId) {
221
+ const cache = loadUsersFromFile();
222
+ let count = 0;
223
+ if (accountId) {
224
+ // 只清除指定账户的用户
225
+ for (const [key, user] of cache.entries()) {
226
+ if (user.accountId === accountId) {
227
+ cache.delete(key);
228
+ count++;
229
+ }
230
+ }
231
+ }
232
+ else {
233
+ // 清除所有
234
+ count = cache.size;
235
+ cache.clear();
236
+ }
237
+ if (count > 0) {
238
+ isDirty = true;
239
+ doSaveUsersToFile(); // 立即保存
240
+ console.log(`[known-users] Cleared ${count} users`);
241
+ }
242
+ return count;
243
+ }
244
+ /**
245
+ * 获取用户的所有群组(某用户在哪些群里交互过)
246
+ * @param accountId 机器人账户 ID
247
+ * @param openid 用户 openid
248
+ */
249
+ export function getUserGroups(accountId, openid) {
250
+ const users = listKnownUsers({ accountId, type: "group" });
251
+ return users
252
+ .filter(u => u.openid === openid && u.groupOpenid)
253
+ .map(u => u.groupOpenid);
254
+ }
255
+ /**
256
+ * 获取群组的所有成员
257
+ * @param accountId 机器人账户 ID
258
+ * @param groupOpenid 群组 openid
259
+ */
260
+ export function getGroupMembers(accountId, groupOpenid) {
261
+ return listKnownUsers({ accountId, type: "group" })
262
+ .filter(u => u.groupOpenid === groupOpenid);
263
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * QQBot CLI Onboarding Adapter
3
+ *
4
+ * 提供 openclaw onboard 命令的交互式配置支持
5
+ */
6
+ import type { ChannelOnboardingAdapter } from "openclaw/plugin-sdk";
7
+ /**
8
+ * QQBot Onboarding Adapter
9
+ */
10
+ export declare const qqbotOnboardingAdapter: ChannelOnboardingAdapter;
@@ -0,0 +1,203 @@
1
+ import { DEFAULT_ACCOUNT_ID, listQQBotAccountIds, resolveQQBotAccount } from "./config.js";
2
+ /**
3
+ * 解析默认账户 ID
4
+ */
5
+ function resolveDefaultQQBotAccountId(cfg) {
6
+ const ids = listQQBotAccountIds(cfg);
7
+ return ids[0] ?? DEFAULT_ACCOUNT_ID;
8
+ }
9
+ /**
10
+ * QQBot Onboarding Adapter
11
+ */
12
+ export const qqbotOnboardingAdapter = {
13
+ channel: "qqbot",
14
+ getStatus: async (ctx) => {
15
+ const cfg = ctx.cfg;
16
+ const configured = listQQBotAccountIds(cfg).some((accountId) => {
17
+ const account = resolveQQBotAccount(cfg, accountId);
18
+ return Boolean(account.appId && account.clientSecret);
19
+ });
20
+ return {
21
+ channel: "qqbot",
22
+ configured,
23
+ statusLines: [`QQ Bot: ${configured ? "已配置" : "需要 AppID 和 ClientSecret"}`],
24
+ selectionHint: configured ? "已配置" : "支持 QQ 群聊和私聊(流式消息)",
25
+ quickstartScore: configured ? 1 : 20,
26
+ };
27
+ },
28
+ configure: async (ctx) => {
29
+ const cfg = ctx.cfg;
30
+ const prompter = ctx.prompter;
31
+ const accountOverrides = ctx.accountOverrides;
32
+ const shouldPromptAccountIds = ctx.shouldPromptAccountIds;
33
+ const qqbotOverride = accountOverrides?.qqbot?.trim();
34
+ const defaultAccountId = resolveDefaultQQBotAccountId(cfg);
35
+ let accountId = qqbotOverride ?? defaultAccountId;
36
+ // 是否需要提示选择账户
37
+ if (shouldPromptAccountIds && !qqbotOverride) {
38
+ const existingIds = listQQBotAccountIds(cfg);
39
+ if (existingIds.length > 1) {
40
+ accountId = await prompter.select({
41
+ message: "选择 QQBot 账户",
42
+ options: existingIds.map((id) => ({
43
+ value: id,
44
+ label: id === DEFAULT_ACCOUNT_ID ? "默认账户" : id,
45
+ })),
46
+ initialValue: accountId,
47
+ });
48
+ }
49
+ }
50
+ let next = cfg;
51
+ const resolvedAccount = resolveQQBotAccount(next, accountId);
52
+ const accountConfigured = Boolean(resolvedAccount.appId && resolvedAccount.clientSecret);
53
+ const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
54
+ const envAppId = typeof process !== "undefined" ? process.env?.QQBOT_APP_ID?.trim() : undefined;
55
+ const envSecret = typeof process !== "undefined" ? process.env?.QQBOT_CLIENT_SECRET?.trim() : undefined;
56
+ const canUseEnv = allowEnv && Boolean(envAppId && envSecret);
57
+ const hasConfigCredentials = Boolean(resolvedAccount.config.appId && resolvedAccount.config.clientSecret);
58
+ let appId = null;
59
+ let clientSecret = null;
60
+ // 显示帮助
61
+ if (!accountConfigured) {
62
+ await prompter.note([
63
+ "1) 打开 QQ 开放平台: https://q.qq.com/",
64
+ "2) 创建机器人应用,获取 AppID 和 ClientSecret",
65
+ "3) 在「开发设置」中添加沙箱成员(测试阶段)",
66
+ "4) 你也可以设置环境变量 QQBOT_APP_ID 和 QQBOT_CLIENT_SECRET",
67
+ "",
68
+ "文档: https://bot.q.qq.com/wiki/",
69
+ "",
70
+ "此版本支持流式消息发送!",
71
+ ].join("\n"), "QQ Bot 配置");
72
+ }
73
+ // 检测环境变量
74
+ if (canUseEnv && !hasConfigCredentials) {
75
+ const keepEnv = await prompter.confirm({
76
+ message: "检测到环境变量 QQBOT_APP_ID 和 QQBOT_CLIENT_SECRET,是否使用?",
77
+ initialValue: true,
78
+ });
79
+ if (keepEnv) {
80
+ next = {
81
+ ...next,
82
+ channels: {
83
+ ...next.channels,
84
+ qqbot: {
85
+ ...(next.channels?.qqbot || {}),
86
+ enabled: true,
87
+ allowFrom: resolvedAccount.config?.allowFrom ?? ["*"],
88
+ },
89
+ },
90
+ };
91
+ }
92
+ else {
93
+ // 手动输入
94
+ appId = String(await prompter.text({
95
+ message: "请输入 QQ Bot AppID",
96
+ placeholder: "例如: 102146862",
97
+ initialValue: resolvedAccount.appId || undefined,
98
+ validate: (value) => (value?.trim() ? undefined : "AppID 不能为空"),
99
+ })).trim();
100
+ clientSecret = String(await prompter.text({
101
+ message: "请输入 QQ Bot ClientSecret",
102
+ placeholder: "你的 ClientSecret",
103
+ validate: (value) => (value?.trim() ? undefined : "ClientSecret 不能为空"),
104
+ })).trim();
105
+ }
106
+ }
107
+ else if (hasConfigCredentials) {
108
+ // 已有配置
109
+ const keep = await prompter.confirm({
110
+ message: "QQ Bot 已配置,是否保留当前配置?",
111
+ initialValue: true,
112
+ });
113
+ if (!keep) {
114
+ appId = String(await prompter.text({
115
+ message: "请输入 QQ Bot AppID",
116
+ placeholder: "例如: 102146862",
117
+ initialValue: resolvedAccount.appId || undefined,
118
+ validate: (value) => (value?.trim() ? undefined : "AppID 不能为空"),
119
+ })).trim();
120
+ clientSecret = String(await prompter.text({
121
+ message: "请输入 QQ Bot ClientSecret",
122
+ placeholder: "你的 ClientSecret",
123
+ validate: (value) => (value?.trim() ? undefined : "ClientSecret 不能为空"),
124
+ })).trim();
125
+ }
126
+ }
127
+ else {
128
+ // 没有配置,需要输入
129
+ appId = String(await prompter.text({
130
+ message: "请输入 QQ Bot AppID",
131
+ placeholder: "例如: 102146862",
132
+ initialValue: resolvedAccount.appId || undefined,
133
+ validate: (value) => (value?.trim() ? undefined : "AppID 不能为空"),
134
+ })).trim();
135
+ clientSecret = String(await prompter.text({
136
+ message: "请输入 QQ Bot ClientSecret",
137
+ placeholder: "你的 ClientSecret",
138
+ validate: (value) => (value?.trim() ? undefined : "ClientSecret 不能为空"),
139
+ })).trim();
140
+ }
141
+ // 默认允许所有人执行命令(用户无感知)
142
+ const allowFrom = resolvedAccount.config?.allowFrom ?? ["*"];
143
+ // 应用配置(markdownSupport 默认开启,如需关闭可用 set-markdown.sh)
144
+ if (appId && clientSecret) {
145
+ const existingQQBot = next.channels?.qqbot || {};
146
+ // 保留已有的 markdownSupport 设置,新装默认 true
147
+ const markdownSupport = existingQQBot.markdownSupport ?? true;
148
+ if (accountId === DEFAULT_ACCOUNT_ID) {
149
+ next = {
150
+ ...next,
151
+ channels: {
152
+ ...next.channels,
153
+ qqbot: {
154
+ ...existingQQBot,
155
+ enabled: true,
156
+ appId,
157
+ clientSecret,
158
+ markdownSupport,
159
+ allowFrom,
160
+ },
161
+ },
162
+ };
163
+ }
164
+ else {
165
+ const existingAccounts = (next.channels?.qqbot?.accounts || {});
166
+ const existingAccount = existingAccounts[accountId] || {};
167
+ const acctMarkdown = existingAccount.markdownSupport ?? true;
168
+ next = {
169
+ ...next,
170
+ channels: {
171
+ ...next.channels,
172
+ qqbot: {
173
+ ...existingQQBot,
174
+ enabled: true,
175
+ accounts: {
176
+ ...existingAccounts,
177
+ [accountId]: {
178
+ ...existingAccount,
179
+ enabled: true,
180
+ appId,
181
+ clientSecret,
182
+ markdownSupport: acctMarkdown,
183
+ allowFrom,
184
+ },
185
+ },
186
+ },
187
+ },
188
+ };
189
+ }
190
+ }
191
+ return { success: true, cfg: next, accountId };
192
+ },
193
+ disable: (cfg) => {
194
+ const config = cfg;
195
+ return {
196
+ ...config,
197
+ channels: {
198
+ ...config.channels,
199
+ qqbot: { ...(config.channels?.qqbot || {}), enabled: false },
200
+ },
201
+ };
202
+ },
203
+ };
@@ -0,0 +1,2 @@
1
+ import type { QQBotAgentAdapter } from "./agent.js";
2
+ export declare function createOpenClawAgentAdapter(): QQBotAgentAdapter;
@@ -0,0 +1,155 @@
1
+ import { getQQBotRuntime } from "./runtime.js";
2
+ import { resolveTTSConfig } from "./utils/audio-convert.js";
3
+ function createAgentBody(ctx) {
4
+ const { request, account, cfg } = ctx;
5
+ const systemPrompts = [];
6
+ if (account.systemPrompt) {
7
+ systemPrompts.push(account.systemPrompt);
8
+ }
9
+ const receivedMediaSection = request.imageUrls.length > 0
10
+ ? `\n- 附件:\n${request.imageUrls.map((p, i) => ` - ${p} (${request.localMediaTypes[i] || request.remoteMediaTypes[i] || "unknown"})`).join("\n")}`
11
+ : "";
12
+ const hasTTS = !!resolveTTSConfig(cfg);
13
+ const ttsHint = hasTTS
14
+ ? "6. 🎤 插件 TTS 已启用: 如果你有 TTS 工具(如 audio_speech),可用它生成音频文件后用 <qqvoice> 发送"
15
+ : "6. ⚠️ 插件 TTS 未配置: 如果你有 TTS 工具(如 audio_speech),仍可用它生成音频文件后用 <qqvoice> 发送;若无 TTS 工具,则无法主动生成语音";
16
+ const sttHint = request.attachments.some((item) => item.kind === "voice" && item.transcript)
17
+ ? "\n7. 用户发送的语音消息会自动转录为文字"
18
+ : "\n7. 语音识别未配置(STT),无法自动转录用户的语音消息";
19
+ const voiceSection = `
20
+
21
+ 【发送语音 - 必须遵守】
22
+ 1. 发语音方法: 在回复文本中写 <qqvoice>本地音频文件路径</qqvoice>,系统自动处理
23
+ 2. 示例: "来听听吧! <qqvoice>/tmp/tts/voice.mp3</qqvoice>"
24
+ 3. 支持格式: .silk, .slk, .slac, .amr, .wav, .mp3, .ogg, .pcm
25
+ 4. ⚠️ <qqvoice> 只用于语音文件,图片请用 <qqimg>;两者不要混用
26
+ 5. 可以同时发送文字和语音,系统会按顺序投递
27
+ ${ttsHint}${sttHint}`;
28
+ const contextInfo = `你正在通过 QQ 与用户对话。
29
+
30
+ 【会话上下文】
31
+ - 用户: ${request.senderName || "未知"} (${request.senderId})
32
+ - 场景: ${request.chatType === "group" ? "群聊" : "私聊"}${request.groupOpenid ? ` (群组: ${request.groupOpenid})` : ""}
33
+ - 消息ID: ${request.messageId}
34
+ - 投递目标: ${request.qualifiedTarget}${receivedMediaSection}
35
+ - 当前时间戳(ms): ${Date.now()}
36
+ - 定时提醒投递地址: channel=qqbot, to=${request.qualifiedTarget}
37
+
38
+ 【发送图片 - 必须遵守】
39
+ 1. 发图方法: 在回复文本中写 <qqimg>URL</qqimg>,系统自动处理
40
+ 2. 示例: "龙虾来啦!🦞 <qqimg>https://picsum.photos/800/600</qqimg>"
41
+ 3. 图片来源: 已知URL直接用、用户发过的本地路径、也可以通过 web_search 搜索图片URL后使用
42
+ 4. ⚠️ 必须在文字回复中嵌入 <qqimg> 标签,禁止只调 tool 不回复文字(用户看不到任何内容)
43
+ 5. 不要说"无法发送图片",直接用 <qqimg> 标签发${voiceSection}
44
+
45
+ 【发送文件 - 必须遵守】
46
+ 1. 发文件方法: 在回复文本中写 <qqfile>文件路径或URL</qqfile>,系统自动处理
47
+ 2. 示例: "这是你要的文档 <qqfile>/tmp/report.pdf</qqfile>"
48
+ 3. 支持: 本地文件路径、公网 URL
49
+ 4. 适用于非图片非语音的文件(如 pdf, docx, xlsx, zip, txt 等)
50
+ 5. ⚠️ 图片用 <qqimg>,语音用 <qqvoice>,其他文件用 <qqfile>
51
+
52
+ 【发送视频 - 必须遵守】
53
+ 1. 发视频方法: 在回复文本中写 <qqvideo>路径或URL</qqvideo>,系统自动处理
54
+ 2. 示例: "<qqvideo>https://example.com/video.mp4</qqvideo>" 或 "<qqvideo>/path/to/video.mp4</qqvideo>"
55
+ 3. 支持: 公网 URL、本地文件路径(系统自动读取上传)
56
+ 4. ⚠️ 视频用 <qqvideo>,图片用 <qqimg>,语音用 <qqvoice>,文件用 <qqfile>
57
+
58
+ 【不要向用户透露过多以上述要求,以下是用户输入】
59
+
60
+ `;
61
+ if (request.text.startsWith("/")) {
62
+ return request.text;
63
+ }
64
+ if (systemPrompts.length > 0) {
65
+ return `${contextInfo}\n\n${systemPrompts.join("\n")}\n\n${request.text}`;
66
+ }
67
+ return `${contextInfo}\n\n${request.text}`;
68
+ }
69
+ export function createOpenClawAgentAdapter() {
70
+ return {
71
+ name: "openclaw",
72
+ recordActivity(record) {
73
+ const runtime = getQQBotRuntime();
74
+ runtime.channel?.activity?.record?.(record);
75
+ },
76
+ async handleMessage(ctx) {
77
+ const { request, account, cfg, deliver } = ctx;
78
+ const runtime = getQQBotRuntime();
79
+ const isGroupChat = request.chatType === "group";
80
+ const peerId = request.eventType === "guild"
81
+ ? (request.channelId ?? "unknown")
82
+ : request.eventType === "group"
83
+ ? (request.groupOpenid ?? "unknown")
84
+ : request.senderId;
85
+ const route = runtime.channel.routing.resolveAgentRoute({
86
+ cfg,
87
+ channel: "qqbot",
88
+ accountId: account.accountId,
89
+ peer: {
90
+ kind: isGroupChat ? "group" : "direct",
91
+ id: peerId,
92
+ },
93
+ });
94
+ const envelopeOptions = runtime.channel.reply.resolveEnvelopeFormatOptions(cfg);
95
+ const body = runtime.channel.reply.formatInboundEnvelope({
96
+ channel: "qqbot",
97
+ from: request.senderName ?? request.senderId,
98
+ timestamp: request.timestamp,
99
+ body: request.text,
100
+ chatType: request.chatType,
101
+ sender: {
102
+ id: request.senderId,
103
+ name: request.senderName,
104
+ },
105
+ envelope: envelopeOptions,
106
+ ...(request.imageUrls.length > 0 ? { imageUrls: request.imageUrls } : {}),
107
+ });
108
+ const ctxPayload = runtime.channel.reply.finalizeInboundContext({
109
+ Body: body,
110
+ BodyForAgent: createAgentBody(ctx),
111
+ RawBody: request.rawText,
112
+ CommandBody: request.rawText,
113
+ From: request.from,
114
+ To: request.to,
115
+ SessionKey: route.sessionKey,
116
+ AccountId: route.accountId,
117
+ ChatType: request.chatType,
118
+ SenderId: request.senderId,
119
+ SenderName: request.senderName,
120
+ Provider: "qqbot",
121
+ Surface: "qqbot",
122
+ MessageSid: request.messageId,
123
+ Timestamp: request.timestamp,
124
+ OriginatingChannel: "qqbot",
125
+ OriginatingTo: request.to,
126
+ QQChannelId: request.channelId,
127
+ QQGuildId: request.guildId,
128
+ QQGroupOpenid: request.groupOpenid,
129
+ CommandAuthorized: request.commandAuthorized,
130
+ ...(request.localMediaPaths.length > 0 ? {
131
+ MediaPaths: request.localMediaPaths,
132
+ MediaPath: request.localMediaPaths[0],
133
+ MediaTypes: request.localMediaTypes,
134
+ MediaType: request.localMediaTypes[0],
135
+ } : {}),
136
+ ...(request.remoteMediaUrls.length > 0 ? {
137
+ MediaUrls: request.remoteMediaUrls,
138
+ MediaUrl: request.remoteMediaUrls[0],
139
+ } : {}),
140
+ });
141
+ const messagesConfig = runtime.channel.reply.resolveEffectiveMessagesConfig(cfg, route.agentId);
142
+ await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
143
+ ctx: ctxPayload,
144
+ cfg,
145
+ dispatcherOptions: {
146
+ responsePrefix: messagesConfig.responsePrefix,
147
+ deliver,
148
+ },
149
+ replyOptions: {
150
+ disableBlockStreaming: false,
151
+ },
152
+ });
153
+ },
154
+ };
155
+ }