feat(qqbot): extract self-contained engine/ architecture with QR-code onboarding, approval handling#67960
Conversation
|
Too many files changed for review. ( |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1d97378bff
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (getExecApprovalReplyMetadata(params.payload as never)) { | ||
| return true; |
There was a problem hiding this comment.
Gate local approval suppression on native capability
shouldSuppressLocalQQBotApprovalPrompt suppresses exec approval payloads as soon as metadata is present, but it only checks account.enabled and secretSource and never checks whether QQBot native approvals are actually enabled. When channels.qqbot.execApprovals.enabled is false (or approvers resolve to none), native delivery is disabled in extensions/qqbot/src/approval-native.ts, yet this hook still drops the /approve ... prompt, leaving users with no approval path for that request.
Useful? React with 👍 / 👎.
| */ | ||
| export function parseApprovalButtonData(buttonData: string): ParsedApprovalAction | null { | ||
| const m = buttonData.match( | ||
| /^approve:((?:(?:exec|plugin):)?[0-9a-f-]+):(allow-once|allow-always|deny)$/i, |
There was a problem hiding this comment.
Accept non-hex approval IDs in button callback parsing
The callback parser only accepts approval IDs matching [0-9a-f-]+, so button payloads like approve:approval-1:allow-once are rejected. The gateway allows caller-provided exec approval IDs (exec.approval.request id), so non-hex IDs are valid inputs; with this parser, QQ button clicks for those requests are ignored and the approval cannot be resolved from native UI.
Useful? React with 👍 / 👎.
1d97378 to
fb77b06
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f138353cdd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if ( | ||
| !inboundIsApproveCommand && | ||
| !sawExecApprovalToolResult && | ||
| collectExecApprovalCommandIds(payload).length > 0 | ||
| ) { |
There was a problem hiding this comment.
Allow explicit /approve responses when no new tool event exists
This branch suppresses any reply that contains an /approve <id> <decision> command whenever the turn did not emit a new exec-approval tool result and the inbound message was not itself an approve command. That also drops legitimate replies where the user explicitly asks the assistant to repeat a prior command string (which the updated prompt guidance now permits), causing the assistant to send nothing. The suppression should key off a true stale-approval signal (or explicit user-intent bypass), not only regex detection plus missing tool metadata.
Useful? React with 👍 / 👎.
f138353 to
8d3fe55
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8d3fe5513a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| if (sessionConversation?.id) { | ||
| const kind = sessionConversation.kind; | ||
| const chatScope: ChatScope = kind === "group" ? "group" : "c2c"; |
There was a problem hiding this comment.
Preserve channel kind when mapping session conversation targets
resolveApprovalRequestSessionConversation can return kind: "channel", but this branch coerces every non-group kind to c2c. For approvals initiated from guild/channel chats (where inbound context uses qqbot:channel:<id>), fallback routing becomes c2c:<channelId>, so native approval delivery is sent to the wrong QQ surface and the originating conversation cannot resolve the approval.
Useful? React with 👍 / 👎.
| if (!sk) { | ||
| return null; | ||
| } | ||
| const m = sk.match(/qqbot:(c2c|direct|group):([A-F0-9]+)/i); |
There was a problem hiding this comment.
Broaden approval target regex to accepted QQ target formats
The target parser only accepts qqbot:(c2c|direct|group):([A-F0-9]+), which excludes formats this plugin already uses, such as qqbot:channel:<id> and hyphenated/UUID-like ids. In requests without a resolvable session-conversation fallback, this causes resolveApprovalTarget to return null, so native approval prompts are dropped instead of being delivered.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6b026d3478
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| return; | ||
| } | ||
|
|
||
| void adapter.resolveApproval(parsed.approvalId, parsed.decision).then((ok) => { |
There was a problem hiding this comment.
Enforce approver checks before resolving button clicks
The INTERACTION_CREATE handler resolves any parsed approval button immediately via adapter.resolveApproval(...) without validating who clicked it. In this commit, the QQ adapter forwards that resolve call to resolveApprovalOverGateway without a senderId, so approver restrictions cannot be enforced downstream. In group/channel conversations, this allows non-approvers to approve or deny exec requests by clicking the button even when channels.qqbot.execApprovals.approvers is configured.
Useful? React with 👍 / 👎.
| bundledFallback: true, | ||
| }); | ||
| if (sessionConversation?.id) { | ||
| const kind = sessionConversation.kind === "group" ? "group" : "c2c"; |
There was a problem hiding this comment.
Preserve channel targets in native origin resolution
resolveOriginTarget coerces every non-group session conversation to c2c (kind === "group" ? "group" : "c2c"). However, session-conversation resolution can return kind: "channel", so channel-origin approvals are rewritten to c2c:<channelId> and routed to the wrong QQ surface when fallback session-conversation routing is used. Fresh evidence beyond the existing thread: this same coercion exists in capability.ts, so fixing only handler-runtime.ts still leaves channel native-routing broken.
Useful? React with 👍 / 👎.
6b026d3 to
53781f1
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 53781f100c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }, | ||
|
|
||
| getTempDir(): string { | ||
| const { resolvePreferredOpenClawTmpDir } = require("openclaw/plugin-sdk/temp-path") as typeof import("openclaw/plugin-sdk/temp-path"); |
There was a problem hiding this comment.
Use createRequire before calling require in ESM adapter
extensions/qqbot is an ESM package ("type": "module"), but this adapter uses bare require(...) without defining it via createRequire. In Node ESM, require is undefined, so code paths that touch adapter helpers like secret checks or temp-dir resolution can throw ReferenceError and prevent QQBot startup/account resolution. Load these modules with await import(...) or initialize const require = createRequire(import.meta.url) in this file.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3485a609b4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| handler: async (ctx) => { | ||
| const arg = ctx.args.trim().toLowerCase(); | ||
|
|
||
| let runtime: ReturnType<NonNullable<typeof _runtimeGetter>>; |
There was a problem hiding this comment.
Restrict /bot-approve to explicit admin senders
This command mutates global exec-approval policy (tools.exec.security/tools.exec.ask) but the handler does not perform any explicit privileged-sender check before allowing /bot-approve off/on/always/reset. In configurations where command access is wildcarded (for example allowFrom: ["*"], which setup defaults can produce), any DM sender can disable approvals for all subsequent exec requests. Add an explicit allowlist/owner guard (like the hard gate used for /bot-logs) before applying config writes.
Useful? React with 👍 / 👎.
| const ttsResult = await deps.tts.textToSpeech({ | ||
| text: ttsText, | ||
| cfg, | ||
| channel: "qqbot", | ||
| }); |
There was a problem hiding this comment.
Restore QQBot-local TTS compatibility in audio replies
The refactored audio payload flow now always calls the generic runtime TTS entrypoint and no longer consults QQBot-local TTS account config, so deployments that previously relied on channels.qqbot.tts lose voice synthesis unless they also configure global messages.tts. This is a behavior regression from the previous QQBot path (plugin-local TTS with global fallback) and should be preserved or migrated explicitly.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3504be63e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // config is missing appId/secret (e.g. a hot-upgrade wiped | ||
| // openclaw.json). We only restore when both fields are empty so a | ||
| // user's intentional clear isn't silently undone. | ||
| if (!account.appId || !account.clientSecret) { |
There was a problem hiding this comment.
Avoid restoring backup credentials after a user logout
This restore gate uses !account.appId || !account.clientSecret, so any account with one missing field will be repopulated from credential-backup-*.json. In practice, logoutAccount clears only secret fields while keeping appId, so the next startup rehydrates the secret from backup and effectively undoes logout/credential clear operations. This is a security and control-flow regression because users cannot reliably disable credentials once a backup exists.
Useful? React with 👍 / 👎.
| if (type !== "c2c") { | ||
| return `💡 请在私聊中使用此指令`; | ||
| } |
There was a problem hiding this comment.
Require sender authorization for /bot-clear-storage
The new /bot-clear-storage handler only checks that the chat type is c2c, then deletes files under ~/.openclaw/media/qqbot/downloads/<appId>, which is shared bot storage for that account. Because there is no admin/allowlist guard here, any DM sender who can reach the bot can trigger destructive cleanup (--force) and remove other users' cached files.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f8a9f0603c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| accountId: account.accountId, | ||
| appId: account.appId, | ||
| accountConfig: account.config, | ||
| commandAuthorized: true, |
There was a problem hiding this comment.
Enforce real sender auth in pre-dispatch slash commands
trySlashCommand executes slash handlers before the message enters the normal inbound pipeline, but the command context is built with commandAuthorized: true unconditionally. With this commit adding state-changing pre-dispatch commands (/bot-approve, /bot-clear-storage), senders who should be blocked by allowFrom / dmPolicy / groupPolicy can still invoke those commands because no real authorization is evaluated on this path. Compute command authorization from the actual sender/policy (or gate pre-dispatch execution through the same access check) before calling matchSlashCommand.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 970bf1c5bd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| } | ||
|
|
||
| const isForce = ctx.args.trim() === "--force"; | ||
| const targetDir = path.join(getHomeDir(), ".openclaw", "media", "qqbot", "downloads", appId); |
There was a problem hiding this comment.
Point /bot-clear-storage at the real downloads directory
/bot-clear-storage builds targetDir as ~/.openclaw/media/qqbot/downloads/<appId>, but QQBot downloads are written under ~/.openclaw/media/qqbot/downloads (for inbound attachments) and .../downloads/url-fallback (for outbound URL fallback). In practice this makes the scan frequently report “empty” and --force skip the files users actually want to remove, so the new cleanup command does not reclaim disk space as advertised.
Useful? React with 👍 / 👎.
| if (!params.content) { | ||
| return json({ error: "content is required when action=add" }); | ||
| } | ||
| const resolvedTo = params.to || ctx.fallbackTo; |
There was a problem hiding this comment.
Normalize DM fallback target before creating reminder jobs
executeRemind now copies ctx.fallbackTo directly into delivery.to; for QQ DM conversations that fallback target is qqbot:dm:<guildId>, but the QQBot target parser only handles c2c/group/channel and treats unknown prefixes as raw C2C IDs. When users create reminders in DM without explicitly passing to, the scheduled job can carry an invalid target and later deliver to the wrong endpoint (or fail) instead of replying to the originating DM conversation.
Useful? React with 👍 / 👎.
970bf1c to
bcb67e6
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 73182255f3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }): Promise<unknown> { | ||
| const { account, event, text, consumeQuoteRef, allowDm } = params; | ||
| const ref = consumeQuoteRef(); | ||
| const target = buildDeliveryTarget(event); |
There was a problem hiding this comment.
Guard DM chunk routing when guildId is unavailable
sendTextChunkToTarget now unconditionally derives the destination with buildDeliveryTarget(event), which maps event.type === "dm" to id = event.guildId. In this commit, the block-delivery path builds deliverEvent without guildId (extensions/qqbot/src/engine/gateway/outbound-dispatch.ts), so DM replies can be sent with an undefined target id and fail at the QQ DM endpoint. This breaks normal text/media-tag reply delivery for DIRECT_MESSAGE_CREATE conversations instead of falling back safely.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 49a44c2abe
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // attachments under sibling directories (e.g. `media/outbound/`) that are already | ||
| // part of the core media allowlist; we mirror that so auto-routed sends work | ||
| // without leaving the plugin's trust boundary. | ||
| const allowedRoots = [getOpenClawMediaDir(), getQQBotMediaDir()]; |
There was a problem hiding this comment.
Narrow payload file allowlist to QQBot-owned roots
resolveQQBotPayloadLocalFilePath now allows any file under ~/.openclaw/media by including getOpenClawMediaDir() in allowedRoots, and this guard is what structured payload handlers rely on before reading/uploading local files. In practice, a QQBot reply payload can now reference media artifacts from other channels/accounts stored under the shared media tree, which is a cross-surface data exposure regression compared with the previous QQBot-only boundary.
Useful? React with 👍 / 👎.
| const onAbort = () => { | ||
| clearTimeout(timer); | ||
| reject(new Error("Aborted")); | ||
| }; | ||
| signal.addEventListener("abort", onAbort, { once: true }); |
There was a problem hiding this comment.
Remove stale abort listeners in token refresh sleep
abortableSleep registers an abort listener on every loop iteration but never removes it when the timer resolves normally, so long-running background refresh accumulates listeners/closures until shutdown. This creates avoidable memory growth and makes abort handling cost scale with runtime duration; detach the listener on the timeout-resolve path.
Useful? React with 👍 / 👎.
Remove qqbot's internal TTS implementation and unify voice synthesis through the framework's global TTS provider registry. - Delete engine/gateway/tts-config.ts (plugin-specific TTS config) - Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64 - Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback) - Strip QQBotTtsSchema from config-schema, plugin.json, and tests - Remove TTS diagnostics logging and hasTTS system prompt from gateway - Delete ~260 lines of TTS code from utils/audio-convert.ts Made-with: Cursor
Add engine-layer modules that are self-contained and portable across both the built-in and standalone qqbot packages: - engine/config: account resolution helpers, field readers - engine/tools: channel API proxy, remind scheduling logic - engine/utils: audio format conversion, duration/error formatting, debug logging Consolidate duplicate utility functions across the codebase: - Merge debug-log.ts into log.ts - Merge error-format.ts into format.ts with full .cause chain support - Unify normalizeLowercase/readNumber/readBoolean/readStringMap into string-normalize.ts, removing private copies in resolve.ts, remind-logic.ts, and audio-convert.ts - Remove dead formatDuration export from audio-convert.ts - Delete unused config/schema.ts and config/helpers.ts Made-with: Cursor
…gement Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include: - Migrate credential clearing and validation logic to engine/config/credentials.ts. - Simplify setup input validation and application in engine/config/setup-logic.ts. - Enhance account resolution and configuration application in engine/config/resolve.ts. - Update channel and messaging logic to utilize the new credential management functions. This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase.
- Extract handleMessage (620 lines) into three modules: - inbound-context.ts: InboundContext type definition - inbound-pipeline.ts: buildInboundContext() - outbound-dispatch.ts: dispatchOutbound() - gateway.ts handleMessage reduced to ~35 line shell - Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE - Add ref/format-message-ref.ts for cache-miss quote formatting - Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead - QueuedMessage: add msgType/msgElements for quote messages
…ge processing Call ensurePlatformAdapter() at the start of bridge/gateway.ts's startGateway() to guarantee the adapter is available when engine code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter(). When the bundler splits code into separate chunks, bootstrap.ts's module-level side-effect registration may not have executed yet by the time the gateway processes its first inbound attachment download. Also fix the TS2339 error in registerApproveRuntimeGetter by using getQQBotRuntime() (full PluginRuntime with config) instead of getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config).
…gistered sendMedia() calls isAudioFile() as part of its media-type dispatch logic before any actual audio processing. When the audio adapter is not yet registered (e.g. framework tool calls sendMedia before gateway startup), isAudioFile() would throw 'OutboundAudioAdapter not registered' even for non-audio files like images. Wrap the getAudio() call in isAudioFile() with try/catch to return false when the adapter is unavailable, allowing non-audio media sends to proceed normally.
Drop the startup / upgrade greeting feature that was folded into the previous reminder + credential-backup commit. The pipeline has proven unnecessary for the fused build and its supporting admin-resolver scaffolding has no other consumers, so both are removed wholesale. - Delete engine/session/startup-greeting.ts and its tests: the first-launch "soul online" / "updated to vX.Y.Z" messages, the per-(accountId, appId) startup marker, the failure cooldown, and the legacy startup-marker.json migration path are all gone. - Delete engine/session/admin-resolver.ts and its tests: admin openid persistence/resolution, upgrade-greeting-target load/clear and the sendStartupGreetings dispatcher only ever served the greeting flow and were not referenced elsewhere. - channel.ts: drop the sendStartupGreetings import and the READY / RESUMED hooks that triggered greetings; credential-backup snapshots stay untouched. - engine/utils/data-paths.ts: remove getAdminMarkerFile / getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile / getStartupMarkerFile / getLegacyStartupMarkerFile along with the now-stale module docblock sections. Credential-backup helpers and safeName are preserved. Net -655 LOC across 6 files. tsc --noEmit passes on extensions/qqbot/tsconfig.json and no references to the removed symbols remain in the workspace.
…undled runtime deps - bootstrap: replace sync require() with static imports for secret-input and temp-path so vitest resolve.alias works correctly (require bypasses vitest aliases causing Cannot find module errors) - format: handle null/undefined in formatErrorMessage before JSON.stringify since JSON.stringify(undefined) returns JS undefined, not a string - gateway/types: reword comment to avoid triggering the channel-import guardrail regex that forbids quoted openclaw/plugin-sdk references - package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root dependencies as required by bundled plugin runtime dependency checks
Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories.
…PI simplification
- Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.) - Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts
- Make hasPlatformAdapter() also check for registered factory, so adapter is always discoverable once bootstrap has run - Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(), delegate entirely to PlatformAdapter.getTempDir() which calls resolvePreferredOpenClawTmpDir() under the hood - Keeps engine/ layer free of openclaw/plugin-sdk imports
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0abb9e66d5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (qqbot?.appId || process.env.QQBOT_APP_ID) { | ||
| ids.add(DEFAULT_ACCOUNT_ID); | ||
| } |
There was a problem hiding this comment.
Include backup-only default account in account discovery
The new credential-recovery flow in channel.ts runs only after an account is started, but account startup is driven by plugin.config.listAccountIds (see src/gateway/server-channels.ts, which returns early when that list is empty). This function only adds the default account when channels.qqbot.appId (or QQBOT_APP_ID) exists, so in the exact hot-upgrade scenario you added recovery for (both appId and clientSecret missing in config, backup file present), it returns [] and the restore path never executes, leaving QQBot offline until manual repair.
Useful? React with 👍 / 👎.
… onboarding, approval handling (openclaw#67960) * feat(qqbot): add core architecture modules * feat(qqbot): extract engine modules with DI adapters * refactor(qqbot): remove plugin-level TTS, delegate to framework Remove qqbot's internal TTS implementation and unify voice synthesis through the framework's global TTS provider registry. - Delete engine/gateway/tts-config.ts (plugin-specific TTS config) - Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64 - Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback) - Strip QQBotTtsSchema from config-schema, plugin.json, and tests - Remove TTS diagnostics logging and hasTTS system prompt from gateway - Delete ~260 lines of TTS code from utils/audio-convert.ts Made-with: Cursor * feat(qqbot): extract shared engine modules for config, tools, and audio Add engine-layer modules that are self-contained and portable across both the built-in and standalone qqbot packages: - engine/config: account resolution helpers, field readers - engine/tools: channel API proxy, remind scheduling logic - engine/utils: audio format conversion, duration/error formatting, debug logging Consolidate duplicate utility functions across the codebase: - Merge debug-log.ts into log.ts - Merge error-format.ts into format.ts with full .cause chain support - Unify normalizeLowercase/readNumber/readBoolean/readStringMap into string-normalize.ts, removing private copies in resolve.ts, remind-logic.ts, and audio-convert.ts - Remove dead formatDuration export from audio-convert.ts - Delete unused config/schema.ts and config/helpers.ts Made-with: Cursor * refactor(qqbot): streamline account configuration and credential management Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include: - Migrate credential clearing and validation logic to engine/config/credentials.ts. - Simplify setup input validation and application in engine/config/setup-logic.ts. - Enhance account resolution and configuration application in engine/config/resolve.ts. - Update channel and messaging logic to utilize the new credential management functions. This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase. * feat(qqbot): simplify api architecture * feat: 支持扫码绑定QQ机器人 * feat(qqbot): refactor gateway into inbound pipeline + outbound dispatch - Extract handleMessage (620 lines) into three modules: - inbound-context.ts: InboundContext type definition - inbound-pipeline.ts: buildInboundContext() - outbound-dispatch.ts: dispatchOutbound() - gateway.ts handleMessage reduced to ~35 line shell - Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE - Add ref/format-message-ref.ts for cache-miss quote formatting - Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead - QueuedMessage: add msgType/msgElements for quote messages * fix(qqbot): fix markdownSupport loss + dynamic User-Agent Root cause: setOpenClawVersion() called _ensureInitialized(true) which cleared _appRegistry, destroying the MessageApi instance created by initApiConfig() with markdownSupport=true. Subsequent block deliver calls created a default markdownSupport=false instance, causing: 1. Markdown messages sent as plain text (msg_type=0 instead of 2) 2. message_reference incorrectly added (only suppressed in MD mode) Fix: ApiClient and TokenManager now accept userAgent as string | (() => string). sender.ts passes the buildUserAgent function reference, so UA changes propagate automatically on next request without rebuilding any objects. - ApiClient: userAgent -> resolveUserAgent getter, called per-request - TokenManager: same pattern - types.ts: ApiClientConfig.userAgent supports string | (() => string) - sender.ts: remove force re-init + _rebuildAppRegistry hack - initSender/setOpenClawVersion only update version variables - _ensureInitialized creates singletons once, never destroys them - _appRegistry is never cleared -> markdownSupport always preserved - runtime.ts: inject framework version via setOpenClawVersion(runtime.version) - gateway.ts: pass openclawVersion to initSender + registerPluginVersion - slash-commands-impl.ts: remove fragile require("../package.json") * feat(qqbot): implement native approval handling and configuration Add a new approval handling system for QQBot that integrates with the existing framework. Key features include: - Introduce `approval-handler.runtime.ts` for managing approval requests via QQ messages with inline keyboard support. - Create `approval-native.ts` as the entry point for QQBot's approval capability, allowing for simplified approval processes without explicit approver lists. - Implement configuration schema for exec approvals, enabling fine-grained control over who can approve requests. - Enhance messaging and interaction handling to support approval decisions through button interactions. This implementation streamlines the approval process, making it more user-friendly and efficient for QQBot users. * refactor(qqbot): enhance error handling across API and messaging modules This update introduces a centralized error formatting utility, `formatErrorMessage`, to improve consistency in error logging throughout the QQBot codebase. Key changes include: - Integration of `formatErrorMessage` in various API client, messaging, and gateway modules to standardize error messages. - Replacement of direct error message handling with the new utility to enhance readability and maintainability. These improvements streamline error reporting and provide clearer insights into issues encountered during operation. * refactor(qqbot): enhance API and messaging structure with type improvements This update refines the API and messaging modules by introducing type enhancements and restructuring function signatures for better clarity and maintainability. Key changes include: - Updated import statements to streamline type usage in and . - Refactored message sending functions to accept options objects, improving readability and flexibility. - Introduced a new method in to facilitate external message-sent notifications. - Enhanced error handling in the retry mechanism to ensure more robust behavior. These modifications aim to improve the overall code quality and developer experience within the QQBot framework. * feat: 优化文案 * refactor(qqbot): unify Logger interfaces + eliminate P0 code smells Logger unification (17 files): - Introduce single EngineLogger interface in engine/types.ts { info, error, warn?, debug? } - Delete 5 fragmented Logger interfaces: GatewayLogger, ReconnectLogger, MessageRefLogger, PathLogger, SenderLogger - Replace all references across engine/ to use EngineLogger directly P0 code smell fixes (sender.ts + messages.ts + outbound-dispatch.ts): - messages.ts: add public notifyMessageSent() method on MessageApi, replacing 8x 'as unknown as { messageSentHook }' private field hack - sender.ts: extract notifyMediaHook() helper, deduplicate 4 media send functions (sendImage/sendVoice/sendVideo/sendFile) - sender.ts: replace magic numbers 1/2/3/4 with MediaFileType enum - sender.ts: remove 4 redundant 'as MessageResponse' type assertions - outbound-dispatch.ts: remove 5 unnecessary 'as never' casts * feat(qqbot): add /bot-clear-storage command + consolidate utils/types into engine/ /bot-clear-storage (slash-commands-impl.ts): - Migrate from standalone version, aligned with its two-step flow: 1. No args: scan ~/.openclaw/media/qqbot/downloads/{appId}/ and display file list with confirmation button 2. --force: delete files + removeEmptyDirs cleanup - C2C only (group chat returns hint) - bot-help: exclude bot-upgrade and bot-clear-storage in group listings Consolidate into engine/: - Delete src/utils/audio-convert.ts (pure re-export shell, zero consumers) - Move 5 test files from src/utils/ to src/engine/utils/ (fix import paths) - Move src/types/silk-wasm.d.ts to src/engine/types/ - Remove empty src/utils/ and src/types/ directories * refactor(qqbot): restructure API and bridge components for improved modularity This update enhances the QQBot framework by reorganizing the API and bridge components, promoting better modularity and maintainability. Key changes include: - Refactored import paths to streamline access to bridge tools and configurations. - Introduced new bridge files for channel entry, runtime, and approval capabilities, centralizing related functionalities. - Updated existing functions to utilize the new bridge structure, ensuring consistency across the codebase. - Removed deprecated functions and types, simplifying the overall architecture. These modifications aim to improve code clarity and facilitate future development within the QQBot ecosystem. * refactor(qqbot): standardize engine log levels and unify log tag prefix - Rename client.ts to api-client.ts to match ApiClient class name - Downgrade ~60 non-critical info logs to debug level across 12 files (token request/response, HTTP request/response, session restore, media tag detection, image classification, quote detection, attachment download/transcode, retry attempts, etc.) - Unify log tag prefix to [qqbot:xxx] format across all engine modules ([core-api] -> [qqbot:api], [token:x] -> [qqbot:token:x], [retry] -> [qqbot:retry], [messages] -> [qqbot:messages], [sender:x] -> [qqbot:x]) - Remove unnecessary reqTs timestamp from api-client.ts log output - Add dispatch event debug log in gateway-connection.ts - Merge sendProactiveMessage into sendText, remove dead code (sendProactiveText import, getRefIdx, QQMessageResult type) - Narrow allow-from.ts type from unknown[] to Array<string | number> * refactor(qqbot): move interaction handler from bridge to engine - Move onInteraction approval handler into engine/gateway.ts as createApprovalInteractionHandler(), eliminating the callback indirection through CoreGatewayContext - Remove onInteraction from CoreGatewayContext interface and its unused InteractionEvent import from gateway/types.ts - Remove getPlatformAdapter, parseApprovalButtonData and InteractionEvent imports from bridge/gateway.ts * refactor(qqbot): route bridge and sender logs through framework logger - Add bridge/logger.ts as a shared logger holder for bridge-layer modules, injected with ctx.log during gateway startup - Replace all console.log/console.error in bridge/ with getBridgeLogger() calls (approval, bootstrap, tools) - Restore framework logger support in sender.ts via initSender() so API-layer logs flow through OpenClaw log system - Remove all direct debugLog/debugError imports from bridge/ * feat(qqbot): per-account isolated resource stack + multi-account logger - sender.ts: global singletons (ApiClient/TokenManager/MediaApi) -> per-account AccountContext - Add _accountRegistry: Map<appId, AccountContext> - Each account owns independent client/tokenMgr/mediaApi/messageApi/logger - registerAccount() atomically sets up all resources - resolveAccount() routes to correct resource stack by appId - Remove _sharedLogger/_loggerRegistry/_appRegistry and old structures - bridge/gateway.ts: createAccountLogger() with auto [accountId] prefix - registerAccount() merges logger + markdownSupport + full API resources - engine-wide: remove ~60 manual [qqbot:${accountId}] log prefixes - Prefixes now auto-injected by per-account logger - Remove prefix/logPrefix parameter chains (outbound/outbound-deliver/typing-keepalive etc) * feat(qqbot): completes fallback path for approval with multi-account isolation When the execApprovals are not configured, multiple QQBot accounts' handlers will attempt to deliver the same approval message. The openid is account-level, and cross-account delivery will trigger a QQ Bot API 500 error. - Add account ownership verification in the fallback shouldHandle: Only match the account's handler when the request includes turnSourceAccountId; if unbound, delivery is only permitted when the number of enabled+secret accounts is ≤1. - Consolidate account ownership determination into the unified export `matchesQQBotApprovalAccount` in `exec-approvals.ts`, with both capability and native runtime paths sharing the same logic to eliminate redundancy. * feat(qqbot): optimize permission validation strategy * feat(qqbot): show plugin version in /bot-version and /bot-help Align /bot-version output with the standalone openclaw-qqbot build so users see both the QQBot plugin version and the OpenClaw framework version. Append the plugin version as a footer in /bot-help as well, matching the standalone UX. Also fix the plugin version lookup that previously rendered as 'vunknown': the old code used a hardcoded '../../package.json' relative path which resolved to 'src/package.json' (non-existent) when executed from raw sources, so the require threw and the default 'unknown' value was retained. The same broken value also leaked into the QQ Bot API User-Agent header. Replace the hardcoded path with a dedicated helper (bridge/plugin-version.ts) that walks up the directory tree from import.meta.url and validates the manifest's name field (@openclaw/qqbot) to avoid misreading the monorepo root package.json. Covered by 6 unit tests. * feat(qqbot): trust shared ~/.openclaw/media root for payload files Add getOpenClawMediaDir() and include it alongside getQQBotMediaDir() in the allowed roots of resolveQQBotPayloadLocalFilePath, so framework-produced attachments under sibling directories (e.g. media/outbound/ written by saveMediaBuffer) are trusted by auto-routed sends without triggering the path-outside-storage guard. Covered by a new test case that verifies files under ~/.openclaw/media/outbound/ resolve successfully. * fix(qqbot): ensure PlatformAdapter is registered before approval delivery After the framework centralized approval handler bootstrap (openclaw#62135), the native approval handler is spawned by the framework layer outside the qqbot gateway startAccount context. This means channel.ts's side-effect `import "./bridge/bootstrap.js"` may not have run, leaving PlatformAdapter unregistered when deliverPending calls resolveQQBotAccount -> getPlatformAdapter(). Extract ensurePlatformAdapter() from bootstrap.ts as an idempotent, re-entrant helper and call it in both capability.ts (load callback) and handler-runtime.ts (deliverPending entry) to guarantee the adapter is available regardless of initialization order. * fix(qqbot): add lazy factory for PlatformAdapter to eliminate import-order dependency The bundler splits qqbot code into multiple chunks where the adapter singleton and its consumers may live in different modules. When a consumer chunk evaluates before the bootstrap side-effect chunk, getPlatformAdapter() throws because the singleton is still null. Introduce registerPlatformAdapterFactory() in adapter/index.ts so getPlatformAdapter() can auto-initialize the adapter on first access. bootstrap.ts registers the factory at module evaluation time alongside the existing eager registration path. Also add error logging in downloadFile's catch block to surface fetch failures. * feat(qqbot): add /bot-approve slash command for exec approval config management Add /bot-approve command to the built-in QQBot plugin, ported from the standalone openclaw-qqbot implementation. This command allows users to manage tools.exec.security and tools.exec.ask settings directly from QQ. Supported sub-commands: /bot-approve on - allowlist + on-miss (recommended) /bot-approve off - full + off (no approval) /bot-approve always - allowlist + always (strict mode) /bot-approve reset - remove overrides, restore framework defaults /bot-approve status - show current security/ask values The runtime config API is injected via registerApproveRuntimeGetter() following the existing dependency injection pattern used by registerVersionResolver() and registerPluginVersion(). * fix(qqbot): ACK INTERACTION_CREATE events before processing approval buttons Send PUT /interactions/{id} immediately upon receiving any INTERACTION_CREATE event to prevent QQ from showing a timeout error to the user. The ACK is fire-and-forget and does not block subsequent approval button resolution. Also resolve merge conflict in pnpm-lock.yaml (keep @tencent-connect/qqbot-connector@1.1.0 and newer @thi.ng/bitstream@2.4.46). * feat(qqbot): enhance reminder functionality with delivery context and credential backup This update improves the QQBot reminder system by introducing a delivery context for reminders, allowing for more flexible target resolution. Key changes include: - Updated reminder logic to utilize a delivery envelope, ensuring that reminders are sent with the correct context. - Implemented credential backup and recovery mechanisms to prevent loss of appId and clientSecret during hot upgrades. - Added tests for credential backup functionality and admin resolver to ensure reliability. - Enhanced the remind tool to automatically resolve the target from the current conversation context when not explicitly provided. These enhancements aim to improve the user experience and reliability of the reminder feature within the QQBot framework. * fix(qqbot): ensure PlatformAdapter is registered before gateway message processing Call ensurePlatformAdapter() at the start of bridge/gateway.ts's startGateway() to guarantee the adapter is available when engine code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter(). When the bundler splits code into separate chunks, bootstrap.ts's module-level side-effect registration may not have executed yet by the time the gateway processes its first inbound attachment download. Also fix the TS2339 error in registerApproveRuntimeGetter by using getQQBotRuntime() (full PluginRuntime with config) instead of getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config). * fix(qqbot): make isAudioFile safe when OutboundAudioAdapter is not registered sendMedia() calls isAudioFile() as part of its media-type dispatch logic before any actual audio processing. When the audio adapter is not yet registered (e.g. framework tool calls sendMedia before gateway startup), isAudioFile() would throw 'OutboundAudioAdapter not registered' even for non-audio files like images. Wrap the getAudio() call in isAudioFile() with try/catch to return false when the adapter is unavailable, allowing non-audio media sends to proceed normally. * refactor(qqbot): remove plugin startup/upgrade greeting pipeline Drop the startup / upgrade greeting feature that was folded into the previous reminder + credential-backup commit. The pipeline has proven unnecessary for the fused build and its supporting admin-resolver scaffolding has no other consumers, so both are removed wholesale. - Delete engine/session/startup-greeting.ts and its tests: the first-launch "soul online" / "updated to vX.Y.Z" messages, the per-(accountId, appId) startup marker, the failure cooldown, and the legacy startup-marker.json migration path are all gone. - Delete engine/session/admin-resolver.ts and its tests: admin openid persistence/resolution, upgrade-greeting-target load/clear and the sendStartupGreetings dispatcher only ever served the greeting flow and were not referenced elsewhere. - channel.ts: drop the sendStartupGreetings import and the READY / RESUMED hooks that triggered greetings; credential-backup snapshots stay untouched. - engine/utils/data-paths.ts: remove getAdminMarkerFile / getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile / getStartupMarkerFile / getLegacyStartupMarkerFile along with the now-stale module docblock sections. Credential-backup helpers and safeName are preserved. Net -655 LOC across 6 files. tsc --noEmit passes on extensions/qqbot/tsconfig.json and no references to the removed symbols remain in the workspace. * fix(qqbot): resolve test failures in extension batch, contracts and bundled runtime deps - bootstrap: replace sync require() with static imports for secret-input and temp-path so vitest resolve.alias works correctly (require bypasses vitest aliases causing Cannot find module errors) - format: handle null/undefined in formatErrorMessage before JSON.stringify since JSON.stringify(undefined) returns JS undefined, not a string - gateway/types: reword comment to avoid triggering the channel-import guardrail regex that forbids quoted openclaw/plugin-sdk references - package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root dependencies as required by bundled plugin runtime dependency checks * chore: revert non-qqbot changes to align with upstream main Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories. * fix(qqbot): remove unused logUnsupportedStructuredMediaTarget after API simplification * fix(qqbot): restore channel-plugin-api.ts for bundled plugin surface convention * fix(qqbot): update CI lint allowlists for restructured engine paths - Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.) - Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts * fix(qqbot): eliminate os.tmpdir() in engine layer via adapter injection - Make hasPlatformAdapter() also check for registered factory, so adapter is always discoverable once bootstrap has run - Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(), delegate entirely to PlatformAdapter.getTempDir() which calls resolvePreferredOpenClawTmpDir() under the hood - Keeps engine/ layer free of openclaw/plugin-sdk imports * chore(qqbot): update CHANGELOG for engine architecture refactor (openclaw#67960) (thanks @cxyhhhhh) --------- Co-authored-by: Bobby <zkd8907@live.com> Co-authored-by: neilhwang <neilhwang@tencent.com> Co-authored-by: sliverp <870080352@qq.com>
… onboarding, approval handling (openclaw#67960) * feat(qqbot): add core architecture modules * feat(qqbot): extract engine modules with DI adapters * refactor(qqbot): remove plugin-level TTS, delegate to framework Remove qqbot's internal TTS implementation and unify voice synthesis through the framework's global TTS provider registry. - Delete engine/gateway/tts-config.ts (plugin-specific TTS config) - Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64 - Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback) - Strip QQBotTtsSchema from config-schema, plugin.json, and tests - Remove TTS diagnostics logging and hasTTS system prompt from gateway - Delete ~260 lines of TTS code from utils/audio-convert.ts Made-with: Cursor * feat(qqbot): extract shared engine modules for config, tools, and audio Add engine-layer modules that are self-contained and portable across both the built-in and standalone qqbot packages: - engine/config: account resolution helpers, field readers - engine/tools: channel API proxy, remind scheduling logic - engine/utils: audio format conversion, duration/error formatting, debug logging Consolidate duplicate utility functions across the codebase: - Merge debug-log.ts into log.ts - Merge error-format.ts into format.ts with full .cause chain support - Unify normalizeLowercase/readNumber/readBoolean/readStringMap into string-normalize.ts, removing private copies in resolve.ts, remind-logic.ts, and audio-convert.ts - Remove dead formatDuration export from audio-convert.ts - Delete unused config/schema.ts and config/helpers.ts Made-with: Cursor * refactor(qqbot): streamline account configuration and credential management Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include: - Migrate credential clearing and validation logic to engine/config/credentials.ts. - Simplify setup input validation and application in engine/config/setup-logic.ts. - Enhance account resolution and configuration application in engine/config/resolve.ts. - Update channel and messaging logic to utilize the new credential management functions. This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase. * feat(qqbot): simplify api architecture * feat: 支持扫码绑定QQ机器人 * feat(qqbot): refactor gateway into inbound pipeline + outbound dispatch - Extract handleMessage (620 lines) into three modules: - inbound-context.ts: InboundContext type definition - inbound-pipeline.ts: buildInboundContext() - outbound-dispatch.ts: dispatchOutbound() - gateway.ts handleMessage reduced to ~35 line shell - Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE - Add ref/format-message-ref.ts for cache-miss quote formatting - Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead - QueuedMessage: add msgType/msgElements for quote messages * fix(qqbot): fix markdownSupport loss + dynamic User-Agent Root cause: setOpenClawVersion() called _ensureInitialized(true) which cleared _appRegistry, destroying the MessageApi instance created by initApiConfig() with markdownSupport=true. Subsequent block deliver calls created a default markdownSupport=false instance, causing: 1. Markdown messages sent as plain text (msg_type=0 instead of 2) 2. message_reference incorrectly added (only suppressed in MD mode) Fix: ApiClient and TokenManager now accept userAgent as string | (() => string). sender.ts passes the buildUserAgent function reference, so UA changes propagate automatically on next request without rebuilding any objects. - ApiClient: userAgent -> resolveUserAgent getter, called per-request - TokenManager: same pattern - types.ts: ApiClientConfig.userAgent supports string | (() => string) - sender.ts: remove force re-init + _rebuildAppRegistry hack - initSender/setOpenClawVersion only update version variables - _ensureInitialized creates singletons once, never destroys them - _appRegistry is never cleared -> markdownSupport always preserved - runtime.ts: inject framework version via setOpenClawVersion(runtime.version) - gateway.ts: pass openclawVersion to initSender + registerPluginVersion - slash-commands-impl.ts: remove fragile require("../package.json") * feat(qqbot): implement native approval handling and configuration Add a new approval handling system for QQBot that integrates with the existing framework. Key features include: - Introduce `approval-handler.runtime.ts` for managing approval requests via QQ messages with inline keyboard support. - Create `approval-native.ts` as the entry point for QQBot's approval capability, allowing for simplified approval processes without explicit approver lists. - Implement configuration schema for exec approvals, enabling fine-grained control over who can approve requests. - Enhance messaging and interaction handling to support approval decisions through button interactions. This implementation streamlines the approval process, making it more user-friendly and efficient for QQBot users. * refactor(qqbot): enhance error handling across API and messaging modules This update introduces a centralized error formatting utility, `formatErrorMessage`, to improve consistency in error logging throughout the QQBot codebase. Key changes include: - Integration of `formatErrorMessage` in various API client, messaging, and gateway modules to standardize error messages. - Replacement of direct error message handling with the new utility to enhance readability and maintainability. These improvements streamline error reporting and provide clearer insights into issues encountered during operation. * refactor(qqbot): enhance API and messaging structure with type improvements This update refines the API and messaging modules by introducing type enhancements and restructuring function signatures for better clarity and maintainability. Key changes include: - Updated import statements to streamline type usage in and . - Refactored message sending functions to accept options objects, improving readability and flexibility. - Introduced a new method in to facilitate external message-sent notifications. - Enhanced error handling in the retry mechanism to ensure more robust behavior. These modifications aim to improve the overall code quality and developer experience within the QQBot framework. * feat: 优化文案 * refactor(qqbot): unify Logger interfaces + eliminate P0 code smells Logger unification (17 files): - Introduce single EngineLogger interface in engine/types.ts { info, error, warn?, debug? } - Delete 5 fragmented Logger interfaces: GatewayLogger, ReconnectLogger, MessageRefLogger, PathLogger, SenderLogger - Replace all references across engine/ to use EngineLogger directly P0 code smell fixes (sender.ts + messages.ts + outbound-dispatch.ts): - messages.ts: add public notifyMessageSent() method on MessageApi, replacing 8x 'as unknown as { messageSentHook }' private field hack - sender.ts: extract notifyMediaHook() helper, deduplicate 4 media send functions (sendImage/sendVoice/sendVideo/sendFile) - sender.ts: replace magic numbers 1/2/3/4 with MediaFileType enum - sender.ts: remove 4 redundant 'as MessageResponse' type assertions - outbound-dispatch.ts: remove 5 unnecessary 'as never' casts * feat(qqbot): add /bot-clear-storage command + consolidate utils/types into engine/ /bot-clear-storage (slash-commands-impl.ts): - Migrate from standalone version, aligned with its two-step flow: 1. No args: scan ~/.openclaw/media/qqbot/downloads/{appId}/ and display file list with confirmation button 2. --force: delete files + removeEmptyDirs cleanup - C2C only (group chat returns hint) - bot-help: exclude bot-upgrade and bot-clear-storage in group listings Consolidate into engine/: - Delete src/utils/audio-convert.ts (pure re-export shell, zero consumers) - Move 5 test files from src/utils/ to src/engine/utils/ (fix import paths) - Move src/types/silk-wasm.d.ts to src/engine/types/ - Remove empty src/utils/ and src/types/ directories * refactor(qqbot): restructure API and bridge components for improved modularity This update enhances the QQBot framework by reorganizing the API and bridge components, promoting better modularity and maintainability. Key changes include: - Refactored import paths to streamline access to bridge tools and configurations. - Introduced new bridge files for channel entry, runtime, and approval capabilities, centralizing related functionalities. - Updated existing functions to utilize the new bridge structure, ensuring consistency across the codebase. - Removed deprecated functions and types, simplifying the overall architecture. These modifications aim to improve code clarity and facilitate future development within the QQBot ecosystem. * refactor(qqbot): standardize engine log levels and unify log tag prefix - Rename client.ts to api-client.ts to match ApiClient class name - Downgrade ~60 non-critical info logs to debug level across 12 files (token request/response, HTTP request/response, session restore, media tag detection, image classification, quote detection, attachment download/transcode, retry attempts, etc.) - Unify log tag prefix to [qqbot:xxx] format across all engine modules ([core-api] -> [qqbot:api], [token:x] -> [qqbot:token:x], [retry] -> [qqbot:retry], [messages] -> [qqbot:messages], [sender:x] -> [qqbot:x]) - Remove unnecessary reqTs timestamp from api-client.ts log output - Add dispatch event debug log in gateway-connection.ts - Merge sendProactiveMessage into sendText, remove dead code (sendProactiveText import, getRefIdx, QQMessageResult type) - Narrow allow-from.ts type from unknown[] to Array<string | number> * refactor(qqbot): move interaction handler from bridge to engine - Move onInteraction approval handler into engine/gateway.ts as createApprovalInteractionHandler(), eliminating the callback indirection through CoreGatewayContext - Remove onInteraction from CoreGatewayContext interface and its unused InteractionEvent import from gateway/types.ts - Remove getPlatformAdapter, parseApprovalButtonData and InteractionEvent imports from bridge/gateway.ts * refactor(qqbot): route bridge and sender logs through framework logger - Add bridge/logger.ts as a shared logger holder for bridge-layer modules, injected with ctx.log during gateway startup - Replace all console.log/console.error in bridge/ with getBridgeLogger() calls (approval, bootstrap, tools) - Restore framework logger support in sender.ts via initSender() so API-layer logs flow through OpenClaw log system - Remove all direct debugLog/debugError imports from bridge/ * feat(qqbot): per-account isolated resource stack + multi-account logger - sender.ts: global singletons (ApiClient/TokenManager/MediaApi) -> per-account AccountContext - Add _accountRegistry: Map<appId, AccountContext> - Each account owns independent client/tokenMgr/mediaApi/messageApi/logger - registerAccount() atomically sets up all resources - resolveAccount() routes to correct resource stack by appId - Remove _sharedLogger/_loggerRegistry/_appRegistry and old structures - bridge/gateway.ts: createAccountLogger() with auto [accountId] prefix - registerAccount() merges logger + markdownSupport + full API resources - engine-wide: remove ~60 manual [qqbot:${accountId}] log prefixes - Prefixes now auto-injected by per-account logger - Remove prefix/logPrefix parameter chains (outbound/outbound-deliver/typing-keepalive etc) * feat(qqbot): completes fallback path for approval with multi-account isolation When the execApprovals are not configured, multiple QQBot accounts' handlers will attempt to deliver the same approval message. The openid is account-level, and cross-account delivery will trigger a QQ Bot API 500 error. - Add account ownership verification in the fallback shouldHandle: Only match the account's handler when the request includes turnSourceAccountId; if unbound, delivery is only permitted when the number of enabled+secret accounts is ≤1. - Consolidate account ownership determination into the unified export `matchesQQBotApprovalAccount` in `exec-approvals.ts`, with both capability and native runtime paths sharing the same logic to eliminate redundancy. * feat(qqbot): optimize permission validation strategy * feat(qqbot): show plugin version in /bot-version and /bot-help Align /bot-version output with the standalone openclaw-qqbot build so users see both the QQBot plugin version and the OpenClaw framework version. Append the plugin version as a footer in /bot-help as well, matching the standalone UX. Also fix the plugin version lookup that previously rendered as 'vunknown': the old code used a hardcoded '../../package.json' relative path which resolved to 'src/package.json' (non-existent) when executed from raw sources, so the require threw and the default 'unknown' value was retained. The same broken value also leaked into the QQ Bot API User-Agent header. Replace the hardcoded path with a dedicated helper (bridge/plugin-version.ts) that walks up the directory tree from import.meta.url and validates the manifest's name field (@openclaw/qqbot) to avoid misreading the monorepo root package.json. Covered by 6 unit tests. * feat(qqbot): trust shared ~/.openclaw/media root for payload files Add getOpenClawMediaDir() and include it alongside getQQBotMediaDir() in the allowed roots of resolveQQBotPayloadLocalFilePath, so framework-produced attachments under sibling directories (e.g. media/outbound/ written by saveMediaBuffer) are trusted by auto-routed sends without triggering the path-outside-storage guard. Covered by a new test case that verifies files under ~/.openclaw/media/outbound/ resolve successfully. * fix(qqbot): ensure PlatformAdapter is registered before approval delivery After the framework centralized approval handler bootstrap (openclaw#62135), the native approval handler is spawned by the framework layer outside the qqbot gateway startAccount context. This means channel.ts's side-effect `import "./bridge/bootstrap.js"` may not have run, leaving PlatformAdapter unregistered when deliverPending calls resolveQQBotAccount -> getPlatformAdapter(). Extract ensurePlatformAdapter() from bootstrap.ts as an idempotent, re-entrant helper and call it in both capability.ts (load callback) and handler-runtime.ts (deliverPending entry) to guarantee the adapter is available regardless of initialization order. * fix(qqbot): add lazy factory for PlatformAdapter to eliminate import-order dependency The bundler splits qqbot code into multiple chunks where the adapter singleton and its consumers may live in different modules. When a consumer chunk evaluates before the bootstrap side-effect chunk, getPlatformAdapter() throws because the singleton is still null. Introduce registerPlatformAdapterFactory() in adapter/index.ts so getPlatformAdapter() can auto-initialize the adapter on first access. bootstrap.ts registers the factory at module evaluation time alongside the existing eager registration path. Also add error logging in downloadFile's catch block to surface fetch failures. * feat(qqbot): add /bot-approve slash command for exec approval config management Add /bot-approve command to the built-in QQBot plugin, ported from the standalone openclaw-qqbot implementation. This command allows users to manage tools.exec.security and tools.exec.ask settings directly from QQ. Supported sub-commands: /bot-approve on - allowlist + on-miss (recommended) /bot-approve off - full + off (no approval) /bot-approve always - allowlist + always (strict mode) /bot-approve reset - remove overrides, restore framework defaults /bot-approve status - show current security/ask values The runtime config API is injected via registerApproveRuntimeGetter() following the existing dependency injection pattern used by registerVersionResolver() and registerPluginVersion(). * fix(qqbot): ACK INTERACTION_CREATE events before processing approval buttons Send PUT /interactions/{id} immediately upon receiving any INTERACTION_CREATE event to prevent QQ from showing a timeout error to the user. The ACK is fire-and-forget and does not block subsequent approval button resolution. Also resolve merge conflict in pnpm-lock.yaml (keep @tencent-connect/qqbot-connector@1.1.0 and newer @thi.ng/bitstream@2.4.46). * feat(qqbot): enhance reminder functionality with delivery context and credential backup This update improves the QQBot reminder system by introducing a delivery context for reminders, allowing for more flexible target resolution. Key changes include: - Updated reminder logic to utilize a delivery envelope, ensuring that reminders are sent with the correct context. - Implemented credential backup and recovery mechanisms to prevent loss of appId and clientSecret during hot upgrades. - Added tests for credential backup functionality and admin resolver to ensure reliability. - Enhanced the remind tool to automatically resolve the target from the current conversation context when not explicitly provided. These enhancements aim to improve the user experience and reliability of the reminder feature within the QQBot framework. * fix(qqbot): ensure PlatformAdapter is registered before gateway message processing Call ensurePlatformAdapter() at the start of bridge/gateway.ts's startGateway() to guarantee the adapter is available when engine code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter(). When the bundler splits code into separate chunks, bootstrap.ts's module-level side-effect registration may not have executed yet by the time the gateway processes its first inbound attachment download. Also fix the TS2339 error in registerApproveRuntimeGetter by using getQQBotRuntime() (full PluginRuntime with config) instead of getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config). * fix(qqbot): make isAudioFile safe when OutboundAudioAdapter is not registered sendMedia() calls isAudioFile() as part of its media-type dispatch logic before any actual audio processing. When the audio adapter is not yet registered (e.g. framework tool calls sendMedia before gateway startup), isAudioFile() would throw 'OutboundAudioAdapter not registered' even for non-audio files like images. Wrap the getAudio() call in isAudioFile() with try/catch to return false when the adapter is unavailable, allowing non-audio media sends to proceed normally. * refactor(qqbot): remove plugin startup/upgrade greeting pipeline Drop the startup / upgrade greeting feature that was folded into the previous reminder + credential-backup commit. The pipeline has proven unnecessary for the fused build and its supporting admin-resolver scaffolding has no other consumers, so both are removed wholesale. - Delete engine/session/startup-greeting.ts and its tests: the first-launch "soul online" / "updated to vX.Y.Z" messages, the per-(accountId, appId) startup marker, the failure cooldown, and the legacy startup-marker.json migration path are all gone. - Delete engine/session/admin-resolver.ts and its tests: admin openid persistence/resolution, upgrade-greeting-target load/clear and the sendStartupGreetings dispatcher only ever served the greeting flow and were not referenced elsewhere. - channel.ts: drop the sendStartupGreetings import and the READY / RESUMED hooks that triggered greetings; credential-backup snapshots stay untouched. - engine/utils/data-paths.ts: remove getAdminMarkerFile / getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile / getStartupMarkerFile / getLegacyStartupMarkerFile along with the now-stale module docblock sections. Credential-backup helpers and safeName are preserved. Net -655 LOC across 6 files. tsc --noEmit passes on extensions/qqbot/tsconfig.json and no references to the removed symbols remain in the workspace. * fix(qqbot): resolve test failures in extension batch, contracts and bundled runtime deps - bootstrap: replace sync require() with static imports for secret-input and temp-path so vitest resolve.alias works correctly (require bypasses vitest aliases causing Cannot find module errors) - format: handle null/undefined in formatErrorMessage before JSON.stringify since JSON.stringify(undefined) returns JS undefined, not a string - gateway/types: reword comment to avoid triggering the channel-import guardrail regex that forbids quoted openclaw/plugin-sdk references - package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root dependencies as required by bundled plugin runtime dependency checks * chore: revert non-qqbot changes to align with upstream main Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories. * fix(qqbot): remove unused logUnsupportedStructuredMediaTarget after API simplification * fix(qqbot): restore channel-plugin-api.ts for bundled plugin surface convention * fix(qqbot): update CI lint allowlists for restructured engine paths - Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.) - Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts * fix(qqbot): eliminate os.tmpdir() in engine layer via adapter injection - Make hasPlatformAdapter() also check for registered factory, so adapter is always discoverable once bootstrap has run - Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(), delegate entirely to PlatformAdapter.getTempDir() which calls resolvePreferredOpenClawTmpDir() under the hood - Keeps engine/ layer free of openclaw/plugin-sdk imports * chore(qqbot): update CHANGELOG for engine architecture refactor (openclaw#67960) (thanks @cxyhhhhh) --------- Co-authored-by: Bobby <zkd8907@live.com> Co-authored-by: neilhwang <neilhwang@tencent.com> Co-authored-by: sliverp <870080352@qq.com>
… onboarding, approval handling (openclaw#67960) * feat(qqbot): add core architecture modules * feat(qqbot): extract engine modules with DI adapters * refactor(qqbot): remove plugin-level TTS, delegate to framework Remove qqbot's internal TTS implementation and unify voice synthesis through the framework's global TTS provider registry. - Delete engine/gateway/tts-config.ts (plugin-specific TTS config) - Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64 - Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback) - Strip QQBotTtsSchema from config-schema, plugin.json, and tests - Remove TTS diagnostics logging and hasTTS system prompt from gateway - Delete ~260 lines of TTS code from utils/audio-convert.ts Made-with: Cursor * feat(qqbot): extract shared engine modules for config, tools, and audio Add engine-layer modules that are self-contained and portable across both the built-in and standalone qqbot packages: - engine/config: account resolution helpers, field readers - engine/tools: channel API proxy, remind scheduling logic - engine/utils: audio format conversion, duration/error formatting, debug logging Consolidate duplicate utility functions across the codebase: - Merge debug-log.ts into log.ts - Merge error-format.ts into format.ts with full .cause chain support - Unify normalizeLowercase/readNumber/readBoolean/readStringMap into string-normalize.ts, removing private copies in resolve.ts, remind-logic.ts, and audio-convert.ts - Remove dead formatDuration export from audio-convert.ts - Delete unused config/schema.ts and config/helpers.ts Made-with: Cursor * refactor(qqbot): streamline account configuration and credential management Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include: - Migrate credential clearing and validation logic to engine/config/credentials.ts. - Simplify setup input validation and application in engine/config/setup-logic.ts. - Enhance account resolution and configuration application in engine/config/resolve.ts. - Update channel and messaging logic to utilize the new credential management functions. This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase. * feat(qqbot): simplify api architecture * feat: 支持扫码绑定QQ机器人 * feat(qqbot): refactor gateway into inbound pipeline + outbound dispatch - Extract handleMessage (620 lines) into three modules: - inbound-context.ts: InboundContext type definition - inbound-pipeline.ts: buildInboundContext() - outbound-dispatch.ts: dispatchOutbound() - gateway.ts handleMessage reduced to ~35 line shell - Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE - Add ref/format-message-ref.ts for cache-miss quote formatting - Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead - QueuedMessage: add msgType/msgElements for quote messages * fix(qqbot): fix markdownSupport loss + dynamic User-Agent Root cause: setOpenClawVersion() called _ensureInitialized(true) which cleared _appRegistry, destroying the MessageApi instance created by initApiConfig() with markdownSupport=true. Subsequent block deliver calls created a default markdownSupport=false instance, causing: 1. Markdown messages sent as plain text (msg_type=0 instead of 2) 2. message_reference incorrectly added (only suppressed in MD mode) Fix: ApiClient and TokenManager now accept userAgent as string | (() => string). sender.ts passes the buildUserAgent function reference, so UA changes propagate automatically on next request without rebuilding any objects. - ApiClient: userAgent -> resolveUserAgent getter, called per-request - TokenManager: same pattern - types.ts: ApiClientConfig.userAgent supports string | (() => string) - sender.ts: remove force re-init + _rebuildAppRegistry hack - initSender/setOpenClawVersion only update version variables - _ensureInitialized creates singletons once, never destroys them - _appRegistry is never cleared -> markdownSupport always preserved - runtime.ts: inject framework version via setOpenClawVersion(runtime.version) - gateway.ts: pass openclawVersion to initSender + registerPluginVersion - slash-commands-impl.ts: remove fragile require("../package.json") * feat(qqbot): implement native approval handling and configuration Add a new approval handling system for QQBot that integrates with the existing framework. Key features include: - Introduce `approval-handler.runtime.ts` for managing approval requests via QQ messages with inline keyboard support. - Create `approval-native.ts` as the entry point for QQBot's approval capability, allowing for simplified approval processes without explicit approver lists. - Implement configuration schema for exec approvals, enabling fine-grained control over who can approve requests. - Enhance messaging and interaction handling to support approval decisions through button interactions. This implementation streamlines the approval process, making it more user-friendly and efficient for QQBot users. * refactor(qqbot): enhance error handling across API and messaging modules This update introduces a centralized error formatting utility, `formatErrorMessage`, to improve consistency in error logging throughout the QQBot codebase. Key changes include: - Integration of `formatErrorMessage` in various API client, messaging, and gateway modules to standardize error messages. - Replacement of direct error message handling with the new utility to enhance readability and maintainability. These improvements streamline error reporting and provide clearer insights into issues encountered during operation. * refactor(qqbot): enhance API and messaging structure with type improvements This update refines the API and messaging modules by introducing type enhancements and restructuring function signatures for better clarity and maintainability. Key changes include: - Updated import statements to streamline type usage in and . - Refactored message sending functions to accept options objects, improving readability and flexibility. - Introduced a new method in to facilitate external message-sent notifications. - Enhanced error handling in the retry mechanism to ensure more robust behavior. These modifications aim to improve the overall code quality and developer experience within the QQBot framework. * feat: 优化文案 * refactor(qqbot): unify Logger interfaces + eliminate P0 code smells Logger unification (17 files): - Introduce single EngineLogger interface in engine/types.ts { info, error, warn?, debug? } - Delete 5 fragmented Logger interfaces: GatewayLogger, ReconnectLogger, MessageRefLogger, PathLogger, SenderLogger - Replace all references across engine/ to use EngineLogger directly P0 code smell fixes (sender.ts + messages.ts + outbound-dispatch.ts): - messages.ts: add public notifyMessageSent() method on MessageApi, replacing 8x 'as unknown as { messageSentHook }' private field hack - sender.ts: extract notifyMediaHook() helper, deduplicate 4 media send functions (sendImage/sendVoice/sendVideo/sendFile) - sender.ts: replace magic numbers 1/2/3/4 with MediaFileType enum - sender.ts: remove 4 redundant 'as MessageResponse' type assertions - outbound-dispatch.ts: remove 5 unnecessary 'as never' casts * feat(qqbot): add /bot-clear-storage command + consolidate utils/types into engine/ /bot-clear-storage (slash-commands-impl.ts): - Migrate from standalone version, aligned with its two-step flow: 1. No args: scan ~/.openclaw/media/qqbot/downloads/{appId}/ and display file list with confirmation button 2. --force: delete files + removeEmptyDirs cleanup - C2C only (group chat returns hint) - bot-help: exclude bot-upgrade and bot-clear-storage in group listings Consolidate into engine/: - Delete src/utils/audio-convert.ts (pure re-export shell, zero consumers) - Move 5 test files from src/utils/ to src/engine/utils/ (fix import paths) - Move src/types/silk-wasm.d.ts to src/engine/types/ - Remove empty src/utils/ and src/types/ directories * refactor(qqbot): restructure API and bridge components for improved modularity This update enhances the QQBot framework by reorganizing the API and bridge components, promoting better modularity and maintainability. Key changes include: - Refactored import paths to streamline access to bridge tools and configurations. - Introduced new bridge files for channel entry, runtime, and approval capabilities, centralizing related functionalities. - Updated existing functions to utilize the new bridge structure, ensuring consistency across the codebase. - Removed deprecated functions and types, simplifying the overall architecture. These modifications aim to improve code clarity and facilitate future development within the QQBot ecosystem. * refactor(qqbot): standardize engine log levels and unify log tag prefix - Rename client.ts to api-client.ts to match ApiClient class name - Downgrade ~60 non-critical info logs to debug level across 12 files (token request/response, HTTP request/response, session restore, media tag detection, image classification, quote detection, attachment download/transcode, retry attempts, etc.) - Unify log tag prefix to [qqbot:xxx] format across all engine modules ([core-api] -> [qqbot:api], [token:x] -> [qqbot:token:x], [retry] -> [qqbot:retry], [messages] -> [qqbot:messages], [sender:x] -> [qqbot:x]) - Remove unnecessary reqTs timestamp from api-client.ts log output - Add dispatch event debug log in gateway-connection.ts - Merge sendProactiveMessage into sendText, remove dead code (sendProactiveText import, getRefIdx, QQMessageResult type) - Narrow allow-from.ts type from unknown[] to Array<string | number> * refactor(qqbot): move interaction handler from bridge to engine - Move onInteraction approval handler into engine/gateway.ts as createApprovalInteractionHandler(), eliminating the callback indirection through CoreGatewayContext - Remove onInteraction from CoreGatewayContext interface and its unused InteractionEvent import from gateway/types.ts - Remove getPlatformAdapter, parseApprovalButtonData and InteractionEvent imports from bridge/gateway.ts * refactor(qqbot): route bridge and sender logs through framework logger - Add bridge/logger.ts as a shared logger holder for bridge-layer modules, injected with ctx.log during gateway startup - Replace all console.log/console.error in bridge/ with getBridgeLogger() calls (approval, bootstrap, tools) - Restore framework logger support in sender.ts via initSender() so API-layer logs flow through OpenClaw log system - Remove all direct debugLog/debugError imports from bridge/ * feat(qqbot): per-account isolated resource stack + multi-account logger - sender.ts: global singletons (ApiClient/TokenManager/MediaApi) -> per-account AccountContext - Add _accountRegistry: Map<appId, AccountContext> - Each account owns independent client/tokenMgr/mediaApi/messageApi/logger - registerAccount() atomically sets up all resources - resolveAccount() routes to correct resource stack by appId - Remove _sharedLogger/_loggerRegistry/_appRegistry and old structures - bridge/gateway.ts: createAccountLogger() with auto [accountId] prefix - registerAccount() merges logger + markdownSupport + full API resources - engine-wide: remove ~60 manual [qqbot:${accountId}] log prefixes - Prefixes now auto-injected by per-account logger - Remove prefix/logPrefix parameter chains (outbound/outbound-deliver/typing-keepalive etc) * feat(qqbot): completes fallback path for approval with multi-account isolation When the execApprovals are not configured, multiple QQBot accounts' handlers will attempt to deliver the same approval message. The openid is account-level, and cross-account delivery will trigger a QQ Bot API 500 error. - Add account ownership verification in the fallback shouldHandle: Only match the account's handler when the request includes turnSourceAccountId; if unbound, delivery is only permitted when the number of enabled+secret accounts is ≤1. - Consolidate account ownership determination into the unified export `matchesQQBotApprovalAccount` in `exec-approvals.ts`, with both capability and native runtime paths sharing the same logic to eliminate redundancy. * feat(qqbot): optimize permission validation strategy * feat(qqbot): show plugin version in /bot-version and /bot-help Align /bot-version output with the standalone openclaw-qqbot build so users see both the QQBot plugin version and the OpenClaw framework version. Append the plugin version as a footer in /bot-help as well, matching the standalone UX. Also fix the plugin version lookup that previously rendered as 'vunknown': the old code used a hardcoded '../../package.json' relative path which resolved to 'src/package.json' (non-existent) when executed from raw sources, so the require threw and the default 'unknown' value was retained. The same broken value also leaked into the QQ Bot API User-Agent header. Replace the hardcoded path with a dedicated helper (bridge/plugin-version.ts) that walks up the directory tree from import.meta.url and validates the manifest's name field (@openclaw/qqbot) to avoid misreading the monorepo root package.json. Covered by 6 unit tests. * feat(qqbot): trust shared ~/.openclaw/media root for payload files Add getOpenClawMediaDir() and include it alongside getQQBotMediaDir() in the allowed roots of resolveQQBotPayloadLocalFilePath, so framework-produced attachments under sibling directories (e.g. media/outbound/ written by saveMediaBuffer) are trusted by auto-routed sends without triggering the path-outside-storage guard. Covered by a new test case that verifies files under ~/.openclaw/media/outbound/ resolve successfully. * fix(qqbot): ensure PlatformAdapter is registered before approval delivery After the framework centralized approval handler bootstrap (openclaw#62135), the native approval handler is spawned by the framework layer outside the qqbot gateway startAccount context. This means channel.ts's side-effect `import "./bridge/bootstrap.js"` may not have run, leaving PlatformAdapter unregistered when deliverPending calls resolveQQBotAccount -> getPlatformAdapter(). Extract ensurePlatformAdapter() from bootstrap.ts as an idempotent, re-entrant helper and call it in both capability.ts (load callback) and handler-runtime.ts (deliverPending entry) to guarantee the adapter is available regardless of initialization order. * fix(qqbot): add lazy factory for PlatformAdapter to eliminate import-order dependency The bundler splits qqbot code into multiple chunks where the adapter singleton and its consumers may live in different modules. When a consumer chunk evaluates before the bootstrap side-effect chunk, getPlatformAdapter() throws because the singleton is still null. Introduce registerPlatformAdapterFactory() in adapter/index.ts so getPlatformAdapter() can auto-initialize the adapter on first access. bootstrap.ts registers the factory at module evaluation time alongside the existing eager registration path. Also add error logging in downloadFile's catch block to surface fetch failures. * feat(qqbot): add /bot-approve slash command for exec approval config management Add /bot-approve command to the built-in QQBot plugin, ported from the standalone openclaw-qqbot implementation. This command allows users to manage tools.exec.security and tools.exec.ask settings directly from QQ. Supported sub-commands: /bot-approve on - allowlist + on-miss (recommended) /bot-approve off - full + off (no approval) /bot-approve always - allowlist + always (strict mode) /bot-approve reset - remove overrides, restore framework defaults /bot-approve status - show current security/ask values The runtime config API is injected via registerApproveRuntimeGetter() following the existing dependency injection pattern used by registerVersionResolver() and registerPluginVersion(). * fix(qqbot): ACK INTERACTION_CREATE events before processing approval buttons Send PUT /interactions/{id} immediately upon receiving any INTERACTION_CREATE event to prevent QQ from showing a timeout error to the user. The ACK is fire-and-forget and does not block subsequent approval button resolution. Also resolve merge conflict in pnpm-lock.yaml (keep @tencent-connect/qqbot-connector@1.1.0 and newer @thi.ng/bitstream@2.4.46). * feat(qqbot): enhance reminder functionality with delivery context and credential backup This update improves the QQBot reminder system by introducing a delivery context for reminders, allowing for more flexible target resolution. Key changes include: - Updated reminder logic to utilize a delivery envelope, ensuring that reminders are sent with the correct context. - Implemented credential backup and recovery mechanisms to prevent loss of appId and clientSecret during hot upgrades. - Added tests for credential backup functionality and admin resolver to ensure reliability. - Enhanced the remind tool to automatically resolve the target from the current conversation context when not explicitly provided. These enhancements aim to improve the user experience and reliability of the reminder feature within the QQBot framework. * fix(qqbot): ensure PlatformAdapter is registered before gateway message processing Call ensurePlatformAdapter() at the start of bridge/gateway.ts's startGateway() to guarantee the adapter is available when engine code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter(). When the bundler splits code into separate chunks, bootstrap.ts's module-level side-effect registration may not have executed yet by the time the gateway processes its first inbound attachment download. Also fix the TS2339 error in registerApproveRuntimeGetter by using getQQBotRuntime() (full PluginRuntime with config) instead of getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config). * fix(qqbot): make isAudioFile safe when OutboundAudioAdapter is not registered sendMedia() calls isAudioFile() as part of its media-type dispatch logic before any actual audio processing. When the audio adapter is not yet registered (e.g. framework tool calls sendMedia before gateway startup), isAudioFile() would throw 'OutboundAudioAdapter not registered' even for non-audio files like images. Wrap the getAudio() call in isAudioFile() with try/catch to return false when the adapter is unavailable, allowing non-audio media sends to proceed normally. * refactor(qqbot): remove plugin startup/upgrade greeting pipeline Drop the startup / upgrade greeting feature that was folded into the previous reminder + credential-backup commit. The pipeline has proven unnecessary for the fused build and its supporting admin-resolver scaffolding has no other consumers, so both are removed wholesale. - Delete engine/session/startup-greeting.ts and its tests: the first-launch "soul online" / "updated to vX.Y.Z" messages, the per-(accountId, appId) startup marker, the failure cooldown, and the legacy startup-marker.json migration path are all gone. - Delete engine/session/admin-resolver.ts and its tests: admin openid persistence/resolution, upgrade-greeting-target load/clear and the sendStartupGreetings dispatcher only ever served the greeting flow and were not referenced elsewhere. - channel.ts: drop the sendStartupGreetings import and the READY / RESUMED hooks that triggered greetings; credential-backup snapshots stay untouched. - engine/utils/data-paths.ts: remove getAdminMarkerFile / getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile / getStartupMarkerFile / getLegacyStartupMarkerFile along with the now-stale module docblock sections. Credential-backup helpers and safeName are preserved. Net -655 LOC across 6 files. tsc --noEmit passes on extensions/qqbot/tsconfig.json and no references to the removed symbols remain in the workspace. * fix(qqbot): resolve test failures in extension batch, contracts and bundled runtime deps - bootstrap: replace sync require() with static imports for secret-input and temp-path so vitest resolve.alias works correctly (require bypasses vitest aliases causing Cannot find module errors) - format: handle null/undefined in formatErrorMessage before JSON.stringify since JSON.stringify(undefined) returns JS undefined, not a string - gateway/types: reword comment to avoid triggering the channel-import guardrail regex that forbids quoted openclaw/plugin-sdk references - package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root dependencies as required by bundled plugin runtime dependency checks * chore: revert non-qqbot changes to align with upstream main Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories. * fix(qqbot): remove unused logUnsupportedStructuredMediaTarget after API simplification * fix(qqbot): restore channel-plugin-api.ts for bundled plugin surface convention * fix(qqbot): update CI lint allowlists for restructured engine paths - Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.) - Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts * fix(qqbot): eliminate os.tmpdir() in engine layer via adapter injection - Make hasPlatformAdapter() also check for registered factory, so adapter is always discoverable once bootstrap has run - Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(), delegate entirely to PlatformAdapter.getTempDir() which calls resolvePreferredOpenClawTmpDir() under the hood - Keeps engine/ layer free of openclaw/plugin-sdk imports * chore(qqbot): update CHANGELOG for engine architecture refactor (openclaw#67960) (thanks @cxyhhhhh) --------- Co-authored-by: Bobby <zkd8907@live.com> Co-authored-by: neilhwang <neilhwang@tencent.com> Co-authored-by: sliverp <870080352@qq.com>
… onboarding, approval handling (openclaw#67960) * feat(qqbot): add core architecture modules * feat(qqbot): extract engine modules with DI adapters * refactor(qqbot): remove plugin-level TTS, delegate to framework Remove qqbot's internal TTS implementation and unify voice synthesis through the framework's global TTS provider registry. - Delete engine/gateway/tts-config.ts (plugin-specific TTS config) - Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64 - Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback) - Strip QQBotTtsSchema from config-schema, plugin.json, and tests - Remove TTS diagnostics logging and hasTTS system prompt from gateway - Delete ~260 lines of TTS code from utils/audio-convert.ts Made-with: Cursor * feat(qqbot): extract shared engine modules for config, tools, and audio Add engine-layer modules that are self-contained and portable across both the built-in and standalone qqbot packages: - engine/config: account resolution helpers, field readers - engine/tools: channel API proxy, remind scheduling logic - engine/utils: audio format conversion, duration/error formatting, debug logging Consolidate duplicate utility functions across the codebase: - Merge debug-log.ts into log.ts - Merge error-format.ts into format.ts with full .cause chain support - Unify normalizeLowercase/readNumber/readBoolean/readStringMap into string-normalize.ts, removing private copies in resolve.ts, remind-logic.ts, and audio-convert.ts - Remove dead formatDuration export from audio-convert.ts - Delete unused config/schema.ts and config/helpers.ts Made-with: Cursor * refactor(qqbot): streamline account configuration and credential management Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include: - Migrate credential clearing and validation logic to engine/config/credentials.ts. - Simplify setup input validation and application in engine/config/setup-logic.ts. - Enhance account resolution and configuration application in engine/config/resolve.ts. - Update channel and messaging logic to utilize the new credential management functions. This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase. * feat(qqbot): simplify api architecture * feat: 支持扫码绑定QQ机器人 * feat(qqbot): refactor gateway into inbound pipeline + outbound dispatch - Extract handleMessage (620 lines) into three modules: - inbound-context.ts: InboundContext type definition - inbound-pipeline.ts: buildInboundContext() - outbound-dispatch.ts: dispatchOutbound() - gateway.ts handleMessage reduced to ~35 line shell - Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE - Add ref/format-message-ref.ts for cache-miss quote formatting - Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead - QueuedMessage: add msgType/msgElements for quote messages * fix(qqbot): fix markdownSupport loss + dynamic User-Agent Root cause: setOpenClawVersion() called _ensureInitialized(true) which cleared _appRegistry, destroying the MessageApi instance created by initApiConfig() with markdownSupport=true. Subsequent block deliver calls created a default markdownSupport=false instance, causing: 1. Markdown messages sent as plain text (msg_type=0 instead of 2) 2. message_reference incorrectly added (only suppressed in MD mode) Fix: ApiClient and TokenManager now accept userAgent as string | (() => string). sender.ts passes the buildUserAgent function reference, so UA changes propagate automatically on next request without rebuilding any objects. - ApiClient: userAgent -> resolveUserAgent getter, called per-request - TokenManager: same pattern - types.ts: ApiClientConfig.userAgent supports string | (() => string) - sender.ts: remove force re-init + _rebuildAppRegistry hack - initSender/setOpenClawVersion only update version variables - _ensureInitialized creates singletons once, never destroys them - _appRegistry is never cleared -> markdownSupport always preserved - runtime.ts: inject framework version via setOpenClawVersion(runtime.version) - gateway.ts: pass openclawVersion to initSender + registerPluginVersion - slash-commands-impl.ts: remove fragile require("../package.json") * feat(qqbot): implement native approval handling and configuration Add a new approval handling system for QQBot that integrates with the existing framework. Key features include: - Introduce `approval-handler.runtime.ts` for managing approval requests via QQ messages with inline keyboard support. - Create `approval-native.ts` as the entry point for QQBot's approval capability, allowing for simplified approval processes without explicit approver lists. - Implement configuration schema for exec approvals, enabling fine-grained control over who can approve requests. - Enhance messaging and interaction handling to support approval decisions through button interactions. This implementation streamlines the approval process, making it more user-friendly and efficient for QQBot users. * refactor(qqbot): enhance error handling across API and messaging modules This update introduces a centralized error formatting utility, `formatErrorMessage`, to improve consistency in error logging throughout the QQBot codebase. Key changes include: - Integration of `formatErrorMessage` in various API client, messaging, and gateway modules to standardize error messages. - Replacement of direct error message handling with the new utility to enhance readability and maintainability. These improvements streamline error reporting and provide clearer insights into issues encountered during operation. * refactor(qqbot): enhance API and messaging structure with type improvements This update refines the API and messaging modules by introducing type enhancements and restructuring function signatures for better clarity and maintainability. Key changes include: - Updated import statements to streamline type usage in and . - Refactored message sending functions to accept options objects, improving readability and flexibility. - Introduced a new method in to facilitate external message-sent notifications. - Enhanced error handling in the retry mechanism to ensure more robust behavior. These modifications aim to improve the overall code quality and developer experience within the QQBot framework. * feat: 优化文案 * refactor(qqbot): unify Logger interfaces + eliminate P0 code smells Logger unification (17 files): - Introduce single EngineLogger interface in engine/types.ts { info, error, warn?, debug? } - Delete 5 fragmented Logger interfaces: GatewayLogger, ReconnectLogger, MessageRefLogger, PathLogger, SenderLogger - Replace all references across engine/ to use EngineLogger directly P0 code smell fixes (sender.ts + messages.ts + outbound-dispatch.ts): - messages.ts: add public notifyMessageSent() method on MessageApi, replacing 8x 'as unknown as { messageSentHook }' private field hack - sender.ts: extract notifyMediaHook() helper, deduplicate 4 media send functions (sendImage/sendVoice/sendVideo/sendFile) - sender.ts: replace magic numbers 1/2/3/4 with MediaFileType enum - sender.ts: remove 4 redundant 'as MessageResponse' type assertions - outbound-dispatch.ts: remove 5 unnecessary 'as never' casts * feat(qqbot): add /bot-clear-storage command + consolidate utils/types into engine/ /bot-clear-storage (slash-commands-impl.ts): - Migrate from standalone version, aligned with its two-step flow: 1. No args: scan ~/.openclaw/media/qqbot/downloads/{appId}/ and display file list with confirmation button 2. --force: delete files + removeEmptyDirs cleanup - C2C only (group chat returns hint) - bot-help: exclude bot-upgrade and bot-clear-storage in group listings Consolidate into engine/: - Delete src/utils/audio-convert.ts (pure re-export shell, zero consumers) - Move 5 test files from src/utils/ to src/engine/utils/ (fix import paths) - Move src/types/silk-wasm.d.ts to src/engine/types/ - Remove empty src/utils/ and src/types/ directories * refactor(qqbot): restructure API and bridge components for improved modularity This update enhances the QQBot framework by reorganizing the API and bridge components, promoting better modularity and maintainability. Key changes include: - Refactored import paths to streamline access to bridge tools and configurations. - Introduced new bridge files for channel entry, runtime, and approval capabilities, centralizing related functionalities. - Updated existing functions to utilize the new bridge structure, ensuring consistency across the codebase. - Removed deprecated functions and types, simplifying the overall architecture. These modifications aim to improve code clarity and facilitate future development within the QQBot ecosystem. * refactor(qqbot): standardize engine log levels and unify log tag prefix - Rename client.ts to api-client.ts to match ApiClient class name - Downgrade ~60 non-critical info logs to debug level across 12 files (token request/response, HTTP request/response, session restore, media tag detection, image classification, quote detection, attachment download/transcode, retry attempts, etc.) - Unify log tag prefix to [qqbot:xxx] format across all engine modules ([core-api] -> [qqbot:api], [token:x] -> [qqbot:token:x], [retry] -> [qqbot:retry], [messages] -> [qqbot:messages], [sender:x] -> [qqbot:x]) - Remove unnecessary reqTs timestamp from api-client.ts log output - Add dispatch event debug log in gateway-connection.ts - Merge sendProactiveMessage into sendText, remove dead code (sendProactiveText import, getRefIdx, QQMessageResult type) - Narrow allow-from.ts type from unknown[] to Array<string | number> * refactor(qqbot): move interaction handler from bridge to engine - Move onInteraction approval handler into engine/gateway.ts as createApprovalInteractionHandler(), eliminating the callback indirection through CoreGatewayContext - Remove onInteraction from CoreGatewayContext interface and its unused InteractionEvent import from gateway/types.ts - Remove getPlatformAdapter, parseApprovalButtonData and InteractionEvent imports from bridge/gateway.ts * refactor(qqbot): route bridge and sender logs through framework logger - Add bridge/logger.ts as a shared logger holder for bridge-layer modules, injected with ctx.log during gateway startup - Replace all console.log/console.error in bridge/ with getBridgeLogger() calls (approval, bootstrap, tools) - Restore framework logger support in sender.ts via initSender() so API-layer logs flow through OpenClaw log system - Remove all direct debugLog/debugError imports from bridge/ * feat(qqbot): per-account isolated resource stack + multi-account logger - sender.ts: global singletons (ApiClient/TokenManager/MediaApi) -> per-account AccountContext - Add _accountRegistry: Map<appId, AccountContext> - Each account owns independent client/tokenMgr/mediaApi/messageApi/logger - registerAccount() atomically sets up all resources - resolveAccount() routes to correct resource stack by appId - Remove _sharedLogger/_loggerRegistry/_appRegistry and old structures - bridge/gateway.ts: createAccountLogger() with auto [accountId] prefix - registerAccount() merges logger + markdownSupport + full API resources - engine-wide: remove ~60 manual [qqbot:${accountId}] log prefixes - Prefixes now auto-injected by per-account logger - Remove prefix/logPrefix parameter chains (outbound/outbound-deliver/typing-keepalive etc) * feat(qqbot): completes fallback path for approval with multi-account isolation When the execApprovals are not configured, multiple QQBot accounts' handlers will attempt to deliver the same approval message. The openid is account-level, and cross-account delivery will trigger a QQ Bot API 500 error. - Add account ownership verification in the fallback shouldHandle: Only match the account's handler when the request includes turnSourceAccountId; if unbound, delivery is only permitted when the number of enabled+secret accounts is ≤1. - Consolidate account ownership determination into the unified export `matchesQQBotApprovalAccount` in `exec-approvals.ts`, with both capability and native runtime paths sharing the same logic to eliminate redundancy. * feat(qqbot): optimize permission validation strategy * feat(qqbot): show plugin version in /bot-version and /bot-help Align /bot-version output with the standalone openclaw-qqbot build so users see both the QQBot plugin version and the OpenClaw framework version. Append the plugin version as a footer in /bot-help as well, matching the standalone UX. Also fix the plugin version lookup that previously rendered as 'vunknown': the old code used a hardcoded '../../package.json' relative path which resolved to 'src/package.json' (non-existent) when executed from raw sources, so the require threw and the default 'unknown' value was retained. The same broken value also leaked into the QQ Bot API User-Agent header. Replace the hardcoded path with a dedicated helper (bridge/plugin-version.ts) that walks up the directory tree from import.meta.url and validates the manifest's name field (@openclaw/qqbot) to avoid misreading the monorepo root package.json. Covered by 6 unit tests. * feat(qqbot): trust shared ~/.openclaw/media root for payload files Add getOpenClawMediaDir() and include it alongside getQQBotMediaDir() in the allowed roots of resolveQQBotPayloadLocalFilePath, so framework-produced attachments under sibling directories (e.g. media/outbound/ written by saveMediaBuffer) are trusted by auto-routed sends without triggering the path-outside-storage guard. Covered by a new test case that verifies files under ~/.openclaw/media/outbound/ resolve successfully. * fix(qqbot): ensure PlatformAdapter is registered before approval delivery After the framework centralized approval handler bootstrap (openclaw#62135), the native approval handler is spawned by the framework layer outside the qqbot gateway startAccount context. This means channel.ts's side-effect `import "./bridge/bootstrap.js"` may not have run, leaving PlatformAdapter unregistered when deliverPending calls resolveQQBotAccount -> getPlatformAdapter(). Extract ensurePlatformAdapter() from bootstrap.ts as an idempotent, re-entrant helper and call it in both capability.ts (load callback) and handler-runtime.ts (deliverPending entry) to guarantee the adapter is available regardless of initialization order. * fix(qqbot): add lazy factory for PlatformAdapter to eliminate import-order dependency The bundler splits qqbot code into multiple chunks where the adapter singleton and its consumers may live in different modules. When a consumer chunk evaluates before the bootstrap side-effect chunk, getPlatformAdapter() throws because the singleton is still null. Introduce registerPlatformAdapterFactory() in adapter/index.ts so getPlatformAdapter() can auto-initialize the adapter on first access. bootstrap.ts registers the factory at module evaluation time alongside the existing eager registration path. Also add error logging in downloadFile's catch block to surface fetch failures. * feat(qqbot): add /bot-approve slash command for exec approval config management Add /bot-approve command to the built-in QQBot plugin, ported from the standalone openclaw-qqbot implementation. This command allows users to manage tools.exec.security and tools.exec.ask settings directly from QQ. Supported sub-commands: /bot-approve on - allowlist + on-miss (recommended) /bot-approve off - full + off (no approval) /bot-approve always - allowlist + always (strict mode) /bot-approve reset - remove overrides, restore framework defaults /bot-approve status - show current security/ask values The runtime config API is injected via registerApproveRuntimeGetter() following the existing dependency injection pattern used by registerVersionResolver() and registerPluginVersion(). * fix(qqbot): ACK INTERACTION_CREATE events before processing approval buttons Send PUT /interactions/{id} immediately upon receiving any INTERACTION_CREATE event to prevent QQ from showing a timeout error to the user. The ACK is fire-and-forget and does not block subsequent approval button resolution. Also resolve merge conflict in pnpm-lock.yaml (keep @tencent-connect/qqbot-connector@1.1.0 and newer @thi.ng/bitstream@2.4.46). * feat(qqbot): enhance reminder functionality with delivery context and credential backup This update improves the QQBot reminder system by introducing a delivery context for reminders, allowing for more flexible target resolution. Key changes include: - Updated reminder logic to utilize a delivery envelope, ensuring that reminders are sent with the correct context. - Implemented credential backup and recovery mechanisms to prevent loss of appId and clientSecret during hot upgrades. - Added tests for credential backup functionality and admin resolver to ensure reliability. - Enhanced the remind tool to automatically resolve the target from the current conversation context when not explicitly provided. These enhancements aim to improve the user experience and reliability of the reminder feature within the QQBot framework. * fix(qqbot): ensure PlatformAdapter is registered before gateway message processing Call ensurePlatformAdapter() at the start of bridge/gateway.ts's startGateway() to guarantee the adapter is available when engine code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter(). When the bundler splits code into separate chunks, bootstrap.ts's module-level side-effect registration may not have executed yet by the time the gateway processes its first inbound attachment download. Also fix the TS2339 error in registerApproveRuntimeGetter by using getQQBotRuntime() (full PluginRuntime with config) instead of getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config). * fix(qqbot): make isAudioFile safe when OutboundAudioAdapter is not registered sendMedia() calls isAudioFile() as part of its media-type dispatch logic before any actual audio processing. When the audio adapter is not yet registered (e.g. framework tool calls sendMedia before gateway startup), isAudioFile() would throw 'OutboundAudioAdapter not registered' even for non-audio files like images. Wrap the getAudio() call in isAudioFile() with try/catch to return false when the adapter is unavailable, allowing non-audio media sends to proceed normally. * refactor(qqbot): remove plugin startup/upgrade greeting pipeline Drop the startup / upgrade greeting feature that was folded into the previous reminder + credential-backup commit. The pipeline has proven unnecessary for the fused build and its supporting admin-resolver scaffolding has no other consumers, so both are removed wholesale. - Delete engine/session/startup-greeting.ts and its tests: the first-launch "soul online" / "updated to vX.Y.Z" messages, the per-(accountId, appId) startup marker, the failure cooldown, and the legacy startup-marker.json migration path are all gone. - Delete engine/session/admin-resolver.ts and its tests: admin openid persistence/resolution, upgrade-greeting-target load/clear and the sendStartupGreetings dispatcher only ever served the greeting flow and were not referenced elsewhere. - channel.ts: drop the sendStartupGreetings import and the READY / RESUMED hooks that triggered greetings; credential-backup snapshots stay untouched. - engine/utils/data-paths.ts: remove getAdminMarkerFile / getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile / getStartupMarkerFile / getLegacyStartupMarkerFile along with the now-stale module docblock sections. Credential-backup helpers and safeName are preserved. Net -655 LOC across 6 files. tsc --noEmit passes on extensions/qqbot/tsconfig.json and no references to the removed symbols remain in the workspace. * fix(qqbot): resolve test failures in extension batch, contracts and bundled runtime deps - bootstrap: replace sync require() with static imports for secret-input and temp-path so vitest resolve.alias works correctly (require bypasses vitest aliases causing Cannot find module errors) - format: handle null/undefined in formatErrorMessage before JSON.stringify since JSON.stringify(undefined) returns JS undefined, not a string - gateway/types: reword comment to avoid triggering the channel-import guardrail regex that forbids quoted openclaw/plugin-sdk references - package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root dependencies as required by bundled plugin runtime dependency checks * chore: revert non-qqbot changes to align with upstream main Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories. * fix(qqbot): remove unused logUnsupportedStructuredMediaTarget after API simplification * fix(qqbot): restore channel-plugin-api.ts for bundled plugin surface convention * fix(qqbot): update CI lint allowlists for restructured engine paths - Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.) - Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts * fix(qqbot): eliminate os.tmpdir() in engine layer via adapter injection - Make hasPlatformAdapter() also check for registered factory, so adapter is always discoverable once bootstrap has run - Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(), delegate entirely to PlatformAdapter.getTempDir() which calls resolvePreferredOpenClawTmpDir() under the hood - Keeps engine/ layer free of openclaw/plugin-sdk imports * chore(qqbot): update CHANGELOG for engine architecture refactor (openclaw#67960) (thanks @cxyhhhhh) --------- Co-authored-by: Bobby <zkd8907@live.com> Co-authored-by: neilhwang <neilhwang@tencent.com> Co-authored-by: sliverp <870080352@qq.com>
… onboarding, approval handling (openclaw#67960) * feat(qqbot): add core architecture modules * feat(qqbot): extract engine modules with DI adapters * refactor(qqbot): remove plugin-level TTS, delegate to framework Remove qqbot's internal TTS implementation and unify voice synthesis through the framework's global TTS provider registry. - Delete engine/gateway/tts-config.ts (plugin-specific TTS config) - Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64 - Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback) - Strip QQBotTtsSchema from config-schema, plugin.json, and tests - Remove TTS diagnostics logging and hasTTS system prompt from gateway - Delete ~260 lines of TTS code from utils/audio-convert.ts Made-with: Cursor * feat(qqbot): extract shared engine modules for config, tools, and audio Add engine-layer modules that are self-contained and portable across both the built-in and standalone qqbot packages: - engine/config: account resolution helpers, field readers - engine/tools: channel API proxy, remind scheduling logic - engine/utils: audio format conversion, duration/error formatting, debug logging Consolidate duplicate utility functions across the codebase: - Merge debug-log.ts into log.ts - Merge error-format.ts into format.ts with full .cause chain support - Unify normalizeLowercase/readNumber/readBoolean/readStringMap into string-normalize.ts, removing private copies in resolve.ts, remind-logic.ts, and audio-convert.ts - Remove dead formatDuration export from audio-convert.ts - Delete unused config/schema.ts and config/helpers.ts Made-with: Cursor * refactor(qqbot): streamline account configuration and credential management Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include: - Migrate credential clearing and validation logic to engine/config/credentials.ts. - Simplify setup input validation and application in engine/config/setup-logic.ts. - Enhance account resolution and configuration application in engine/config/resolve.ts. - Update channel and messaging logic to utilize the new credential management functions. This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase. * feat(qqbot): simplify api architecture * feat: 支持扫码绑定QQ机器人 * feat(qqbot): refactor gateway into inbound pipeline + outbound dispatch - Extract handleMessage (620 lines) into three modules: - inbound-context.ts: InboundContext type definition - inbound-pipeline.ts: buildInboundContext() - outbound-dispatch.ts: dispatchOutbound() - gateway.ts handleMessage reduced to ~35 line shell - Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE - Add ref/format-message-ref.ts for cache-miss quote formatting - Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead - QueuedMessage: add msgType/msgElements for quote messages * fix(qqbot): fix markdownSupport loss + dynamic User-Agent Root cause: setOpenClawVersion() called _ensureInitialized(true) which cleared _appRegistry, destroying the MessageApi instance created by initApiConfig() with markdownSupport=true. Subsequent block deliver calls created a default markdownSupport=false instance, causing: 1. Markdown messages sent as plain text (msg_type=0 instead of 2) 2. message_reference incorrectly added (only suppressed in MD mode) Fix: ApiClient and TokenManager now accept userAgent as string | (() => string). sender.ts passes the buildUserAgent function reference, so UA changes propagate automatically on next request without rebuilding any objects. - ApiClient: userAgent -> resolveUserAgent getter, called per-request - TokenManager: same pattern - types.ts: ApiClientConfig.userAgent supports string | (() => string) - sender.ts: remove force re-init + _rebuildAppRegistry hack - initSender/setOpenClawVersion only update version variables - _ensureInitialized creates singletons once, never destroys them - _appRegistry is never cleared -> markdownSupport always preserved - runtime.ts: inject framework version via setOpenClawVersion(runtime.version) - gateway.ts: pass openclawVersion to initSender + registerPluginVersion - slash-commands-impl.ts: remove fragile require("../package.json") * feat(qqbot): implement native approval handling and configuration Add a new approval handling system for QQBot that integrates with the existing framework. Key features include: - Introduce `approval-handler.runtime.ts` for managing approval requests via QQ messages with inline keyboard support. - Create `approval-native.ts` as the entry point for QQBot's approval capability, allowing for simplified approval processes without explicit approver lists. - Implement configuration schema for exec approvals, enabling fine-grained control over who can approve requests. - Enhance messaging and interaction handling to support approval decisions through button interactions. This implementation streamlines the approval process, making it more user-friendly and efficient for QQBot users. * refactor(qqbot): enhance error handling across API and messaging modules This update introduces a centralized error formatting utility, `formatErrorMessage`, to improve consistency in error logging throughout the QQBot codebase. Key changes include: - Integration of `formatErrorMessage` in various API client, messaging, and gateway modules to standardize error messages. - Replacement of direct error message handling with the new utility to enhance readability and maintainability. These improvements streamline error reporting and provide clearer insights into issues encountered during operation. * refactor(qqbot): enhance API and messaging structure with type improvements This update refines the API and messaging modules by introducing type enhancements and restructuring function signatures for better clarity and maintainability. Key changes include: - Updated import statements to streamline type usage in and . - Refactored message sending functions to accept options objects, improving readability and flexibility. - Introduced a new method in to facilitate external message-sent notifications. - Enhanced error handling in the retry mechanism to ensure more robust behavior. These modifications aim to improve the overall code quality and developer experience within the QQBot framework. * feat: 优化文案 * refactor(qqbot): unify Logger interfaces + eliminate P0 code smells Logger unification (17 files): - Introduce single EngineLogger interface in engine/types.ts { info, error, warn?, debug? } - Delete 5 fragmented Logger interfaces: GatewayLogger, ReconnectLogger, MessageRefLogger, PathLogger, SenderLogger - Replace all references across engine/ to use EngineLogger directly P0 code smell fixes (sender.ts + messages.ts + outbound-dispatch.ts): - messages.ts: add public notifyMessageSent() method on MessageApi, replacing 8x 'as unknown as { messageSentHook }' private field hack - sender.ts: extract notifyMediaHook() helper, deduplicate 4 media send functions (sendImage/sendVoice/sendVideo/sendFile) - sender.ts: replace magic numbers 1/2/3/4 with MediaFileType enum - sender.ts: remove 4 redundant 'as MessageResponse' type assertions - outbound-dispatch.ts: remove 5 unnecessary 'as never' casts * feat(qqbot): add /bot-clear-storage command + consolidate utils/types into engine/ /bot-clear-storage (slash-commands-impl.ts): - Migrate from standalone version, aligned with its two-step flow: 1. No args: scan ~/.openclaw/media/qqbot/downloads/{appId}/ and display file list with confirmation button 2. --force: delete files + removeEmptyDirs cleanup - C2C only (group chat returns hint) - bot-help: exclude bot-upgrade and bot-clear-storage in group listings Consolidate into engine/: - Delete src/utils/audio-convert.ts (pure re-export shell, zero consumers) - Move 5 test files from src/utils/ to src/engine/utils/ (fix import paths) - Move src/types/silk-wasm.d.ts to src/engine/types/ - Remove empty src/utils/ and src/types/ directories * refactor(qqbot): restructure API and bridge components for improved modularity This update enhances the QQBot framework by reorganizing the API and bridge components, promoting better modularity and maintainability. Key changes include: - Refactored import paths to streamline access to bridge tools and configurations. - Introduced new bridge files for channel entry, runtime, and approval capabilities, centralizing related functionalities. - Updated existing functions to utilize the new bridge structure, ensuring consistency across the codebase. - Removed deprecated functions and types, simplifying the overall architecture. These modifications aim to improve code clarity and facilitate future development within the QQBot ecosystem. * refactor(qqbot): standardize engine log levels and unify log tag prefix - Rename client.ts to api-client.ts to match ApiClient class name - Downgrade ~60 non-critical info logs to debug level across 12 files (token request/response, HTTP request/response, session restore, media tag detection, image classification, quote detection, attachment download/transcode, retry attempts, etc.) - Unify log tag prefix to [qqbot:xxx] format across all engine modules ([core-api] -> [qqbot:api], [token:x] -> [qqbot:token:x], [retry] -> [qqbot:retry], [messages] -> [qqbot:messages], [sender:x] -> [qqbot:x]) - Remove unnecessary reqTs timestamp from api-client.ts log output - Add dispatch event debug log in gateway-connection.ts - Merge sendProactiveMessage into sendText, remove dead code (sendProactiveText import, getRefIdx, QQMessageResult type) - Narrow allow-from.ts type from unknown[] to Array<string | number> * refactor(qqbot): move interaction handler from bridge to engine - Move onInteraction approval handler into engine/gateway.ts as createApprovalInteractionHandler(), eliminating the callback indirection through CoreGatewayContext - Remove onInteraction from CoreGatewayContext interface and its unused InteractionEvent import from gateway/types.ts - Remove getPlatformAdapter, parseApprovalButtonData and InteractionEvent imports from bridge/gateway.ts * refactor(qqbot): route bridge and sender logs through framework logger - Add bridge/logger.ts as a shared logger holder for bridge-layer modules, injected with ctx.log during gateway startup - Replace all console.log/console.error in bridge/ with getBridgeLogger() calls (approval, bootstrap, tools) - Restore framework logger support in sender.ts via initSender() so API-layer logs flow through OpenClaw log system - Remove all direct debugLog/debugError imports from bridge/ * feat(qqbot): per-account isolated resource stack + multi-account logger - sender.ts: global singletons (ApiClient/TokenManager/MediaApi) -> per-account AccountContext - Add _accountRegistry: Map<appId, AccountContext> - Each account owns independent client/tokenMgr/mediaApi/messageApi/logger - registerAccount() atomically sets up all resources - resolveAccount() routes to correct resource stack by appId - Remove _sharedLogger/_loggerRegistry/_appRegistry and old structures - bridge/gateway.ts: createAccountLogger() with auto [accountId] prefix - registerAccount() merges logger + markdownSupport + full API resources - engine-wide: remove ~60 manual [qqbot:${accountId}] log prefixes - Prefixes now auto-injected by per-account logger - Remove prefix/logPrefix parameter chains (outbound/outbound-deliver/typing-keepalive etc) * feat(qqbot): completes fallback path for approval with multi-account isolation When the execApprovals are not configured, multiple QQBot accounts' handlers will attempt to deliver the same approval message. The openid is account-level, and cross-account delivery will trigger a QQ Bot API 500 error. - Add account ownership verification in the fallback shouldHandle: Only match the account's handler when the request includes turnSourceAccountId; if unbound, delivery is only permitted when the number of enabled+secret accounts is ≤1. - Consolidate account ownership determination into the unified export `matchesQQBotApprovalAccount` in `exec-approvals.ts`, with both capability and native runtime paths sharing the same logic to eliminate redundancy. * feat(qqbot): optimize permission validation strategy * feat(qqbot): show plugin version in /bot-version and /bot-help Align /bot-version output with the standalone openclaw-qqbot build so users see both the QQBot plugin version and the OpenClaw framework version. Append the plugin version as a footer in /bot-help as well, matching the standalone UX. Also fix the plugin version lookup that previously rendered as 'vunknown': the old code used a hardcoded '../../package.json' relative path which resolved to 'src/package.json' (non-existent) when executed from raw sources, so the require threw and the default 'unknown' value was retained. The same broken value also leaked into the QQ Bot API User-Agent header. Replace the hardcoded path with a dedicated helper (bridge/plugin-version.ts) that walks up the directory tree from import.meta.url and validates the manifest's name field (@openclaw/qqbot) to avoid misreading the monorepo root package.json. Covered by 6 unit tests. * feat(qqbot): trust shared ~/.openclaw/media root for payload files Add getOpenClawMediaDir() and include it alongside getQQBotMediaDir() in the allowed roots of resolveQQBotPayloadLocalFilePath, so framework-produced attachments under sibling directories (e.g. media/outbound/ written by saveMediaBuffer) are trusted by auto-routed sends without triggering the path-outside-storage guard. Covered by a new test case that verifies files under ~/.openclaw/media/outbound/ resolve successfully. * fix(qqbot): ensure PlatformAdapter is registered before approval delivery After the framework centralized approval handler bootstrap (openclaw#62135), the native approval handler is spawned by the framework layer outside the qqbot gateway startAccount context. This means channel.ts's side-effect `import "./bridge/bootstrap.js"` may not have run, leaving PlatformAdapter unregistered when deliverPending calls resolveQQBotAccount -> getPlatformAdapter(). Extract ensurePlatformAdapter() from bootstrap.ts as an idempotent, re-entrant helper and call it in both capability.ts (load callback) and handler-runtime.ts (deliverPending entry) to guarantee the adapter is available regardless of initialization order. * fix(qqbot): add lazy factory for PlatformAdapter to eliminate import-order dependency The bundler splits qqbot code into multiple chunks where the adapter singleton and its consumers may live in different modules. When a consumer chunk evaluates before the bootstrap side-effect chunk, getPlatformAdapter() throws because the singleton is still null. Introduce registerPlatformAdapterFactory() in adapter/index.ts so getPlatformAdapter() can auto-initialize the adapter on first access. bootstrap.ts registers the factory at module evaluation time alongside the existing eager registration path. Also add error logging in downloadFile's catch block to surface fetch failures. * feat(qqbot): add /bot-approve slash command for exec approval config management Add /bot-approve command to the built-in QQBot plugin, ported from the standalone openclaw-qqbot implementation. This command allows users to manage tools.exec.security and tools.exec.ask settings directly from QQ. Supported sub-commands: /bot-approve on - allowlist + on-miss (recommended) /bot-approve off - full + off (no approval) /bot-approve always - allowlist + always (strict mode) /bot-approve reset - remove overrides, restore framework defaults /bot-approve status - show current security/ask values The runtime config API is injected via registerApproveRuntimeGetter() following the existing dependency injection pattern used by registerVersionResolver() and registerPluginVersion(). * fix(qqbot): ACK INTERACTION_CREATE events before processing approval buttons Send PUT /interactions/{id} immediately upon receiving any INTERACTION_CREATE event to prevent QQ from showing a timeout error to the user. The ACK is fire-and-forget and does not block subsequent approval button resolution. Also resolve merge conflict in pnpm-lock.yaml (keep @tencent-connect/qqbot-connector@1.1.0 and newer @thi.ng/bitstream@2.4.46). * feat(qqbot): enhance reminder functionality with delivery context and credential backup This update improves the QQBot reminder system by introducing a delivery context for reminders, allowing for more flexible target resolution. Key changes include: - Updated reminder logic to utilize a delivery envelope, ensuring that reminders are sent with the correct context. - Implemented credential backup and recovery mechanisms to prevent loss of appId and clientSecret during hot upgrades. - Added tests for credential backup functionality and admin resolver to ensure reliability. - Enhanced the remind tool to automatically resolve the target from the current conversation context when not explicitly provided. These enhancements aim to improve the user experience and reliability of the reminder feature within the QQBot framework. * fix(qqbot): ensure PlatformAdapter is registered before gateway message processing Call ensurePlatformAdapter() at the start of bridge/gateway.ts's startGateway() to guarantee the adapter is available when engine code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter(). When the bundler splits code into separate chunks, bootstrap.ts's module-level side-effect registration may not have executed yet by the time the gateway processes its first inbound attachment download. Also fix the TS2339 error in registerApproveRuntimeGetter by using getQQBotRuntime() (full PluginRuntime with config) instead of getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config). * fix(qqbot): make isAudioFile safe when OutboundAudioAdapter is not registered sendMedia() calls isAudioFile() as part of its media-type dispatch logic before any actual audio processing. When the audio adapter is not yet registered (e.g. framework tool calls sendMedia before gateway startup), isAudioFile() would throw 'OutboundAudioAdapter not registered' even for non-audio files like images. Wrap the getAudio() call in isAudioFile() with try/catch to return false when the adapter is unavailable, allowing non-audio media sends to proceed normally. * refactor(qqbot): remove plugin startup/upgrade greeting pipeline Drop the startup / upgrade greeting feature that was folded into the previous reminder + credential-backup commit. The pipeline has proven unnecessary for the fused build and its supporting admin-resolver scaffolding has no other consumers, so both are removed wholesale. - Delete engine/session/startup-greeting.ts and its tests: the first-launch "soul online" / "updated to vX.Y.Z" messages, the per-(accountId, appId) startup marker, the failure cooldown, and the legacy startup-marker.json migration path are all gone. - Delete engine/session/admin-resolver.ts and its tests: admin openid persistence/resolution, upgrade-greeting-target load/clear and the sendStartupGreetings dispatcher only ever served the greeting flow and were not referenced elsewhere. - channel.ts: drop the sendStartupGreetings import and the READY / RESUMED hooks that triggered greetings; credential-backup snapshots stay untouched. - engine/utils/data-paths.ts: remove getAdminMarkerFile / getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile / getStartupMarkerFile / getLegacyStartupMarkerFile along with the now-stale module docblock sections. Credential-backup helpers and safeName are preserved. Net -655 LOC across 6 files. tsc --noEmit passes on extensions/qqbot/tsconfig.json and no references to the removed symbols remain in the workspace. * fix(qqbot): resolve test failures in extension batch, contracts and bundled runtime deps - bootstrap: replace sync require() with static imports for secret-input and temp-path so vitest resolve.alias works correctly (require bypasses vitest aliases causing Cannot find module errors) - format: handle null/undefined in formatErrorMessage before JSON.stringify since JSON.stringify(undefined) returns JS undefined, not a string - gateway/types: reword comment to avoid triggering the channel-import guardrail regex that forbids quoted openclaw/plugin-sdk references - package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root dependencies as required by bundled plugin runtime dependency checks * chore: revert non-qqbot changes to align with upstream main Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories. * fix(qqbot): remove unused logUnsupportedStructuredMediaTarget after API simplification * fix(qqbot): restore channel-plugin-api.ts for bundled plugin surface convention * fix(qqbot): update CI lint allowlists for restructured engine paths - Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.) - Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts * fix(qqbot): eliminate os.tmpdir() in engine layer via adapter injection - Make hasPlatformAdapter() also check for registered factory, so adapter is always discoverable once bootstrap has run - Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(), delegate entirely to PlatformAdapter.getTempDir() which calls resolvePreferredOpenClawTmpDir() under the hood - Keeps engine/ layer free of openclaw/plugin-sdk imports * chore(qqbot): update CHANGELOG for engine architecture refactor (openclaw#67960) (thanks @cxyhhhhh) --------- Co-authored-by: Bobby <zkd8907@live.com> Co-authored-by: neilhwang <neilhwang@tencent.com> Co-authored-by: sliverp <870080352@qq.com>
… onboarding, approval handling (openclaw#67960) * feat(qqbot): add core architecture modules * feat(qqbot): extract engine modules with DI adapters * refactor(qqbot): remove plugin-level TTS, delegate to framework Remove qqbot's internal TTS implementation and unify voice synthesis through the framework's global TTS provider registry. - Delete engine/gateway/tts-config.ts (plugin-specific TTS config) - Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64 - Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback) - Strip QQBotTtsSchema from config-schema, plugin.json, and tests - Remove TTS diagnostics logging and hasTTS system prompt from gateway - Delete ~260 lines of TTS code from utils/audio-convert.ts Made-with: Cursor * feat(qqbot): extract shared engine modules for config, tools, and audio Add engine-layer modules that are self-contained and portable across both the built-in and standalone qqbot packages: - engine/config: account resolution helpers, field readers - engine/tools: channel API proxy, remind scheduling logic - engine/utils: audio format conversion, duration/error formatting, debug logging Consolidate duplicate utility functions across the codebase: - Merge debug-log.ts into log.ts - Merge error-format.ts into format.ts with full .cause chain support - Unify normalizeLowercase/readNumber/readBoolean/readStringMap into string-normalize.ts, removing private copies in resolve.ts, remind-logic.ts, and audio-convert.ts - Remove dead formatDuration export from audio-convert.ts - Delete unused config/schema.ts and config/helpers.ts Made-with: Cursor * refactor(qqbot): streamline account configuration and credential management Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include: - Migrate credential clearing and validation logic to engine/config/credentials.ts. - Simplify setup input validation and application in engine/config/setup-logic.ts. - Enhance account resolution and configuration application in engine/config/resolve.ts. - Update channel and messaging logic to utilize the new credential management functions. This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase. * feat(qqbot): simplify api architecture * feat: 支持扫码绑定QQ机器人 * feat(qqbot): refactor gateway into inbound pipeline + outbound dispatch - Extract handleMessage (620 lines) into three modules: - inbound-context.ts: InboundContext type definition - inbound-pipeline.ts: buildInboundContext() - outbound-dispatch.ts: dispatchOutbound() - gateway.ts handleMessage reduced to ~35 line shell - Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE - Add ref/format-message-ref.ts for cache-miss quote formatting - Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead - QueuedMessage: add msgType/msgElements for quote messages * fix(qqbot): fix markdownSupport loss + dynamic User-Agent Root cause: setOpenClawVersion() called _ensureInitialized(true) which cleared _appRegistry, destroying the MessageApi instance created by initApiConfig() with markdownSupport=true. Subsequent block deliver calls created a default markdownSupport=false instance, causing: 1. Markdown messages sent as plain text (msg_type=0 instead of 2) 2. message_reference incorrectly added (only suppressed in MD mode) Fix: ApiClient and TokenManager now accept userAgent as string | (() => string). sender.ts passes the buildUserAgent function reference, so UA changes propagate automatically on next request without rebuilding any objects. - ApiClient: userAgent -> resolveUserAgent getter, called per-request - TokenManager: same pattern - types.ts: ApiClientConfig.userAgent supports string | (() => string) - sender.ts: remove force re-init + _rebuildAppRegistry hack - initSender/setOpenClawVersion only update version variables - _ensureInitialized creates singletons once, never destroys them - _appRegistry is never cleared -> markdownSupport always preserved - runtime.ts: inject framework version via setOpenClawVersion(runtime.version) - gateway.ts: pass openclawVersion to initSender + registerPluginVersion - slash-commands-impl.ts: remove fragile require("../package.json") * feat(qqbot): implement native approval handling and configuration Add a new approval handling system for QQBot that integrates with the existing framework. Key features include: - Introduce `approval-handler.runtime.ts` for managing approval requests via QQ messages with inline keyboard support. - Create `approval-native.ts` as the entry point for QQBot's approval capability, allowing for simplified approval processes without explicit approver lists. - Implement configuration schema for exec approvals, enabling fine-grained control over who can approve requests. - Enhance messaging and interaction handling to support approval decisions through button interactions. This implementation streamlines the approval process, making it more user-friendly and efficient for QQBot users. * refactor(qqbot): enhance error handling across API and messaging modules This update introduces a centralized error formatting utility, `formatErrorMessage`, to improve consistency in error logging throughout the QQBot codebase. Key changes include: - Integration of `formatErrorMessage` in various API client, messaging, and gateway modules to standardize error messages. - Replacement of direct error message handling with the new utility to enhance readability and maintainability. These improvements streamline error reporting and provide clearer insights into issues encountered during operation. * refactor(qqbot): enhance API and messaging structure with type improvements This update refines the API and messaging modules by introducing type enhancements and restructuring function signatures for better clarity and maintainability. Key changes include: - Updated import statements to streamline type usage in and . - Refactored message sending functions to accept options objects, improving readability and flexibility. - Introduced a new method in to facilitate external message-sent notifications. - Enhanced error handling in the retry mechanism to ensure more robust behavior. These modifications aim to improve the overall code quality and developer experience within the QQBot framework. * feat: 优化文案 * refactor(qqbot): unify Logger interfaces + eliminate P0 code smells Logger unification (17 files): - Introduce single EngineLogger interface in engine/types.ts { info, error, warn?, debug? } - Delete 5 fragmented Logger interfaces: GatewayLogger, ReconnectLogger, MessageRefLogger, PathLogger, SenderLogger - Replace all references across engine/ to use EngineLogger directly P0 code smell fixes (sender.ts + messages.ts + outbound-dispatch.ts): - messages.ts: add public notifyMessageSent() method on MessageApi, replacing 8x 'as unknown as { messageSentHook }' private field hack - sender.ts: extract notifyMediaHook() helper, deduplicate 4 media send functions (sendImage/sendVoice/sendVideo/sendFile) - sender.ts: replace magic numbers 1/2/3/4 with MediaFileType enum - sender.ts: remove 4 redundant 'as MessageResponse' type assertions - outbound-dispatch.ts: remove 5 unnecessary 'as never' casts * feat(qqbot): add /bot-clear-storage command + consolidate utils/types into engine/ /bot-clear-storage (slash-commands-impl.ts): - Migrate from standalone version, aligned with its two-step flow: 1. No args: scan ~/.openclaw/media/qqbot/downloads/{appId}/ and display file list with confirmation button 2. --force: delete files + removeEmptyDirs cleanup - C2C only (group chat returns hint) - bot-help: exclude bot-upgrade and bot-clear-storage in group listings Consolidate into engine/: - Delete src/utils/audio-convert.ts (pure re-export shell, zero consumers) - Move 5 test files from src/utils/ to src/engine/utils/ (fix import paths) - Move src/types/silk-wasm.d.ts to src/engine/types/ - Remove empty src/utils/ and src/types/ directories * refactor(qqbot): restructure API and bridge components for improved modularity This update enhances the QQBot framework by reorganizing the API and bridge components, promoting better modularity and maintainability. Key changes include: - Refactored import paths to streamline access to bridge tools and configurations. - Introduced new bridge files for channel entry, runtime, and approval capabilities, centralizing related functionalities. - Updated existing functions to utilize the new bridge structure, ensuring consistency across the codebase. - Removed deprecated functions and types, simplifying the overall architecture. These modifications aim to improve code clarity and facilitate future development within the QQBot ecosystem. * refactor(qqbot): standardize engine log levels and unify log tag prefix - Rename client.ts to api-client.ts to match ApiClient class name - Downgrade ~60 non-critical info logs to debug level across 12 files (token request/response, HTTP request/response, session restore, media tag detection, image classification, quote detection, attachment download/transcode, retry attempts, etc.) - Unify log tag prefix to [qqbot:xxx] format across all engine modules ([core-api] -> [qqbot:api], [token:x] -> [qqbot:token:x], [retry] -> [qqbot:retry], [messages] -> [qqbot:messages], [sender:x] -> [qqbot:x]) - Remove unnecessary reqTs timestamp from api-client.ts log output - Add dispatch event debug log in gateway-connection.ts - Merge sendProactiveMessage into sendText, remove dead code (sendProactiveText import, getRefIdx, QQMessageResult type) - Narrow allow-from.ts type from unknown[] to Array<string | number> * refactor(qqbot): move interaction handler from bridge to engine - Move onInteraction approval handler into engine/gateway.ts as createApprovalInteractionHandler(), eliminating the callback indirection through CoreGatewayContext - Remove onInteraction from CoreGatewayContext interface and its unused InteractionEvent import from gateway/types.ts - Remove getPlatformAdapter, parseApprovalButtonData and InteractionEvent imports from bridge/gateway.ts * refactor(qqbot): route bridge and sender logs through framework logger - Add bridge/logger.ts as a shared logger holder for bridge-layer modules, injected with ctx.log during gateway startup - Replace all console.log/console.error in bridge/ with getBridgeLogger() calls (approval, bootstrap, tools) - Restore framework logger support in sender.ts via initSender() so API-layer logs flow through OpenClaw log system - Remove all direct debugLog/debugError imports from bridge/ * feat(qqbot): per-account isolated resource stack + multi-account logger - sender.ts: global singletons (ApiClient/TokenManager/MediaApi) -> per-account AccountContext - Add _accountRegistry: Map<appId, AccountContext> - Each account owns independent client/tokenMgr/mediaApi/messageApi/logger - registerAccount() atomically sets up all resources - resolveAccount() routes to correct resource stack by appId - Remove _sharedLogger/_loggerRegistry/_appRegistry and old structures - bridge/gateway.ts: createAccountLogger() with auto [accountId] prefix - registerAccount() merges logger + markdownSupport + full API resources - engine-wide: remove ~60 manual [qqbot:${accountId}] log prefixes - Prefixes now auto-injected by per-account logger - Remove prefix/logPrefix parameter chains (outbound/outbound-deliver/typing-keepalive etc) * feat(qqbot): completes fallback path for approval with multi-account isolation When the execApprovals are not configured, multiple QQBot accounts' handlers will attempt to deliver the same approval message. The openid is account-level, and cross-account delivery will trigger a QQ Bot API 500 error. - Add account ownership verification in the fallback shouldHandle: Only match the account's handler when the request includes turnSourceAccountId; if unbound, delivery is only permitted when the number of enabled+secret accounts is ≤1. - Consolidate account ownership determination into the unified export `matchesQQBotApprovalAccount` in `exec-approvals.ts`, with both capability and native runtime paths sharing the same logic to eliminate redundancy. * feat(qqbot): optimize permission validation strategy * feat(qqbot): show plugin version in /bot-version and /bot-help Align /bot-version output with the standalone openclaw-qqbot build so users see both the QQBot plugin version and the OpenClaw framework version. Append the plugin version as a footer in /bot-help as well, matching the standalone UX. Also fix the plugin version lookup that previously rendered as 'vunknown': the old code used a hardcoded '../../package.json' relative path which resolved to 'src/package.json' (non-existent) when executed from raw sources, so the require threw and the default 'unknown' value was retained. The same broken value also leaked into the QQ Bot API User-Agent header. Replace the hardcoded path with a dedicated helper (bridge/plugin-version.ts) that walks up the directory tree from import.meta.url and validates the manifest's name field (@openclaw/qqbot) to avoid misreading the monorepo root package.json. Covered by 6 unit tests. * feat(qqbot): trust shared ~/.openclaw/media root for payload files Add getOpenClawMediaDir() and include it alongside getQQBotMediaDir() in the allowed roots of resolveQQBotPayloadLocalFilePath, so framework-produced attachments under sibling directories (e.g. media/outbound/ written by saveMediaBuffer) are trusted by auto-routed sends without triggering the path-outside-storage guard. Covered by a new test case that verifies files under ~/.openclaw/media/outbound/ resolve successfully. * fix(qqbot): ensure PlatformAdapter is registered before approval delivery After the framework centralized approval handler bootstrap (openclaw#62135), the native approval handler is spawned by the framework layer outside the qqbot gateway startAccount context. This means channel.ts's side-effect `import "./bridge/bootstrap.js"` may not have run, leaving PlatformAdapter unregistered when deliverPending calls resolveQQBotAccount -> getPlatformAdapter(). Extract ensurePlatformAdapter() from bootstrap.ts as an idempotent, re-entrant helper and call it in both capability.ts (load callback) and handler-runtime.ts (deliverPending entry) to guarantee the adapter is available regardless of initialization order. * fix(qqbot): add lazy factory for PlatformAdapter to eliminate import-order dependency The bundler splits qqbot code into multiple chunks where the adapter singleton and its consumers may live in different modules. When a consumer chunk evaluates before the bootstrap side-effect chunk, getPlatformAdapter() throws because the singleton is still null. Introduce registerPlatformAdapterFactory() in adapter/index.ts so getPlatformAdapter() can auto-initialize the adapter on first access. bootstrap.ts registers the factory at module evaluation time alongside the existing eager registration path. Also add error logging in downloadFile's catch block to surface fetch failures. * feat(qqbot): add /bot-approve slash command for exec approval config management Add /bot-approve command to the built-in QQBot plugin, ported from the standalone openclaw-qqbot implementation. This command allows users to manage tools.exec.security and tools.exec.ask settings directly from QQ. Supported sub-commands: /bot-approve on - allowlist + on-miss (recommended) /bot-approve off - full + off (no approval) /bot-approve always - allowlist + always (strict mode) /bot-approve reset - remove overrides, restore framework defaults /bot-approve status - show current security/ask values The runtime config API is injected via registerApproveRuntimeGetter() following the existing dependency injection pattern used by registerVersionResolver() and registerPluginVersion(). * fix(qqbot): ACK INTERACTION_CREATE events before processing approval buttons Send PUT /interactions/{id} immediately upon receiving any INTERACTION_CREATE event to prevent QQ from showing a timeout error to the user. The ACK is fire-and-forget and does not block subsequent approval button resolution. Also resolve merge conflict in pnpm-lock.yaml (keep @tencent-connect/qqbot-connector@1.1.0 and newer @thi.ng/bitstream@2.4.46). * feat(qqbot): enhance reminder functionality with delivery context and credential backup This update improves the QQBot reminder system by introducing a delivery context for reminders, allowing for more flexible target resolution. Key changes include: - Updated reminder logic to utilize a delivery envelope, ensuring that reminders are sent with the correct context. - Implemented credential backup and recovery mechanisms to prevent loss of appId and clientSecret during hot upgrades. - Added tests for credential backup functionality and admin resolver to ensure reliability. - Enhanced the remind tool to automatically resolve the target from the current conversation context when not explicitly provided. These enhancements aim to improve the user experience and reliability of the reminder feature within the QQBot framework. * fix(qqbot): ensure PlatformAdapter is registered before gateway message processing Call ensurePlatformAdapter() at the start of bridge/gateway.ts's startGateway() to guarantee the adapter is available when engine code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter(). When the bundler splits code into separate chunks, bootstrap.ts's module-level side-effect registration may not have executed yet by the time the gateway processes its first inbound attachment download. Also fix the TS2339 error in registerApproveRuntimeGetter by using getQQBotRuntime() (full PluginRuntime with config) instead of getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config). * fix(qqbot): make isAudioFile safe when OutboundAudioAdapter is not registered sendMedia() calls isAudioFile() as part of its media-type dispatch logic before any actual audio processing. When the audio adapter is not yet registered (e.g. framework tool calls sendMedia before gateway startup), isAudioFile() would throw 'OutboundAudioAdapter not registered' even for non-audio files like images. Wrap the getAudio() call in isAudioFile() with try/catch to return false when the adapter is unavailable, allowing non-audio media sends to proceed normally. * refactor(qqbot): remove plugin startup/upgrade greeting pipeline Drop the startup / upgrade greeting feature that was folded into the previous reminder + credential-backup commit. The pipeline has proven unnecessary for the fused build and its supporting admin-resolver scaffolding has no other consumers, so both are removed wholesale. - Delete engine/session/startup-greeting.ts and its tests: the first-launch "soul online" / "updated to vX.Y.Z" messages, the per-(accountId, appId) startup marker, the failure cooldown, and the legacy startup-marker.json migration path are all gone. - Delete engine/session/admin-resolver.ts and its tests: admin openid persistence/resolution, upgrade-greeting-target load/clear and the sendStartupGreetings dispatcher only ever served the greeting flow and were not referenced elsewhere. - channel.ts: drop the sendStartupGreetings import and the READY / RESUMED hooks that triggered greetings; credential-backup snapshots stay untouched. - engine/utils/data-paths.ts: remove getAdminMarkerFile / getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile / getStartupMarkerFile / getLegacyStartupMarkerFile along with the now-stale module docblock sections. Credential-backup helpers and safeName are preserved. Net -655 LOC across 6 files. tsc --noEmit passes on extensions/qqbot/tsconfig.json and no references to the removed symbols remain in the workspace. * fix(qqbot): resolve test failures in extension batch, contracts and bundled runtime deps - bootstrap: replace sync require() with static imports for secret-input and temp-path so vitest resolve.alias works correctly (require bypasses vitest aliases causing Cannot find module errors) - format: handle null/undefined in formatErrorMessage before JSON.stringify since JSON.stringify(undefined) returns JS undefined, not a string - gateway/types: reword comment to avoid triggering the channel-import guardrail regex that forbids quoted openclaw/plugin-sdk references - package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root dependencies as required by bundled plugin runtime dependency checks * chore: revert non-qqbot changes to align with upstream main Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories. * fix(qqbot): remove unused logUnsupportedStructuredMediaTarget after API simplification * fix(qqbot): restore channel-plugin-api.ts for bundled plugin surface convention * fix(qqbot): update CI lint allowlists for restructured engine paths - Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.) - Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts * fix(qqbot): eliminate os.tmpdir() in engine layer via adapter injection - Make hasPlatformAdapter() also check for registered factory, so adapter is always discoverable once bootstrap has run - Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(), delegate entirely to PlatformAdapter.getTempDir() which calls resolvePreferredOpenClawTmpDir() under the hood - Keeps engine/ layer free of openclaw/plugin-sdk imports * chore(qqbot): update CHANGELOG for engine architecture refactor (openclaw#67960) (thanks @cxyhhhhh) --------- Co-authored-by: Bobby <zkd8907@live.com> Co-authored-by: neilhwang <neilhwang@tencent.com> Co-authored-by: sliverp <870080352@qq.com>
… onboarding, approval handling (openclaw#67960) * feat(qqbot): add core architecture modules * feat(qqbot): extract engine modules with DI adapters * refactor(qqbot): remove plugin-level TTS, delegate to framework Remove qqbot's internal TTS implementation and unify voice synthesis through the framework's global TTS provider registry. - Delete engine/gateway/tts-config.ts (plugin-specific TTS config) - Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64 - Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback) - Strip QQBotTtsSchema from config-schema, plugin.json, and tests - Remove TTS diagnostics logging and hasTTS system prompt from gateway - Delete ~260 lines of TTS code from utils/audio-convert.ts Made-with: Cursor * feat(qqbot): extract shared engine modules for config, tools, and audio Add engine-layer modules that are self-contained and portable across both the built-in and standalone qqbot packages: - engine/config: account resolution helpers, field readers - engine/tools: channel API proxy, remind scheduling logic - engine/utils: audio format conversion, duration/error formatting, debug logging Consolidate duplicate utility functions across the codebase: - Merge debug-log.ts into log.ts - Merge error-format.ts into format.ts with full .cause chain support - Unify normalizeLowercase/readNumber/readBoolean/readStringMap into string-normalize.ts, removing private copies in resolve.ts, remind-logic.ts, and audio-convert.ts - Remove dead formatDuration export from audio-convert.ts - Delete unused config/schema.ts and config/helpers.ts Made-with: Cursor * refactor(qqbot): streamline account configuration and credential management Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include: - Migrate credential clearing and validation logic to engine/config/credentials.ts. - Simplify setup input validation and application in engine/config/setup-logic.ts. - Enhance account resolution and configuration application in engine/config/resolve.ts. - Update channel and messaging logic to utilize the new credential management functions. This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase. * feat(qqbot): simplify api architecture * feat: 支持扫码绑定QQ机器人 * feat(qqbot): refactor gateway into inbound pipeline + outbound dispatch - Extract handleMessage (620 lines) into three modules: - inbound-context.ts: InboundContext type definition - inbound-pipeline.ts: buildInboundContext() - outbound-dispatch.ts: dispatchOutbound() - gateway.ts handleMessage reduced to ~35 line shell - Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE - Add ref/format-message-ref.ts for cache-miss quote formatting - Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead - QueuedMessage: add msgType/msgElements for quote messages * fix(qqbot): fix markdownSupport loss + dynamic User-Agent Root cause: setOpenClawVersion() called _ensureInitialized(true) which cleared _appRegistry, destroying the MessageApi instance created by initApiConfig() with markdownSupport=true. Subsequent block deliver calls created a default markdownSupport=false instance, causing: 1. Markdown messages sent as plain text (msg_type=0 instead of 2) 2. message_reference incorrectly added (only suppressed in MD mode) Fix: ApiClient and TokenManager now accept userAgent as string | (() => string). sender.ts passes the buildUserAgent function reference, so UA changes propagate automatically on next request without rebuilding any objects. - ApiClient: userAgent -> resolveUserAgent getter, called per-request - TokenManager: same pattern - types.ts: ApiClientConfig.userAgent supports string | (() => string) - sender.ts: remove force re-init + _rebuildAppRegistry hack - initSender/setOpenClawVersion only update version variables - _ensureInitialized creates singletons once, never destroys them - _appRegistry is never cleared -> markdownSupport always preserved - runtime.ts: inject framework version via setOpenClawVersion(runtime.version) - gateway.ts: pass openclawVersion to initSender + registerPluginVersion - slash-commands-impl.ts: remove fragile require("../package.json") * feat(qqbot): implement native approval handling and configuration Add a new approval handling system for QQBot that integrates with the existing framework. Key features include: - Introduce `approval-handler.runtime.ts` for managing approval requests via QQ messages with inline keyboard support. - Create `approval-native.ts` as the entry point for QQBot's approval capability, allowing for simplified approval processes without explicit approver lists. - Implement configuration schema for exec approvals, enabling fine-grained control over who can approve requests. - Enhance messaging and interaction handling to support approval decisions through button interactions. This implementation streamlines the approval process, making it more user-friendly and efficient for QQBot users. * refactor(qqbot): enhance error handling across API and messaging modules This update introduces a centralized error formatting utility, `formatErrorMessage`, to improve consistency in error logging throughout the QQBot codebase. Key changes include: - Integration of `formatErrorMessage` in various API client, messaging, and gateway modules to standardize error messages. - Replacement of direct error message handling with the new utility to enhance readability and maintainability. These improvements streamline error reporting and provide clearer insights into issues encountered during operation. * refactor(qqbot): enhance API and messaging structure with type improvements This update refines the API and messaging modules by introducing type enhancements and restructuring function signatures for better clarity and maintainability. Key changes include: - Updated import statements to streamline type usage in and . - Refactored message sending functions to accept options objects, improving readability and flexibility. - Introduced a new method in to facilitate external message-sent notifications. - Enhanced error handling in the retry mechanism to ensure more robust behavior. These modifications aim to improve the overall code quality and developer experience within the QQBot framework. * feat: 优化文案 * refactor(qqbot): unify Logger interfaces + eliminate P0 code smells Logger unification (17 files): - Introduce single EngineLogger interface in engine/types.ts { info, error, warn?, debug? } - Delete 5 fragmented Logger interfaces: GatewayLogger, ReconnectLogger, MessageRefLogger, PathLogger, SenderLogger - Replace all references across engine/ to use EngineLogger directly P0 code smell fixes (sender.ts + messages.ts + outbound-dispatch.ts): - messages.ts: add public notifyMessageSent() method on MessageApi, replacing 8x 'as unknown as { messageSentHook }' private field hack - sender.ts: extract notifyMediaHook() helper, deduplicate 4 media send functions (sendImage/sendVoice/sendVideo/sendFile) - sender.ts: replace magic numbers 1/2/3/4 with MediaFileType enum - sender.ts: remove 4 redundant 'as MessageResponse' type assertions - outbound-dispatch.ts: remove 5 unnecessary 'as never' casts * feat(qqbot): add /bot-clear-storage command + consolidate utils/types into engine/ /bot-clear-storage (slash-commands-impl.ts): - Migrate from standalone version, aligned with its two-step flow: 1. No args: scan ~/.openclaw/media/qqbot/downloads/{appId}/ and display file list with confirmation button 2. --force: delete files + removeEmptyDirs cleanup - C2C only (group chat returns hint) - bot-help: exclude bot-upgrade and bot-clear-storage in group listings Consolidate into engine/: - Delete src/utils/audio-convert.ts (pure re-export shell, zero consumers) - Move 5 test files from src/utils/ to src/engine/utils/ (fix import paths) - Move src/types/silk-wasm.d.ts to src/engine/types/ - Remove empty src/utils/ and src/types/ directories * refactor(qqbot): restructure API and bridge components for improved modularity This update enhances the QQBot framework by reorganizing the API and bridge components, promoting better modularity and maintainability. Key changes include: - Refactored import paths to streamline access to bridge tools and configurations. - Introduced new bridge files for channel entry, runtime, and approval capabilities, centralizing related functionalities. - Updated existing functions to utilize the new bridge structure, ensuring consistency across the codebase. - Removed deprecated functions and types, simplifying the overall architecture. These modifications aim to improve code clarity and facilitate future development within the QQBot ecosystem. * refactor(qqbot): standardize engine log levels and unify log tag prefix - Rename client.ts to api-client.ts to match ApiClient class name - Downgrade ~60 non-critical info logs to debug level across 12 files (token request/response, HTTP request/response, session restore, media tag detection, image classification, quote detection, attachment download/transcode, retry attempts, etc.) - Unify log tag prefix to [qqbot:xxx] format across all engine modules ([core-api] -> [qqbot:api], [token:x] -> [qqbot:token:x], [retry] -> [qqbot:retry], [messages] -> [qqbot:messages], [sender:x] -> [qqbot:x]) - Remove unnecessary reqTs timestamp from api-client.ts log output - Add dispatch event debug log in gateway-connection.ts - Merge sendProactiveMessage into sendText, remove dead code (sendProactiveText import, getRefIdx, QQMessageResult type) - Narrow allow-from.ts type from unknown[] to Array<string | number> * refactor(qqbot): move interaction handler from bridge to engine - Move onInteraction approval handler into engine/gateway.ts as createApprovalInteractionHandler(), eliminating the callback indirection through CoreGatewayContext - Remove onInteraction from CoreGatewayContext interface and its unused InteractionEvent import from gateway/types.ts - Remove getPlatformAdapter, parseApprovalButtonData and InteractionEvent imports from bridge/gateway.ts * refactor(qqbot): route bridge and sender logs through framework logger - Add bridge/logger.ts as a shared logger holder for bridge-layer modules, injected with ctx.log during gateway startup - Replace all console.log/console.error in bridge/ with getBridgeLogger() calls (approval, bootstrap, tools) - Restore framework logger support in sender.ts via initSender() so API-layer logs flow through OpenClaw log system - Remove all direct debugLog/debugError imports from bridge/ * feat(qqbot): per-account isolated resource stack + multi-account logger - sender.ts: global singletons (ApiClient/TokenManager/MediaApi) -> per-account AccountContext - Add _accountRegistry: Map<appId, AccountContext> - Each account owns independent client/tokenMgr/mediaApi/messageApi/logger - registerAccount() atomically sets up all resources - resolveAccount() routes to correct resource stack by appId - Remove _sharedLogger/_loggerRegistry/_appRegistry and old structures - bridge/gateway.ts: createAccountLogger() with auto [accountId] prefix - registerAccount() merges logger + markdownSupport + full API resources - engine-wide: remove ~60 manual [qqbot:${accountId}] log prefixes - Prefixes now auto-injected by per-account logger - Remove prefix/logPrefix parameter chains (outbound/outbound-deliver/typing-keepalive etc) * feat(qqbot): completes fallback path for approval with multi-account isolation When the execApprovals are not configured, multiple QQBot accounts' handlers will attempt to deliver the same approval message. The openid is account-level, and cross-account delivery will trigger a QQ Bot API 500 error. - Add account ownership verification in the fallback shouldHandle: Only match the account's handler when the request includes turnSourceAccountId; if unbound, delivery is only permitted when the number of enabled+secret accounts is ≤1. - Consolidate account ownership determination into the unified export `matchesQQBotApprovalAccount` in `exec-approvals.ts`, with both capability and native runtime paths sharing the same logic to eliminate redundancy. * feat(qqbot): optimize permission validation strategy * feat(qqbot): show plugin version in /bot-version and /bot-help Align /bot-version output with the standalone openclaw-qqbot build so users see both the QQBot plugin version and the OpenClaw framework version. Append the plugin version as a footer in /bot-help as well, matching the standalone UX. Also fix the plugin version lookup that previously rendered as 'vunknown': the old code used a hardcoded '../../package.json' relative path which resolved to 'src/package.json' (non-existent) when executed from raw sources, so the require threw and the default 'unknown' value was retained. The same broken value also leaked into the QQ Bot API User-Agent header. Replace the hardcoded path with a dedicated helper (bridge/plugin-version.ts) that walks up the directory tree from import.meta.url and validates the manifest's name field (@openclaw/qqbot) to avoid misreading the monorepo root package.json. Covered by 6 unit tests. * feat(qqbot): trust shared ~/.openclaw/media root for payload files Add getOpenClawMediaDir() and include it alongside getQQBotMediaDir() in the allowed roots of resolveQQBotPayloadLocalFilePath, so framework-produced attachments under sibling directories (e.g. media/outbound/ written by saveMediaBuffer) are trusted by auto-routed sends without triggering the path-outside-storage guard. Covered by a new test case that verifies files under ~/.openclaw/media/outbound/ resolve successfully. * fix(qqbot): ensure PlatformAdapter is registered before approval delivery After the framework centralized approval handler bootstrap (openclaw#62135), the native approval handler is spawned by the framework layer outside the qqbot gateway startAccount context. This means channel.ts's side-effect `import "./bridge/bootstrap.js"` may not have run, leaving PlatformAdapter unregistered when deliverPending calls resolveQQBotAccount -> getPlatformAdapter(). Extract ensurePlatformAdapter() from bootstrap.ts as an idempotent, re-entrant helper and call it in both capability.ts (load callback) and handler-runtime.ts (deliverPending entry) to guarantee the adapter is available regardless of initialization order. * fix(qqbot): add lazy factory for PlatformAdapter to eliminate import-order dependency The bundler splits qqbot code into multiple chunks where the adapter singleton and its consumers may live in different modules. When a consumer chunk evaluates before the bootstrap side-effect chunk, getPlatformAdapter() throws because the singleton is still null. Introduce registerPlatformAdapterFactory() in adapter/index.ts so getPlatformAdapter() can auto-initialize the adapter on first access. bootstrap.ts registers the factory at module evaluation time alongside the existing eager registration path. Also add error logging in downloadFile's catch block to surface fetch failures. * feat(qqbot): add /bot-approve slash command for exec approval config management Add /bot-approve command to the built-in QQBot plugin, ported from the standalone openclaw-qqbot implementation. This command allows users to manage tools.exec.security and tools.exec.ask settings directly from QQ. Supported sub-commands: /bot-approve on - allowlist + on-miss (recommended) /bot-approve off - full + off (no approval) /bot-approve always - allowlist + always (strict mode) /bot-approve reset - remove overrides, restore framework defaults /bot-approve status - show current security/ask values The runtime config API is injected via registerApproveRuntimeGetter() following the existing dependency injection pattern used by registerVersionResolver() and registerPluginVersion(). * fix(qqbot): ACK INTERACTION_CREATE events before processing approval buttons Send PUT /interactions/{id} immediately upon receiving any INTERACTION_CREATE event to prevent QQ from showing a timeout error to the user. The ACK is fire-and-forget and does not block subsequent approval button resolution. Also resolve merge conflict in pnpm-lock.yaml (keep @tencent-connect/qqbot-connector@1.1.0 and newer @thi.ng/bitstream@2.4.46). * feat(qqbot): enhance reminder functionality with delivery context and credential backup This update improves the QQBot reminder system by introducing a delivery context for reminders, allowing for more flexible target resolution. Key changes include: - Updated reminder logic to utilize a delivery envelope, ensuring that reminders are sent with the correct context. - Implemented credential backup and recovery mechanisms to prevent loss of appId and clientSecret during hot upgrades. - Added tests for credential backup functionality and admin resolver to ensure reliability. - Enhanced the remind tool to automatically resolve the target from the current conversation context when not explicitly provided. These enhancements aim to improve the user experience and reliability of the reminder feature within the QQBot framework. * fix(qqbot): ensure PlatformAdapter is registered before gateway message processing Call ensurePlatformAdapter() at the start of bridge/gateway.ts's startGateway() to guarantee the adapter is available when engine code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter(). When the bundler splits code into separate chunks, bootstrap.ts's module-level side-effect registration may not have executed yet by the time the gateway processes its first inbound attachment download. Also fix the TS2339 error in registerApproveRuntimeGetter by using getQQBotRuntime() (full PluginRuntime with config) instead of getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config). * fix(qqbot): make isAudioFile safe when OutboundAudioAdapter is not registered sendMedia() calls isAudioFile() as part of its media-type dispatch logic before any actual audio processing. When the audio adapter is not yet registered (e.g. framework tool calls sendMedia before gateway startup), isAudioFile() would throw 'OutboundAudioAdapter not registered' even for non-audio files like images. Wrap the getAudio() call in isAudioFile() with try/catch to return false when the adapter is unavailable, allowing non-audio media sends to proceed normally. * refactor(qqbot): remove plugin startup/upgrade greeting pipeline Drop the startup / upgrade greeting feature that was folded into the previous reminder + credential-backup commit. The pipeline has proven unnecessary for the fused build and its supporting admin-resolver scaffolding has no other consumers, so both are removed wholesale. - Delete engine/session/startup-greeting.ts and its tests: the first-launch "soul online" / "updated to vX.Y.Z" messages, the per-(accountId, appId) startup marker, the failure cooldown, and the legacy startup-marker.json migration path are all gone. - Delete engine/session/admin-resolver.ts and its tests: admin openid persistence/resolution, upgrade-greeting-target load/clear and the sendStartupGreetings dispatcher only ever served the greeting flow and were not referenced elsewhere. - channel.ts: drop the sendStartupGreetings import and the READY / RESUMED hooks that triggered greetings; credential-backup snapshots stay untouched. - engine/utils/data-paths.ts: remove getAdminMarkerFile / getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile / getStartupMarkerFile / getLegacyStartupMarkerFile along with the now-stale module docblock sections. Credential-backup helpers and safeName are preserved. Net -655 LOC across 6 files. tsc --noEmit passes on extensions/qqbot/tsconfig.json and no references to the removed symbols remain in the workspace. * fix(qqbot): resolve test failures in extension batch, contracts and bundled runtime deps - bootstrap: replace sync require() with static imports for secret-input and temp-path so vitest resolve.alias works correctly (require bypasses vitest aliases causing Cannot find module errors) - format: handle null/undefined in formatErrorMessage before JSON.stringify since JSON.stringify(undefined) returns JS undefined, not a string - gateway/types: reword comment to avoid triggering the channel-import guardrail regex that forbids quoted openclaw/plugin-sdk references - package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root dependencies as required by bundled plugin runtime dependency checks * chore: revert non-qqbot changes to align with upstream main Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories. * fix(qqbot): remove unused logUnsupportedStructuredMediaTarget after API simplification * fix(qqbot): restore channel-plugin-api.ts for bundled plugin surface convention * fix(qqbot): update CI lint allowlists for restructured engine paths - Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.) - Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts * fix(qqbot): eliminate os.tmpdir() in engine layer via adapter injection - Make hasPlatformAdapter() also check for registered factory, so adapter is always discoverable once bootstrap has run - Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(), delegate entirely to PlatformAdapter.getTempDir() which calls resolvePreferredOpenClawTmpDir() under the hood - Keeps engine/ layer free of openclaw/plugin-sdk imports * chore(qqbot): update CHANGELOG for engine architecture refactor (openclaw#67960) (thanks @cxyhhhhh) --------- Co-authored-by: Bobby <zkd8907@live.com> Co-authored-by: neilhwang <neilhwang@tencent.com> Co-authored-by: sliverp <870080352@qq.com>
… onboarding, approval handling (openclaw#67960) * feat(qqbot): add core architecture modules * feat(qqbot): extract engine modules with DI adapters * refactor(qqbot): remove plugin-level TTS, delegate to framework Remove qqbot's internal TTS implementation and unify voice synthesis through the framework's global TTS provider registry. - Delete engine/gateway/tts-config.ts (plugin-specific TTS config) - Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64 - Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback) - Strip QQBotTtsSchema from config-schema, plugin.json, and tests - Remove TTS diagnostics logging and hasTTS system prompt from gateway - Delete ~260 lines of TTS code from utils/audio-convert.ts Made-with: Cursor * feat(qqbot): extract shared engine modules for config, tools, and audio Add engine-layer modules that are self-contained and portable across both the built-in and standalone qqbot packages: - engine/config: account resolution helpers, field readers - engine/tools: channel API proxy, remind scheduling logic - engine/utils: audio format conversion, duration/error formatting, debug logging Consolidate duplicate utility functions across the codebase: - Merge debug-log.ts into log.ts - Merge error-format.ts into format.ts with full .cause chain support - Unify normalizeLowercase/readNumber/readBoolean/readStringMap into string-normalize.ts, removing private copies in resolve.ts, remind-logic.ts, and audio-convert.ts - Remove dead formatDuration export from audio-convert.ts - Delete unused config/schema.ts and config/helpers.ts Made-with: Cursor * refactor(qqbot): streamline account configuration and credential management Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include: - Migrate credential clearing and validation logic to engine/config/credentials.ts. - Simplify setup input validation and application in engine/config/setup-logic.ts. - Enhance account resolution and configuration application in engine/config/resolve.ts. - Update channel and messaging logic to utilize the new credential management functions. This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase. * feat(qqbot): simplify api architecture * feat: 支持扫码绑定QQ机器人 * feat(qqbot): refactor gateway into inbound pipeline + outbound dispatch - Extract handleMessage (620 lines) into three modules: - inbound-context.ts: InboundContext type definition - inbound-pipeline.ts: buildInboundContext() - outbound-dispatch.ts: dispatchOutbound() - gateway.ts handleMessage reduced to ~35 line shell - Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE - Add ref/format-message-ref.ts for cache-miss quote formatting - Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead - QueuedMessage: add msgType/msgElements for quote messages * fix(qqbot): fix markdownSupport loss + dynamic User-Agent Root cause: setOpenClawVersion() called _ensureInitialized(true) which cleared _appRegistry, destroying the MessageApi instance created by initApiConfig() with markdownSupport=true. Subsequent block deliver calls created a default markdownSupport=false instance, causing: 1. Markdown messages sent as plain text (msg_type=0 instead of 2) 2. message_reference incorrectly added (only suppressed in MD mode) Fix: ApiClient and TokenManager now accept userAgent as string | (() => string). sender.ts passes the buildUserAgent function reference, so UA changes propagate automatically on next request without rebuilding any objects. - ApiClient: userAgent -> resolveUserAgent getter, called per-request - TokenManager: same pattern - types.ts: ApiClientConfig.userAgent supports string | (() => string) - sender.ts: remove force re-init + _rebuildAppRegistry hack - initSender/setOpenClawVersion only update version variables - _ensureInitialized creates singletons once, never destroys them - _appRegistry is never cleared -> markdownSupport always preserved - runtime.ts: inject framework version via setOpenClawVersion(runtime.version) - gateway.ts: pass openclawVersion to initSender + registerPluginVersion - slash-commands-impl.ts: remove fragile require("../package.json") * feat(qqbot): implement native approval handling and configuration Add a new approval handling system for QQBot that integrates with the existing framework. Key features include: - Introduce `approval-handler.runtime.ts` for managing approval requests via QQ messages with inline keyboard support. - Create `approval-native.ts` as the entry point for QQBot's approval capability, allowing for simplified approval processes without explicit approver lists. - Implement configuration schema for exec approvals, enabling fine-grained control over who can approve requests. - Enhance messaging and interaction handling to support approval decisions through button interactions. This implementation streamlines the approval process, making it more user-friendly and efficient for QQBot users. * refactor(qqbot): enhance error handling across API and messaging modules This update introduces a centralized error formatting utility, `formatErrorMessage`, to improve consistency in error logging throughout the QQBot codebase. Key changes include: - Integration of `formatErrorMessage` in various API client, messaging, and gateway modules to standardize error messages. - Replacement of direct error message handling with the new utility to enhance readability and maintainability. These improvements streamline error reporting and provide clearer insights into issues encountered during operation. * refactor(qqbot): enhance API and messaging structure with type improvements This update refines the API and messaging modules by introducing type enhancements and restructuring function signatures for better clarity and maintainability. Key changes include: - Updated import statements to streamline type usage in and . - Refactored message sending functions to accept options objects, improving readability and flexibility. - Introduced a new method in to facilitate external message-sent notifications. - Enhanced error handling in the retry mechanism to ensure more robust behavior. These modifications aim to improve the overall code quality and developer experience within the QQBot framework. * feat: 优化文案 * refactor(qqbot): unify Logger interfaces + eliminate P0 code smells Logger unification (17 files): - Introduce single EngineLogger interface in engine/types.ts { info, error, warn?, debug? } - Delete 5 fragmented Logger interfaces: GatewayLogger, ReconnectLogger, MessageRefLogger, PathLogger, SenderLogger - Replace all references across engine/ to use EngineLogger directly P0 code smell fixes (sender.ts + messages.ts + outbound-dispatch.ts): - messages.ts: add public notifyMessageSent() method on MessageApi, replacing 8x 'as unknown as { messageSentHook }' private field hack - sender.ts: extract notifyMediaHook() helper, deduplicate 4 media send functions (sendImage/sendVoice/sendVideo/sendFile) - sender.ts: replace magic numbers 1/2/3/4 with MediaFileType enum - sender.ts: remove 4 redundant 'as MessageResponse' type assertions - outbound-dispatch.ts: remove 5 unnecessary 'as never' casts * feat(qqbot): add /bot-clear-storage command + consolidate utils/types into engine/ /bot-clear-storage (slash-commands-impl.ts): - Migrate from standalone version, aligned with its two-step flow: 1. No args: scan ~/.openclaw/media/qqbot/downloads/{appId}/ and display file list with confirmation button 2. --force: delete files + removeEmptyDirs cleanup - C2C only (group chat returns hint) - bot-help: exclude bot-upgrade and bot-clear-storage in group listings Consolidate into engine/: - Delete src/utils/audio-convert.ts (pure re-export shell, zero consumers) - Move 5 test files from src/utils/ to src/engine/utils/ (fix import paths) - Move src/types/silk-wasm.d.ts to src/engine/types/ - Remove empty src/utils/ and src/types/ directories * refactor(qqbot): restructure API and bridge components for improved modularity This update enhances the QQBot framework by reorganizing the API and bridge components, promoting better modularity and maintainability. Key changes include: - Refactored import paths to streamline access to bridge tools and configurations. - Introduced new bridge files for channel entry, runtime, and approval capabilities, centralizing related functionalities. - Updated existing functions to utilize the new bridge structure, ensuring consistency across the codebase. - Removed deprecated functions and types, simplifying the overall architecture. These modifications aim to improve code clarity and facilitate future development within the QQBot ecosystem. * refactor(qqbot): standardize engine log levels and unify log tag prefix - Rename client.ts to api-client.ts to match ApiClient class name - Downgrade ~60 non-critical info logs to debug level across 12 files (token request/response, HTTP request/response, session restore, media tag detection, image classification, quote detection, attachment download/transcode, retry attempts, etc.) - Unify log tag prefix to [qqbot:xxx] format across all engine modules ([core-api] -> [qqbot:api], [token:x] -> [qqbot:token:x], [retry] -> [qqbot:retry], [messages] -> [qqbot:messages], [sender:x] -> [qqbot:x]) - Remove unnecessary reqTs timestamp from api-client.ts log output - Add dispatch event debug log in gateway-connection.ts - Merge sendProactiveMessage into sendText, remove dead code (sendProactiveText import, getRefIdx, QQMessageResult type) - Narrow allow-from.ts type from unknown[] to Array<string | number> * refactor(qqbot): move interaction handler from bridge to engine - Move onInteraction approval handler into engine/gateway.ts as createApprovalInteractionHandler(), eliminating the callback indirection through CoreGatewayContext - Remove onInteraction from CoreGatewayContext interface and its unused InteractionEvent import from gateway/types.ts - Remove getPlatformAdapter, parseApprovalButtonData and InteractionEvent imports from bridge/gateway.ts * refactor(qqbot): route bridge and sender logs through framework logger - Add bridge/logger.ts as a shared logger holder for bridge-layer modules, injected with ctx.log during gateway startup - Replace all console.log/console.error in bridge/ with getBridgeLogger() calls (approval, bootstrap, tools) - Restore framework logger support in sender.ts via initSender() so API-layer logs flow through OpenClaw log system - Remove all direct debugLog/debugError imports from bridge/ * feat(qqbot): per-account isolated resource stack + multi-account logger - sender.ts: global singletons (ApiClient/TokenManager/MediaApi) -> per-account AccountContext - Add _accountRegistry: Map<appId, AccountContext> - Each account owns independent client/tokenMgr/mediaApi/messageApi/logger - registerAccount() atomically sets up all resources - resolveAccount() routes to correct resource stack by appId - Remove _sharedLogger/_loggerRegistry/_appRegistry and old structures - bridge/gateway.ts: createAccountLogger() with auto [accountId] prefix - registerAccount() merges logger + markdownSupport + full API resources - engine-wide: remove ~60 manual [qqbot:${accountId}] log prefixes - Prefixes now auto-injected by per-account logger - Remove prefix/logPrefix parameter chains (outbound/outbound-deliver/typing-keepalive etc) * feat(qqbot): completes fallback path for approval with multi-account isolation When the execApprovals are not configured, multiple QQBot accounts' handlers will attempt to deliver the same approval message. The openid is account-level, and cross-account delivery will trigger a QQ Bot API 500 error. - Add account ownership verification in the fallback shouldHandle: Only match the account's handler when the request includes turnSourceAccountId; if unbound, delivery is only permitted when the number of enabled+secret accounts is ≤1. - Consolidate account ownership determination into the unified export `matchesQQBotApprovalAccount` in `exec-approvals.ts`, with both capability and native runtime paths sharing the same logic to eliminate redundancy. * feat(qqbot): optimize permission validation strategy * feat(qqbot): show plugin version in /bot-version and /bot-help Align /bot-version output with the standalone openclaw-qqbot build so users see both the QQBot plugin version and the OpenClaw framework version. Append the plugin version as a footer in /bot-help as well, matching the standalone UX. Also fix the plugin version lookup that previously rendered as 'vunknown': the old code used a hardcoded '../../package.json' relative path which resolved to 'src/package.json' (non-existent) when executed from raw sources, so the require threw and the default 'unknown' value was retained. The same broken value also leaked into the QQ Bot API User-Agent header. Replace the hardcoded path with a dedicated helper (bridge/plugin-version.ts) that walks up the directory tree from import.meta.url and validates the manifest's name field (@openclaw/qqbot) to avoid misreading the monorepo root package.json. Covered by 6 unit tests. * feat(qqbot): trust shared ~/.openclaw/media root for payload files Add getOpenClawMediaDir() and include it alongside getQQBotMediaDir() in the allowed roots of resolveQQBotPayloadLocalFilePath, so framework-produced attachments under sibling directories (e.g. media/outbound/ written by saveMediaBuffer) are trusted by auto-routed sends without triggering the path-outside-storage guard. Covered by a new test case that verifies files under ~/.openclaw/media/outbound/ resolve successfully. * fix(qqbot): ensure PlatformAdapter is registered before approval delivery After the framework centralized approval handler bootstrap (openclaw#62135), the native approval handler is spawned by the framework layer outside the qqbot gateway startAccount context. This means channel.ts's side-effect `import "./bridge/bootstrap.js"` may not have run, leaving PlatformAdapter unregistered when deliverPending calls resolveQQBotAccount -> getPlatformAdapter(). Extract ensurePlatformAdapter() from bootstrap.ts as an idempotent, re-entrant helper and call it in both capability.ts (load callback) and handler-runtime.ts (deliverPending entry) to guarantee the adapter is available regardless of initialization order. * fix(qqbot): add lazy factory for PlatformAdapter to eliminate import-order dependency The bundler splits qqbot code into multiple chunks where the adapter singleton and its consumers may live in different modules. When a consumer chunk evaluates before the bootstrap side-effect chunk, getPlatformAdapter() throws because the singleton is still null. Introduce registerPlatformAdapterFactory() in adapter/index.ts so getPlatformAdapter() can auto-initialize the adapter on first access. bootstrap.ts registers the factory at module evaluation time alongside the existing eager registration path. Also add error logging in downloadFile's catch block to surface fetch failures. * feat(qqbot): add /bot-approve slash command for exec approval config management Add /bot-approve command to the built-in QQBot plugin, ported from the standalone openclaw-qqbot implementation. This command allows users to manage tools.exec.security and tools.exec.ask settings directly from QQ. Supported sub-commands: /bot-approve on - allowlist + on-miss (recommended) /bot-approve off - full + off (no approval) /bot-approve always - allowlist + always (strict mode) /bot-approve reset - remove overrides, restore framework defaults /bot-approve status - show current security/ask values The runtime config API is injected via registerApproveRuntimeGetter() following the existing dependency injection pattern used by registerVersionResolver() and registerPluginVersion(). * fix(qqbot): ACK INTERACTION_CREATE events before processing approval buttons Send PUT /interactions/{id} immediately upon receiving any INTERACTION_CREATE event to prevent QQ from showing a timeout error to the user. The ACK is fire-and-forget and does not block subsequent approval button resolution. Also resolve merge conflict in pnpm-lock.yaml (keep @tencent-connect/qqbot-connector@1.1.0 and newer @thi.ng/bitstream@2.4.46). * feat(qqbot): enhance reminder functionality with delivery context and credential backup This update improves the QQBot reminder system by introducing a delivery context for reminders, allowing for more flexible target resolution. Key changes include: - Updated reminder logic to utilize a delivery envelope, ensuring that reminders are sent with the correct context. - Implemented credential backup and recovery mechanisms to prevent loss of appId and clientSecret during hot upgrades. - Added tests for credential backup functionality and admin resolver to ensure reliability. - Enhanced the remind tool to automatically resolve the target from the current conversation context when not explicitly provided. These enhancements aim to improve the user experience and reliability of the reminder feature within the QQBot framework. * fix(qqbot): ensure PlatformAdapter is registered before gateway message processing Call ensurePlatformAdapter() at the start of bridge/gateway.ts's startGateway() to guarantee the adapter is available when engine code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter(). When the bundler splits code into separate chunks, bootstrap.ts's module-level side-effect registration may not have executed yet by the time the gateway processes its first inbound attachment download. Also fix the TS2339 error in registerApproveRuntimeGetter by using getQQBotRuntime() (full PluginRuntime with config) instead of getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config). * fix(qqbot): make isAudioFile safe when OutboundAudioAdapter is not registered sendMedia() calls isAudioFile() as part of its media-type dispatch logic before any actual audio processing. When the audio adapter is not yet registered (e.g. framework tool calls sendMedia before gateway startup), isAudioFile() would throw 'OutboundAudioAdapter not registered' even for non-audio files like images. Wrap the getAudio() call in isAudioFile() with try/catch to return false when the adapter is unavailable, allowing non-audio media sends to proceed normally. * refactor(qqbot): remove plugin startup/upgrade greeting pipeline Drop the startup / upgrade greeting feature that was folded into the previous reminder + credential-backup commit. The pipeline has proven unnecessary for the fused build and its supporting admin-resolver scaffolding has no other consumers, so both are removed wholesale. - Delete engine/session/startup-greeting.ts and its tests: the first-launch "soul online" / "updated to vX.Y.Z" messages, the per-(accountId, appId) startup marker, the failure cooldown, and the legacy startup-marker.json migration path are all gone. - Delete engine/session/admin-resolver.ts and its tests: admin openid persistence/resolution, upgrade-greeting-target load/clear and the sendStartupGreetings dispatcher only ever served the greeting flow and were not referenced elsewhere. - channel.ts: drop the sendStartupGreetings import and the READY / RESUMED hooks that triggered greetings; credential-backup snapshots stay untouched. - engine/utils/data-paths.ts: remove getAdminMarkerFile / getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile / getStartupMarkerFile / getLegacyStartupMarkerFile along with the now-stale module docblock sections. Credential-backup helpers and safeName are preserved. Net -655 LOC across 6 files. tsc --noEmit passes on extensions/qqbot/tsconfig.json and no references to the removed symbols remain in the workspace. * fix(qqbot): resolve test failures in extension batch, contracts and bundled runtime deps - bootstrap: replace sync require() with static imports for secret-input and temp-path so vitest resolve.alias works correctly (require bypasses vitest aliases causing Cannot find module errors) - format: handle null/undefined in formatErrorMessage before JSON.stringify since JSON.stringify(undefined) returns JS undefined, not a string - gateway/types: reword comment to avoid triggering the channel-import guardrail regex that forbids quoted openclaw/plugin-sdk references - package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root dependencies as required by bundled plugin runtime dependency checks * chore: revert non-qqbot changes to align with upstream main Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories. * fix(qqbot): remove unused logUnsupportedStructuredMediaTarget after API simplification * fix(qqbot): restore channel-plugin-api.ts for bundled plugin surface convention * fix(qqbot): update CI lint allowlists for restructured engine paths - Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.) - Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts * fix(qqbot): eliminate os.tmpdir() in engine layer via adapter injection - Make hasPlatformAdapter() also check for registered factory, so adapter is always discoverable once bootstrap has run - Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(), delegate entirely to PlatformAdapter.getTempDir() which calls resolvePreferredOpenClawTmpDir() under the hood - Keeps engine/ layer free of openclaw/plugin-sdk imports * chore(qqbot): update CHANGELOG for engine architecture refactor (openclaw#67960) (thanks @cxyhhhhh) --------- Co-authored-by: Bobby <zkd8907@live.com> Co-authored-by: neilhwang <neilhwang@tencent.com> Co-authored-by: sliverp <870080352@qq.com>
… onboarding, approval handling (openclaw#67960) * feat(qqbot): add core architecture modules * feat(qqbot): extract engine modules with DI adapters * refactor(qqbot): remove plugin-level TTS, delegate to framework Remove qqbot's internal TTS implementation and unify voice synthesis through the framework's global TTS provider registry. - Delete engine/gateway/tts-config.ts (plugin-specific TTS config) - Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64 - Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback) - Strip QQBotTtsSchema from config-schema, plugin.json, and tests - Remove TTS diagnostics logging and hasTTS system prompt from gateway - Delete ~260 lines of TTS code from utils/audio-convert.ts Made-with: Cursor * feat(qqbot): extract shared engine modules for config, tools, and audio Add engine-layer modules that are self-contained and portable across both the built-in and standalone qqbot packages: - engine/config: account resolution helpers, field readers - engine/tools: channel API proxy, remind scheduling logic - engine/utils: audio format conversion, duration/error formatting, debug logging Consolidate duplicate utility functions across the codebase: - Merge debug-log.ts into log.ts - Merge error-format.ts into format.ts with full .cause chain support - Unify normalizeLowercase/readNumber/readBoolean/readStringMap into string-normalize.ts, removing private copies in resolve.ts, remind-logic.ts, and audio-convert.ts - Remove dead formatDuration export from audio-convert.ts - Delete unused config/schema.ts and config/helpers.ts Made-with: Cursor * refactor(qqbot): streamline account configuration and credential management Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include: - Migrate credential clearing and validation logic to engine/config/credentials.ts. - Simplify setup input validation and application in engine/config/setup-logic.ts. - Enhance account resolution and configuration application in engine/config/resolve.ts. - Update channel and messaging logic to utilize the new credential management functions. This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase. * feat(qqbot): simplify api architecture * feat: 支持扫码绑定QQ机器人 * feat(qqbot): refactor gateway into inbound pipeline + outbound dispatch - Extract handleMessage (620 lines) into three modules: - inbound-context.ts: InboundContext type definition - inbound-pipeline.ts: buildInboundContext() - outbound-dispatch.ts: dispatchOutbound() - gateway.ts handleMessage reduced to ~35 line shell - Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE - Add ref/format-message-ref.ts for cache-miss quote formatting - Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead - QueuedMessage: add msgType/msgElements for quote messages * fix(qqbot): fix markdownSupport loss + dynamic User-Agent Root cause: setOpenClawVersion() called _ensureInitialized(true) which cleared _appRegistry, destroying the MessageApi instance created by initApiConfig() with markdownSupport=true. Subsequent block deliver calls created a default markdownSupport=false instance, causing: 1. Markdown messages sent as plain text (msg_type=0 instead of 2) 2. message_reference incorrectly added (only suppressed in MD mode) Fix: ApiClient and TokenManager now accept userAgent as string | (() => string). sender.ts passes the buildUserAgent function reference, so UA changes propagate automatically on next request without rebuilding any objects. - ApiClient: userAgent -> resolveUserAgent getter, called per-request - TokenManager: same pattern - types.ts: ApiClientConfig.userAgent supports string | (() => string) - sender.ts: remove force re-init + _rebuildAppRegistry hack - initSender/setOpenClawVersion only update version variables - _ensureInitialized creates singletons once, never destroys them - _appRegistry is never cleared -> markdownSupport always preserved - runtime.ts: inject framework version via setOpenClawVersion(runtime.version) - gateway.ts: pass openclawVersion to initSender + registerPluginVersion - slash-commands-impl.ts: remove fragile require("../package.json") * feat(qqbot): implement native approval handling and configuration Add a new approval handling system for QQBot that integrates with the existing framework. Key features include: - Introduce `approval-handler.runtime.ts` for managing approval requests via QQ messages with inline keyboard support. - Create `approval-native.ts` as the entry point for QQBot's approval capability, allowing for simplified approval processes without explicit approver lists. - Implement configuration schema for exec approvals, enabling fine-grained control over who can approve requests. - Enhance messaging and interaction handling to support approval decisions through button interactions. This implementation streamlines the approval process, making it more user-friendly and efficient for QQBot users. * refactor(qqbot): enhance error handling across API and messaging modules This update introduces a centralized error formatting utility, `formatErrorMessage`, to improve consistency in error logging throughout the QQBot codebase. Key changes include: - Integration of `formatErrorMessage` in various API client, messaging, and gateway modules to standardize error messages. - Replacement of direct error message handling with the new utility to enhance readability and maintainability. These improvements streamline error reporting and provide clearer insights into issues encountered during operation. * refactor(qqbot): enhance API and messaging structure with type improvements This update refines the API and messaging modules by introducing type enhancements and restructuring function signatures for better clarity and maintainability. Key changes include: - Updated import statements to streamline type usage in and . - Refactored message sending functions to accept options objects, improving readability and flexibility. - Introduced a new method in to facilitate external message-sent notifications. - Enhanced error handling in the retry mechanism to ensure more robust behavior. These modifications aim to improve the overall code quality and developer experience within the QQBot framework. * feat: 优化文案 * refactor(qqbot): unify Logger interfaces + eliminate P0 code smells Logger unification (17 files): - Introduce single EngineLogger interface in engine/types.ts { info, error, warn?, debug? } - Delete 5 fragmented Logger interfaces: GatewayLogger, ReconnectLogger, MessageRefLogger, PathLogger, SenderLogger - Replace all references across engine/ to use EngineLogger directly P0 code smell fixes (sender.ts + messages.ts + outbound-dispatch.ts): - messages.ts: add public notifyMessageSent() method on MessageApi, replacing 8x 'as unknown as { messageSentHook }' private field hack - sender.ts: extract notifyMediaHook() helper, deduplicate 4 media send functions (sendImage/sendVoice/sendVideo/sendFile) - sender.ts: replace magic numbers 1/2/3/4 with MediaFileType enum - sender.ts: remove 4 redundant 'as MessageResponse' type assertions - outbound-dispatch.ts: remove 5 unnecessary 'as never' casts * feat(qqbot): add /bot-clear-storage command + consolidate utils/types into engine/ /bot-clear-storage (slash-commands-impl.ts): - Migrate from standalone version, aligned with its two-step flow: 1. No args: scan ~/.openclaw/media/qqbot/downloads/{appId}/ and display file list with confirmation button 2. --force: delete files + removeEmptyDirs cleanup - C2C only (group chat returns hint) - bot-help: exclude bot-upgrade and bot-clear-storage in group listings Consolidate into engine/: - Delete src/utils/audio-convert.ts (pure re-export shell, zero consumers) - Move 5 test files from src/utils/ to src/engine/utils/ (fix import paths) - Move src/types/silk-wasm.d.ts to src/engine/types/ - Remove empty src/utils/ and src/types/ directories * refactor(qqbot): restructure API and bridge components for improved modularity This update enhances the QQBot framework by reorganizing the API and bridge components, promoting better modularity and maintainability. Key changes include: - Refactored import paths to streamline access to bridge tools and configurations. - Introduced new bridge files for channel entry, runtime, and approval capabilities, centralizing related functionalities. - Updated existing functions to utilize the new bridge structure, ensuring consistency across the codebase. - Removed deprecated functions and types, simplifying the overall architecture. These modifications aim to improve code clarity and facilitate future development within the QQBot ecosystem. * refactor(qqbot): standardize engine log levels and unify log tag prefix - Rename client.ts to api-client.ts to match ApiClient class name - Downgrade ~60 non-critical info logs to debug level across 12 files (token request/response, HTTP request/response, session restore, media tag detection, image classification, quote detection, attachment download/transcode, retry attempts, etc.) - Unify log tag prefix to [qqbot:xxx] format across all engine modules ([core-api] -> [qqbot:api], [token:x] -> [qqbot:token:x], [retry] -> [qqbot:retry], [messages] -> [qqbot:messages], [sender:x] -> [qqbot:x]) - Remove unnecessary reqTs timestamp from api-client.ts log output - Add dispatch event debug log in gateway-connection.ts - Merge sendProactiveMessage into sendText, remove dead code (sendProactiveText import, getRefIdx, QQMessageResult type) - Narrow allow-from.ts type from unknown[] to Array<string | number> * refactor(qqbot): move interaction handler from bridge to engine - Move onInteraction approval handler into engine/gateway.ts as createApprovalInteractionHandler(), eliminating the callback indirection through CoreGatewayContext - Remove onInteraction from CoreGatewayContext interface and its unused InteractionEvent import from gateway/types.ts - Remove getPlatformAdapter, parseApprovalButtonData and InteractionEvent imports from bridge/gateway.ts * refactor(qqbot): route bridge and sender logs through framework logger - Add bridge/logger.ts as a shared logger holder for bridge-layer modules, injected with ctx.log during gateway startup - Replace all console.log/console.error in bridge/ with getBridgeLogger() calls (approval, bootstrap, tools) - Restore framework logger support in sender.ts via initSender() so API-layer logs flow through OpenClaw log system - Remove all direct debugLog/debugError imports from bridge/ * feat(qqbot): per-account isolated resource stack + multi-account logger - sender.ts: global singletons (ApiClient/TokenManager/MediaApi) -> per-account AccountContext - Add _accountRegistry: Map<appId, AccountContext> - Each account owns independent client/tokenMgr/mediaApi/messageApi/logger - registerAccount() atomically sets up all resources - resolveAccount() routes to correct resource stack by appId - Remove _sharedLogger/_loggerRegistry/_appRegistry and old structures - bridge/gateway.ts: createAccountLogger() with auto [accountId] prefix - registerAccount() merges logger + markdownSupport + full API resources - engine-wide: remove ~60 manual [qqbot:${accountId}] log prefixes - Prefixes now auto-injected by per-account logger - Remove prefix/logPrefix parameter chains (outbound/outbound-deliver/typing-keepalive etc) * feat(qqbot): completes fallback path for approval with multi-account isolation When the execApprovals are not configured, multiple QQBot accounts' handlers will attempt to deliver the same approval message. The openid is account-level, and cross-account delivery will trigger a QQ Bot API 500 error. - Add account ownership verification in the fallback shouldHandle: Only match the account's handler when the request includes turnSourceAccountId; if unbound, delivery is only permitted when the number of enabled+secret accounts is ≤1. - Consolidate account ownership determination into the unified export `matchesQQBotApprovalAccount` in `exec-approvals.ts`, with both capability and native runtime paths sharing the same logic to eliminate redundancy. * feat(qqbot): optimize permission validation strategy * feat(qqbot): show plugin version in /bot-version and /bot-help Align /bot-version output with the standalone openclaw-qqbot build so users see both the QQBot plugin version and the OpenClaw framework version. Append the plugin version as a footer in /bot-help as well, matching the standalone UX. Also fix the plugin version lookup that previously rendered as 'vunknown': the old code used a hardcoded '../../package.json' relative path which resolved to 'src/package.json' (non-existent) when executed from raw sources, so the require threw and the default 'unknown' value was retained. The same broken value also leaked into the QQ Bot API User-Agent header. Replace the hardcoded path with a dedicated helper (bridge/plugin-version.ts) that walks up the directory tree from import.meta.url and validates the manifest's name field (@openclaw/qqbot) to avoid misreading the monorepo root package.json. Covered by 6 unit tests. * feat(qqbot): trust shared ~/.openclaw/media root for payload files Add getOpenClawMediaDir() and include it alongside getQQBotMediaDir() in the allowed roots of resolveQQBotPayloadLocalFilePath, so framework-produced attachments under sibling directories (e.g. media/outbound/ written by saveMediaBuffer) are trusted by auto-routed sends without triggering the path-outside-storage guard. Covered by a new test case that verifies files under ~/.openclaw/media/outbound/ resolve successfully. * fix(qqbot): ensure PlatformAdapter is registered before approval delivery After the framework centralized approval handler bootstrap (openclaw#62135), the native approval handler is spawned by the framework layer outside the qqbot gateway startAccount context. This means channel.ts's side-effect `import "./bridge/bootstrap.js"` may not have run, leaving PlatformAdapter unregistered when deliverPending calls resolveQQBotAccount -> getPlatformAdapter(). Extract ensurePlatformAdapter() from bootstrap.ts as an idempotent, re-entrant helper and call it in both capability.ts (load callback) and handler-runtime.ts (deliverPending entry) to guarantee the adapter is available regardless of initialization order. * fix(qqbot): add lazy factory for PlatformAdapter to eliminate import-order dependency The bundler splits qqbot code into multiple chunks where the adapter singleton and its consumers may live in different modules. When a consumer chunk evaluates before the bootstrap side-effect chunk, getPlatformAdapter() throws because the singleton is still null. Introduce registerPlatformAdapterFactory() in adapter/index.ts so getPlatformAdapter() can auto-initialize the adapter on first access. bootstrap.ts registers the factory at module evaluation time alongside the existing eager registration path. Also add error logging in downloadFile's catch block to surface fetch failures. * feat(qqbot): add /bot-approve slash command for exec approval config management Add /bot-approve command to the built-in QQBot plugin, ported from the standalone openclaw-qqbot implementation. This command allows users to manage tools.exec.security and tools.exec.ask settings directly from QQ. Supported sub-commands: /bot-approve on - allowlist + on-miss (recommended) /bot-approve off - full + off (no approval) /bot-approve always - allowlist + always (strict mode) /bot-approve reset - remove overrides, restore framework defaults /bot-approve status - show current security/ask values The runtime config API is injected via registerApproveRuntimeGetter() following the existing dependency injection pattern used by registerVersionResolver() and registerPluginVersion(). * fix(qqbot): ACK INTERACTION_CREATE events before processing approval buttons Send PUT /interactions/{id} immediately upon receiving any INTERACTION_CREATE event to prevent QQ from showing a timeout error to the user. The ACK is fire-and-forget and does not block subsequent approval button resolution. Also resolve merge conflict in pnpm-lock.yaml (keep @tencent-connect/qqbot-connector@1.1.0 and newer @thi.ng/bitstream@2.4.46). * feat(qqbot): enhance reminder functionality with delivery context and credential backup This update improves the QQBot reminder system by introducing a delivery context for reminders, allowing for more flexible target resolution. Key changes include: - Updated reminder logic to utilize a delivery envelope, ensuring that reminders are sent with the correct context. - Implemented credential backup and recovery mechanisms to prevent loss of appId and clientSecret during hot upgrades. - Added tests for credential backup functionality and admin resolver to ensure reliability. - Enhanced the remind tool to automatically resolve the target from the current conversation context when not explicitly provided. These enhancements aim to improve the user experience and reliability of the reminder feature within the QQBot framework. * fix(qqbot): ensure PlatformAdapter is registered before gateway message processing Call ensurePlatformAdapter() at the start of bridge/gateway.ts's startGateway() to guarantee the adapter is available when engine code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter(). When the bundler splits code into separate chunks, bootstrap.ts's module-level side-effect registration may not have executed yet by the time the gateway processes its first inbound attachment download. Also fix the TS2339 error in registerApproveRuntimeGetter by using getQQBotRuntime() (full PluginRuntime with config) instead of getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config). * fix(qqbot): make isAudioFile safe when OutboundAudioAdapter is not registered sendMedia() calls isAudioFile() as part of its media-type dispatch logic before any actual audio processing. When the audio adapter is not yet registered (e.g. framework tool calls sendMedia before gateway startup), isAudioFile() would throw 'OutboundAudioAdapter not registered' even for non-audio files like images. Wrap the getAudio() call in isAudioFile() with try/catch to return false when the adapter is unavailable, allowing non-audio media sends to proceed normally. * refactor(qqbot): remove plugin startup/upgrade greeting pipeline Drop the startup / upgrade greeting feature that was folded into the previous reminder + credential-backup commit. The pipeline has proven unnecessary for the fused build and its supporting admin-resolver scaffolding has no other consumers, so both are removed wholesale. - Delete engine/session/startup-greeting.ts and its tests: the first-launch "soul online" / "updated to vX.Y.Z" messages, the per-(accountId, appId) startup marker, the failure cooldown, and the legacy startup-marker.json migration path are all gone. - Delete engine/session/admin-resolver.ts and its tests: admin openid persistence/resolution, upgrade-greeting-target load/clear and the sendStartupGreetings dispatcher only ever served the greeting flow and were not referenced elsewhere. - channel.ts: drop the sendStartupGreetings import and the READY / RESUMED hooks that triggered greetings; credential-backup snapshots stay untouched. - engine/utils/data-paths.ts: remove getAdminMarkerFile / getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile / getStartupMarkerFile / getLegacyStartupMarkerFile along with the now-stale module docblock sections. Credential-backup helpers and safeName are preserved. Net -655 LOC across 6 files. tsc --noEmit passes on extensions/qqbot/tsconfig.json and no references to the removed symbols remain in the workspace. * fix(qqbot): resolve test failures in extension batch, contracts and bundled runtime deps - bootstrap: replace sync require() with static imports for secret-input and temp-path so vitest resolve.alias works correctly (require bypasses vitest aliases causing Cannot find module errors) - format: handle null/undefined in formatErrorMessage before JSON.stringify since JSON.stringify(undefined) returns JS undefined, not a string - gateway/types: reword comment to avoid triggering the channel-import guardrail regex that forbids quoted openclaw/plugin-sdk references - package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root dependencies as required by bundled plugin runtime dependency checks * chore: revert non-qqbot changes to align with upstream main Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories. * fix(qqbot): remove unused logUnsupportedStructuredMediaTarget after API simplification * fix(qqbot): restore channel-plugin-api.ts for bundled plugin surface convention * fix(qqbot): update CI lint allowlists for restructured engine paths - Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.) - Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts * fix(qqbot): eliminate os.tmpdir() in engine layer via adapter injection - Make hasPlatformAdapter() also check for registered factory, so adapter is always discoverable once bootstrap has run - Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(), delegate entirely to PlatformAdapter.getTempDir() which calls resolvePreferredOpenClawTmpDir() under the hood - Keeps engine/ layer free of openclaw/plugin-sdk imports * chore(qqbot): update CHANGELOG for engine architecture refactor (openclaw#67960) (thanks @cxyhhhhh) --------- Co-authored-by: Bobby <zkd8907@live.com> Co-authored-by: neilhwang <neilhwang@tencent.com> Co-authored-by: sliverp <870080352@qq.com>
Summary
extensions/qqbot) had all gateway logic in a single 1500+ line monolith file tightly coupled to the framework plugin-sdk, with fragmented Logger interfaces (6 duplicate definitions) and missing slash commands compared to the standalone version.engine/package (63 modules, zeroopenclaw/plugin-sdkimports) with 7 DI injection points for framework decouplinggateway.tsinto inbound pipeline + outbound dispatch + independent API/messaging/session/ref layersEngineLogger/bot-clear-storageslash command (migrated from standalone)src/utils/andsrc/types/intoengine/openclaw-qqbotuntouched; frameworksrc/plugin-sdkuntouched; other channel plugins unaffected.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
N/A
Regression Test Plan (if applicable)
engine/—audio.test,file-utils.test,format.test,image-size.test,media-tags.test,platform.test,text-parsing.test,config/resolve.test,approval/index.test,tools/remind-logic.testUser-visible / Behavior Changes
/bot-clear-storageslash command added (scan and clean QQBot download files, C2C only)/bot-helpno longer shows C2C-only commands (bot-upgrade,bot-clear-storage) in group chatsOpenClaw/unknown→OpenClaw/{version}Diagram (if applicable)
Security Impact (required)
NoNo(token management moved to engine/api/token.ts, behavior unchanged)NoYes— added/bot-clear-storage(deletes local download files only, C2C-gated)NoYes, explain risk + mitigation:/bot-clear-storageonly deletes files under~/.openclaw/media/qqbot/downloads/{appId}/, scoped to the current bot's appId. Two-step confirmation flow (scan →--forceconfirm). C2C only.Repro + Verification
Environment
Steps
/bot-clear-storagein C2C chat--forcedeletion flow/bot-helpin group chat, verifybot-clear-storageis excludedExpected
/bot-clear-storagescans and lists files with confirmation button/bot-clear-storage --forcedeletes files and reports results/bot-helpin group excludes C2C-only commandsActual
Evidence
/bot-clear-storagemanually verified in C2C conversationQQBotPlugin/{ver} (Node/{ver}; {os}; OpenClaw/{ver})Human Verification (required)
/bot-clear-storagescan + force delete;/bot-helpgroup exclusion; User-Agent format; approval button handlingReview Conversations
Compatibility / Migration
YesNoNoRisks and Mitigations
/bot-clear-storageaccidental file deletion~/.openclaw/media/qqbot/downloads/{appId}/only; two-step confirmation flow; C2C restriction