mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 20:30:45 +00:00
* feat(qqbot): implement unified media upload handling and introduce chunked upload support
This commit enhances the media upload functionality by introducing a unified `sendMedia` method that consolidates the previous separate methods for sending images, voice messages, videos, and files. Key changes include:
- Added `uploadChunked` function for future chunked media uploads, currently marked as not implemented.
- Introduced `MediaSource` abstraction to handle various media types (URLs, base64, local files, buffers) uniformly.
- Updated existing media handling logic to utilize the new `sendMedia` method, ensuring consistent media processing across different types.
- Enhanced error handling and validation for media uploads, including MIME type checks and file size limits.
These changes aim to streamline the media upload process and prepare for future enhancements in handling larger files through chunked uploads.
* feat(qqbot): enhance media upload capabilities with chunked upload support
This commit updates the media upload functionality by implementing chunked upload support for larger files. Key changes include:
- Revised the `SKILL.md` documentation to clarify media file size limits and local file path requirements.
- Introduced a new test suite for the chunked media upload functionality, ensuring robust error handling and upload processes.
- Updated the media handling logic to enforce per-file-type upload ceilings, allowing for seamless integration of chunked uploads.
- Enhanced error handling for daily upload limits, providing user-friendly messages when limits are exceeded.
These improvements aim to streamline the media upload process and accommodate larger files effectively.
* feat(qqbot): add C2C streaming API support for message delivery
This commit introduces support for the QQ C2C official `stream_messages` API, enabling single-message typing-style updates. Key changes include:
- Updated the configuration schema to include a new `c2cStreamApi` boolean option for enabling the C2C streaming API.
- Enhanced the `QQBotAccountConfig` interface to accommodate the new streaming option.
- Implemented a `StreamingController` to manage the lifecycle of C2C stream messages, ensuring proper handling of media tags and message boundaries.
- Updated the outbound dispatch logic to utilize the new streaming capabilities, allowing for more dynamic message delivery in one-to-one chats.
These enhancements aim to improve the responsiveness and interactivity of message delivery within the QQBot framework.
* feat(qqbot): implement group chat support and unify adapter/DI architecture
- Implement group message history tracking with pending history buffer
(record on skip, render on @-mention reply)
- Add mention detection and gating: explicit @bot, implicit quote-reply,
ignoreOtherMentions, configurable activation mode (mention/always)
- Add group activation resolution with session store persistence
- Add message queue with per-peer FIFO and group message merging
(batch multiple rapid messages into one merged payload)
- Add deliver debounce to merge rapid outbound text bursts into
single messages, with media flush and maxWait cap
- Add group config resolution: per-group prompt, history limit,
wildcard and specific group overrides
- Enrich history attachments with local paths from processAttachments
so that history context renders downloaded paths instead of ephemeral
QQ CDN URLs
- Merge ports/ directory into adapter/ as single entry point
- Expand EngineAdapters to 5 required ports: history, mentionGate,
audioConvert, outboundAudio, commands
- Remove global register/get singletons in favor of constructor
injection and one-time init
- Add createEngineAdapters() in bridge/gateway.ts as single assembly point
- Extract monolithic buildInboundContext into 11 discrete stages:
access, content, quote, refidx, group-gate, envelope, assembly
- Extract group chat modules: history, mention, activation,
message-gating, deliver-debounce
- Extract config/group.ts, utils/attachment-tags.ts
* feat(qqbot): add /bot-streaming command for C2C message streaming control
This commit introduces the `/bot-streaming` command, allowing users to enable or disable streaming for message delivery in C2C chats. Key changes include:
- Implementation of the `isStreamingConfigEnabled` function to check the current streaming configuration.
- Command handler for `/bot-streaming` that provides usage instructions and manages the streaming state.
- Updates to the command's response messages to inform users of the current streaming status and how to toggle it.
These enhancements aim to improve user experience by providing a straightforward way to manage streaming message delivery in private chats.
* feat(qqbot): extract interaction handler and add remote config query/update support
- Extract INTERACTION_CREATE handler from gateway.ts into a dedicated
interaction-handler.ts module for better separation of concerns
- Add config query (type=2001) and config update (type=2002) interaction
branches that read/write claw_cfg via runtime.config API
- Register INTERACTION intent (1<<26) in FULL_INTENTS to receive
INTERACTION_CREATE events from the gateway
- Add InteractionType constants (CONFIG_QUERY, CONFIG_UPDATE)
- Extend GatewayPluginRuntime with optional config API (loadConfig,
writeConfigFile) for interaction handler access
- Add QQBotAccountConfigView interface for typed config field access
- Extend acknowledgeInteraction to accept optional data payload for
rich ACK responses (e.g. claw_cfg snapshot)
- Export getFrameworkVersion from slash-commands-impl for version
reporting in config snapshots
- Remove unused eslint-disable directive in streaming-media-send.ts
* feat(qqbot): enhance account management and logging capabilities
- Introduced `toGatewayAccount` function to map resolved QQBot accounts to the engine's gateway account structure.
- Added `persistAccountCredentialSnapshot` function to streamline credential backup during gateway events.
- Updated the `qqbotPlugin` to utilize the new account mapping and credential persistence functions, improving the handling of account data.
- Enhanced logging functionality by modifying the `EngineLogger` interface to support metadata in log messages.
- Implemented new commands for managing logs and clearing storage, providing users with better control over their data and system resources.
- Registered multiple built-in commands for improved user interaction, including `/bot-logs` for exporting logs and `/bot-clear-storage` for managing downloaded files.
- Updated configuration schemas to reflect new options and improve clarity for users.
* fix(qqbot): resolve oxlint errors and update raw-fetch allowlist
- Replace unnecessary `else` after `return` in outbound-media-send.ts (6 occurrences)
- Use `Number.parseInt` instead of global `parseInt` in outbound.ts and streaming-media-send.ts
- Use `Number.isNaN` instead of global `isNaN` in register-basic.ts
- Prefer `**` over `Math.pow` in media-chunked.ts
- Convert interface with call signature to function type in commands.port.ts
- Update api-client.ts allowlist line number (108→124) and add media-chunked.ts:552 to raw-fetch allowlist
* docs(qqbot): translate streaming-c2c.ts header comments to English
* feat(qqbot): add voiceMediaTypes
* feat: restore dispatch changes
* fix(qqbot): align test files with updated engine interfaces after rebase
- inbound-attachments.test: replace removed registerAudioConvertAdapter
with AudioConvertPort, pass audioConvert in ProcessContext
- inbound-pipeline.self-echo.test: add required adapters field to
InboundPipelineDeps mock (history, mentionGate, audioConvert,
outboundAudio, commands)
- outbound-dispatch.test: add required skipped field to InboundContext
* fix(qqbot): update test assertions to match refactored engine interfaces
- inbound-pipeline.self-echo.test: self-echo blocking was moved upstream;
update test to expect non-blocked pipeline behavior
- outbound-dispatch.test: TTS voice path now uses unified sendMedia
instead of sendVoiceMessage; add sendMedia mock and update assertion
- format-ref-entry.test: attachment format changed from [image: ...]
to MEDIA: tag syntax via renderAttachmentTags; update expected output
* refactor(qqbot): migrate from deprecated config API to current/replaceConfigFile
Replace all usages of deprecated runtime config methods:
- loadConfig() → current()
- writeConfigFile(cfg) → replaceConfigFile({ nextConfig, afterWrite })
Updated files:
- bridge/narrowing.ts: writeOpenClawConfigThroughRuntime
- adapter/commands.port.ts: ApproveRuntimeGetter type signature
- commands/builtin/register-approve.ts: loadExecConfig, writeExecConfig, reset
- commands/builtin/register-streaming.ts: config read/write
- gateway/interaction-handler.ts: config query/update handlers
- gateway/types.ts: GatewayPluginRuntime.config interface
* feat(qqbot): update package.json
* fix(qqbot): replace deprecated config-runtime import with config-types subpath
Bundled plugin lint requires focused plugin-sdk subpaths.
- gateway.ts: openclaw/plugin-sdk/config-runtime → config-types
- narrowing.ts: openclaw/plugin-sdk/config-runtime → config-types
* feat(qqbot): group chat support, C2C streaming, chunked media upload, and architecture refactor (#70624) (thanks @cxyhhhhh)
---------
Co-authored-by: Bobby <zkd8907@live.com>
Co-authored-by: sliverp <870080352@qq.com>
153 lines
5.9 KiB
TypeScript
153 lines
5.9 KiB
TypeScript
/**
|
|
* Engine adapter layer — all external dependency interfaces unified here.
|
|
*
|
|
* This directory is the **single source of truth** for every interface
|
|
* the engine uses to talk to the outside world.
|
|
*
|
|
* ## Two-layer DI architecture
|
|
*
|
|
* ### Layer 1: EngineAdapters (构造参数注入 — preferred)
|
|
*
|
|
* Used for capabilities consumed within the pipeline call stack.
|
|
* Injected once via {@link CoreGatewayContext.adapters}, threaded
|
|
* through {@link InboundPipelineDeps.adapters}, consumed by stages.
|
|
*
|
|
* - {@link HistoryPort} — group history record/build/clear
|
|
* - {@link MentionGatePort} — mention + command gate evaluation
|
|
* - {@link AudioConvertPort} — inbound SILK→WAV conversion
|
|
* - {@link OutboundAudioPort} — outbound WAV→SILK conversion
|
|
* - {@link CommandsPort} — slash-command version/approve dependencies
|
|
*
|
|
* ### Layer 2: PlatformAdapter (global singleton — leaf utilities)
|
|
*
|
|
* Used by leaf utility functions (`file-utils`, `image-size`,
|
|
* `platform`, `config/resolve`) that sit outside the pipeline and
|
|
* cannot receive a `deps` parameter. Registered once at startup.
|
|
*
|
|
* - {@link PlatformAdapter} — SSRF, secrets, media fetch, temp dir
|
|
*/
|
|
|
|
import type { FetchMediaOptions, FetchMediaResult, SecretInputRef } from "./types.js";
|
|
|
|
// ============ Re-exports (port interfaces) ============
|
|
|
|
export type { HistoryPort, HistoryEntryLike } from "./history.port.js";
|
|
export type {
|
|
MentionGatePort,
|
|
MentionFacts,
|
|
MentionPolicy,
|
|
MentionGateDecision,
|
|
ImplicitMentionKind,
|
|
} from "./mention-gate.port.js";
|
|
export type { AudioConvertPort, OutboundAudioPort } from "./audio.port.js";
|
|
export type { CommandsPort, ApproveRuntimeGetter } from "./commands.port.js";
|
|
|
|
// ============ EngineAdapters (aggregated port injection) ============
|
|
|
|
/**
|
|
* Aggregated adapter ports injected via `CoreGatewayContext.adapters`.
|
|
*
|
|
* All fields are required — the bridge layer must provide every adapter.
|
|
* The engine no longer falls back to built-in implementations.
|
|
*/
|
|
export interface EngineAdapters {
|
|
/** Group history record/build/clear — backed by SDK `reply-history`. */
|
|
history: import("./history.port.js").HistoryPort;
|
|
/** Mention + command gate evaluation — backed by SDK `channel-mention-gating`. */
|
|
mentionGate: import("./mention-gate.port.js").MentionGatePort;
|
|
/** Inbound audio conversion (SILK→WAV, voice detection). */
|
|
audioConvert: import("./audio.port.js").AudioConvertPort;
|
|
/** Outbound audio conversion (WAV→SILK, audio detection). */
|
|
outboundAudio: import("./audio.port.js").OutboundAudioPort;
|
|
/** Slash-command dependencies (version, approve runtime). */
|
|
commands: import("./commands.port.js").CommandsPort;
|
|
}
|
|
|
|
// ============ PlatformAdapter (global singleton — leaf utilities) ============
|
|
|
|
/** Platform adapter that leaf utilities use for framework-specific operations. */
|
|
export interface PlatformAdapter {
|
|
/** Validate that a remote URL is safe to fetch (SSRF protection). */
|
|
validateRemoteUrl(url: string, options?: { allowPrivate?: boolean }): Promise<void>;
|
|
|
|
/** Resolve a secret value (SecretInput or plain string) to a plain string. */
|
|
resolveSecret(value: string | SecretInputRef | undefined): Promise<string | undefined>;
|
|
|
|
/** Download a remote file to a local directory. Returns the local file path. */
|
|
downloadFile(url: string, destDir: string, filename?: string): Promise<string>;
|
|
|
|
/**
|
|
* Fetch remote media with SSRF protection.
|
|
* Replaces direct usage of `fetchRemoteMedia` from `plugin-sdk/media-runtime`.
|
|
*/
|
|
fetchMedia(options: FetchMediaOptions): Promise<FetchMediaResult>;
|
|
|
|
/** Return the preferred temporary directory for the platform. */
|
|
getTempDir(): string;
|
|
|
|
/** Check whether a secret input value has been configured (non-empty). */
|
|
hasConfiguredSecret(value: unknown): boolean;
|
|
|
|
/**
|
|
* Normalize a raw SecretInput value into a plain string.
|
|
* For unresolved references (e.g. `$secret:xxx`), returns the raw reference string.
|
|
*/
|
|
normalizeSecretInputString(value: unknown): string | undefined;
|
|
|
|
/**
|
|
* Resolve a SecretInput value into the final plain-text secret.
|
|
* For secret references, resolves them to actual values via the platform's secret store.
|
|
*/
|
|
resolveSecretInputString(params: { value: unknown; path: string }): string | undefined;
|
|
|
|
/**
|
|
* Submit an approval decision to the framework's approval gateway.
|
|
* Optional — only available when the framework supports approvals.
|
|
* Returns true if the decision was submitted successfully.
|
|
*/
|
|
resolveApproval?(approvalId: string, decision: string): Promise<boolean>;
|
|
}
|
|
|
|
let _adapter: PlatformAdapter | null = null;
|
|
let _adapterFactory: (() => PlatformAdapter) | null = null;
|
|
|
|
/** Register the platform adapter. Called once during startup. */
|
|
export function registerPlatformAdapter(adapter: PlatformAdapter): void {
|
|
_adapter = adapter;
|
|
}
|
|
|
|
/**
|
|
* Register a factory that creates the PlatformAdapter on first access.
|
|
*
|
|
* This decouples adapter availability from side-effect import ordering.
|
|
* The factory is invoked at most once — on the first `getPlatformAdapter()`
|
|
* call when no adapter has been explicitly registered yet.
|
|
*/
|
|
export function registerPlatformAdapterFactory(factory: () => PlatformAdapter): void {
|
|
_adapterFactory = factory;
|
|
}
|
|
|
|
/**
|
|
* Get the registered platform adapter.
|
|
*
|
|
* If no adapter has been explicitly registered yet but a factory was provided
|
|
* via `registerPlatformAdapterFactory()`, the factory is invoked to create
|
|
* and register the adapter automatically.
|
|
*/
|
|
export function getPlatformAdapter(): PlatformAdapter {
|
|
if (!_adapter && _adapterFactory) {
|
|
_adapter = _adapterFactory();
|
|
}
|
|
if (!_adapter) {
|
|
throw new Error(
|
|
"PlatformAdapter not registered. Call registerPlatformAdapter() during bootstrap.",
|
|
);
|
|
}
|
|
return _adapter;
|
|
}
|
|
|
|
/** Check whether a platform adapter has been registered (or can be created from a factory). */
|
|
export function hasPlatformAdapter(): boolean {
|
|
return _adapter !== null || _adapterFactory !== null;
|
|
}
|