Skip to content

Feature/add qq channel#52986

Merged
frankekn merged 36 commits intoopenclaw:mainfrom
sliverp:feature/add-qq-channel
Mar 31, 2026
Merged

Feature/add qq channel#52986
frankekn merged 36 commits intoopenclaw:mainfrom
sliverp:feature/add-qq-channel

Conversation

@sliverp
Copy link
Copy Markdown
Contributor

@sliverp sliverp commented Mar 23, 2026

Summary

  • Problem: OpenClaw lacks QQ Bot channel support; users cannot connect to QQ via the official QQ Bot API.
  • Why it matters: QQ is a major messaging platform; adding native channel support expands OpenClaw's reach to QQ users for both group and direct messaging.
  • What changed: Added a complete QQ Bot channel extension (extensions/qqbot/) with gateway, messaging, slash commands, session management, tools, setup wizard, and registered it in bundled plugin metadata.
  • What did NOT change (scope boundary): No modifications to core plugin loader, setup registry, or onboard flow logic. Changes are confined to the new extension and its registration points.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • [] Gateway / orchestration
  • [] Skills / tool execution
  • Auth / tokens
  • [] Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #

User-visible / Behavior Changes

  • New "QQ Bot" option appears in the openclaw onboard channel selection list.
  • Guided setup wizard prompts for AppID and AppSecret separately.
  • Supports QQBOT_APP_ID / QQBOT_CLIENT_SECRET environment variable auto-detection.
  • Runtime support for QQ group chat and direct message send/receive, slash commands, image/audio media, proactive messaging, and session management.
  • Three new skills added: qqbot-channel, qqbot-media, qqbot-remind.

Security Impact (required)

  • New permissions/capabilities? Yes — new QQ Bot API network calls
  • Secrets/tokens handling changed? Yes — new AppID/AppSecret credential storage
  • New/changed network calls? Yes — communicates with QQ Open Platform API
  • Command/tool execution surface changed? Yes — new channel/remind tools
  • Data access scope changed? No
  • Risk + mitigation: AppID/AppSecret are managed via the standard credential storage flow (same as telegram/discord). Network calls target only official QQ API endpoints. Tools follow the existing channel tool security model with no privilege escalation.

Repro + Verification

Environment

  • OS: Linux
  • Runtime/container: Node.js
  • Model/provider: N/A
  • Integration/channel: QQ Bot
  • Relevant config: channels.qqbot.appId + channels.qqbot.clientSecret

Steps

  1. Run openclaw onboard
  2. Select channel → "QQ Bot"
  3. Follow the setup wizard to enter AppID and AppSecret
  4. Start OpenClaw and send a message via QQ

Expected

  • Setup wizard guides through AppID and AppSecret input; QQ Bot gateway connects and handles messages.

Actual

  • As expected after this change.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: Confirmed qqbotPlugin in channel.ts includes setupWizard; verified registration in bundled.ts and bundled-plugin-metadata.generated.ts; cross-referenced with telegram/line/signal for structural consistency.
  • Edge cases checked: Verified both "full" and "setup-only" registration modes resolve setupWizard correctly; confirmed bundledChannelSetupPlugins fallback path includes qqbot.
  • What you did not verify: End-to-end runtime test with a live QQ Bot account; multi-account scenario.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes — additive extension, no impact on existing channels
  • Config/env changes? Yes — new optional channels.qqbot.* config section and QQBOT_APP_ID/QQBOT_CLIENT_SECRET env vars
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert the commits, or simply do not configure channels.qqbot — the plugin will not load.
  • Files/config to restore: src/channels/plugins/bundled.ts, src/plugins/bundled-plugin-metadata.generated.ts to pre-qqbot versions.
  • Known bad symptoms reviewers should watch for: QQ Bot option missing from onboard list; gateway connection failures without graceful degradation.

Risks and Mitigations

  • Risk: QQ Open Platform API changes could break gateway connectivity.
    • Mitigation: Gateway includes reconnection and error handling; API calls are isolated in api.ts for easy adaptation.
  • Risk: New extension increases bundle size for users who don't need QQ Bot.
    • Mitigation: Plugin is disabled by default and only loaded when explicitly configured or selected during onboard.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 23, 2026

Greptile Summary

This PR adds a complete QQ Bot channel extension to OpenClaw, enabling group chat and direct messaging via the official QQ Open Platform API. The implementation follows the existing patterns used by Telegram, Discord, Line, and Signal: a setup-only plugin for openclaw onboard, a full runtime plugin for message gateway/delivery, credential backup/recovery, a local image server, WebSocket gateway with reconnection, session persistence, slash commands, TTS integration, and three new skills.

The structural integration (registration in bundled.ts and bundled-plugin-metadata.generated.ts) is clean and additive with no changes to existing channels.

Issues found:

  • Token parsing silently discards credentials when the AppSecret contains a colon. The split(":") + parts.length === 2 guard in applyAccountConfig (both channel.ts:132–138 and channel.setup.ts:89–94) leaves appId and clientSecret as empty strings without any error or warning, causing a confusing late failure in the gateway. Fix: split only on the first colon using indexOf/slice.

  • Image server fallback URL uses 0.0.0.0. When ensureImageServer is called after the server is already running and no publicBaseUrl is configured, it returns http://0.0.0.0:PORT (gateway.ts:112 and :120). This address is not externally routable, so QQ servers will fail to fetch images sent through this path. The function should return null (disabling image serving) rather than an unroutable URL.

  • Unconditional console.log calls throughout the hot path (channel.ts, gateway.ts, and several other files) emit message-content previews and account metadata to stdout in production. These should be guarded behind the structured log abstraction or a debug flag.

