Skip to content

feat: wechat channel#1666

Merged
serrrfirat merged 63 commits into
mainfrom
feat/wechat-integration
May 7, 2026
Merged

feat: wechat channel#1666
serrrfirat merged 63 commits into
mainfrom
feat/wechat-integration

Conversation

@hanakannzashi
Copy link
Copy Markdown
Member

@hanakannzashi hanakannzashi commented Mar 26, 2026

Summary

  • Added a first-party channels-src/wechat WASM channel for IronClaw with single-account WeChat DM support: QR login, long-poll getupdates, outbound sendmessage, context_token persistence, and typing indicators.
  • Added host-side interactive WeChat QR login flow in the extension manager and web gateway so setup can complete from the browser and persist wechat_bot_token automatically.
  • Registered and wired the WeChat channel across the registry/setup flow, and renamed internal weixin identifiers to wechat for consistency with the product-facing channel name.
  • Fixed gateway UI state and setup UX so non-pairing channels like WeChat show as Active after QR login, stepper labels localize correctly, and the QR flow uses a separate-tab CTA instead of a broken inline image.

Change Type

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • CI/Infrastructure
  • Security
  • Dependencies

Linked Issue

#1584

Validation

  • cargo fmt
  • cargo clippy --all --benches --tests --examples --all-features
  • Relevant tests pass: cargo test test_extensions_setup_returns_interactive_login_for_wechat --lib --quiet, cargo test test_extensions_wechat_login_poll_broadcasts_auth_completed_and_activates --lib --quiet, cargo test test_wechat_interactive_login_poll_persists_state_and_activates --lib --quiet, cargo test test_inject_channel_settings_uses_owner_scope --lib --quiet, cargo test test_requires_binding_detects_dm_owner_fields --lib --quiet, cargo test test_wechat_active_channel_does_not_require_pairing_status --lib --quiet, cargo test --lib wechat --quiet, cargo check -q, cargo check --manifest-path channels-src/wechat/Cargo.toml -q, cargo check --manifest-path channels-src/wechat/Cargo.toml --target wasm32-wasip2 -q, cargo clippy --all-targets -- -D warnings, cargo clippy --manifest-path channels-src/wechat/Cargo.toml --target wasm32-wasip2 -- -D warnings
  • Manual testing: verified real WeChat QR login via the web gateway, confirmed DM text send/receive works, typing indicators appear, and the extension card/setup modal now reflect the connected state correctly after login

Security Impact

Touches authenticated extension setup/login flows, secret persistence, and outbound HTTP from the WeChat WASM channel to ilinkai.weixin.qq.com and the WeChat CDN. No auth model, sandbox boundary, or secret exposure guarantees were weakened; the channel continues to rely on existing secret injection and HTTP allowlist enforcement.

Database Impact

None. No schema changes or migrations. This reuses existing settings/secrets persistence paths and remains backend-agnostic for both PostgreSQL and libSQL.

Blast Radius

Touches the WASM channel host path, extension manager setup/login flow, web gateway extension UI, and the new WeChat channel implementation. Regressions would most likely affect extension setup state classification, interactive login UX, or WeChat channel activation/polling.

Rollback Plan

Revert the WeChat-specific extension manager/web gateway changes and remove the channels-src/wechat registration/artifacts. If a partial rollback is needed, disable the wechat registry entry and remove the installed wechat.wasm / wechat.capabilities.json artifacts so existing channels continue unaffected while the host/UI changes are reverted.


Review track: C

@github-actions github-actions Bot added scope: channel/web Web gateway channel scope: channel/wasm WASM channel runtime scope: extensions Extension management scope: docs Documentation size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: new First-time contributor labels Mar 26, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the platform's messaging capabilities by adding a new WeChat channel. It introduces a seamless interactive QR code login experience for channels, improving user onboarding and ensuring that channels requiring only authentication (rather than explicit pairing) are correctly reflected in the UI. The changes also include backend logic for managing interactive login sessions and persisting dynamic channel settings.

