Skip to content

Refactor: centralize native approval lifecycle assembly#62135

Merged
gumadeiras merged 25 commits into
mainfrom
codex/approval-capability-runtime
Apr 7, 2026
Merged

Refactor: centralize native approval lifecycle assembly#62135
gumadeiras merged 25 commits into
mainfrom
codex/approval-capability-runtime

Conversation

@gumadeiras

@gumadeiras gumadeiras commented Apr 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Problem: native approval lifecycle ownership was split across per-channel handlers, gateway wiring, and legacy fallback seams.
  • Why it matters: channel-native approvals drifted by channel, new channels had to duplicate bootstrap/runtime plumbing, and core could not assemble the lifecycle generically from plugin-owned capabilities.
  • What changed: core now assembles native approval bootstrap/runtime/routing from approvalCapability plus channel runtime contexts; Discord, Matrix, Slack, and Telegram moved onto the shared seams; legacy bundled approval fallback wiring was removed.
  • What did NOT change (scope boundary): channel-specific auth, delivery targets, rendering, and native interaction handling still stay plugin-owned.

Change Type (select all)

  • Refactor required for the fix
  • Docs

Scope (select all touched areas)

  • Gateway / orchestration
  • Integrations
  • API / contracts

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: approval lifecycle assembly lived in multiple channel-specific seams instead of one shared core path.
  • Missing detection / guardrail: core did not have a generic runtime-context-driven bootstrap seam for native approvals.
  • Contributing context (if known): bundled channels had accumulated parallel approval handlers and compatibility layers over time.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Seam / integration test
  • Target test or file: src/gateway/server-channels.approval-bootstrap.test.ts, src/infra/approval-handler-runtime.test.ts, src/infra/approval-native-runtime.test.ts, src/plugins/runtime/runtime-channel.test.ts
  • Scenario the test should lock in: runtime-context registration boots/stops the shared approval lifecycle correctly and routes native approval delivery/notice/auth through the canonical capability seams.
  • Why this is the smallest reliable guardrail: the behavior spans gateway startup, runtime context registration, capability assembly, and channel-native adapters.
  • Existing test that already covers this (if any): changed-path approval/channel/runtime suites above.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • Native approval lifecycle is now assembled generically from channel capabilities and runtime contexts instead of per-channel bootstrap glue.
  • Bundled channels now use canonical approvalCapability seams instead of legacy bundled approval fallback wiring.

Diagram (if applicable)

Before:
[channel-specific monitor/provider] -> [channel-specific approval bootstrap/runtime glue] -> [native approval lifecycle]

After:
[channel runtime context + approvalCapability] -> [shared core bootstrap/runtime/routing] -> [plugin-owned auth/delivery/render/interaction]

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local repo worktree
  • Model/provider: N/A
  • Integration/channel (if any): Discord, Matrix, Slack, Telegram
  • Relevant config (redacted): approval-capable channel configs

Steps

  1. Start gateway/channel runtime for an approval-capable account.
  2. Register channel-native approval runtime context.
  3. Trigger exec and plugin approval flows on migrated channels.

Expected

  • Core boots/stops one shared approval lifecycle per runtime context and routes auth/delivery/notice handling through channel capabilities.

Actual

  • Matches expected in targeted local verification.

Evidence

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

Human Verification (required)

  • Verified scenarios: changed-path approval/channel/runtime suites, build, Plugin SDK API baseline check.
  • Edge cases checked: runtime-context replacement, disabled native runtimes, routed-elsewhere notices, Matrix plugin-native parity, legacy bundled fallback removal.
  • What you did not verify: full pnpm test suite.

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? No
  • Config/env changes? No
  • Migration needed? Yes
  • If yes, exact upgrade steps: channel plugins must expose approval auth/delivery/runtime behavior through approvalCapability; legacy bundled approval fallback wiring is no longer used.

Risks and Mitigations

  • Risk: approval lifecycle regressions can surface at channel startup/runtime-context boundaries.
    • Mitigation: shared bootstrap/runtime regression coverage across gateway, runtime-channel, and approval-native seams.
  • Risk: third-party channel plugins still on removed legacy approval seams need migration.
    • Mitigation: canonical Plugin SDK/docs now point to approvalCapability as the only supported path.

