@openclaw/qqbot 2026.5.12 → 2026.5.14-beta.2

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.
@@ -1,621 +0,0 @@
1
- import { t as asOptionalObjectRecord } from "./string-normalize-C46CS-F1.js";
2
- import { l as ensurePlatformAdapter, u as getBridgeLogger } from "./resolve-D3lCbUze.js";
3
- import { a as resolveApprovalTarget } from "./approval-cg0SVahb.js";
4
- import { a as resolveQQBotAccount, n as applyQQBotAccountConfig, t as DEFAULT_ACCOUNT_ID$1 } from "./config-BT8KP_H1.js";
5
- import { a as resolveQQBotExecApprovalConfig, i as matchesQQBotApprovalAccount, n as isQQBotExecApprovalAuthorizedSender, o as shouldHandleQQBotExecApprovalRequest, r as isQQBotExecApprovalClientEnabled, t as isQQBotExecApprovalApprover } from "./exec-approvals-W4oL9R6C.js";
6
- import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-BCxobX3l.js";
7
- import { n as writeOpenClawConfigThroughRuntime, t as toGatewayAccount } from "./narrowing-BoieBTIU.js";
8
- import { t as getQQBotRuntime } from "./runtime-CZyFkpnB.js";
9
- import { n as normalizeTarget, s as getQQBotDataPath, t as looksLikeQQBotTarget } from "./target-parser-C8HaD-ik.js";
10
- import { getExecApprovalReplyMetadata } from "openclaw/plugin-sdk/approval-runtime";
11
- import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-message";
12
- import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-delivery-runtime";
13
- import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
14
- import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
15
- import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
16
- import fs from "node:fs";
17
- import { replaceFileAtomicSync } from "openclaw/plugin-sdk/security-runtime";
18
- import path from "node:path";
19
- import { loadJsonFile } from "openclaw/plugin-sdk/json-store";
20
- //#region extensions/qqbot/src/bridge/approval/capability.ts
21
- /**
22
- * QQ Bot Approval Capability — entry point.
23
- *
24
- * QQBot uses a simpler approval model than Telegram/Slack: any user who
25
- * can see the inline-keyboard buttons can approve. No explicit approver
26
- * list is required — the bot simply sends the approval message to the
27
- * originating conversation and whoever clicks the button resolves it.
28
- *
29
- * When `execApprovals` IS configured, it gates which requests are
30
- * handled natively and who is authorized. When it is NOT configured,
31
- * QQBot falls back to "always handle, anyone can approve".
32
- */
33
- /**
34
- * When `execApprovals` is configured, delegate to the profile-based
35
- * check. Otherwise fall back to target-resolvability plus the shared
36
- * per-account ownership rule in `matchesQQBotApprovalAccount` so that
37
- * each QQBot account handler only delivers approvals that originated
38
- * from its own account (openids are account-scoped — cross-account
39
- * delivery fails with 500 on the QQ Bot API).
40
- */
41
- function shouldHandleRequest(params) {
42
- if (hasExecApprovalConfig(params)) return shouldHandleQQBotExecApprovalRequest(params);
43
- if (!canResolveTarget(params.request)) return false;
44
- return matchesQQBotApprovalAccount({
45
- cfg: params.cfg,
46
- accountId: params.accountId,
47
- request: params.request
48
- });
49
- }
50
- function hasExecApprovalConfig(params) {
51
- return resolveQQBotExecApprovalConfig(params) !== void 0;
52
- }
53
- function isNativeDeliveryEnabled(params) {
54
- if (hasExecApprovalConfig(params)) return isQQBotExecApprovalClientEnabled(params);
55
- const account = resolveQQBotAccount(params.cfg, params.accountId);
56
- return account.enabled && account.secretSource !== "none";
57
- }
58
- function canResolveTarget(request) {
59
- if (resolveApprovalTarget(request.request.sessionKey ?? null, request.request.turnSourceTo ?? null)) return true;
60
- return resolveApprovalRequestSessionConversation({
61
- request,
62
- channel: "qqbot",
63
- bundledFallback: true
64
- })?.id != null;
65
- }
66
- function createQQBotApprovalCapability() {
67
- return createChannelApprovalCapability({
68
- authorizeActorAction: ({ cfg, accountId, senderId, approvalKind }) => {
69
- if (hasExecApprovalConfig({
70
- cfg,
71
- accountId
72
- })) return (approvalKind === "plugin" ? isQQBotExecApprovalApprover({
73
- cfg,
74
- accountId,
75
- senderId
76
- }) : isQQBotExecApprovalAuthorizedSender({
77
- cfg,
78
- accountId,
79
- senderId
80
- })) ? { authorized: true } : {
81
- authorized: false,
82
- reason: "You are not authorized to approve this request."
83
- };
84
- return { authorized: true };
85
- },
86
- getActionAvailabilityState: ({ cfg, accountId }) => {
87
- return isNativeDeliveryEnabled({
88
- cfg,
89
- accountId
90
- }) ? { kind: "enabled" } : { kind: "disabled" };
91
- },
92
- getExecInitiatingSurfaceState: ({ cfg, accountId }) => {
93
- return isNativeDeliveryEnabled({
94
- cfg,
95
- accountId
96
- }) ? { kind: "enabled" } : { kind: "disabled" };
97
- },
98
- describeExecApprovalSetup: ({ accountId }) => {
99
- return `QQBot native exec approvals are enabled by default. To restrict who can approve, configure \`${accountId && accountId !== "default" ? `channels.qqbot.accounts.${accountId}` : "channels.qqbot"}.execApprovals.approvers\` with QQ user OpenIDs.`;
100
- },
101
- delivery: {
102
- hasConfiguredDmRoute: () => true,
103
- shouldSuppressForwardingFallback: (input) => {
104
- const channel = normalizeOptionalString(input.target?.channel);
105
- if (channel !== "qqbot") return false;
106
- const accountId = normalizeOptionalString(input.target?.accountId) ?? normalizeOptionalString(input.request?.request?.turnSourceAccountId);
107
- const result = isNativeDeliveryEnabled({
108
- cfg: input.cfg,
109
- accountId
110
- });
111
- getBridgeLogger().debug?.(`[qqbot:approval] shouldSuppressForwardingFallback channel=${channel} accountId=${accountId} → ${result}`);
112
- return result;
113
- }
114
- },
115
- native: {
116
- describeDeliveryCapabilities: ({ cfg, accountId }) => ({
117
- enabled: isNativeDeliveryEnabled({
118
- cfg,
119
- accountId
120
- }),
121
- preferredSurface: "origin",
122
- supportsOriginSurface: true,
123
- supportsApproverDmSurface: false,
124
- notifyOriginWhenDmOnly: false
125
- }),
126
- resolveOriginTarget: ({ request }) => {
127
- const target = resolveApprovalTarget(request.request.sessionKey ?? null, request.request.turnSourceTo ?? null);
128
- if (target) return { to: `${target.type}:${target.id}` };
129
- const sessionConversation = resolveApprovalRequestSessionConversation({
130
- request,
131
- channel: "qqbot",
132
- bundledFallback: true
133
- });
134
- if (sessionConversation?.id) return { to: `${sessionConversation.kind === "group" ? "group" : "c2c"}:${sessionConversation.id}` };
135
- return null;
136
- }
137
- },
138
- nativeRuntime: createLazyChannelApprovalNativeRuntimeAdapter({
139
- eventKinds: ["exec", "plugin"],
140
- isConfigured: ({ cfg, accountId }) => {
141
- const result = isNativeDeliveryEnabled({
142
- cfg,
143
- accountId
144
- });
145
- getBridgeLogger().debug?.(`[qqbot:approval] nativeRuntime.isConfigured accountId=${accountId} → ${result}`);
146
- return result;
147
- },
148
- shouldHandle: ({ cfg, accountId, request }) => {
149
- const result = shouldHandleRequest({
150
- cfg,
151
- accountId,
152
- request
153
- });
154
- getBridgeLogger().debug?.(`[qqbot:approval] nativeRuntime.shouldHandle accountId=${accountId} → ${result}`);
155
- return result;
156
- },
157
- load: async () => {
158
- ensurePlatformAdapter();
159
- return (await import("./handler-runtime-ZgvjnyoH.js")).qqbotApprovalNativeRuntime;
160
- }
161
- })
162
- });
163
- }
164
- const qqbotApprovalCapability = createQQBotApprovalCapability();
165
- let _cachedCapability;
166
- function getQQBotApprovalCapability() {
167
- _cachedCapability ??= qqbotApprovalCapability;
168
- return _cachedCapability;
169
- }
170
- //#endregion
171
- //#region extensions/qqbot/src/engine/utils/data-paths.ts
172
- /**
173
- * Centralised filename helpers for persisted QQBot state.
174
- *
175
- * Every persistence module routes file paths through these helpers so the
176
- * naming convention stays in sync and legacy migrations are handled
177
- * consistently.
178
- *
179
- * Key design decisions:
180
- * - Credential backup is keyed only by `accountId` because recovery runs
181
- * exactly when the appId is missing from config.
182
- */
183
- /**
184
- * Normalise an identifier so it is safe to embed in a filename.
185
- * Keeps alphanumerics, dot, underscore, dash; everything else becomes `_`.
186
- */
187
- function safeName(id) {
188
- return id.replace(/[^a-zA-Z0-9._-]/g, "_");
189
- }
190
- /**
191
- * Per-accountId credential backup file. Not keyed by appId because the
192
- * whole point of this file is to recover credentials when appId is
193
- * missing from the live config.
194
- */
195
- function getCredentialBackupFile(accountId) {
196
- return path.join(getQQBotDataPath("data"), `credential-backup-${safeName(accountId)}.json`);
197
- }
198
- /** Legacy single-file credential backup (pre-multi-account-isolation). */
199
- function getLegacyCredentialBackupFile() {
200
- return path.join(getQQBotDataPath("data"), "credential-backup.json");
201
- }
202
- //#endregion
203
- //#region extensions/qqbot/src/engine/config/credential-backup.ts
204
- /**
205
- * Credential backup & recovery.
206
- * 凭证暂存与恢复。
207
- *
208
- * Solves the "hot-upgrade interrupted, appId/secret vanished from
209
- * openclaw.json" failure mode.
210
- *
211
- * Mechanics:
212
- * - After each successful gateway start we snapshot the currently
213
- * resolved `appId` / `clientSecret` to a per-account backup file.
214
- * - During plugin startup, if the live config has an empty appId or
215
- * secret, the gateway consults the backup and restores the values
216
- * via the config mutation API.
217
- * - Backups live under `~/.openclaw/qqbot/data/` so they survive
218
- * plugin directory replacement.
219
- *
220
- * Safety notes:
221
- * - Only restore when credentials are **actually empty** — never
222
- * overwrite a user's intentional config change.
223
- * - Atomic write (temp file + rename) to avoid torn files.
224
- * - Per-account file: `credential-backup-<accountId>.json`. We do
225
- * **not** also key by appId because recovery happens precisely
226
- * when appId is unknown.
227
- * - Legacy single `credential-backup.json` is migrated automatically
228
- * when the stored accountId matches the caller.
229
- */
230
- /** Persist a credential snapshot (called once gateway reaches READY). */
231
- function saveCredentialBackup(accountId, appId, clientSecret) {
232
- if (!appId || !clientSecret) return;
233
- try {
234
- const backupPath = getCredentialBackupFile(accountId);
235
- const data = {
236
- accountId,
237
- appId,
238
- clientSecret,
239
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
240
- };
241
- replaceFileAtomicSync({
242
- filePath: backupPath,
243
- content: `${JSON.stringify(data, null, 2)}\n`,
244
- tempPrefix: ".qqbot-credential-backup"
245
- });
246
- } catch {}
247
- }
248
- /**
249
- * Load a credential snapshot for `accountId`.
250
- *
251
- * Consults the new per-account file first; falls back to the legacy
252
- * global backup file and migrates it when the embedded `accountId`
253
- * matches the request. Returns `null` when no usable backup exists.
254
- */
255
- function loadCredentialBackup(accountId) {
256
- try {
257
- if (accountId) {
258
- const data = loadJsonFile(getCredentialBackupFile(accountId));
259
- if (data?.appId && data.clientSecret) return data;
260
- }
261
- const legacy = getLegacyCredentialBackupFile();
262
- const data = loadJsonFile(legacy);
263
- if (data) {
264
- if (!data?.appId || !data?.clientSecret) return null;
265
- if (accountId && data.accountId !== accountId) return null;
266
- if (data.accountId) try {
267
- replaceFileAtomicSync({
268
- filePath: getCredentialBackupFile(data.accountId),
269
- content: `${JSON.stringify(data, null, 2)}\n`,
270
- tempPrefix: ".qqbot-credential-backup"
271
- });
272
- fs.unlinkSync(legacy);
273
- } catch {}
274
- return data;
275
- }
276
- } catch {}
277
- return null;
278
- }
279
- //#endregion
280
- //#region extensions/qqbot/src/engine/config/credentials.ts
281
- /**
282
- * QQBot credential management (pure logic layer).
283
- * QQBot 凭证管理(纯逻辑层)。
284
- *
285
- * Credential clearing and field-level cleanup for logout and setup
286
- * flows. All functions operate on plain objects (Record<string, unknown>)
287
- * and stay framework-agnostic.
288
- */
289
- /**
290
- * Remove clientSecret / clientSecretFile from a QQBot account config.
291
- *
292
- * Returns a shallow-cloned config with credentials removed, plus flags
293
- * indicating whether anything actually changed.
294
- */
295
- function clearAccountCredentials(cfg, accountId) {
296
- const nextCfg = { ...cfg };
297
- const channels = asOptionalObjectRecord(cfg.channels);
298
- const nextQQBot = channels?.qqbot ? { ...asOptionalObjectRecord(channels.qqbot) } : void 0;
299
- let cleared = false;
300
- let changed = false;
301
- if (nextQQBot) {
302
- const qqbot = nextQQBot;
303
- if (accountId === "default") {
304
- if (qqbot.clientSecret) {
305
- delete qqbot.clientSecret;
306
- cleared = true;
307
- changed = true;
308
- }
309
- if (qqbot.clientSecretFile) {
310
- delete qqbot.clientSecretFile;
311
- cleared = true;
312
- changed = true;
313
- }
314
- }
315
- const accounts = qqbot.accounts;
316
- if (accounts && accountId in accounts) {
317
- const entry = accounts[accountId];
318
- if (entry && "clientSecret" in entry) {
319
- delete entry.clientSecret;
320
- cleared = true;
321
- changed = true;
322
- }
323
- if (entry && "clientSecretFile" in entry) {
324
- delete entry.clientSecretFile;
325
- cleared = true;
326
- changed = true;
327
- }
328
- if (entry && Object.keys(entry).length === 0) {
329
- delete accounts[accountId];
330
- changed = true;
331
- }
332
- }
333
- }
334
- if (changed && nextQQBot) nextCfg.channels = {
335
- ...channels,
336
- qqbot: nextQQBot
337
- };
338
- return {
339
- nextCfg,
340
- cleared,
341
- changed
342
- };
343
- }
344
- //#endregion
345
- //#region extensions/qqbot/src/channel.ts
346
- let gatewayModulePromise;
347
- function loadGatewayModule() {
348
- gatewayModulePromise ??= import("./gateway-Bbsd8-ne.js");
349
- return gatewayModulePromise;
350
- }
351
- function createQQBotSendReceipt(params) {
352
- const messageId = params.messageId?.trim();
353
- return createMessageReceiptFromOutboundResults({
354
- results: messageId ? [{
355
- channel: "qqbot",
356
- messageId,
357
- conversationId: params.target
358
- }] : [],
359
- threadId: params.target,
360
- kind: params.kind
361
- });
362
- }
363
- async function sendQQBotText(params) {
364
- await loadGatewayModule();
365
- const account = resolveQQBotAccount(params.cfg, params.accountId);
366
- const { sendText } = await import("./outbound-BLWish89.js").then((n) => n.t);
367
- const result = await sendText({
368
- to: params.to,
369
- text: params.text,
370
- accountId: params.accountId,
371
- replyToId: params.replyToId,
372
- account: toGatewayAccount(account)
373
- });
374
- return {
375
- channel: "qqbot",
376
- messageId: result.messageId ?? "",
377
- receipt: createQQBotSendReceipt({
378
- messageId: result.messageId,
379
- target: params.to,
380
- kind: "text"
381
- }),
382
- meta: result.error ? { error: result.error } : void 0
383
- };
384
- }
385
- async function sendQQBotMedia(params) {
386
- await loadGatewayModule();
387
- const account = resolveQQBotAccount(params.cfg, params.accountId);
388
- const { sendMedia } = await import("./outbound-BLWish89.js").then((n) => n.t);
389
- const result = await sendMedia({
390
- to: params.to,
391
- text: params.text ?? "",
392
- mediaUrl: params.mediaUrl ?? "",
393
- accountId: params.accountId,
394
- replyToId: params.replyToId,
395
- account: toGatewayAccount(account)
396
- });
397
- return {
398
- channel: "qqbot",
399
- messageId: result.messageId ?? "",
400
- receipt: createQQBotSendReceipt({
401
- messageId: result.messageId,
402
- target: params.to,
403
- kind: "media"
404
- }),
405
- meta: result.error ? { error: result.error } : void 0
406
- };
407
- }
408
- function toQQBotMessageSendResult(result) {
409
- return {
410
- messageId: result.messageId,
411
- receipt: result.receipt
412
- };
413
- }
414
- const qqbotMessageAdapter = defineChannelMessageAdapter({
415
- id: "qqbot",
416
- durableFinal: { capabilities: {
417
- text: true,
418
- media: true,
419
- replyTo: true
420
- } },
421
- send: {
422
- text: async (ctx) => toQQBotMessageSendResult(await sendQQBotText({
423
- cfg: ctx.cfg,
424
- to: ctx.to,
425
- text: ctx.text,
426
- accountId: ctx.accountId,
427
- replyToId: ctx.replyToId
428
- })),
429
- media: async (ctx) => toQQBotMessageSendResult(await sendQQBotMedia({
430
- cfg: ctx.cfg,
431
- to: ctx.to,
432
- text: ctx.text,
433
- mediaUrl: ctx.mediaUrl,
434
- accountId: ctx.accountId,
435
- replyToId: ctx.replyToId
436
- }))
437
- }
438
- });
439
- const EXEC_APPROVAL_COMMAND_RE = /\/approve(?:@[^\s]+)?\s+[A-Za-z0-9][A-Za-z0-9._:-]*\s+(?:allow-once|allow-always|always|deny)\b/i;
440
- function persistAccountCredentialSnapshot(account) {
441
- if (account.appId && account.clientSecret) saveCredentialBackup(account.accountId, account.appId, account.clientSecret);
442
- }
443
- function shouldSuppressLocalQQBotApprovalPrompt(params) {
444
- if (params.hint?.kind !== "approval-pending" || params.hint.approvalKind !== "exec") return false;
445
- const account = resolveQQBotAccount(params.cfg, params.accountId);
446
- if (!account.enabled || account.secretSource === "none") return false;
447
- if (getExecApprovalReplyMetadata(params.payload)) return true;
448
- const text = typeof params.payload.text === "string" ? params.payload.text : "";
449
- return EXEC_APPROVAL_COMMAND_RE.test(text);
450
- }
451
- const qqbotPlugin = {
452
- id: "qqbot",
453
- setupWizard: qqbotSetupWizard,
454
- meta: { ...qqbotMeta },
455
- capabilities: {
456
- chatTypes: ["direct", "group"],
457
- media: true,
458
- reactions: false,
459
- threads: false,
460
- blockStreaming: true
461
- },
462
- reload: { configPrefixes: ["channels.qqbot"] },
463
- configSchema: qqbotChannelConfigSchema,
464
- config: {
465
- ...qqbotConfigAdapter,
466
- /**
467
- * Treat an account as configured when either the live config has
468
- * credentials OR a recoverable credential backup exists. This mirrors
469
- * the standalone plugin and lets the gateway survive a hot upgrade
470
- * that wiped openclaw.json mid-flight.
471
- */
472
- isConfigured: (account) => {
473
- if (qqbotConfigAdapter.isConfigured(account)) return true;
474
- if (!account) return false;
475
- const backup = loadCredentialBackup(account.accountId);
476
- return Boolean(backup?.appId && backup?.clientSecret);
477
- }
478
- },
479
- setup: { ...qqbotSetupAdapterShared },
480
- approvalCapability: getQQBotApprovalCapability(),
481
- message: qqbotMessageAdapter,
482
- messaging: {
483
- targetPrefixes: ["qqbot"],
484
- /** Normalize common QQ Bot target formats into the canonical qqbot:... form. */
485
- normalizeTarget,
486
- targetResolver: {
487
- /** Return true when the id looks like a QQ Bot target. */
488
- looksLikeId: looksLikeQQBotTarget,
489
- hint: "QQ Bot target format: qqbot:c2c:openid (direct) or qqbot:group:groupid (group)"
490
- }
491
- },
492
- outbound: {
493
- deliveryMode: "direct",
494
- chunker: (text, limit) => getQQBotRuntime().channel.text.chunkMarkdownText(text, limit),
495
- chunkerMode: "markdown",
496
- textChunkLimit: 5e3,
497
- shouldSuppressLocalPayloadPrompt: ({ cfg, accountId, payload, hint }) => shouldSuppressLocalQQBotApprovalPrompt({
498
- cfg,
499
- accountId,
500
- payload,
501
- hint
502
- }),
503
- sendText: async ({ to, text, accountId, replyToId, cfg }) => await sendQQBotText({
504
- cfg,
505
- to,
506
- text,
507
- accountId,
508
- replyToId
509
- }),
510
- sendMedia: async ({ to, text, mediaUrl, accountId, replyToId, cfg }) => await sendQQBotMedia({
511
- cfg,
512
- to,
513
- text,
514
- mediaUrl,
515
- accountId,
516
- replyToId
517
- })
518
- },
519
- gateway: {
520
- startAccount: async (ctx) => {
521
- let { account, cfg } = ctx;
522
- const { abortSignal, log } = ctx;
523
- if (!account.appId || !account.clientSecret) {
524
- const backup = loadCredentialBackup(account.accountId);
525
- if (backup?.appId && backup?.clientSecret) try {
526
- const nextCfg = applyQQBotAccountConfig(cfg, account.accountId, {
527
- appId: backup.appId,
528
- clientSecret: backup.clientSecret
529
- });
530
- await writeOpenClawConfigThroughRuntime(getQQBotRuntime(), nextCfg);
531
- cfg = nextCfg;
532
- account = resolveQQBotAccount(nextCfg, account.accountId);
533
- log?.info(`[qqbot:${account.accountId}] Restored credentials from backup (appId=${account.appId})`);
534
- } catch (err) {
535
- log?.error(`[qqbot:${account.accountId}] Failed to restore credentials from backup: ${err instanceof Error ? err.message : String(err)}`);
536
- }
537
- }
538
- const { startGateway } = await loadGatewayModule();
539
- log?.info(`[qqbot:${account.accountId}] Starting gateway — appId=${account.appId}, enabled=${account.enabled}, name=${account.name ?? "unnamed"}`);
540
- await startGateway({
541
- account,
542
- abortSignal,
543
- cfg,
544
- log,
545
- channelRuntime: ctx.channelRuntime,
546
- onReady: () => {
547
- log?.info(`[qqbot:${account.accountId}] Gateway ready`);
548
- ctx.setStatus({
549
- ...ctx.getStatus(),
550
- running: true,
551
- connected: true,
552
- lastConnectedAt: Date.now()
553
- });
554
- persistAccountCredentialSnapshot(account);
555
- },
556
- onResumed: () => {
557
- log?.info(`[qqbot:${account.accountId}] Gateway resumed`);
558
- ctx.setStatus({
559
- ...ctx.getStatus(),
560
- running: true,
561
- connected: true,
562
- lastConnectedAt: Date.now()
563
- });
564
- persistAccountCredentialSnapshot(account);
565
- },
566
- onError: (error) => {
567
- log?.error(`[qqbot:${account.accountId}] Gateway error: ${error.message}`);
568
- ctx.setStatus({
569
- ...ctx.getStatus(),
570
- lastError: error.message
571
- });
572
- }
573
- });
574
- },
575
- logoutAccount: async ({ accountId, cfg }) => {
576
- const { nextCfg, cleared, changed } = clearAccountCredentials(cfg, accountId);
577
- if (changed) await writeOpenClawConfigThroughRuntime(getQQBotRuntime(), nextCfg);
578
- const loggedOut = resolveQQBotAccount(changed ? nextCfg : cfg, accountId).secretSource === "none";
579
- return {
580
- ok: true,
581
- cleared,
582
- envToken: Boolean(process.env.QQBOT_CLIENT_SECRET),
583
- loggedOut
584
- };
585
- }
586
- },
587
- status: {
588
- defaultRuntime: {
589
- accountId: DEFAULT_ACCOUNT_ID$1,
590
- running: false,
591
- connected: false,
592
- lastConnectedAt: null,
593
- lastError: null,
594
- lastInboundAt: null,
595
- lastOutboundAt: null
596
- },
597
- buildChannelSummary: ({ snapshot }) => ({
598
- configured: snapshot.configured ?? false,
599
- tokenSource: snapshot.tokenSource ?? "none",
600
- running: snapshot.running ?? false,
601
- connected: snapshot.connected ?? false,
602
- lastConnectedAt: snapshot.lastConnectedAt ?? null,
603
- lastError: snapshot.lastError ?? null
604
- }),
605
- buildAccountSnapshot: ({ account, runtime }) => ({
606
- accountId: account?.accountId ?? DEFAULT_ACCOUNT_ID$1,
607
- name: account?.name,
608
- enabled: account?.enabled ?? false,
609
- configured: Boolean(account?.appId && account?.clientSecret),
610
- tokenSource: account?.secretSource,
611
- running: runtime?.running ?? false,
612
- connected: runtime?.connected ?? false,
613
- lastConnectedAt: runtime?.lastConnectedAt ?? null,
614
- lastError: runtime?.lastError ?? null,
615
- lastInboundAt: runtime?.lastInboundAt ?? null,
616
- lastOutboundAt: runtime?.lastOutboundAt ?? null
617
- })
618
- }
619
- };
620
- //#endregion
621
- export { qqbotPlugin as t };