Highlights

  • New WeChat Channel Integration: Introduced a first-party WASM channel for WeChat, enabling single-account direct messaging with QR login, long-polling for updates, outbound message sending, context token persistence, and typing indicators.
  • Interactive QR Login Flow: Implemented a host-side interactive WeChat QR login flow within the extension manager and web gateway, allowing users to complete setup from the browser and automatically persist the wechat_bot_token.
  • Improved Channel Setup UX: Fixed gateway UI state and setup user experience to correctly display non-pairing channels like WeChat as 'Active' after QR login, localize stepper labels, and utilize a separate-tab CTA for the QR flow instead of a broken inline image.
  • Internal Renaming for Consistency: Renamed internal weixin identifiers to wechat across the registry and setup flow to align with the product-facing channel name.
  • Dynamic Channel Configuration Injection: Enhanced the channel setup process to dynamically inject channel-specific settings, such as the WeChat base URL, into the channel's configuration after interactive login.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new WeChat channel extension for IronClaw, enabling direct-message chat via long polling. The changes include the implementation of WeChat API interactions, state management, and a build script for the WASM component. It also updates the feature parity documentation and extension registry. Significant modifications were made to the core application to support interactive login flows, including new API routes and UI components for handling QR code-based authentication. A new requires_binding field was added to channel capabilities to better categorize activation statuses. Review comments highlight a potential issue where unwrap_or(0) for the ret field in WeChat API responses could mask actual errors, suggesting a more explicit check for Some(0) to prevent silent failures.

Comment thread channels-src/wechat/src/api.rs Outdated
Comment thread channels-src/wechat/src/lib.rs Outdated
Comment thread channels-src/wechat/src/lib.rs Outdated
Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Security-Focused Review: WeChat Channel (iLink Bot)

Large PR from new contributor. Extra scrutiny applied. Important note: this is the iLink Bot protocol (JSON over HTTPS, long-polling), NOT the WeChat Official Account platform (XML, webhooks). No XML parsing, no webhook signature verification needed.

Critical

  1. random_wechat_uin() is predictable (channels-src/wechat/src/api.rs): Uses base64(timestamp_ms % u32::MAX) -- completely guessable. If X-WECHAT-UIN has any security purpose in the iLink protocol, this is trivially spoofable. Use actual randomness or document as non-security-relevant.

High

  1. baseurl from QR response stored without validation (src/extensions/wechat_login.rs): The iLink server's response can set baseurl to any string, which gets persisted and used for all subsequent API calls (including those carrying the bot_token). An attacker who controls the QR response (MITM, rogue QR) could redirect all authenticated traffic to a malicious server. Validate that baseurl is HTTPS and matches known WeChat domains (e.g., *.weixin.qq.com, *.wechat.com).

  2. Bot token received without provenance verification: QR login receives bot_token from iLink server with no certificate pinning. Acceptable for most channels but WeChat operates in environments where MITM is a real concern. Document the trust assumption.

Medium

  1. Client-side poll loop with 0ms delay (src/channels/web/static/app.js): setTimeout(fn, 0) on pending/scanned/refreshed status creates a tight loop hammering the server. Use 2-5 second backoff.

  2. No message deduplication: Messages from getupdates are emitted without checking if already processed. Track message_id in persistent state to prevent replay.

  3. CSP relaxed globally (src/channels/web/server.rs): img-src adds liteapp.weixin.qq.com for all pages, not just WeChat setup. Acceptable for MVP but should be documented.

Architecture -- Good

  • Correctly follows WASM channel pattern (wit_bindgen, Guest trait, all callbacks)
  • Module-owned initialization per CLAUDE.md
  • requires_binding addition for QR-login-based channels is clean
  • No new dependencies beyond standard ones (wit-bindgen, serde, base64)
  • No .unwrap()/.expect() in production code