Copilot AI review requested due to automatic review settings April 6, 2026 21:21
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: discord Channel integration: discord channel: matrix Channel integration: matrix channel: slack Channel integration: slack channel: telegram Channel integration: telegram gateway Gateway runtime scripts Repository scripts size: XL maintainer Maintainer-authored PR labels Apr 6, 2026
@gumadeiras gumadeiras added the bad-barnacle Suppress Barnacle automation on this issue or PR. label Apr 6, 2026
@gumadeiras gumadeiras self-assigned this Apr 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR moves native approval lifecycle assembly into core, driven by channel capabilities plus a runtime-context registry, and migrates built-in channels to register their runtime-owned objects (clients/tokens/apps) via that registry instead of channel-specific approval handler wrappers.

Changes:

  • Add channel.runtimeContexts to the plugin runtime surface plus helper APIs (channel-runtime-context) for register/get/watch and task-scoped cleanup.
  • Introduce capability-driven native approval handler/runtime seams (approvalCapability.nativeRuntime) with new core bootstrap/resolver/helpers and updated SDK exports/docs.
  • Migrate Discord/Matrix/Slack/Telegram to register approval runtime context and provide lazy-loaded native runtime adapters; remove legacy exec-approval handler classes/tests and add regression coverage.

Reviewed changes

Copilot reviewed 76 out of 76 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/helpers/plugins/plugin-runtime-mock.ts Adds runtimeContexts mocks to support tests using the new runtime surface.
src/plugins/runtime/types-channel.ts Defines runtime-context registry types and attaches to PluginRuntimeChannel.
src/plugins/runtime/runtime-channel.ts Implements in-memory runtime-context registry with register/get/watch.
src/plugins/runtime/runtime-channel.test.ts Adds unit tests for runtime-context registry behavior.
src/plugin-sdk/channel-runtime-context.ts Exposes runtime-context helpers via the Plugin SDK.
src/plugin-sdk/approval-reply-runtime.ts Exports additional approval reply helpers/types for channel runtimes.
src/plugin-sdk/approval-handler-runtime.ts Adds new SDK entrypoint re-exporting core approval handler runtime helpers/types.
src/plugin-sdk/approval-delivery-helpers.ts Extends approval capability helpers to include nativeRuntime.
src/plugin-sdk/approval-delivery-helpers.test.ts Covers nativeRuntime propagation through capability split/legacy compatibility.
src/infra/exec-approval-reply.ts Refactors interactive reply building around action descriptors and exports helper.
src/infra/exec-approval-channel-runtime.ts Adds onStopped hook for stop-time cleanup coordination.
src/infra/channel-runtime-context.ts Implements register/get/watch wrappers and task-scoped runtime that tracks leases.
src/infra/approval-view-model.ts Adds shared view-model builders for pending/resolved/expired exec + plugin approvals.
src/infra/approval-native-target-key.ts Introduces shared key builder for native-target dedupe/comparison.
src/infra/approval-native-runtime.ts Enhances native approval runtime delivery to return delivery plan/targets and coordinate route notices.
src/infra/approval-native-runtime.test.ts Adds tests for routed-elsewhere notices and gateway send behavior; updates delivery plan assertions.
src/infra/approval-native-route-notice.ts Implements formatting for aggregated “routed elsewhere” notice text.
src/infra/approval-native-route-notice.test.ts Tests destination labeling and unique/sorted notice formatting.
src/infra/approval-native-route-coordinator.ts Adds cross-runtime coordinator to aggregate delivery outcomes and emit one origin notice.
src/infra/approval-native-delivery.ts Switches native-target dedupe to use shared target-key helper.
src/infra/approval-handler-runtime.test.ts Adds tests for capability-driven handler creation and lazy runtime adapter behavior.
src/infra/approval-handler-bootstrap.ts Adds gateway-side bootstrap that starts/stops native approval handlers based on runtime context registration.
src/infra/approval-handler-bootstrap.test.ts Tests bootstrap lifecycle vs runtime-context registration/unregistration.
src/infra/approval-gateway-resolver.ts Adds shared helper to resolve approvals over the operator gateway with plugin fallback.
src/infra/approval-gateway-resolver.test.ts Tests resolver routing, fallback behavior, and stop/stopAndWait cleanup.
src/gateway/server-channels.ts Starts approval bootstrap per channel account and uses task-scoped channel runtime for cleanup.
src/gateway/server-channels.test.ts Updates channelRuntime tests; adds failure mode for partial runtime surfaces.
src/gateway/server-channels.approval-bootstrap.test.ts Verifies bootstrap starts/stops with channel lifecycle and scoped runtime cleanup.
src/channels/plugins/types.adapters.ts Adds nativeRuntime to approval adapter/capability types and clarifies channelRuntime expectations.
src/channels/plugins/approvals.ts Propagates nativeRuntime through legacy/capability resolution and adapter construction.
src/channels/plugins/approvals.test.ts Adds coverage for nativeRuntime in capability/adapter resolution.
scripts/lib/plugin-sdk-entrypoints.json Adds new SDK entrypoints for approval handler runtime and runtime-context helpers.
package.json Exports the new Plugin SDK entrypoints.
extensions/telegram/src/monitor.ts Registers approval runtime context instead of instantiating a Telegram exec approvals handler.
extensions/telegram/src/monitor-webhook.runtime.ts Removes webhook runtime export of legacy exec approvals handler.
extensions/telegram/src/monitor-polling.runtime.ts Removes polling runtime export of legacy exec approvals handler.
extensions/telegram/src/exec-approvals-handler.ts Removes legacy Telegram exec approvals handler implementation.
extensions/telegram/src/exec-approvals-handler.test.ts Removes legacy Telegram exec approvals handler tests.
extensions/telegram/src/exec-approval-resolver.ts Switches to shared gateway resolver helper.
extensions/telegram/src/channel.ts Passes channelRuntime through provider startup options.
extensions/telegram/src/approval-native.ts Adds nativeRuntime via lazy adapter loading for Telegram approvals.
extensions/telegram/src/approval-handler.runtime.ts Introduces Telegram native runtime adapter implementation using shared view/actions.
extensions/slack/src/monitor/types.ts Adds channelRuntime option for monitor so it can register runtime context.
extensions/slack/src/monitor/provider.ts Registers approval runtime context instead of starting/stopping legacy exec approvals handler.
extensions/slack/src/monitor/exec-approvals.ts Removes legacy Slack exec approvals handler implementation.
extensions/slack/src/monitor/exec-approvals.test.ts Removes legacy Slack exec approvals handler tests.
extensions/slack/src/doctor-contract.ts Formatting-only import change.
extensions/slack/src/channel.ts Passes channelRuntime through provider startup options.
extensions/slack/src/approval-native.ts Adds nativeRuntime via lazy adapter loading for Slack approvals.
extensions/slack/src/approval-handler.runtime.ts Introduces Slack native runtime adapter implementation using shared view/actions.
extensions/matrix/src/matrix/monitor/index.ts Registers approval runtime context instead of starting/stopping legacy exec approvals handler.
extensions/matrix/src/matrix/monitor/handler.ts Formatting-only import move.
extensions/matrix/src/matrix/client/config.ts Formatting-only signature reflow.
extensions/matrix/src/exec-approvals-handler.ts Removes legacy Matrix exec approvals handler implementation.
extensions/matrix/src/exec-approvals-handler.test.ts Removes legacy Matrix exec approvals handler tests.
extensions/matrix/src/exec-approval-resolver.ts Switches to shared gateway resolver helper.
extensions/matrix/src/channel.ts Passes channelRuntime through provider startup options.
extensions/matrix/src/approval-native.ts Adds nativeRuntime to approval capability and adapter composition.
extensions/matrix/src/approval-handler.runtime.ts Introduces Matrix native runtime adapter implementation using shared view/actions.
extensions/discord/src/test-support/provider.test-support.ts Updates provider mocks for new exec approval button context wiring.
extensions/discord/src/monitor/provider.ts Registers approval runtime context; wires exec approval UI via context factory; removes handler from lifecycle params.
extensions/discord/src/monitor/provider.test.ts Adds assertions for exec approval context wiring and runtime-context registration.
extensions/discord/src/monitor/provider.lifecycle.ts Removes lifecycle management of legacy exec approvals handler.
extensions/discord/src/monitor/provider.lifecycle.test.ts Updates lifecycle tests after removing exec approvals handler start/stop.
extensions/discord/src/doctor-contract.ts Formatting-only conditional reflow.
extensions/discord/src/channel.ts Passes channelRuntime through provider startup options.
extensions/discord/src/approval-native.ts Adds nativeRuntime via lazy adapter loading for Discord approvals.
docs/plugins/sdk-overview.md Documents new SDK entrypoints.
docs/plugins/sdk-migration.md Adds migration guidance for approval native-runtime + runtime-context registry.
docs/plugins/sdk-channel-plugins.md Documents new nativeRuntime and runtime-context patterns for channel plugins.
docs/plugins/architecture.md Updates plugin architecture docs to mention native runtime handler seam.
docs/.generated/plugin-sdk-api-baseline.sha256 Updates SDK API baseline hashes.