Confidence Score: 3/5

  • Mostly safe to merge — setup wizard and gateway structure are solid — but the colon-splitting token bug and unroutable image-server URL are concrete functional defects worth fixing before shipping.
  • Two P1 logic bugs: (1) the appId:secret token parser silently fails when the AppSecret contains a colon, leaving users with an empty configuration and a confusing late-stage error; (2) the image-server fallback returns http://0.0.0.0:PORT as an external URL, which QQ servers cannot reach. Both affect realistic usage scenarios. The P2 console.log noise is a quality concern but not blocking.
  • extensions/qqbot/src/channel.ts and extensions/qqbot/src/channel.setup.ts (token parsing), extensions/qqbot/src/gateway.ts (image server URL fallback)
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/qqbot/src/channel.ts
Line: 232-247

Comment:
**Production debug logs throughout hot path**

Several `console.log` calls here (and in `gateway.ts`, `api.ts`, and many other files) emit message content previews, account IDs, and full error details directly to stdout in production. This pattern appears across the entire extension (e.g., `channel.ts:98-101`, `channel.ts:241-243`, `channel.ts:255-257`, and extensively throughout `gateway.ts`).

Beyond log noise, the message-content preview at line 237 (`text?.slice(0, 100)`) will surface portions of potentially private user messages in process output.

Consider routing all debug-level output through the structured `log` abstraction provided in the gateway/channel context (which already uses `log?.info(...)` / `log?.error(...)`), or guarding them behind a debug flag, rather than calling `console.log` unconditionally.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/qqbot/src/gateway.ts
Line: 111-112

Comment:
**Image server returns unroutable `0.0.0.0` URL when already running**

When `isImageServerRunning()` is true (a second account connecting, or a reconnect), the function returns `http://0.0.0.0:${IMAGE_SERVER_PORT}` as the `baseUrl`. This address is not a valid external URL — `0.0.0.0` is not reachable from the QQ Open Platform servers, so any image message sent using this URL will silently fail.

The same placeholder also appears below at line 120 for the initial start path.

The fallback should be `localhost` (for same-machine QQ Bot setups) or the function should require a configured `imageServerBaseUrl` before enabling image serving. At minimum, a log warning should be emitted when no `publicBaseUrl` is available:

```suggestion
    return publicBaseUrl ?? null;
```

This way the caller treats a missing public URL the same as "image server not available" rather than constructing broken image URLs.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/qqbot/src/channel.ts
Line: 132-138

Comment:
**Silent token parsing failure on colons in AppSecret**

The token is split by `:` and the guard `parts.length === 2` means any AppSecret that contains a colon (common in base64-encoded or UUID-style secrets) causes both `appId` and the secret to silently remain empty strings. The function still calls `applyQQBotAccountConfig` with those empty values, so the configuration appears to succeed but the gateway will fail to start later with a confusing "missing appId or clientSecret" error.

The same bug exists at the identical block in `extensions/qqbot/src/channel.setup.ts:89-94`.

A safe fix is to split only on the **first** colon (using `indexOf`/`slice`) instead of `split(":")`, so secrets that contain colons are handled correctly.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: fix review" | Re-trigger Greptile

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 08e4026bc9

ℹ️ 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".

@openclaw-barnacle openclaw-barnacle bot added the docs Improvements or additions to documentation label Mar 23, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a1ca15f67

ℹ️ 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b4f8aff18

ℹ️ 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".

@sliverp sliverp force-pushed the feature/add-qq-channel branch from 7b4f8af to 9ee69e5 Compare March 24, 2026 09:37
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ee69e52b4

ℹ️ 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".

@frankekn
Copy link
Copy Markdown
Contributor

@codex review

@frankekn
Copy link
Copy Markdown
Contributor

Thanks for the work here. Moving the QQ Bot plugin in-tree is directionally fine, but I can't move this to /prepare-pr yet because there are a few merge blockers that need to be fixed first:

  1. Docs path mismatch
    extensions/qqbot/src/channel.ts:47 and extensions/qqbot/src/channel.setup.ts:28 use /docs/channels/qqbot, but channel plugin contracts expect /channels/... and the package manifest already advertises /channels/qqbot.

  2. Config schema is effectively missing
    extensions/qqbot/openclaw.plugin.json:4-7 declares an empty configSchema, but the plugin reads and writes a substantial channels.qqbot config surface (appId, clientSecret, allowFrom, audioFormatPolicy, upgradeMode, etc.). If this is landing as a bundled first-party plugin, the manifest needs to describe the real config contract.

  3. First-party migration is incomplete
    The bundled package/update metadata uses @openclaw/qqbot, but the slash-command and upgrade flow still hardcode the old external identity (tencent-connect/openclaw-qqbot / openclaw-qqbot) in multiple places, for example:

    • extensions/qqbot/src/slash-commands.ts:325
    • extensions/qqbot/src/slash-commands.ts:525-543
    • extensions/qqbot/src/slash-commands.ts:615
    • extensions/qqbot/src/slash-commands.ts:941
  4. Secret persistence needs to be revisited before bundling
    extensions/qqbot/src/credential-backup.ts writes clientSecret to a new plaintext backup file under ~/.openclaw/qqbot/data/credential-backup.json, and extensions/qqbot/src/channel.ts:263-282 auto-restores from it. We should not land a new extra on-disk secret copy like this without aligning it to OpenClaw's normal credential handling.

  5. Missing merge-readiness basics for a bundled channel
    This PR adds a large new bundled plugin surface, but I don't see targeted tests for the new behavior and I don't see a changelog update.

Also, the branch is behind current main, so please rebase/refresh once the blockers above are addressed.

Once those are fixed, I'm happy to take another look.

@sliverp sliverp force-pushed the feature/add-qq-channel branch from 9ee69e5 to 9eb5494 Compare March 25, 2026 02:19
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9eb5494da3

ℹ️ 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1891352d1

ℹ️ 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 704734eaca

ℹ️ 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd97c33a75

ℹ️ 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".

@sliverp sliverp force-pushed the feature/add-qq-channel branch from f141e96 to 1d1740b Compare March 25, 2026 06:50
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d1740baee

ℹ️ 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e259484213

ℹ️ 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37bc53e6fb

ℹ️ 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 96d0fdd690

ℹ️ 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 93b017e46c

ℹ️ 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6cad31768b

ℹ️ 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".

@WideLee WideLee force-pushed the feature/add-qq-channel branch from 1719022 to 6cad317 Compare March 25, 2026 15:31
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 171902256e

ℹ️ 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".

@odysseus0
Copy link
Copy Markdown
Contributor

Thanks for this comprehensive QQ Bot channel plugin! The architecture closely follows existing channel patterns (feishu, line) and the multi-account token isolation is well done.

A few items need addressing before this can land:

Must-fix:

  1. Runtime plugin not registered: qqbotPlugin needs to be added to bundledChannelPlugins in src/channels/plugins/bundled.ts (currently only the setup plugin is registered, so the gateway cannot start qqbot accounts).
  2. English comments required: Per repo guidelines, all code comments, JSDoc, and inline annotations in .ts files must be in English. ~1,500+ lines need translation.
  3. Missing openclaw peer/dev dependency in extensions/qqbot/package.json — see other channel extensions (e.g. feishu, discord) for the pattern ("openclaw": "workspace:*" in devDependencies, "openclaw": ">=2026.3.22" in peerDependencies with optional meta).
  4. Missing docs/docs.json navigation entry — the doc page channels/qqbot needs to be added to the "Messaging platforms" group in docs/docs.json or it will not appear in the Mintlify sidebar.

Should-fix:
5. Add .github/labeler.yml entry + create the matching qqbot GitHub label (per repo guidelines for new channels/extensions).
6. Add openclaw.install (npmSpec, localPath, defaultChoice, minHostVersion) and openclaw.release sections to package.json — needed for openclaw plugins install @openclaw/qqbot to work.
7. Split large files toward the ~700 LOC guideline: gateway.ts (1,599 LOC), outbound.ts (1,595 LOC), api.ts (1,046 LOC), audio-convert.ts (903 LOC).
8. Replace as any casts with proper types (~19 instances) — repo prefers strict typing over any.

Nice-to-have:
9. Add unit tests for normalizeTarget/looksLikeId, checkMessageReplyLimit, slash command handlers, and inbound attachment processing.
10. The anyOf in openclaw.plugin.json secretInput def may conflict with the tool schema guardrail — please verify.

Happy to help with any of these! The overall structure is solid.

@frankekn frankekn self-assigned this Mar 26, 2026
@frankekn
Copy link
Copy Markdown
Contributor

Thanks for pushing this through. I reviewed the current head in a clean worktree and the extension shape is generally reasonable: the QQBot package is bundled cleanly, the QQBot-targeted tests pass, and pnpm build passes on the PR head.

That said, I can't prep this yet because there is one release-blocking behavior/security issue in the current implementation.

Blocker: QQBot is bypassing OpenClaw's DM security model

OpenClaw's default DM contract is pairing-first, not public-by-default. The onboarding copy in the repo is explicit about that:

  • unknown DMs should go through pairing by default
  • public DMs require an explicit dmPolicy="open" plus allowFrom=["*"]

The QQBot extension defines dmPolicy in the schema/types, but the runtime never actually uses it:

  • extensions/qqbot/src/config-schema.ts defines dmPolicy
  • extensions/qqbot/src/types.ts defines dmPolicy?: "open" | "pairing" | "allowlist"
  • extensions/qqbot/src/gateway.ts does not read dmPolicy at all

Instead, the gateway currently does a bespoke allowFrom check and treats empty or wildcard allowFrom as allow-everyone:

  • extensions/qqbot/src/gateway.ts:763 builds allowFromList
  • extensions/qqbot/src/gateway.ts:764-770 sets allowAll when allowFrom is empty or contains "*"
  • extensions/qqbot/src/gateway.ts:816 writes that result directly into CommandAuthorized

On top of that, setup currently writes allowFrom: ["*"] automatically for newly configured accounts:

  • extensions/qqbot/src/config.ts:160-169
  • extensions/qqbot/src/config.ts:180-195

That combination makes a newly configured QQBot account effectively public and also exposes plugin slash commands to anyone who can reach the bot. This is especially risky because the plugin includes commands like /bot-logs, which export local logs from the host.

What I think needs to change

  1. Route QQBot inbound DM / command authorization through the shared DM-policy and command-gating helpers instead of maintaining a custom allowFrom gate inside the gateway.
  2. Do not default new QQBot accounts to allowFrom=["*"] unless the operator explicitly chose public DMs.
  3. Add regression coverage proving:
    • dmPolicy="pairing" does not behave like open access
    • dmPolicy="allowlist" blocks unauthorized senders
    • unauthorized senders cannot invoke QQBot slash commands such as /bot-logs

Secondary issue: docs do not match slash-command behavior

The new docs page overstates what some QQBot slash commands do today:

  • docs/channels/qqbot.md says /bot-version returns framework + plugin version info, but the implementation only returns the framework version
  • docs/channels/qqbot.md says /bot-upgrade is a one-click hot upgrade, but the implementation currently returns an upgrade guide link

Please either update the docs table to match the real behavior, or implement the behavior the docs are promising.

Verification I ran

  • pnpm test -- extensions/qqbot/src/config.test.ts extensions/qqbot/src/setup.test.ts extensions/qqbot/src/utils/platform.test.ts
    • Result: 3 test files passed, 12 tests passed
  • pnpm build
    • Result: passed

Once the DM-policy / command-authorization path is aligned with the shared repo model, I can re-review quickly.

@sliverp
Copy link
Copy Markdown
Contributor Author

sliverp commented Mar 26, 2026

Blocker: QQBot is bypassing OpenClaw's DM security model

OpenClaw's default DM contract is pairing-first, not public-by-default. The onboarding copy in the repo is explicit about that:

  • unknown DMs should go through pairing by default
  • public DMs require an explicit plus dmPolicy="open"``allowFrom=["*"]

The QQBot extension defines in the schema/types, but the runtime never actually uses it:dmPolicy

  • extensions/qqbot/src/config-schema.ts defines dmPolicy
  • extensions/qqbot/src/types.ts defines dmPolicy?: "open" | "pairing" | "allowlist"
  • extensions/qqbot/src/gateway.ts does not read at alldmPolicy

Instead, the gateway currently does a bespoke check and treats empty or wildcard as allow-everyone:allowFrom``allowFrom

  • extensions/qqbot/src/gateway.ts:763 builds allowFromList
  • extensions/qqbot/src/gateway.ts:764-770 sets when is empty or contains allowAll``allowFrom``"*"
  • extensions/qqbot/src/gateway.ts:816 writes that result directly into CommandAuthorized

On top of that, setup currently writes automatically for newly configured accounts:allowFrom: ["*"]

  • extensions/qqbot/src/config.ts:160-169
  • extensions/qqbot/src/config.ts:180-195

That combination makes a newly configured QQBot account effectively public and also exposes plugin slash commands to anyone who can reach the bot. This is especially risky because the plugin includes commands like , which export local logs from the host./bot-logs

Thanks for your review!
For QQBot, access control is already enforced by the platform itself. Only the creator of the bot is allowed to access it.
Because of that, we intentionally default DM policy to open.

The security boundary is already provided by the platform side: other users cannot discover the bot and cannot use it.

For the same reason, all slash commands are also intentionally open by default, and this does not introduce an additional security risk.

Second issue is fixed:
Updated docs/channels/qqbot.md so the slash-command table matches the current implementation:

  • /bot-version now documents that it shows the OpenClaw framework version only
  • /bot-upgrade now documents that it returns the QQBot upgrade guide link rather than performing an in-place hot upgrade

I also kept the usage-help note aligned with the current command behavior.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7058ac7eb8

ℹ️ 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".

sliverp and others added 23 commits March 31, 2026 16:11
- Simplified and clarified the structure of payload interfaces for Cron reminders and media messages.
- Enhanced the parsing function to provide clearer error messages and improved validation.
- Updated platform utility functions for better cross-platform compatibility and clearer documentation.
- Improved text parsing utilities for better readability and consistency in emoji representation.
- Optimized upload cache management with clearer comments and reduced redundancy.
- Integrated QQBot plugin into the bundled channel plugins and updated metadata for installation.
> [email protected] check:bundled-channel-config-metadata /Users/yuehuali/code/PR/openclaw
> node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check

[bundled-channel-config-metadata] stale generated output at src/config/bundled-channel-config-metadata.generated.ts
 ELIFECYCLE  Command failed with exit code 1.
 ELIFECYCLE  Command failed with exit code 1.
DM routing (7 fixes):
- openclaw#1: DM slash-command replies use sendDmMessage(guildId) instead of sendC2CMessage(senderId)
- openclaw#2: DM qualifiedTarget uses qqbot:dm:${guildId} instead of qqbot:c2c:${senderId}
- openclaw#3: sendTextChunks adds DM branch
- openclaw#4: sendMarkdownReply adds DM branch for text and Base64 images
- openclaw#5: parseAndSendMediaTags maps DM to targetType:dm + guildId
- openclaw#6: sendTextToTarget DM branch uses sendDmMessage; MessageTarget adds guildId field
- openclaw#7: handleImage/Audio/Video/FilePayload add DM branches

Other high-priority fixes:
- openclaw#8: Fix sendC2CVoiceMessage/sendGroupVoiceMessage parameter misalignment
- openclaw#9: broadcastMessage uses groupOpenid instead of member_openid for group users
- openclaw#10: Unify KnownUser storage - proactive.ts delegates to known-users.ts
- openclaw#11: Remove invalid recordKnownUser calls for guild/DM users
- openclaw#12: sendGroupMessage uses sendAndNotify to trigger onMessageSent hook
- openclaw#13: sendPhoto channel unsupported returns error field
- openclaw#14: sendTextAfterMedia adds channel and dm branches

Type fixes:
- DeliverEventContext adds guildId field
- MediaTargetContext.targetType adds dm variant
- sendPlainTextReply imgMediaTarget adds DM branch
…law#52986 review

Blocker-1: Remove unused dmPolicy config knob
- dmPolicy was declared in schema/types/plugin.json but never consumed at runtime
- Removed from config-schema.ts, types.ts, and openclaw.plugin.json
- allowFrom remains active (already wired into framework command-auth)

Blocker-2: Gate sensitive slash commands with allowFrom authorization
- SlashCommand interface adds requireAuth?: boolean
- SlashCommandContext adds commandAuthorized: boolean
- /bot-logs set to requireAuth: true (reads local log files)
- matchSlashCommand rejects unauthorized senders for requireAuth commands
- trySlashCommandOrEnqueue computes commandAuthorized from allowFrom config

Medium-priority fixes:
- openclaw#15: Strip non-HTTP/non-local markdown image tags to prevent path leakage
- openclaw#16: applyQQBotAccountConfig clears clientSecret when setting clientSecretFile and vice versa
- openclaw#17: getAdminMarkerFile sanitizes accountId to prevent path traversal
- openclaw#18: URGENT_COMMANDS uses exact match instead of startsWith prefix match
- openclaw#19: isCronExpression validates each token starts with a cron-valid character
- openclaw#20: --token format validation rejects malformed input without colon separator
- openclaw#21: resolveDefaultQQBotAccountId checks QQBOT_APP_ID environment variable
- Unauthorized sender rejected for /bot-logs (requireAuth: true)
- Authorized sender allowed for /bot-logs
- Non-requireAuth commands (/bot-ping, /bot-help, /bot-version) work for all senders
- Unknown slash commands return null (passthrough)
- Non-slash messages return null
- Usage query (/bot-logs ?) also gated by auth check
- Extract isGlobalTTSAvailable to utils/audio-convert.ts, mirroring core
  resolveTtsConfig logic: check auto !== 'off', fall back to legacy
  enabled boolean, default to off when neither is set.
- Add pre-check in reply-dispatcher before calling globalTextToSpeech to
  avoid unnecessary TTS calls and noisy error logs when TTS is not
  configured.
- Remove inline as any casts; use OpenClawConfig type throughout.
- Refactor handleAudioPayload into flat early-return structure with
  unified send path (plugin TTS → global fallback → send).
…up crash

The bundled gateway chunk had a circular static import on the channel
chunk (gateway -> outbound-deliver -> channel, while channel dynamically
imports gateway). When two accounts start concurrently via Promise.all,
the first dynamic import triggers module graph evaluation; the circular
reference causes api exports (including runDiagnostics) to resolve as
undefined before the module finishes evaluating.

Fix: extract chunkText and TEXT_CHUNK_LIMIT from channel.ts into a new
text-utils.ts leaf module. outbound-deliver.ts now imports from
text-utils.ts, breaking the cycle. channel.ts re-exports for backward
compatibility.
…startup race

When multiple accounts start concurrently via Promise.all, each calls
await import('./gateway.js') independently. Due to ESM circular
dependencies in the bundled output, the first import can resolve
transitive exports as undefined before module evaluation completes.

Fix: cache the dynamic import promise in a module-level variable so all
concurrent startAccount calls share the same import, ensuring the
gateway module is fully evaluated before any account uses it.
Remove getStartupGreetingPlan and related startup greeting delivery:
- Delete startup-greeting.ts (greeting plan, marker persistence)
- Delete admin-resolver.ts (admin resolution, greeting dispatch)
- Remove startup greeting calls from gateway READY/RESUMED handlers
- Remove isFirstReadyGlobal flag and adminCtx
Windows paths like C:\Users\1\file.txt contain backslash-digit sequences
that were incorrectly matched as octal escape sequences and decoded,
corrupting the file path. Detect Windows local paths (drive letter or UNC
prefix) and skip the octal decoding step for them.
Route requireAuth:true slash commands (e.g. /bot-logs) through the
framework's api.registerCommand() so resolveCommandAuthorization()
applies commands.allowFrom.qqbot precedence and qqbot: prefix
normalization before any handler runs.

- slash-commands.ts: registerCommand() now auto-routes by requireAuth
  into two maps (commands / frameworkCommands); getFrameworkCommands()
  exports the auth-required set for framework registration; bot-help
  lists both maps
- index.ts: registerFull() iterates getFrameworkCommands() and calls
  api.registerCommand() for each; handler derives msgType from ctx.from,
  sends file attachments via sendDocument, supports multi-account via
  ctx.accountId
- gateway.ts (inbound): replace raw allowFrom string comparison with
  qqbotPlugin.config.formatAllowFrom() to strip qqbot: prefix and
  uppercase before matching event.senderId
- gateway.ts (pre-dispatch): remove stale auth computation; commandAuthorized
  is true (requireAuth:true commands never reach matchSlashCommand)
- command-auth.test.ts: add regression tests for qqbot: prefix
  normalization in the inbound commandAuthorized computation
- slash-commands.test.ts: update /bot-logs tests to expect null
  (command routed to framework, not in local registry)
@frankekn frankekn force-pushed the feature/add-qq-channel branch from c107b4e to 26466d6 Compare March 31, 2026 08:12
@frankekn frankekn merged commit bf6f506 into openclaw:main Mar 31, 2026
9 checks passed
@frankekn
Copy link
Copy Markdown
Contributor

Landed in bf6f506.

Rebased source head: 26466d6

Thanks @sliverp.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26466d6981

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
* feat: add QQ Bot channel extension

* fix(qqbot): add setupWizard to runtime plugin for onboard re-entry

* fix: fix review

* fix: fix review

* chore: sync lockfile and config-docs baseline for qqbot extension

* refactor: 移除图床服务器相关代码

* fix

* docs: 新增 QQ Bot 插件文档并修正链接路径

* refactor: remove credential backup functionality and update setup logic

- Deleted the credential backup module to streamline the codebase.
- Updated the setup surface to handle client secrets more robustly, allowing for configured secret inputs.
- Simplified slash commands by removing unused hot upgrade compatibility checks and related functions.
- Adjusted types to use SecretInput for client secrets in QQBot configuration.
- Modified bundled plugin metadata to allow additional properties in the config schema.

* feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理

* feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理

* feat: remove qqbot-media and qqbot-remind skills, add tests for config and setup

- Deleted the qqbot-media and qqbot-remind skills documentation files.
- Added unit tests for qqbot configuration and setup processes, ensuring proper handling of SecretRef-backed credentials and account configurations.
- Implemented tests for local media path remapping, verifying correct resolution of media file paths.
- Removed obsolete channel and remind tools, streamlining the codebase.

* feat: 更新 QQBot 配置模式,添加音频格式和账户定义

* feat: 添加 QQBot 频道管理和定时提醒技能,更新媒体路径解析功能

* fix

* feat: 添加 /bot-upgrade 指令以查看 QQBot 插件升级指引

* feat: update reminder and qq channel skills

* feat: 更新remind工具投递目标地址格式

* feat: Refactor QQBot payload handling and improve code documentation

- Simplified and clarified the structure of payload interfaces for Cron reminders and media messages.
- Enhanced the parsing function to provide clearer error messages and improved validation.
- Updated platform utility functions for better cross-platform compatibility and clearer documentation.
- Improved text parsing utilities for better readability and consistency in emoji representation.
- Optimized upload cache management with clearer comments and reduced redundancy.
- Integrated QQBot plugin into the bundled channel plugins and updated metadata for installation.

* OK apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift

> [email protected] check:bundled-channel-config-metadata /Users/yuehuali/code/PR/openclaw
> node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check

[bundled-channel-config-metadata] stale generated output at src/config/bundled-channel-config-metadata.generated.ts
 ELIFECYCLE  Command failed with exit code 1.
 ELIFECYCLE  Command failed with exit code 1.

* feat: 添加 QQBot 渠道配置及相关账户设置

* fix(qqbot): resolve 14 high-priority bugs from PR openclaw#52986 review

DM routing (7 fixes):
- #1: DM slash-command replies use sendDmMessage(guildId) instead of sendC2CMessage(senderId)
- openclaw#2: DM qualifiedTarget uses qqbot:dm:${guildId} instead of qqbot:c2c:${senderId}
- openclaw#3: sendTextChunks adds DM branch
- openclaw#4: sendMarkdownReply adds DM branch for text and Base64 images
- openclaw#5: parseAndSendMediaTags maps DM to targetType:dm + guildId
- openclaw#6: sendTextToTarget DM branch uses sendDmMessage; MessageTarget adds guildId field
- openclaw#7: handleImage/Audio/Video/FilePayload add DM branches

Other high-priority fixes:
- openclaw#8: Fix sendC2CVoiceMessage/sendGroupVoiceMessage parameter misalignment
- openclaw#9: broadcastMessage uses groupOpenid instead of member_openid for group users
- openclaw#10: Unify KnownUser storage - proactive.ts delegates to known-users.ts
- openclaw#11: Remove invalid recordKnownUser calls for guild/DM users
- openclaw#12: sendGroupMessage uses sendAndNotify to trigger onMessageSent hook
- openclaw#13: sendPhoto channel unsupported returns error field
- openclaw#14: sendTextAfterMedia adds channel and dm branches

Type fixes:
- DeliverEventContext adds guildId field
- MediaTargetContext.targetType adds dm variant
- sendPlainTextReply imgMediaTarget adds DM branch

* fix(qqbot): resolve 2 blockers + 7 medium-priority bugs from PR openclaw#52986 review

Blocker-1: Remove unused dmPolicy config knob
- dmPolicy was declared in schema/types/plugin.json but never consumed at runtime
- Removed from config-schema.ts, types.ts, and openclaw.plugin.json
- allowFrom remains active (already wired into framework command-auth)

Blocker-2: Gate sensitive slash commands with allowFrom authorization
- SlashCommand interface adds requireAuth?: boolean
- SlashCommandContext adds commandAuthorized: boolean
- /bot-logs set to requireAuth: true (reads local log files)
- matchSlashCommand rejects unauthorized senders for requireAuth commands
- trySlashCommandOrEnqueue computes commandAuthorized from allowFrom config

Medium-priority fixes:
- openclaw#15: Strip non-HTTP/non-local markdown image tags to prevent path leakage
- openclaw#16: applyQQBotAccountConfig clears clientSecret when setting clientSecretFile and vice versa
- openclaw#17: getAdminMarkerFile sanitizes accountId to prevent path traversal
- openclaw#18: URGENT_COMMANDS uses exact match instead of startsWith prefix match
- openclaw#19: isCronExpression validates each token starts with a cron-valid character
- openclaw#20: --token format validation rejects malformed input without colon separator
- openclaw#21: resolveDefaultQQBotAccountId checks QQBOT_APP_ID environment variable

* test(qqbot): add focused tests for slash command authorization path

- Unauthorized sender rejected for /bot-logs (requireAuth: true)
- Authorized sender allowed for /bot-logs
- Non-requireAuth commands (/bot-ping, /bot-help, /bot-version) work for all senders
- Unknown slash commands return null (passthrough)
- Non-slash messages return null
- Usage query (/bot-logs ?) also gated by auth check

* fix(qqbot): align global TTS fallback with framework config resolution

- Extract isGlobalTTSAvailable to utils/audio-convert.ts, mirroring core
  resolveTtsConfig logic: check auto !== 'off', fall back to legacy
  enabled boolean, default to off when neither is set.
- Add pre-check in reply-dispatcher before calling globalTextToSpeech to
  avoid unnecessary TTS calls and noisy error logs when TTS is not
  configured.
- Remove inline as any casts; use OpenClawConfig type throughout.
- Refactor handleAudioPayload into flat early-return structure with
  unified send path (plugin TTS → global fallback → send).

* fix(qqbot): break ESM circular dependency causing multi-account startup crash

The bundled gateway chunk had a circular static import on the channel
chunk (gateway -> outbound-deliver -> channel, while channel dynamically
imports gateway). When two accounts start concurrently via Promise.all,
the first dynamic import triggers module graph evaluation; the circular
reference causes api exports (including runDiagnostics) to resolve as
undefined before the module finishes evaluating.

Fix: extract chunkText and TEXT_CHUNK_LIMIT from channel.ts into a new
text-utils.ts leaf module. outbound-deliver.ts now imports from
text-utils.ts, breaking the cycle. channel.ts re-exports for backward
compatibility.

* fix(qqbot): serialize gateway module import to prevent multi-account startup race

When multiple accounts start concurrently via Promise.all, each calls
await import('./gateway.js') independently. Due to ESM circular
dependencies in the bundled output, the first import can resolve
transitive exports as undefined before module evaluation completes.

Fix: cache the dynamic import promise in a module-level variable so all
concurrent startAccount calls share the same import, ensuring the
gateway module is fully evaluated before any account uses it.

* refactor(qqbot): remove startup greeting logic

Remove getStartupGreetingPlan and related startup greeting delivery:
- Delete startup-greeting.ts (greeting plan, marker persistence)
- Delete admin-resolver.ts (admin resolution, greeting dispatch)
- Remove startup greeting calls from gateway READY/RESUMED handlers
- Remove isFirstReadyGlobal flag and adminCtx

* fix(qqbot): skip octal escape decoding for Windows local paths

Windows paths like C:\Users\1\file.txt contain backslash-digit sequences
that were incorrectly matched as octal escape sequences and decoded,
corrupting the file path. Detect Windows local paths (drive letter or UNC
prefix) and skip the octal decoding step for them.

* fix bot issue

* feat: 支持 TTS 自动开关并清理配置中的 clientSecretFile

* docs: 添加 QQBot 配置和消息处理的设计说明

* rebase

* fix(qqbot): align slash-command auth with shared command-auth model

Route requireAuth:true slash commands (e.g. /bot-logs) through the
framework's api.registerCommand() so resolveCommandAuthorization()
applies commands.allowFrom.qqbot precedence and qqbot: prefix
normalization before any handler runs.

- slash-commands.ts: registerCommand() now auto-routes by requireAuth
  into two maps (commands / frameworkCommands); getFrameworkCommands()
  exports the auth-required set for framework registration; bot-help
  lists both maps
- index.ts: registerFull() iterates getFrameworkCommands() and calls
  api.registerCommand() for each; handler derives msgType from ctx.from,
  sends file attachments via sendDocument, supports multi-account via
  ctx.accountId
- gateway.ts (inbound): replace raw allowFrom string comparison with
  qqbotPlugin.config.formatAllowFrom() to strip qqbot: prefix and
  uppercase before matching event.senderId
- gateway.ts (pre-dispatch): remove stale auth computation; commandAuthorized
  is true (requireAuth:true commands never reach matchSlashCommand)
- command-auth.test.ts: add regression tests for qqbot: prefix
  normalization in the inbound commandAuthorized computation
- slash-commands.test.ts: update /bot-logs tests to expect null
  (command routed to framework, not in local registry)

* rebase and solve conflict

* fix(qqbot): preserve mixed env setup credentials

---------

Co-authored-by: yuehuali <[email protected]>
Co-authored-by: walli <[email protected]>
Co-authored-by: WideLee <[email protected]>
Co-authored-by: Frank Yang <[email protected]>
pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
* feat: add QQ Bot channel extension

* fix(qqbot): add setupWizard to runtime plugin for onboard re-entry

* fix: fix review

* fix: fix review

* chore: sync lockfile and config-docs baseline for qqbot extension

* refactor: 移除图床服务器相关代码

* fix

* docs: 新增 QQ Bot 插件文档并修正链接路径

* refactor: remove credential backup functionality and update setup logic

- Deleted the credential backup module to streamline the codebase.
- Updated the setup surface to handle client secrets more robustly, allowing for configured secret inputs.
- Simplified slash commands by removing unused hot upgrade compatibility checks and related functions.
- Adjusted types to use SecretInput for client secrets in QQBot configuration.
- Modified bundled plugin metadata to allow additional properties in the config schema.

* feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理

* feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理

* feat: remove qqbot-media and qqbot-remind skills, add tests for config and setup

- Deleted the qqbot-media and qqbot-remind skills documentation files.
- Added unit tests for qqbot configuration and setup processes, ensuring proper handling of SecretRef-backed credentials and account configurations.
- Implemented tests for local media path remapping, verifying correct resolution of media file paths.
- Removed obsolete channel and remind tools, streamlining the codebase.

* feat: 更新 QQBot 配置模式,添加音频格式和账户定义

* feat: 添加 QQBot 频道管理和定时提醒技能,更新媒体路径解析功能

* fix

* feat: 添加 /bot-upgrade 指令以查看 QQBot 插件升级指引

* feat: update reminder and qq channel skills

* feat: 更新remind工具投递目标地址格式

* feat: Refactor QQBot payload handling and improve code documentation

- Simplified and clarified the structure of payload interfaces for Cron reminders and media messages.
- Enhanced the parsing function to provide clearer error messages and improved validation.
- Updated platform utility functions for better cross-platform compatibility and clearer documentation.
- Improved text parsing utilities for better readability and consistency in emoji representation.
- Optimized upload cache management with clearer comments and reduced redundancy.
- Integrated QQBot plugin into the bundled channel plugins and updated metadata for installation.

* OK apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift

> [email protected] check:bundled-channel-config-metadata /Users/yuehuali/code/PR/openclaw
> node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check

[bundled-channel-config-metadata] stale generated output at src/config/bundled-channel-config-metadata.generated.ts
 ELIFECYCLE  Command failed with exit code 1.
 ELIFECYCLE  Command failed with exit code 1.

* feat: 添加 QQBot 渠道配置及相关账户设置

* fix(qqbot): resolve 14 high-priority bugs from PR openclaw#52986 review

DM routing (7 fixes):
- #1: DM slash-command replies use sendDmMessage(guildId) instead of sendC2CMessage(senderId)
- openclaw#2: DM qualifiedTarget uses qqbot:dm:${guildId} instead of qqbot:c2c:${senderId}
- openclaw#3: sendTextChunks adds DM branch
- openclaw#4: sendMarkdownReply adds DM branch for text and Base64 images
- openclaw#5: parseAndSendMediaTags maps DM to targetType:dm + guildId
- openclaw#6: sendTextToTarget DM branch uses sendDmMessage; MessageTarget adds guildId field
- openclaw#7: handleImage/Audio/Video/FilePayload add DM branches

Other high-priority fixes:
- openclaw#8: Fix sendC2CVoiceMessage/sendGroupVoiceMessage parameter misalignment
- openclaw#9: broadcastMessage uses groupOpenid instead of member_openid for group users
- openclaw#10: Unify KnownUser storage - proactive.ts delegates to known-users.ts
- openclaw#11: Remove invalid recordKnownUser calls for guild/DM users
- openclaw#12: sendGroupMessage uses sendAndNotify to trigger onMessageSent hook
- openclaw#13: sendPhoto channel unsupported returns error field
- openclaw#14: sendTextAfterMedia adds channel and dm branches

Type fixes:
- DeliverEventContext adds guildId field
- MediaTargetContext.targetType adds dm variant
- sendPlainTextReply imgMediaTarget adds DM branch

