This project follows the Keep a Changelog format.
classifyFetchError— network error classification: NewclassifyFetchErrorutility insrc/api/api.tsclassifies fetch-level errors intodns/tcp/tls/timeout/unknown.apiGetFetchandapiPostFetchnow log structured error details (type, description, code) on failure, making network troubleshooting significantly easier. Includes full test coverage for ENOTFOUND, ECONNREFUSED, ETIMEDOUT, SSL/TLS, AbortError, and more.sendMessageresponse validation:sendMessagenow parses the server response (SendMessageRespwithret/errmsg) and throws on non-zeroret, preventing silent delivery failures.
SESSION_EXPIRED_ERRCODE→STALE_TOKEN_ERRCODE: Renamed insrc/api/session-guard.tsto more accurately describe the token-stale condition (the error code -14 indicates a stale/expired token, not a session expiry). All references inmonitor.tsand tests updated.- Error logging improvements:
getUpdateserrors inmonitor.tsnow includeclassifyFetchErrorclassification (type, description, code).- Removed duplicate
errLoglines inmonitor.ts; onlyaLog.errorremains. - CDN upload failure logs (
cdn-upload.ts) now include redacted URL and error cause. downloadRemoteImageToTemp(upload.ts) now logs detailed fetch network errors with cause.- API GET/POST fetch failures (
api.ts) now log redacted URL, timeout, and error classification.
- Minimum host version bumped:
peerDependencies.openclawandinstall.minHostVersionraised from>=2026.3.22to>=2026.5.12.
outbound-hooks.test.ts: New test file coveringapplyWeixinMessageSendingHook(no hooks, content modification, cancellation, error recovery) andemitWeixinMessageSent(no hooks, success, failure with fire-and-forget) scenarios.
pairing.test.tsmock path:vi.mocktarget corrected from"openclaw/plugin-sdk"to"openclaw/plugin-sdk/infra-runtime".api.test.tssendMessage mock response: Success test case mock now returns"{}"instead of"", matching the updatedsendMessagelogic that parses the response body.
- Tool-call progress messages:
WeixinReplyProgressSendersendsTOOL_CALL_START/TOOL_CALL_RESULTprogress messages when the model executes tools. Configurable via thereplyProgressMessageschannel option (default:true). - Abort signal support for in-flight requests:
apiPostFetch/getUpdatesnow accept an externalAbortSignal. When the gateway stops or hot-reloads a channel, the in-flight long-poll is cancelled immediately instead of waiting for the server-side timeout.
iLink-App-Id/iLink-App-ClientVersionheaders were empty /0in production.readPackageJsonresolvedpackage.jsonvia a fixed../../fromimport.meta.url, but the TypeScript build (withindex.tsplussrc/**/*.tsintsconfig.include) emitsdist/src/api/api.js(extrasrc/segment), so the resolved path landed on the non-existentdist/package.jsonand the catch returned{}. Replaced with a walk-up that searches for the plugin's ownpackage.json(validated bynamecontainingopenclaw-weixinor by the presence ofilink_appid), tolerating both dev (src/api/) and built (dist/src/api/) layouts. Adds tests insrc/api/api.test.tscovering the compiled layout, dev layout, nestednode_modules/<dep>/package.jsonshadowing, missing manifest, and malformed manifest.openclaw channels loginexited non-zero when the bot was already bound to this OpenClaw, which caused automated installers (e.g.openclaw-weixin-installer) to report a misleading "首次连接未完成" message and continue past a successful state. The QR poller now returnsalreadyConnected: truefor the server'sbinded_redirectstatus, andauth.logininchannel.tstreats it as a successful no-op (no save, no throw) so the CLI exits cleanly.
- Node 24 / undici compatibility —
TypeError: fetch failedon every request. Drop the manually-setContent-Lengthheader frombuildHeaders. The bundled undici in Node 24 rejects pre-setContent-LengthwithUND_ERR_INVALID_ARG: invalid content-length header, breaking all CGI calls. Lettingfetchcompute it from the request body restores network calls on Node 24. - OpenClaw ≥ 2026.5.x — Weixin runtime initialization timeout restart loop. Replace the module-scope
pluginRuntimeglobal (and removesrc/runtime.tsalong with it) with thectx.channelRuntimeinjected by the gateway per call. The previous global was set during plugin registration, but newer hosts inject a per-call runtime surface, so the global was missing/stale at startup and the channel kept timing out and restarting.
- Dead scripts and shims:
scripts/test-full-upload.ts/scripts/test-upload-url.tsdebug scripts and the unused legacyindex.tsre-exports. No behavior change for consumers.
- Ship compiled runtime in the npm tarball:
dist/is added tofilesandpackage.json#openclaw.runtimeExtensionsis set to["./dist/index.js"]. The host loads the prebuilt JS entry directly instead of relying on source-only TypeScript at install time, which avoids therequires compiled runtime output for TypeScript entry index.tserror on stricter host versions. openclaw.plugin.jsonchannel config: DeclarechannelsandchannelConfigsinopenclaw.plugin.jsonso newer hosts (≥ 2026.4.x) can render the channel selection UI without falling back topackage.json#openclaw.
bot_agentrequest field: Outgoing CGI requests now carry an upstream-app-suppliedbot_agent(UA-stylename/version (comment)grammar, multi-product allowed). Configurable per upstream app via channel config and sanitized bysanitizeBotAgentinsrc/api/api.ts; falls back toOpenClawwhen missing or invalid.local_token_liston QR fetch:fetchQRCodenow posts the most recent localbot_tokens (up to 10), enabling the server to recognize already-bound bots and reply withbinded_redirectinstead of issuing a duplicate session.- Pair-code login flow: Support entering a pair-code (
verify_code) when the QR scan triggers a server-side challenge;waitForWeixinLoginhandlesneed_verifycode/verify_code_blockedstates with a stdin prompt and bounded retries. binded_redirecthandling: New status branch in QR polling that prints✅ 已连接过此 OpenClaw,无需重复连接。and returns gracefully when the scanned bot is already bound to this OpenClaw.- Connection status notify (start/stop): Emit
notifyStartfromgateway.startAccount(after the provider is announced) andnotifyStopfrom a newgateway.stopAccounthook, so the upstream Weixin server can reconcile per-account online state.
- QR login UX: Reword the QR/scan prompts and remove the client-side timeout from
fetchQRCode/startWeixinLoginWithQr— only server / stack limits now bound the long-poll.
- Connection status notify (start/stop) — initial introduction:
notifyStarton account startup andnotifyStopon shutdown via the newgateway.stopAccounthook. (Carried into the 2.3.x line as well.)
- Outbound hook support: Add
message_sending(pre-send interception/modification) andmessage_sent(post-send notification) hook integration for all outbound paths —sendText,sendMedia, and the inbound-replydeliverinprocess-message. Hook logic is extracted into a sharedsrc/messaging/outbound-hooks.tsmodule.
- Cleanup: Remove unused
mediaUrlparameter fromsendWeixinOutboundsignature.
- Markdown filter:
StreamingMarkdownFilternow preserves more Markdown constructs in outbound text.
- Plugin registration re-entrance: Lazy-import
monitorWeixinProviderinsidestartAccountinchannel.tsto avoid pulling in the monitor → process-message → command-auth chain at plugin registration time, which could re-enter the plugin/provider registry before the account starts. - Initialization side effect: Lazy-import
resolveSenderCommandAuthorizationWithRuntime/resolveDirectDmAuthorizationOutcomeinprocess-message.tsto preventensureContextWindowCacheLoadedfrom being triggered during module initialization, which causedloadOpenClawPluginsre-entrance.
- Tool-call outbound path:
sendWeixinOutboundnow appliesStreamingMarkdownFilterto the outbound text, consistent with the model-output path inprocess-message.
- QR login: Remove client-side timeout for
get_bot_qrcode; the request is no longer aborted on a fixed deadline (server / stack limits still apply).
StreamingMarkdownFilter(src/messaging/markdown-filter.ts): outbound text no longer runs through whole-stringmarkdownToPlainTextstripping; a streaming character filter replaces it, so Markdown goes from effectively unsupported to partially supported.
- Outbound text path:
process-messageusesStreamingMarkdownFilter(feed/flush) per deliver chunk instead ofmarkdownToPlainText.
markdownToPlainTextfromsrc/messaging/send.ts(and its tests fromsend.test.ts); coverage moves tomarkdown-filter.test.ts.
- Config reload after login: On each successful Weixin login, bump
channels.openclaw-weixin.channelConfigUpdatedAt(ISO 8601) inopenclaw.jsonso the gateway reloads config from disk, instead of writing an emptyaccounts: {}placeholder. - QR login: Increase client timeout for
get_bot_qrcodefrom 5s to 10s. - Docs: Uninstall instructions now use
openclaw plugins uninstall @tencent-weixin/openclaw-weixin(aligned with the plugins CLI). - Logging:
debug-checklog line no longer includesstateDir/OPENCLAW_STATE_DIR.
openclaw-weixinCLI subcommands (src/weixin-cli.tsand registration inindex.ts). Use the hostopenclaw plugins uninstall …flow instead.
- Resolves the dangerous code pattern warning when installing the plugin on OpenClaw 2026.3.31+ (host plugin install / static checks).