Minor

  1. inject_channel_secrets_into_config scope change (src/channels/wasm/setup.rs): Changed from hardcoded "default" to owner_id. Verify Feishu secrets still load correctly -- they may have been stored under the old "default" scope.

Verdict

Request changes. The baseurl validation (item 2) is the most important -- it's an open redirect for all authenticated API traffic. The client-side polling backoff (item 4) is also needed to prevent self-DoS. The rest are lower priority but should be addressed.

Good first contribution overall -- architecture is sound, code quality is high, and the iLink Bot protocol choice is well-suited to the WASM channel model.

@hanakannzashi hanakannzashi added the WIP Work in progress, don't merge label Mar 26, 2026
@github-actions github-actions Bot added scope: agent Agent core (agent loop, router, scheduler) scope: dependencies Dependency updates labels Mar 26, 2026
Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Re-review: WeChat channel (3 new commits)

One fix, three findings still open:

# Finding Status
1 random_wechat_uin predictable Not addressed -- still timestamp-based
2 base_url stored without validation Not addressed -- no URL validation
3 Bot token without provenance check Not addressed
4 Client poll 0ms delay Fixed -- poll_interval_ms defaults 30s with .max(30_000) enforcement
5 No message dedup Partially -- cursor persistence helps, no client-side ID dedup

New image messaging code interpolates user-influenced params (encrypted_query_param, filekey) directly into CDN URLs without sanitization -- minor additional concern.

Items 1-2 remain blocking. The base_url validation is the highest priority -- it's an open redirect for all authenticated API traffic.

Comment thread src/extensions/manager.rs
Comment thread crates/ironclaw_gateway/static/js/surfaces/extensions.js
Comment thread src/extensions/manager.rs
@hanakannzashi hanakannzashi removed the WIP Work in progress, don't merge label Apr 28, 2026
@henrypark133 henrypark133 changed the base branch from staging to main May 1, 2026 06:18
Copy link
Copy Markdown
Contributor

@think-in-universe think-in-universe left a comment

Choose a reason for hiding this comment

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

I'd like to approve this PR as testing work well.

I didn't go through all the code changes by myself due to time constraint, but don't want to block the feature.

@serrrfirat serrrfirat requested a review from zmanian May 7, 2026 09:29
@serrrfirat serrrfirat enabled auto-merge May 7, 2026 09:30
…lper

Move all SILK decoding into a workspace-excluded crate
(`crates/ironclaw_silk_decoder/`) and invoke it from the host as a
subprocess over stdin/stdout. The main `cargo build` no longer pulls
`silk-rs` (and its `bindgen`/`libclang` build chain), addressing the
review concern flagged on PR #1666 by zmanian and Copilot.

Behavior is unchanged when the helper is installed: WeChat voice
attachments are transcoded SILK→WAV and `mime_type` rewritten to
`audio/wav`. Without the helper, the existing graceful-degrade path
preserves raw `audio/silk` bytes and logs that the decoder is missing.

The host looks up the helper in this order:
1. `IRONCLAW_SILK_DECODER` env var
2. Sibling of the running `ironclaw` executable
3. `ironclaw-silk-decoder` on `$PATH`

Caller-level tests use a Unix shell stub binary (under `tempfile`) to
drive the spawn-binary path through `maybe_transcode_wechat_silk_attachment`,
covering success, decoder-failure (caller fallback), non-RIFF rejection,
and empty-input cases.

WIP: this is the native-binary variant. A WASM-target follow-up may
replace the native binary with a single `silk-decoder.wasm` artifact
matching the WeChat channel distribution pattern.

https://claude.ai/code/session_01C5cjqPY1PkVRexraKWmmsz
claude added 4 commits May 7, 2026 11:18
Add an explicit empty `[workspace]` table so cargo treats the crate as
its own root rather than trying to inherit the IronClaw workspace.
Without this, `cargo build --manifest-path crates/ironclaw_silk_decoder/Cargo.toml`
errors with "current package believes it's in a workspace when it's not".

Commit the standalone Cargo.lock for reproducible builds of the helper.