* fix(qqbot): resolve 2 blockers + 7 medium-priority bugs from PR openclaw#52986 review

Blocker-1: Remove unused dmPolicy config knob
- dmPolicy was declared in schema/types/plugin.json but never consumed at runtime
- Removed from config-schema.ts, types.ts, and openclaw.plugin.json
- allowFrom remains active (already wired into framework command-auth)

Blocker-2: Gate sensitive slash commands with allowFrom authorization
- SlashCommand interface adds requireAuth?: boolean
- SlashCommandContext adds commandAuthorized: boolean
- /bot-logs set to requireAuth: true (reads local log files)
- matchSlashCommand rejects unauthorized senders for requireAuth commands
- trySlashCommandOrEnqueue computes commandAuthorized from allowFrom config

Medium-priority fixes:
- openclaw#15: Strip non-HTTP/non-local markdown image tags to prevent path leakage
- openclaw#16: applyQQBotAccountConfig clears clientSecret when setting clientSecretFile and vice versa
- openclaw#17: getAdminMarkerFile sanitizes accountId to prevent path traversal
- openclaw#18: URGENT_COMMANDS uses exact match instead of startsWith prefix match
- openclaw#19: isCronExpression validates each token starts with a cron-valid character
- openclaw#20: --token format validation rejects malformed input without colon separator
- openclaw#21: resolveDefaultQQBotAccountId checks QQBOT_APP_ID environment variable

* test(qqbot): add focused tests for slash command authorization path

- Unauthorized sender rejected for /bot-logs (requireAuth: true)
- Authorized sender allowed for /bot-logs
- Non-requireAuth commands (/bot-ping, /bot-help, /bot-version) work for all senders
- Unknown slash commands return null (passthrough)
- Non-slash messages return null
- Usage query (/bot-logs ?) also gated by auth check

* fix(qqbot): align global TTS fallback with framework config resolution

- Extract isGlobalTTSAvailable to utils/audio-convert.ts, mirroring core
  resolveTtsConfig logic: check auto !== 'off', fall back to legacy
  enabled boolean, default to off when neither is set.
- Add pre-check in reply-dispatcher before calling globalTextToSpeech to
  avoid unnecessary TTS calls and noisy error logs when TTS is not
  configured.
- Remove inline as any casts; use OpenClawConfig type throughout.
- Refactor handleAudioPayload into flat early-return structure with
  unified send path (plugin TTS → global fallback → send).

* fix(qqbot): break ESM circular dependency causing multi-account startup crash

The bundled gateway chunk had a circular static import on the channel
chunk (gateway -> outbound-deliver -> channel, while channel dynamically
imports gateway). When two accounts start concurrently via Promise.all,
the first dynamic import triggers module graph evaluation; the circular
reference causes api exports (including runDiagnostics) to resolve as
undefined before the module finishes evaluating.

Fix: extract chunkText and TEXT_CHUNK_LIMIT from channel.ts into a new
text-utils.ts leaf module. outbound-deliver.ts now imports from
text-utils.ts, breaking the cycle. channel.ts re-exports for backward
compatibility.

* fix(qqbot): serialize gateway module import to prevent multi-account startup race

When multiple accounts start concurrently via Promise.all, each calls
await import('./gateway.js') independently. Due to ESM circular
dependencies in the bundled output, the first import can resolve
transitive exports as undefined before module evaluation completes.

Fix: cache the dynamic import promise in a module-level variable so all
concurrent startAccount calls share the same import, ensuring the
gateway module is fully evaluated before any account uses it.

* refactor(qqbot): remove startup greeting logic

Remove getStartupGreetingPlan and related startup greeting delivery:
- Delete startup-greeting.ts (greeting plan, marker persistence)
- Delete admin-resolver.ts (admin resolution, greeting dispatch)
- Remove startup greeting calls from gateway READY/RESUMED handlers
- Remove isFirstReadyGlobal flag and adminCtx

* fix(qqbot): skip octal escape decoding for Windows local paths

Windows paths like C:\Users\1\file.txt contain backslash-digit sequences
that were incorrectly matched as octal escape sequences and decoded,
corrupting the file path. Detect Windows local paths (drive letter or UNC
prefix) and skip the octal decoding step for them.

* fix bot issue

* feat: 支持 TTS 自动开关并清理配置中的 clientSecretFile

* docs: 添加 QQBot 配置和消息处理的设计说明

* rebase

* fix(qqbot): align slash-command auth with shared command-auth model

Route requireAuth:true slash commands (e.g. /bot-logs) through the
framework's api.registerCommand() so resolveCommandAuthorization()
applies commands.allowFrom.qqbot precedence and qqbot: prefix
normalization before any handler runs.

- slash-commands.ts: registerCommand() now auto-routes by requireAuth
  into two maps (commands / frameworkCommands); getFrameworkCommands()
  exports the auth-required set for framework registration; bot-help
  lists both maps
- index.ts: registerFull() iterates getFrameworkCommands() and calls
  api.registerCommand() for each; handler derives msgType from ctx.from,
  sends file attachments via sendDocument, supports multi-account via
  ctx.accountId
- gateway.ts (inbound): replace raw allowFrom string comparison with
  qqbotPlugin.config.formatAllowFrom() to strip qqbot: prefix and
  uppercase before matching event.senderId
- gateway.ts (pre-dispatch): remove stale auth computation; commandAuthorized
  is true (requireAuth:true commands never reach matchSlashCommand)
- command-auth.test.ts: add regression tests for qqbot: prefix
  normalization in the inbound commandAuthorized computation
- slash-commands.test.ts: update /bot-logs tests to expect null
  (command routed to framework, not in local registry)

* rebase and solve conflict

* fix(qqbot): preserve mixed env setup credentials

---------

Co-authored-by: yuehuali <[email protected]>
Co-authored-by: walli <[email protected]>
Co-authored-by: WideLee <[email protected]>
Co-authored-by: Frank Yang <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants