Architecture
openclaw-weixin adapts the Weixin HTTP/CDN protocol to the OpenClaw channel runtime. The plugin owns login, account state, long polling, message conversion, and outbound media transfer. OpenClaw owns routing, sessions, command authorization, reply generation, hooks, and the unified media store.
The wire-level endpoint and message shapes are documented in the backend API protocol.
Component map
| Component | Responsibility |
|---|---|
index.ts | Validate host compatibility and register the channel |
src/channel.ts | Implement the OpenClaw channel contract and account lifecycle |
src/auth/ | QR login, account persistence, ID compatibility, and pairing |
src/api/ | Build authenticated backend requests and classify failures |
src/monitor/monitor.ts | Poll updates, persist cursors, and schedule inbound work |
src/messaging/process-message.ts | Authorize, route, record, and dispatch one inbound message |
src/messaging/send*.ts | Convert outbound text/media to backend message items |
src/cdn/, src/media/ | Encrypt, upload, download, decrypt, and transcode media |
src/storage/ | Resolve state paths and persist the polling cursor |
Plugin and account lifecycle
flowchart TD
A[index.ts register] --> B[Check OpenClaw host version]
B --> C[Register weixinPlugin]
C --> D{Operation}
D -->|login| E[Start QR session]
E --> F[Wait for confirmation]
F --> G[Persist account and pairing state]
G --> H[Trigger channel reload]
D -->|start account| I[Restore context tokens]
I --> J[Notify backend start]
J --> K[Run monitor loop]
D -->|stop or reload| L[Abort active poll]
L --> M[Notify backend stop]The plugin/channel ID and state layout are compatibility surfaces. A successful login may replace stale account records for the same Weixin user, but it must not silently merge unrelated accounts.
Inbound flow
sequenceDiagram
participant Backend as Weixin backend
participant Monitor as monitorWeixinProvider
participant Processor as processOneMessage
participant Runtime as OpenClaw channel runtime
Monitor->>Backend: getUpdates(cursor, abort signal)
Backend-->>Monitor: messages + next cursor
Monitor->>Monitor: persist cursor and account-scoped context token
Monitor->>Processor: schedule message
Processor->>Processor: handle slash command or download media
Processor->>Runtime: authorize sender and resolve agent route
Processor->>Runtime: record inbound session
Processor->>Runtime: dispatch reply
Runtime-->>Processor: text, media, and item lifecycle eventsOrdinary messages are serialized until OpenClaw accepts the turn, after which polling can admit the next message. Plugin approval commands use a separate lane so an active ordinary turn cannot block approval.
Outbound flow
flowchart LR
A[OpenClaw outbound request] --> B{Account ID supplied?}
B -->|yes| C[Resolve configured account]
B -->|no| D[Resolve by account-scoped context token]
D --> C
C --> E[Check active session]
E --> F[Run message_sending hook]
F -->|cancelled| G[Return without backend send]
F -->|continue| H{Text or media?}
H -->|text| I[Filter markdown and call sendMessage]
H -->|media| J[Download if remote]
J --> K[Encrypt and upload to CDN]
K --> L[Build media message item]
I --> M[Emit message_sent hook]
L --> MWith multiple accounts, an omitted account ID is valid only when exactly one account can be selected. Ambiguous or missing context must fail rather than risk sending from the wrong bot.
Persistent state
Paths are relative to the OpenClaw state directory unless an existing framework override applies.
| Path | Contents |
|---|---|
openclaw-weixin/accounts.json | Registered normalized account IDs |
openclaw-weixin/accounts/<accountId>.json | Token, backend URL, save time, and linked user ID |
openclaw-weixin/accounts/<accountId>.sync.json | getUpdates cursor |
openclaw-weixin/accounts/<accountId>.context-tokens.json | Recipient context tokens for that account |
openclaw-weixin/replay-dedupe/<accountId>.json | Inbound getUpdates replay tombstones (24h; newer hosts may map the path to SQLite) |
credentials/openclaw-weixin-<accountId>-allowFrom.json | Framework pairing allow-list |
openclaw.json | Channel configuration and account overrides |
Loaders retain fallbacks for legacy raw IDs, a legacy single-account credential file, and older sync-buffer paths. Changes to these fallbacks require migration tests.
Failure and privacy boundaries
- Backend HTTP errors are surfaced with actionable context but without raw authorization or context tokens.
- A stale token pauses all requests for that account before polling resumes.
- Long polls receive the gateway abort signal so stop/reload does not wait for a server timeout.
- Media logs must redact URLs and encrypted query parameters.
- Tests and examples use synthetic IDs such as
account-1anduser-1.
Test seams
- API tests replace
fetchand assert request/response boundaries. - Monitor tests mock polling, cursor persistence, context storage, and message processing.
- Message-processing tests use a minimal typed channel-runtime fake.
- Account and storage tests point
OPENCLAW_STATE_DIRto isolated temporary directories. - Shared builders live under
test/helpers/; test-only files must not be emitted intodist/.