https://claude.ai/code/session_01C5cjqPY1PkVRexraKWmmsz
…tants

Two lints the Clippy job flagged on the silk-decoder host integration:

1. `question_mark` — replace `if let Err(error) = writer.await.map_err(...)?
   { return Err(error); }` with `writer.await.map_err(...)??;`. Same
   semantics, idiomatic.
2. `assertions_on_constants` — promote the runtime
   `assert!(MAX_DECODED_WAV_BYTES >= MAX_ATTACHMENT_BYTES)` invariant
   to a `const _: () = assert!(...)` so it's checked at compile time
   and doesn't burn a test slot.

No behavioral change.

https://claude.ai/code/session_01C5cjqPY1PkVRexraKWmmsz
Three async stub-binary tests and two resolve-command tests all mutate
the same `IRONCLAW_SILK_DECODER` env var. Under cargo's parallel
test threads they trampled each other, causing flaky failures (each
test passed in isolation, two failed when run together). The CI
"Run Tests" job hit this on the latest commit.

Adopt the existing `crate::config::helpers::lock_env()` mutex pattern
already used by shell.rs, llm/recording.rs, pairing/approval.rs,
restart.rs, etc. EnvGuard now holds the lock as a field so the env
var stays restored before another test takes the lock; ordering is
guaranteed because Drop runs the custom restoration body before
field-drop releases the mutex.

Verified locally: all 13 channels::wasm::attachment_hydration tests
pass with the default parallel test runner.

https://claude.ai/code/session_01C5cjqPY1PkVRexraKWmmsz
silk-codec is a sibling fork of the same upstream Skype SILK SDK
sources used by silk-rs, but newer and better-maintained:

- bindgen 0.72 (vs silk-rs's 0.59 from 2021)
- cc 1.2 (vs older)
- thiserror 2.0
- Identical decode_silk(src, sample_rate) -> Result<Vec<u8>, SilkError>
  signature; both implementations skip the 0x02 Tencent flag and
  require the "#!SILK_V3" header, so behavior on WeChat voice notes
  is unchanged.

This is purely a tooling refresh in the standalone helper crate. The
main IronClaw build still has zero SILK / libclang dependency. The
host's subprocess invocation, capability gating, size caps, and
graceful-degrade-to-raw-SILK fallback are all unchanged.

Verified locally:
- crates/ironclaw_silk_decoder: 6/6 unit tests pass
- ironclaw lib: 13/13 attachment_hydration tests pass (parallel runner)
- cargo fmt --check: clean

Note: PR #1666's spec-compatibility comment in Cargo.toml previously
named silk-rs; the renamed comment in this commit reflects the new
dependency. No other host code references either crate by name.

https://claude.ai/code/session_01C5cjqPY1PkVRexraKWmmsz
Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Approving — the silk-decoder isolation from #3341 has been folded into this branch, so the main cargo build no longer requires libclang while WeChat voice memos are still transcribed end-to-end via the standalone crates/ironclaw_silk_decoder/ helper. Verified the silk-decoder crate, build.sh, and the host-side subprocess invocation are all present at HEAD 204bd3e23.

@serrrfirat serrrfirat added this pull request to the merge queue May 7, 2026
Merged via the queue into main with commit f948e11 May 7, 2026
77 checks passed
@serrrfirat serrrfirat deleted the feat/wechat-integration branch May 7, 2026 14:39
This was referenced May 7, 2026
@ironclaw-ci ironclaw-ci Bot mentioned this pull request May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: experienced 6-19 merged PRs risk: medium Business logic, config, or moderate-risk modules scope: agent Agent core (agent loop, router, scheduler) scope: channel/wasm WASM channel runtime scope: channel/web Web gateway channel scope: channel Channel infrastructure scope: dependencies Dependency updates scope: docs Documentation scope: extensions Extension management scope: llm LLM integration scope: tool/builtin Built-in tools scope: tool Tool infrastructure size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants