@openclaw/qqbot 2026.5.1-beta.1 → 2026.5.2-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.
@@ -6,6 +6,12 @@
6
6
  "channels": [
7
7
  "qqbot"
8
8
  ],
9
+ "contracts": {
10
+ "tools": [
11
+ "qqbot_channel_api",
12
+ "qqbot_remind"
13
+ ]
14
+ },
9
15
  "channelEnvVars": {
10
16
  "qqbot": [
11
17
  "QQBOT_APP_ID",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.5.1-beta.1",
3
+ "version": "2026.5.2-beta.1",
4
4
  "private": false,
5
5
  "description": "OpenClaw QQ Bot channel plugin",
6
6
  "repository": {
@@ -21,7 +21,7 @@
21
21
  "openclaw": "workspace:*"
22
22
  },
23
23
  "peerDependencies": {
24
- "openclaw": ">=2026.4.27"
24
+ "openclaw": ">=2026.5.2-beta.1"
25
25
  },
26
26
  "peerDependenciesMeta": {
27
27
  "openclaw": {
@@ -50,10 +50,10 @@
50
50
  "minHostVersion": ">=2026.4.10"
51
51
  },
52
52
  "compat": {
53
- "pluginApi": ">=2026.4.27"
53
+ "pluginApi": ">=2026.5.2-beta.1"
54
54
  },
55
55
  "build": {
56
- "openclawVersion": "2026.5.1-beta.1"
56
+ "openclawVersion": "2026.5.2-beta.1"
57
57
  },
58
58
  "bundle": {
59
59
  "includeInCore": false
@@ -206,19 +206,3 @@ export function flushRefIndex(): void {
206
206
  compactFile();
207
207
  }
208
208
  }
209
-
210
- /** Return ref-index stats for diagnostics. */
211
- export function getRefIndexStats(): {
212
- size: number;
213
- maxEntries: number;
214
- totalLinesOnDisk: number;
215
- filePath: string;
216
- } {
217
- const store = loadFromFile();
218
- return {
219
- size: store.size,
220
- maxEntries: MAX_ENTRIES,
221
- totalLinesOnDisk,
222
- filePath: getRefIndexFile(),
223
- };
224
- }
@@ -13,7 +13,7 @@ import { debugLog, debugError } from "../utils/log.js";
13
13
  import { getQQBotDataDir, getQQBotDataPath } from "../utils/platform.js";
14
14
 
15
15
  /** Persisted record for a user who has interacted with the bot. */
16
- export interface KnownUser {
16
+ interface KnownUser {
17
17
  openid: string;
18
18
  type: ChatScope;
19
19
  nickname?: string;
@@ -135,120 +135,3 @@ export function recordKnownUser(user: {
135
135
  isDirty = true;
136
136
  saveUsersToFile();
137
137
  }
138
-
139
- /** Look up one known user. */
140
- export function getKnownUser(
141
- accountId: string,
142
- openid: string,
143
- type: ChatScope = "c2c",
144
- groupOpenid?: string,
145
- ): KnownUser | undefined {
146
- return loadUsersFromFile().get(makeUserKey({ accountId, openid, type, groupOpenid }));
147
- }
148
-
149
- /** List known users with optional filtering and sorting. */
150
- export function listKnownUsers(options?: {
151
- accountId?: string;
152
- type?: ChatScope;
153
- activeWithin?: number;
154
- limit?: number;
155
- sortBy?: "lastSeenAt" | "firstSeenAt" | "interactionCount";
156
- sortOrder?: "asc" | "desc";
157
- }): KnownUser[] {
158
- let users = Array.from(loadUsersFromFile().values());
159
- if (options?.accountId) {
160
- users = users.filter((u) => u.accountId === options.accountId);
161
- }
162
- if (options?.type) {
163
- users = users.filter((u) => u.type === options.type);
164
- }
165
- if (options?.activeWithin) {
166
- const cutoff = Date.now() - options.activeWithin;
167
- users = users.filter((u) => u.lastSeenAt >= cutoff);
168
- }
169
- const sortBy = options?.sortBy ?? "lastSeenAt";
170
- const sortOrder = options?.sortOrder ?? "desc";
171
- users.sort((a, b) => {
172
- const aV = a[sortBy] ?? 0;
173
- const bV = b[sortBy] ?? 0;
174
- return sortOrder === "asc" ? aV - bV : bV - aV;
175
- });
176
- if (options?.limit && options.limit > 0) {
177
- users = users.slice(0, options.limit);
178
- }
179
- return users;
180
- }
181
-
182
- /** Return summary stats for known users. */
183
- export function getKnownUsersStats(accountId?: string): {
184
- totalUsers: number;
185
- c2cUsers: number;
186
- groupUsers: number;
187
- activeIn24h: number;
188
- activeIn7d: number;
189
- } {
190
- const users = listKnownUsers({ accountId });
191
- const now = Date.now();
192
- const day = 86400000;
193
- return {
194
- totalUsers: users.length,
195
- c2cUsers: users.filter((u) => u.type === "c2c").length,
196
- groupUsers: users.filter((u) => u.type === "group").length,
197
- activeIn24h: users.filter((u) => now - u.lastSeenAt < day).length,
198
- activeIn7d: users.filter((u) => now - u.lastSeenAt < 7 * day).length,
199
- };
200
- }
201
-
202
- /** Remove one user record. */
203
- export function removeKnownUser(
204
- accountId: string,
205
- openid: string,
206
- type: ChatScope = "c2c",
207
- groupOpenid?: string,
208
- ): boolean {
209
- const cache = loadUsersFromFile();
210
- const key = makeUserKey({ accountId, openid, type, groupOpenid });
211
- if (cache.has(key)) {
212
- cache.delete(key);
213
- isDirty = true;
214
- saveUsersToFile();
215
- debugLog(`[known-users] Removed user ${openid}`);
216
- return true;
217
- }
218
- return false;
219
- }
220
-
221
- /** Clear all user records, optionally scoped to one account. */
222
- export function clearKnownUsers(accountId?: string): number {
223
- const cache = loadUsersFromFile();
224
- let count = 0;
225
- if (accountId) {
226
- for (const [key, user] of cache.entries()) {
227
- if (user.accountId === accountId) {
228
- cache.delete(key);
229
- count++;
230
- }
231
- }
232
- } else {
233
- count = cache.size;
234
- cache.clear();
235
- }
236
- if (count > 0) {
237
- isDirty = true;
238
- doSaveUsersToFile();
239
- debugLog(`[known-users] Cleared ${count} users`);
240
- }
241
- return count;
242
- }
243
-
244
- /** Return all groups in which a user has interacted. */
245
- export function getUserGroups(accountId: string, openid: string): string[] {
246
- return listKnownUsers({ accountId, type: "group" })
247
- .filter((u) => u.openid === openid && u.groupOpenid)
248
- .map((u) => u.groupOpenid!);
249
- }
250
-
251
- /** Return all recorded members for one group. */
252
- export function getGroupMembers(accountId: string, groupOpenid: string): KnownUser[] {
253
- return listKnownUsers({ accountId, type: "group" }).filter((u) => u.groupOpenid === groupOpenid);
254
- }
@@ -62,16 +62,6 @@ function getCandidateSessionPaths(accountId: string): string[] {
62
62
  return primaryPath === legacyPath ? [primaryPath] : [primaryPath, legacyPath];
63
63
  }
64
64
 
65
- function isSessionFileName(file: string): boolean {
66
- return file.startsWith("session-") && file.endsWith(".json");
67
- }
68
-
69
- function readSessionStateFile(file: string): { filePath: string; state: SessionState } {
70
- const filePath = path.join(getSessionDir(), file);
71
- const data = fs.readFileSync(filePath, "utf-8");
72
- return { filePath, state: JSON.parse(data) as SessionState };
73
- }
74
-
75
65
  /** Load a saved session, rejecting expired or mismatched appId entries. */
76
66
  export function loadSession(accountId: string, expectedAppId?: string): SessionState | null {
77
67
  try {
@@ -212,73 +202,3 @@ export function clearSession(accountId: string): void {
212
202
  );
213
203
  }
214
204
  }
215
-
216
- /** Update only lastSeq on the persisted session. */
217
- export function updateLastSeq(accountId: string, lastSeq: number): void {
218
- const existing = loadSession(accountId);
219
- if (existing?.sessionId) {
220
- saveSession({ ...existing, lastSeq });
221
- }
222
- }
223
-
224
- /** Load all saved sessions from disk. */
225
- export function getAllSessions(): SessionState[] {
226
- const sessions = new Map<string, SessionState>();
227
- try {
228
- const sessionDir = getSessionDir();
229
- if (!fs.existsSync(sessionDir)) {
230
- return [];
231
- }
232
- const files = fs.readdirSync(sessionDir);
233
-
234
- for (const file of files) {
235
- if (isSessionFileName(file)) {
236
- try {
237
- const { state } = readSessionStateFile(file);
238
- if (typeof state.accountId !== "string" || !state.accountId) {
239
- continue;
240
- }
241
- const existing = sessions.get(state.accountId);
242
- if (!existing || (state.savedAt ?? 0) >= (existing.savedAt ?? 0)) {
243
- sessions.set(state.accountId, state);
244
- }
245
- } catch {}
246
- }
247
- }
248
- } catch {}
249
- return [...sessions.values()];
250
- }
251
-
252
- /** Remove expired session files from disk. */
253
- export function cleanupExpiredSessions(): number {
254
- let cleaned = 0;
255
- try {
256
- const sessionDir = getSessionDir();
257
- if (!fs.existsSync(sessionDir)) {
258
- return 0;
259
- }
260
- const now = Date.now();
261
- const files = fs.readdirSync(sessionDir);
262
-
263
- for (const file of files) {
264
- if (isSessionFileName(file)) {
265
- const filePath = path.join(sessionDir, file);
266
- try {
267
- const { state } = readSessionStateFile(file);
268
-
269
- if (now - state.savedAt > SESSION_EXPIRE_TIME) {
270
- fs.unlinkSync(filePath);
271
- cleaned++;
272
- debugLog(`[session-store] Cleaned expired session: ${file}`);
273
- }
274
- } catch {
275
- try {
276
- fs.unlinkSync(filePath);
277
- cleaned++;
278
- } catch {}
279
- }
280
- }
281
- }
282
- } catch {}
283
- return cleaned;
284
- }
@@ -38,14 +38,12 @@ describe("qqbot storage laziness", () => {
38
38
 
39
39
  const qqbotRoot = path.join(homeDir, ".openclaw", "qqbot");
40
40
 
41
- const sessionStore = await import("../session/session-store.js");
41
+ await import("../session/session-store.js");
42
42
  await import("../session/known-users.js");
43
43
  await import("../ref/store.js");
44
44
  const { loadCredentialBackup } = await import("../config/credential-backup.js");
45
45
 
46
46
  expect(loadCredentialBackup("default")).toBeNull();
47
- expect(sessionStore.getAllSessions()).toEqual([]);
48
- expect(sessionStore.cleanupExpiredSessions()).toBe(0);
49
47
  expect(fs.existsSync(qqbotRoot)).toBe(false);
50
48
  });
51
49
 
@@ -1 +0,0 @@
1
- 2026-04-29T11:29:16.564Z