Comment thread src/infra/approval-native-target-key.ts Outdated
Comment thread extensions/matrix/src/approval-handler.runtime.ts Outdated
@greptile-apps

greptile-apps Bot commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves native approval lifecycle assembly into core driven by channel capabilities and runtime contexts, and migrates Discord, Matrix, Slack, and Telegram onto the new seams. It is a well-structured refactor with thorough test coverage, generation-tracked handler lifecycle management in the bootstrap, proper test isolation via clearExecApprovalNativeRouteStateForTest, and a clean lazy-loading boundary for extension code.

Confidence Score: 5/5

This PR is safe to merge; all remaining findings are P2 style suggestions.

Large, well-executed refactor with comprehensive test coverage across all migrated channels, generation-safe concurrency handling in the approval-handler-bootstrap, correct module-level singleton coordination in the route-coordinator with test cleanup, and no P0/P1 logic bugs found. The single observation about async observe wrappers in the lazy adapter is a minor style concern with negligible practical impact.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/infra/approval-handler-runtime.ts
Line: 585-592

Comment:
**Async observe wrappers silently drop lazy-load failures**

The observe callbacks in the lazy adapter are `async`, returning `Promise<void>` where the type expects `void`. Callers in `createChannelApprovalHandlerFromCapability` (e.g., `nativeRuntime.observe?.onDeliveryError?.({...})`) discard the return value, so if `loadOptional` rejects here the error becomes a silent unhandled Promise rejection. This diverges from the non-lazy adapter which calls the callbacks synchronously. In practice the module is already loaded by the time observe callbacks fire (transport methods run first), but if module loading ever fails at this point the failure would be invisible.

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

Reviews (1): Last reviewed commit: "refactor: assemble native approvals from..." | Re-trigger Greptile

Comment thread src/infra/approval-handler-runtime.ts

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

Copy link
Copy Markdown

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: 6a03ea1e1a

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

Comment thread src/infra/approval-native-runtime.ts
@gumadeiras gumadeiras force-pushed the codex/approval-capability-runtime branch 3 times, most recently from 7a0910f to 5d993b4 Compare April 6, 2026 22:46

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

Copy link
Copy Markdown

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: 5d993b4ac6

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

Comment thread src/channels/plugins/approvals.ts
@openclaw-barnacle openclaw-barnacle Bot added channel: googlechat Channel integration: googlechat channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: signal Channel integration: signal channel: zalo Channel integration: zalo channel: feishu Channel integration: feishu labels Apr 6, 2026

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

Copy link
Copy Markdown

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: 35bb7b66f6

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

Comment thread src/gateway/server-channels.ts Outdated
Comment thread src/infra/approval-handler-runtime.ts Outdated

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

Copy link
Copy Markdown

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: 7290bf4862

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

Comment thread src/gateway/server-channels.ts Outdated
eleqtrizit pushed a commit that referenced this pull request Apr 8, 2026
Merged via squash.

Prepared head SHA: b7c20a7
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
sliverp pushed a commit to cxyhhhhh/openclaw that referenced this pull request Apr 21, 2026
…very

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.
sliverp added a commit that referenced this pull request Apr 21, 2026
… onboarding, approval handling (#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 (#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 (#67960) (thanks @cxyhhhhh)

---------

Co-authored-by: Bobby <zkd8907@live.com>
Co-authored-by: neilhwang <neilhwang@tencent.com>
Co-authored-by: sliverp <870080352@qq.com>
gdibble pushed a commit to gdibble/openclaw that referenced this pull request Apr 21, 2026
… 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>
medikoo pushed a commit to medikoo/openclaw that referenced this pull request Apr 24, 2026
… 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>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
Merged via squash.

Prepared head SHA: b7c20a7
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
Merged via squash.

Prepared head SHA: b7c20a7
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
… 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>
zhonghe0615 pushed a commit to zhonghe0615/openclaw that referenced this pull request May 7, 2026
… 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>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
Merged via squash.

Prepared head SHA: b7c20a7
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
… 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>
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
… 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>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
Merged via squash.

Prepared head SHA: b7c20a7
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling bad-barnacle Suppress Barnacle automation on this issue or PR. channel: discord Channel integration: discord channel: feishu Channel integration: feishu channel: googlechat Channel integration: googlechat channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: signal Channel integration: signal channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo docs Improvements or additions to documentation gateway Gateway runtime maintainer Maintainer-authored PR scripts Repository scripts size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants