Skip to content

feat(messaging): add WeChat (personal) channel for OpenClaw#3186

Merged
cv merged 28 commits into
NVIDIA:mainfrom
sandl99:u/sdang/wechat-integration
May 15, 2026
Merged

feat(messaging): add WeChat (personal) channel for OpenClaw#3186
cv merged 28 commits into
NVIDIA:mainfrom
sandl99:u/sdang/wechat-integration

Conversation

@sandl99

@sandl99 sandl99 commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds WeChat as a first-class messaging channel for OpenClaw agents (issue #3006). The supported mode in this release is personal WeChat via Tencent's iLink gateway (bot_type=3), captured through a host-side QR scan during nemoclaw onboard so the in-sandbox plugin
starts already-logged-in and no bot token is ever baked into the image. WeChat Official Account and WeCom are explicitly out of scope.

Acceptance Criteria (#3006)

  • wechat is listed as a supported messaging channel in CLI help/docs and channel listing output
  • A new or existing NemoClaw sandbox can be configured for WeChat without custom user-maintained bridge code (narrower workflow: new sandbox via nemoclaw onboard; existing sandbox via re-onboard. The post-onboard channels add wechat limitation is documented per
    criterion 5.)
  • WeChat credentials are handled through OpenShell provider plumbing with the same secret-redaction guarantees as other messaging integrations
  • Onboard/rebuild preserves and applies WeChat channel configuration consistently (onboard ✅; rebuild durability after nemoclaw onboard --fresh is a known follow-up — see "Known follow-ups" below)
  • Channel add/remove/start/stop behavior is implemented or the first-supported limitations are explicitly documented
  • Required WeChat network policy presets are added and covered by tests
  • Docs include setup steps, credential requirements, supported WeChat mode, troubleshooting notes, and any known limitations
  • Tests cover provider registration, config generation, policy inclusion, credential redaction, channel persistence, and at least one mocked or integration-safe send/receive path (all unit-test coverage is in; the integration-safe E2E for the QR flow is the remaining
    piece — see "Known follow-ups" below)

Changes

Channel registry and CLI surface

  • nemoclaw <sandbox> channels list shows WeChat alongside Telegram, Discord, and Slack.
  • Pluggable HOST_QR_LOGIN_HANDLERS registry (src/lib/host-qr-handlers.ts) — onboard dispatches by loginMethod so future host-qr channels register one handler and the dispatch code stays channel-agnostic.

Host-side iLink QR login

  • src/ext/wechat/qr.ts — iLink HTTP client (fetchWechatQrSession, pollWechatQrStatus, WechatQrError) with a 10 s bootstrap timeout, the documented iLink-App-Id / iLink-App-ClientVersion headers, abort + 5xx fall-through as benign wait, and strict validation
    of the parsed status against the WechatQrStatus union.
  • src/ext/wechat/login.ts — orchestrator: 8-minute deadline, ≤3 QR refreshes, handles scaned / scaned_but_redirect (rebinds the per-account base URL) / expired / confirmed, with a [wechat] diagnostic sink silenced via NEMOCLAW_WECHAT_QUIET=1. Returns a
    discriminated WechatLoginResult.
  • The token, accountId, baseUrl, and userId come out of the QR result; the operator user ID is auto-added to WECHAT_ALLOWED_IDS.

Credential and secret handling

  • Token persisted as the <sandbox>-wechat-bridge OpenShell provider via upsertMessagingProviders; SHA-256 recorded in registry.<sandbox>.providerCredentialHashes.
  • openshell:resolve:env:WECHAT_BOT_TOKEN placeholder substituted at egress by the L7 proxy — token is never on disk inside the running container.
  • WECHAT_BOT_TOKEN registered in KNOWN_CREDENTIAL_ENV_KEYS for redaction.
  • nemoclaw-blueprint/scripts/wechat-diagnostics.js strips bot_token=, "bot_token":, Bearer, and api_key patterns from any stderr it routes.

Sandbox image and config generation

  • Dockerfile.base pre-installs @tencent-weixin/openclaw-weixin@2.4.2 --pin as the sandbox user so onboard never reaches the public npm registry for it.
  • scripts/generate-openclaw-config.py activates plugins.entries.openclaw-weixin.enabled=true unconditionally and invokes scripts/seed-wechat-accounts.py as a subprocess. The chicken-and-egg with openclaw plugins install is gone — plugin install moved into the base.
  • scripts/seed-wechat-accounts.py writes the upstream plugin's account store at image-build time: <stateDir>/openclaw-weixin/accounts/<id>.json (token = openshell:resolve:env:WECHAT_BOT_TOKEN), <stateDir>/openclaw-weixin/accounts.json (append-only index), and
    patches channels.openclaw-weixin.accounts.<id>.enabled=true into openclaw.json. Silent no-op when no accountId was captured.
  • NEMOCLAW_WECHAT_ENABLED build-arg gate removed everywhere — the seed self-gates on accountId presence.

Network policy

  • nemoclaw-blueprint/policies/presets/wechat.yaml opens *.wechat.com:443 and *.weixin.qq.com:443 (both wildcards needed because iLink uses per-account dynamic base URLs behind IDC redirects).

Channels-add limitation (documented narrower workflow)

  • nemoclaw <sandbox> channels add wechat exits with an explicit error pointing to nemoclaw onboard. The paste-prompt path cannot capture a WeChat bot token (it only exists after the iLink QR handshake) and has no slot for the accountId/baseUrl/userId metadata the
    bridge needs. channels remove/start/stop wechat work normally for existing WeChat-enabled sandboxes.

Docs

  • docs/manage-sandboxes/messaging-channels.md now covers WeChat end-to-end: channel-requirements row, supported-mode statement, QR-login flow, OpenShell provider naming, DM-only model + WECHAT_ALLOWED_IDS auto-population, NEMOCLAW_WECHAT_QUIET knob, the channels add wechat limitation with reason and workaround, cross-sandbox accountId uniqueness rule, and the third-party iLink ToS / data-residency note.
  • docs/reference/commands.md adds the NEMOCLAW_WECHAT_QUIET env-var row.

Tests (~11 files)

  • Provider registration: src/lib/messaging-conflict.test.ts.
  • Config generation: test/generate-openclaw-config.test.ts (plugin entry, seed chain, channels block).
  • Network policy: test/policies.test.ts (*.wechat.com + *.weixin.qq.com).
  • Credential redaction: test/credentials.test.ts, test/wechat-diagnostics.test.ts.
  • Session persistence: src/lib/state/onboard-session.test.ts (wechatConfig parse/persist/round-trip).
  • Host-qr orchestration + iLink client: src/ext/wechat/login.test.ts, src/ext/wechat/qr.test.ts.
  • Channel registration: src/lib/sandbox-channels.test.ts, src/lib/agent/defs.test.ts.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Verification

  • npx prek run --all-files passes
  • npm test passes
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Known follow-ups (not blocking #3006 closure):

  • E2E test for the QR flow (criterion 8 partial — PR description's pre-existing TODO).

Hermes scope: WeChat support for the Hermes agent has been pulled from this PR and will ship as a follow-up — agents/hermes/ is untouched in the merge diff.


Signed-off-by: San Dang sdang@nvidia.com

Summary by CodeRabbit

  • New Features

    • Added WeChat (personal) messaging channel integration alongside existing Telegram, Discord, and Slack channels
    • Implemented host-side QR login flow during onboarding with per-account credential capture
    • Added per-account WeChat state persistence and resumption across sandbox rebuilds
  • Documentation

    • Documented WeChat channel requirements, QR login setup, and DM-only delivery constraints
    • Added NEMOCLAW_WECHAT_QUIET environment variable for suppressing host-side diagnostic output
    • Updated messaging channels guide with WeChat setup procedures and troubleshooting
  • Infrastructure

    • Updated Docker build pipeline to incorporate WeChat plugin and account state seeding
    • Added WeChat-specific network policies for API access
    • Extended credential management to support WeChat tokens and account metadata

Review Change Stack

@copy-pr-bot

copy-pr-bot Bot commented May 7, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

This PR adds WeChat (personal) as a first-class messaging channel to NemoClaw, enabling operators to authenticate via iLink-based QR login and send/receive messages through the OpenClaw-weixin plugin. The implementation spans host-side QR client logic, build-time account seeding, runtime diagnostics, network policies, and multi-layer integration with onboarding, sandbox creation, and state persistence.

Changes

WeChat Messaging Channel Integration

Layer / File(s) Summary
Channel Definition & Types
src/lib/sandbox-channels.ts, src/lib/sandbox-channels.test.ts
ChannelDef adds optional loginMethod: "host-qr" field; KNOWN_CHANNELS gains new wechat entry with DM-only access and host-QR login, with test coverage for channel properties and list materialization.
iLink QR Client
src/ext/wechat/qr.ts, src/ext/wechat/qr.test.ts
Implements iLink bootstrap and long-poll endpoints with typed contracts (WechatQrSession, WechatQrStatusResponse), error hierarchy (WechatQrError), timeout/abort handling, and comprehensive unit tests for session fetching and status polling.
QR Login Orchestration
src/ext/wechat/login.ts, src/ext/wechat/login.test.ts
End-to-end QR login orchestration with terminal rendering (using qrcode-terminal), polling loop, IDC redirect support, QR refresh budgeting, credential extraction, and deterministic timeout/abort test scenarios.
Host QR Handler Registry
src/lib/host-qr-handlers.ts
Generic HostQrLoginResult discriminant with kind (ok/timeout/expired/aborted/error) and optional metadata (token, extraEnv, defaultUserId, summary); WeChat handler adapter that dispatches to orchestration and maps credentials into standardized output.
Onboarding Session State
src/lib/state/onboard-session.ts, src/lib/state/onboard-session.test.ts
Adds disabledChannels and wechatConfig (with accountId, baseUrl, userId) to persisted session model, includes parsing/validation helper, and test coverage for save/load round-trips and explicit null clearing.
Account Seeding Script
scripts/seed-wechat-accounts.py, test/seed-wechat-accounts.test.ts
Python entrypoint that decodes NEMOCLAW_WECHAT_CONFIG_B64, atomically writes per-account credentials and index files under .openclaw/openclaw-weixin, and conditionally patches openclaw.json with channel registration, including functional test coverage for resilience and channel-state gating.
OpenClaw Config Generation
scripts/generate-openclaw-config.py, test/generate-openclaw-config.test.ts
Generates OpenClaw JSON with unconditional openclaw-weixin plugin enable, documents non-decoding of NEMOCLAW_WECHAT_CONFIG_B64, conditionally invokes seed script after plugin install, with test assertions for config shape.
Docker Build Integration
Dockerfile
Copies seed-wechat-accounts.py into image, declares ARG NEMOCLAW_WECHAT_CONFIG_B64 (empty Base64 JSON default), promotes it to ENV, adds post-config RUN step to install openclaw-weixin plugin and seed accounts, sets ENV NPM_CONFIG_OFFLINE=true, and creates wechat state directory.
Sandbox Build Context
src/lib/sandbox-build-context.ts
Stages scripts/seed-wechat-accounts.py into optimized build context so Docker RUN steps can access it during image build.
Runtime Diagnostics
nemoclaw-blueprint/scripts/wechat-diagnostics.js, test/wechat-diagnostics.test.ts
Installs process-wide idempotent diagnostic wrapper that hooks HTTP/stderr to emit provider-ready signals on iLink 2xx responses and inference-failure annotations with credential redaction and 600-char truncation, with test coverage for idempotency, path/host matching, and account-id defaulting.
Network Policies & Presets
nemoclaw-blueprint/policies/presets/wechat.yaml, test/policies.test.ts
Defines wechat policy preset with REST egress to ilinkai.weixin.qq.com and ilinkai.wechat.com on 443, GET/POST allowed on /**, enforcement enabled, scoped to Node.js binaries, with YAML schema and endpoint tests.
Channel Infrastructure
src/lib/messaging-conflict.ts, src/lib/messaging-channel-config.test.ts, src/lib/credentials/store.ts, agents/openclaw/manifest.yaml, test/credentials.test.ts
Adds wechat to provider-suffix mapping and known-channels, extends credential/config env allowlists with WECHAT_BOT_TOKEN and WECHAT_ALLOWED_IDS, declares wechat support in agent manifest, and adds conflict detection/backfill tests.
Onboarding Channel Setup
src/lib/actions/sandbox/policy-channel.ts
Extracts paste-token acquisition into separate helper; adds acquireHostQrChannel for host-QR login with cached-token reuse, non-interactive rejection, handler dispatch, and metadata stashing; auto-applies policy preset on channel add with status logging and removal hints.
Sandbox Creation & Persistence
src/lib/onboard.ts
Maps WECHAT_BOT_TOKEN to wechat channel; captures account metadata from env or session; persists wechatConfig to session; conditionally encodes into NEMOCLAW_WECHAT_CONFIG_B64; manages disabledChannels across sandbox operations; in setupMessagingChannels, routes host-QR channels through handler dispatch with error normalization and env/credential persistence.
Rebuild & Resume State
src/lib/actions/sandbox/rebuild.ts
Loads onboarding session pre-destructive rebuild; restores WeChat account metadata to process.env only when unset; preserves disabledChannels across delete/recreate flow; on resume, computes wechatConfigChanged to gate sandbox recreation on account metadata drift.
Sandbox Security Auditing
src/lib/state/sandbox.ts, test/security-sandbox-tar-traversal.test.ts, test/snapshot.test.ts
Whitelists specific npm symlinks in backup audit by matching source path and target; updates tar extraction security checks with allowlist validation; adds comprehensive test coverage for symlink escape detection and allowlist matching.
Test Fixtures & Infrastructure
src/lib/actions/inference-set.test.ts, test/sandbox-provisioning.test.ts
Updates session fixture to include wechatConfig and disabledChannels fields; extends provisioning test coverage for seed-script file permissions.
Documentation
docs/manage-sandboxes/messaging-channels.md, docs/reference/commands.md, Dockerfile.base
Documents WeChat as supported messaging channel with host-QR login flow, build-time account persistence, DM allowlisting, provider state, troubleshooting, and env variable reference (NEMOCLAW_WECHAT_QUIET); adds version-bump requirement for base image rebuilds.
Dependencies
package.json
Adds qrcode-terminal NPM dependency for terminal QR rendering during host-side login.

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 A WeChat connection blooms so bright,
With QR codes scanned in terminal light!
iLink protocols dance, diagnostics sing,
And accounts are seeded on image-build wing—
Now messages flow from host to the nest,
A hopping good integration, simply the best! 🎊

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@wscurran wscurran added integration: wechat WeChat integration behavior NemoClaw CLI and removed integration: wechat WeChat integration behavior labels May 7, 2026
@wscurran

wscurran commented May 7, 2026

Copy link
Copy Markdown
Contributor

✨ Thanks for submitting this PR that adds WeChat as a first-class DM-only messaging channel. This change involves integrating the @tencent-weixin/openclaw-weixin plugin and modifying the onboarding process to capture the bot token via a host-side QR scan.

Ping @ericksoa when ready for review.

@wscurran wscurran added integration: wechat WeChat integration behavior and removed enhancement: integration labels May 7, 2026
@copy-pr-bot

copy-pr-bot Bot commented May 10, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@sandl99 sandl99 force-pushed the u/sdang/wechat-integration branch from 27a041d to a82508a Compare May 10, 2026 14:56

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 8

🧹 Nitpick comments (3)
nemoclaw-blueprint/policies/presets/wechat.yaml (1)

18-38: Run the network-policy E2E for this preset before merge.

This adds wildcard egress on two public TLDs, so a selective network-policy-e2e run is the safest check that deny-by-default, whitelist, hot-reload, and SSRF behavior still hold with the new preset.

As per coding guidelines "nemoclaw-blueprint/policies/**: This directory contains network policy definitions and presets. Changes affect sandbox egress rules and SSRF filtering. E2E test recommendation: network-policy-e2e."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nemoclaw-blueprint/policies/presets/wechat.yaml` around lines 18 - 38, Run
the network-policy-e2e test targeting the new preset named "wechat_bridge" under
network_policies to validate wildcard egress to hosts "*.wechat.com" and
"*.weixin.qq.com"; specifically execute the selective network-policy-e2e suite
and verify deny-by-default, whitelist enforcement, hot-reload behavior, and SSRF
filtering still work with the added endpoints and binaries entries (check
endpoints -> host entries and binaries paths) before merging.
src/ext/wechat/login.ts (1)

136-251: 🏗️ Heavy lift

Split the login state machine into smaller helpers.

runWechatHostQrLogin() is carrying bootstrap, polling, redirect handling, expiry recovery, timeout/abort checks, and user messaging in one branch-heavy loop. Pulling the per-state transitions into helpers would make the protocol easier to extend and safer to change.

As per coding guidelines, **/*.{js,ts,tsx}: Keep function complexity low.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ext/wechat/login.ts` around lines 136 - 251, The runWechatHostQrLogin
function is too large and stateful; split the per-state logic into small helper
functions to reduce complexity: extract the polling loop into a short
coordinator in runWechatHostQrLogin that calls state handlers like
handleWait(status), handleScanned(status), handleRedirect(status),
handleExpired(session), and handleConfirmed(status) (or similar names) so each
helper owns its transitions and side effects (calls to opts.sleep, opts.log,
emitQr, fetchWechatQrSession, and updating
currentBaseUrl/scannedAnnounced/qrRefreshCount). Keep the existing calls to
pollWechatQrStatus, fetchWechatQrSession, emitQr, errorMessage, and debug,
ensure abort/timeout checks remain in the coordinator, and return the same
WechatLoginResult values from the helpers (or bubble them up) so behavior is
unchanged while reducing complexity in runWechatHostQrLogin.
scripts/generate-openclaw-config.py (1)

400-404: 💤 Low value

Consider removing unused _wechat_config parsing or adding a validation check.

The _wechat_config variable is decoded but never used in this file. While the comment block at lines 450-467 explains that WeChat channel configuration is handled by seed-wechat-accounts.py, parsing the config here without using it may cause confusion.

Consider either:

  1. Removing the decode entirely since it's not needed here, or
  2. Adding a validation check (e.g., verifying required fields exist) if early validation is intended
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate-openclaw-config.py` around lines 400 - 404, The decoded but
unused variable _wechat_config (created from
env.get("NEMOCLAW_WECHAT_CONFIG_B64") via base64.b64decode and json.loads)
should either be removed to avoid confusion or validated if early checks are
intended: either delete the whole _wechat_config parsing expression, or after
creating _wechat_config verify required keys (e.g., "app_id", "app_secret" or
whatever seed-wechat-accounts.py expects) and raise/log a clear error if missing
so the intent is explicit; update corresponding comments to reference
seed-wechat-accounts.py if you keep validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agents/hermes/config/messaging-config.ts`:
- Around line 46-63: The code writes WeChat values directly into envLines via
envLines.push (e.g., WECHAT_ALLOWED_USERS, WECHAT_ACCOUNT_ID, WECHAT_BASE_URL,
WECHAT_USER_ID) and can be poisoned by unescaped CR/LF or other unsafe
characters; before pushing any allowedIds.wechat or wechatConfig.* values,
validate/escape them (reject or strip CR/LF and other control/newline chars, or
replace with safe encoded form) and normalize to strings so the resulting `.env`
entries cannot inject extra lines or break parsing; apply this check where
envLines.push is called for WECHAT_* values.

In `@agents/hermes/policy-additions.yaml`:
- Around line 274-294: The wechat policy block is missing the "*.wechat.com"
endpoint and its binaries differ from the wechat preset; update the wechat
policy's endpoints list to include a second entry with host "*.wechat.com" (port
443, protocol rest, enforcement enforce, same allow rules as the existing
"*.weixin.qq.com" entry) and align the binaries list to match the preset by
using /usr/local/bin/node and /usr/bin/node (remove the python entries); locate
these changes in the wechat policy's endpoints and binaries arrays.

In `@nemoclaw-blueprint/scripts/wechat-diagnostics.js`:
- Around line 84-87: The isWechatHost function currently only matches
'weixin.qq.com' and its subdomains; update it to also recognize 'wechat.com' and
its subdomains by extending the return condition in isWechatHost to check for
hostname === 'wechat.com' || hostname.endsWith('.wechat.com') (keeping the
existing checks for 'weixin.qq.com' and its subdomains and the initial falsy
hostname guard).

In `@src/ext/wechat/login.ts`:
- Around line 154-155: qrRefreshCount is initialized to 1 which, combined with
the pre-check increment, causes an off-by-one allowing only two QR refreshes
instead of three; change the initialization of qrRefreshCount to 0 (or move the
increment to after the limit check) so that the increment/check logic in the QR
refresh path permits three refreshes, and apply the same fix to the other
occurrence referenced by the block around the qrRefreshCount usage (the second
QR refresh logic at the later block).

In `@src/ext/wechat/qr.ts`:
- Around line 223-228: The code currently JSON.parse(text) and only checks that
parsed?.status is a string, which allows other fields to be wrong; update the
parsing logic where parsed is produced (the block creating parsed as
WechatQrStatusResponse) to fully validate the expected shape instead of casting:
verify required fields and their types/allowed values on parsed (e.g., status is
one of the known status strings, any expected fields like baseurl/session/token
are present and of the correct primitive types) and if validation fails throw
new WechatQrError("parse", "...") with a clear message; keep the returned value
typed as WechatQrStatusResponse only after validation succeeds so downstream
code that consumes parsed (the parsed variable / WechatQrStatusResponse) cannot
receive malformed payloads.
- Around line 115-133: fetchWechatQrSession lacks an abort/timeout: wrap the
transport call in an AbortController with a configurable timeout (e.g.,
opts.timeoutMs with a sensible default like 10000ms), pass controller.signal
into transport(url.toString(), { method: "GET", headers: buildIlinkHeaders(),
signal }), set a timer that calls controller.abort() after the timeout and
clears the timer after a response or error, and catch an AbortError to rethrow a
WechatQrError("network", "WeChat QR init request timed out") so the caller gets
a clear timeout path; mirror the pattern used in pollWechatQrStatus for
signal/timeout handling and reference symbols fetchWechatQrSession, transport,
WechatQrClientOptions, and buildIlinkHeaders.

In `@src/lib/host-qr-handlers.ts`:
- Around line 40-65: The wechat handler can throw during the lazy require or
when calling runWechatHostQrLogin, causing an unstructured rejection; wrap the
require/import and the await runWechatHostQrLogin() call in a try/catch inside
the wechat async function (the handler named "wechat" that calls
runWechatHostQrLogin) and on any thrown error return a normalized { kind:
"error", message: String(err) } result (or include err.message) so failures
become structured onboarding errors instead of crashing the registry.

In `@src/lib/onboard.ts`:
- Around line 7790-7838: The code treats a channel as "already configured" if
getMessagingToken(ch.envKey) returns a token, but that skips restoring any
previously-captured host-QR metadata (extraEnv like
WECHAT_ACCOUNT_ID/WECHAT_BASE_URL/WECHAT_USER_ID), causing rebuild/resume to
lose required fields; fix by persisting extraEnv when a host-QR handler succeeds
(in the same place that calls saveCredential and sets process.env in the host-qr
branch) and rehydrate those saved extraEnv entries before early-return when
getMessagingToken(ch.envKey) is truthy (i.e., add logic near the
getMessagingToken check to load and restore those saved env entries into
process.env or the same credential store used by createSandbox); reference
symbols: getMessagingToken, HOST_QR_LOGIN_HANDLERS, saveCredential,
createSandbox, and the channel object ch/env keys.

---

Nitpick comments:
In `@nemoclaw-blueprint/policies/presets/wechat.yaml`:
- Around line 18-38: Run the network-policy-e2e test targeting the new preset
named "wechat_bridge" under network_policies to validate wildcard egress to
hosts "*.wechat.com" and "*.weixin.qq.com"; specifically execute the selective
network-policy-e2e suite and verify deny-by-default, whitelist enforcement,
hot-reload behavior, and SSRF filtering still work with the added endpoints and
binaries entries (check endpoints -> host entries and binaries paths) before
merging.

In `@scripts/generate-openclaw-config.py`:
- Around line 400-404: The decoded but unused variable _wechat_config (created
from env.get("NEMOCLAW_WECHAT_CONFIG_B64") via base64.b64decode and json.loads)
should either be removed to avoid confusion or validated if early checks are
intended: either delete the whole _wechat_config parsing expression, or after
creating _wechat_config verify required keys (e.g., "app_id", "app_secret" or
whatever seed-wechat-accounts.py expects) and raise/log a clear error if missing
so the intent is explicit; update corresponding comments to reference
seed-wechat-accounts.py if you keep validation.

In `@src/ext/wechat/login.ts`:
- Around line 136-251: The runWechatHostQrLogin function is too large and
stateful; split the per-state logic into small helper functions to reduce
complexity: extract the polling loop into a short coordinator in
runWechatHostQrLogin that calls state handlers like handleWait(status),
handleScanned(status), handleRedirect(status), handleExpired(session), and
handleConfirmed(status) (or similar names) so each helper owns its transitions
and side effects (calls to opts.sleep, opts.log, emitQr, fetchWechatQrSession,
and updating currentBaseUrl/scannedAnnounced/qrRefreshCount). Keep the existing
calls to pollWechatQrStatus, fetchWechatQrSession, emitQr, errorMessage, and
debug, ensure abort/timeout checks remain in the coordinator, and return the
same WechatLoginResult values from the helpers (or bubble them up) so behavior
is unchanged while reducing complexity in runWechatHostQrLogin.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 18e13cfc-4efd-4941-8d70-7904cc955bf6

📥 Commits

Reviewing files that changed from the base of the PR and between 34d9f61 and a82508a.

📒 Files selected for processing (35)
  • Dockerfile
  • agents/hermes/config/build-env.ts
  • agents/hermes/config/hermes-config.ts
  • agents/hermes/config/messaging-config.ts
  • agents/hermes/generate-config.ts
  • agents/hermes/manifest.yaml
  • agents/hermes/policy-additions.yaml
  • agents/openclaw/manifest.yaml
  • nemoclaw-blueprint/policies/presets/wechat.yaml
  • nemoclaw-blueprint/scripts/wechat-diagnostics.js
  • scripts/generate-openclaw-config.py
  • scripts/seed-wechat-accounts.py
  • src/ext/wechat/login.test.ts
  • src/ext/wechat/login.ts
  • src/ext/wechat/qr.test.ts
  • src/ext/wechat/qr.ts
  • src/lib/agent/defs.test.ts
  • src/lib/credentials/store.ts
  • src/lib/host-qr-handlers.ts
  • src/lib/messaging-channel-config.test.ts
  • src/lib/messaging-conflict.test.ts
  • src/lib/messaging-conflict.ts
  • src/lib/onboard.ts
  • src/lib/sandbox-build-context.ts
  • src/lib/sandbox-channels.test.ts
  • src/lib/sandbox-channels.ts
  • src/lib/state/onboard-session.test.ts
  • src/lib/state/onboard-session.ts
  • test/credentials.test.ts
  • test/generate-hermes-config.test.ts
  • test/generate-openclaw-config.test.ts
  • test/policies.test.ts
  • test/sandbox-provisioning.test.ts
  • test/seed-wechat-accounts.test.ts
  • test/wechat-diagnostics.test.ts

Comment thread agents/hermes/config/messaging-config.ts Outdated
Comment thread agents/hermes/policy-additions.yaml Outdated
Comment thread nemoclaw-blueprint/scripts/wechat-diagnostics.js
Comment thread src/ext/wechat/login.ts Outdated
Comment thread src/ext/wechat/qr.ts
Comment thread src/ext/wechat/qr.ts Outdated
Comment thread src/lib/host-qr-handlers.ts
Comment thread src/lib/onboard.ts Outdated
sandl99 added a commit to sandl99/NemoClaw that referenced this pull request May 11, 2026
Three coupled changes on top of the prior WeChat work in this branch,
plus the CodeRabbit review feedback.

Build-time:
- Install @tencent-weixin/openclaw-weixin@2.4.2 in Dockerfile.base so
  onboarding never reaches npm for it. Activate the plugin entry from
  generate-openclaw-config.py (the base's openclaw.json gets rewritten
  wholesale on every onboard, so a base-set enable would not survive).
  Chain seed-wechat-accounts.py from that script via subprocess — the
  single image-build RUN now does plugin enable + per-account seeding,
  gated on whether an accountId was captured.
- Drop the NEMOCLAW_WECHAT_ENABLED build-arg gate everywhere; the seed
  script self-gates on accountId presence and is a silent no-op when
  the operator did not go through a host-side QR login.

Runtime:
- Guard 'channels add wechat' (and any other host-qr channel) — the
  paste-prompt path cannot capture WeChat credentials. The command
  exits with a clear message pointing to 'nemoclaw onboard'.
- Resume fallback in onboard.ts: when WECHAT_* env vars are absent
  (cached-token path skips the host-qr handler), hydrate wechatConfig
  from session.wechatConfig so the rebuild does not bake an empty
  NEMOCLAW_WECHAT_CONFIG_B64 and silently disable the bridge.

CodeRabbit fixes (PR NVIDIA#3186):
- sanitize WeChat env-line values against CR/LF/NUL injection
- widen wechat-diagnostics.js host match to *.wechat.com
- qrRefreshCount off-by-one in login.ts (was only 2/3 refreshes)
- AbortController + 10s timeout on fetchWechatQrSession
- validate parsed status against the WechatQrStatus union
- wrap host-qr handler require + call in try/catch
- drop unused _wechat_config decode in generate-openclaw-config.py

Hermes scope:
- Revert all WeChat-related additions under agents/hermes/ (build-env,
  hermes-config, messaging-config, manifest, policy-additions) plus
  the 3 hermes wechat tests. Hermes integration will ship in a
  follow-up PR.

Document NEMOCLAW_WECHAT_QUIET in docs/reference/commands.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: San Dang <sdang@nvidia.com>
@sandl99 sandl99 marked this pull request as ready for review May 11, 2026 04:04

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/lib/onboard.ts (1)

5486-5524: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist WeChat QR metadata somewhere durable, and merge it fill-only-if-missing.

This still only survives the current onboard session. A fresh nemoclaw onboard creates a new session before this code runs, so a saved WECHAT_BOT_TOKEN can still rebuild with no WECHAT_ACCOUNT_ID / WECHAT_BASE_URL / WECHAT_USER_ID. Also, the fallback on Lines 5501-5505 only runs when all fields are missing, so a partial env state will overwrite the recorded triple with a partial object on Lines 5517-5524.

Store the QR metadata in state that survives fresh onboard runs, and backfill missing fields individually instead of replacing the whole object.

Based on learnings: implement fill-only-if-missing semantics so stored values only backfill missing entries and never depend on overwriting explicitly provided environment state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard.ts` around lines 5486 - 5524, The code must persist WeChat QR
metadata into durable state and merge recorded values on a per-field
fill-only-if-missing basis instead of overwriting the whole object; update the
block that builds wechatConfig (the branch guarded by
enabledTokenEnvKeys.has("WECHAT_BOT_TOKEN") and the normalizeCredentialValue
calls) to: 1) read a durable store (not just onboardSession.loadSession()) that
survives fresh `nemoclaw onboard` runs to retrieve prior wechat metadata, 2) for
each field (accountId, baseUrl, userId) set wechatConfig.field = envValue ||
recordedValue (i.e., only backfill when the env-normalized value is empty), and
3) when calling onboardSession.updateSession (current.wechatConfig) write back
the merged object but do not overwrite recorded durable storage with
empty/missing values—only persist newly discovered non-empty fields. Use the
existing symbols wechatConfig, normalizeCredentialValue,
onboardSession.loadSession(), and onboardSession.updateSession() to locate the
logic to change.
🧹 Nitpick comments (1)
Dockerfile.base (1)

208-241: Run the container-level E2E matrix for this base-image change before merge.

This layer now bakes a third-party plugin install path into the base image; please validate with the recommended E2E jobs (cloud-e2e, sandbox-survival-e2e, hermes-e2e, rebuild-openclaw-e2e), not only unit tests.

As per coding guidelines, Dockerfile.base: "This file affects the sandbox container image. Layer ordering, permissions, and baked config changes are only testable with a real container build."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile.base` around lines 208 - 241, The Dockerfile.base change bakes the
`@tencent-weixin/openclaw-weixin` plugin (ARG WECHAT_PLUGIN_VERSION) into a
sandbox layer via the RUN openclaw plugins install command while switching USER
sandbox/root; before merging, build this base image and run the full container
E2E matrix (cloud-e2e, sandbox-survival-e2e, hermes-e2e, rebuild-openclaw-e2e),
verifying the plugin files exist under /sandbox/.openclaw/extensions, file
ownership/permissions are correct for the sandbox user, the agent process starts
cleanly, onboarding and openclaw doctor --fix behavior remain unchanged, and the
plugin version aligns with WECHAT_ILINK_CLIENT_VERSION in src/ext/wechat/qr.ts.
Ensure any failures are fixed (adjust layering, permissions, or install
location) and re-run the E2E jobs until all pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Dockerfile.base`:
- Around line 236-241: The final USER directive currently leaves the runtime
image running as root (the lingering "USER root" override); change the final
USER in the Dockerfile to a non-root user (e.g., "USER sandbox") after all
root-required setup is finished so derived runtime images do not run as
root—ensure this mirrors the intended pattern where Dockerfile.base temporarily
switches to root for setup and then switches back to the non-root user (USER
sandbox) at the end.

In `@src/lib/onboard.ts`:
- Around line 7803-7822: The call to await handler() (from
HOST_QR_LOGIN_HANDLERS[ch.name]) must be guarded with a try/catch so thrown
errors don't abort onboarding; wrap the handler invocation in a try block and
catch any exception, log/format a reason (e.g. "handler threw: <error
message>"), set result to a failure-equivalent (or handle the error path
directly) and then run the same skip logic (console.log `Skipped ${ch.name}
(...)`, enabled.delete(ch.name), continue) as for non-"ok" results so the
channel is skipped and onboarding continues.

---

Duplicate comments:
In `@src/lib/onboard.ts`:
- Around line 5486-5524: The code must persist WeChat QR metadata into durable
state and merge recorded values on a per-field fill-only-if-missing basis
instead of overwriting the whole object; update the block that builds
wechatConfig (the branch guarded by enabledTokenEnvKeys.has("WECHAT_BOT_TOKEN")
and the normalizeCredentialValue calls) to: 1) read a durable store (not just
onboardSession.loadSession()) that survives fresh `nemoclaw onboard` runs to
retrieve prior wechat metadata, 2) for each field (accountId, baseUrl, userId)
set wechatConfig.field = envValue || recordedValue (i.e., only backfill when the
env-normalized value is empty), and 3) when calling onboardSession.updateSession
(current.wechatConfig) write back the merged object but do not overwrite
recorded durable storage with empty/missing values—only persist newly discovered
non-empty fields. Use the existing symbols wechatConfig,
normalizeCredentialValue, onboardSession.loadSession(), and
onboardSession.updateSession() to locate the logic to change.

---

Nitpick comments:
In `@Dockerfile.base`:
- Around line 208-241: The Dockerfile.base change bakes the
`@tencent-weixin/openclaw-weixin` plugin (ARG WECHAT_PLUGIN_VERSION) into a
sandbox layer via the RUN openclaw plugins install command while switching USER
sandbox/root; before merging, build this base image and run the full container
E2E matrix (cloud-e2e, sandbox-survival-e2e, hermes-e2e, rebuild-openclaw-e2e),
verifying the plugin files exist under /sandbox/.openclaw/extensions, file
ownership/permissions are correct for the sandbox user, the agent process starts
cleanly, onboarding and openclaw doctor --fix behavior remain unchanged, and the
plugin version aligns with WECHAT_ILINK_CLIENT_VERSION in src/ext/wechat/qr.ts.
Ensure any failures are fixed (adjust layering, permissions, or install
location) and re-run the E2E jobs until all pass.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 27f55c29-4e2f-4bb9-a1bd-7362bd013439

📥 Commits

Reviewing files that changed from the base of the PR and between a82508a and e23486b.

📒 Files selected for processing (14)
  • Dockerfile
  • Dockerfile.base
  • docs/reference/commands.md
  • nemoclaw-blueprint/scripts/wechat-diagnostics.js
  • scripts/generate-openclaw-config.py
  • scripts/seed-wechat-accounts.py
  • src/ext/wechat/login.ts
  • src/ext/wechat/qr.ts
  • src/lib/actions/sandbox/policy-channel.ts
  • src/lib/agent/defs.test.ts
  • src/lib/host-qr-handlers.ts
  • src/lib/onboard.ts
  • test/generate-openclaw-config.test.ts
  • test/seed-wechat-accounts.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • nemoclaw-blueprint/scripts/wechat-diagnostics.js
  • src/ext/wechat/qr.ts
  • src/ext/wechat/login.ts
  • src/lib/host-qr-handlers.ts

Comment thread Dockerfile.base Outdated
Comment thread src/lib/onboard.ts Outdated
@sandl99 sandl99 changed the title feat(messaging): add WeChat (personal) channel feat(messaging): add WeChat (personal) channel for OpenClaw May 11, 2026

@coderabbitai coderabbitai Bot 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.

♻️ Duplicate comments (2)
src/lib/onboard.ts (2)

7834-7841: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist WeChat QR metadata before leaving step 5.

result.extraEnv only lands in process.env here. If onboarding is interrupted after messaging setup but before createSandbox() updates current.wechatConfig, resume keeps WECHAT_BOT_TOKEN but loses WECHAT_ACCOUNT_ID / WECHAT_BASE_URL / WECHAT_USER_ID, so the sandbox falls back to a fresh QR login.

Suggested fix
       if (result.extraEnv) {
         for (const [key, value] of Object.entries(result.extraEnv)) {
           process.env[key] = value;
         }
+        onboardSession.updateSession((current: Session) => {
+          current.wechatConfig = {
+            accountId:
+              result.extraEnv.WECHAT_ACCOUNT_ID ?? current.wechatConfig?.accountId,
+            baseUrl:
+              result.extraEnv.WECHAT_BASE_URL ?? current.wechatConfig?.baseUrl,
+            userId:
+              result.extraEnv.WECHAT_USER_ID ?? current.wechatConfig?.userId,
+          };
+          return current;
+        });
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard.ts` around lines 7834 - 7841, The extraEnv values are only
written to process.env, so if onboarding is interrupted before createSandbox()
sets current.wechatConfig the account metadata (WECHAT_ACCOUNT_ID,
WECHAT_BASE_URL, WECHAT_USER_ID) is lost; update the persistent onboarding state
with those WeChat fields as soon as result.extraEnv is available (e.g. merge the
relevant keys from result.extraEnv into current.wechatConfig or call the
existing state/save function) in the same block that writes process.env,
ensuring WECHAT_BOT_TOKEN, WECHAT_ACCOUNT_ID, WECHAT_BASE_URL and WECHAT_USER_ID
are stored to current.wechatConfig so resume flows use them even if
createSandbox() never runs.

7809-7816: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrap the host-QR handler call in try/catch, not promise .catch().

Line 7813 still lets a synchronous throw escape before .catch() is attached, so onboarding can abort instead of skipping the channel. If a future handler stops being async or throws during setup, this path regresses.

Suggested fix
-      const result = await handler().catch((err: unknown) => ({
-        kind: "error" as const,
-        message: err instanceof Error ? err.message : String(err),
-      }));
+      let result;
+      try {
+        result = await handler();
+      } catch (err: unknown) {
+        result = {
+          kind: "error" as const,
+          message: err instanceof Error ? err.message : String(err),
+        };
+      }
In JavaScript/TypeScript, does `await handler().catch(...)` catch exceptions thrown synchronously before `handler()` returns a promise?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard.ts` around lines 7809 - 7816, The current code uses await
handler().catch(...) which does not catch synchronous throws thrown before
handler() returns a Promise; replace that pattern with a try/catch around the
await call: call handler() inside a try block (const result = await handler()),
and in the catch block set result to the structured error object ({ kind:
"error" as const, message: err instanceof Error ? err.message : String(err) }).
Update the variable declaration for result so it exists in the outer scope
(e.g., let result;) and ensure the error object uses the same shape expected
downstream.
🧹 Nitpick comments (3)
docs/manage-sandboxes/messaging-channels.md (3)

31-31: ⚡ Quick win

Replace clause-separating colons with periods/commas.

Lines 31, 71, and 75 use colons as general punctuation rather than to introduce lists.

As per coding guidelines, "Colons should only introduce a list. Flag colons used as general punctuation between clauses."

Also applies to: 71-71, 75-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 31, Replace
clause-separating colons used as general punctuation with periods or commas in
the three sentences flagged: change "WeChat is an exception: it is wired up
during `nemoclaw onboard` only — see [Add Channels After
Onboarding](`#add-channels-after-onboarding`) for the limitation." to use a period
or comma instead of the colon, and similarly update the sentences at the other
flagged occurrences (the lines containing the same colon-used-as-clause pattern
at lines 71 and 75) so colons only introduce lists; ensure the punctuation flows
grammatically (period or comma) and keep the backticked command and link text
intact.

7-7: ⚡ Quick win

Split description.agent to one sentence per source line.

Line 7 packs two sentences into one line, which breaks the docs diff-readability rule.

As per coding guidelines, "One sentence per line in source (makes diffs readable)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 7, The description.agent
string currently contains two sentences on one source line; split it so each
sentence is on its own line to follow the "one sentence per line" rule — locate
the description.agent entry in messaging-channels.md (the agent: "Explains how
Telegram, Discord, Slack, and WeChat reach the sandboxed OpenClaw agent through
OpenShell-managed processes and NemoClaw channel commands. Use when setting up
messaging channels, chat interfaces, or integrations without relying on nemoclaw
tunnel start for bridges.") and break it into two lines, one with the sentence
about which platforms reach the sandboxed OpenClaw agent and a second line
starting "Use when setting up..." so each sentence is a separate source line.

69-75: ⚡ Quick win

Reduce em-dash density in this paragraph (LLM pattern detected).

This paragraph uses multiple em dashes; keep at most one per paragraph and use periods/commas for the rest.

As per coding guidelines, "Excessive em dashes. One per paragraph is fine; multiple per paragraph or em dashes used instead of commas/periods should be flagged."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` around lines 69 - 75, The
paragraph in messaging-channels.md overuses em dashes; edit the text so it
contains at most one em dash and convert other dash-separated clauses into
sentences or comma clauses, preserving meaning and tokens like
<sandbox>-wechat-bridge, openshell:resolve:env:WECHAT_BOT_TOKEN, and the env var
NEMOCLAW_WECHAT_QUIET; ensure the sentence about personal WeChat (bot_type=3),
the QR handshake flow that captures token/accountId/baseUrl/userId, and the
DM-only allowIdsMode: "dm" behavior are preserved but reflowed with periods or
commas instead of multiple em dashes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/lib/onboard.ts`:
- Around line 7834-7841: The extraEnv values are only written to process.env, so
if onboarding is interrupted before createSandbox() sets current.wechatConfig
the account metadata (WECHAT_ACCOUNT_ID, WECHAT_BASE_URL, WECHAT_USER_ID) is
lost; update the persistent onboarding state with those WeChat fields as soon as
result.extraEnv is available (e.g. merge the relevant keys from result.extraEnv
into current.wechatConfig or call the existing state/save function) in the same
block that writes process.env, ensuring WECHAT_BOT_TOKEN, WECHAT_ACCOUNT_ID,
WECHAT_BASE_URL and WECHAT_USER_ID are stored to current.wechatConfig so resume
flows use them even if createSandbox() never runs.
- Around line 7809-7816: The current code uses await handler().catch(...) which
does not catch synchronous throws thrown before handler() returns a Promise;
replace that pattern with a try/catch around the await call: call handler()
inside a try block (const result = await handler()), and in the catch block set
result to the structured error object ({ kind: "error" as const, message: err
instanceof Error ? err.message : String(err) }). Update the variable declaration
for result so it exists in the outer scope (e.g., let result;) and ensure the
error object uses the same shape expected downstream.

---

Nitpick comments:
In `@docs/manage-sandboxes/messaging-channels.md`:
- Line 31: Replace clause-separating colons used as general punctuation with
periods or commas in the three sentences flagged: change "WeChat is an
exception: it is wired up during `nemoclaw onboard` only — see [Add Channels
After Onboarding](`#add-channels-after-onboarding`) for the limitation." to use a
period or comma instead of the colon, and similarly update the sentences at the
other flagged occurrences (the lines containing the same colon-used-as-clause
pattern at lines 71 and 75) so colons only introduce lists; ensure the
punctuation flows grammatically (period or comma) and keep the backticked
command and link text intact.
- Line 7: The description.agent string currently contains two sentences on one
source line; split it so each sentence is on its own line to follow the "one
sentence per line" rule — locate the description.agent entry in
messaging-channels.md (the agent: "Explains how Telegram, Discord, Slack, and
WeChat reach the sandboxed OpenClaw agent through OpenShell-managed processes
and NemoClaw channel commands. Use when setting up messaging channels, chat
interfaces, or integrations without relying on nemoclaw tunnel start for
bridges.") and break it into two lines, one with the sentence about which
platforms reach the sandboxed OpenClaw agent and a second line starting "Use
when setting up..." so each sentence is a separate source line.
- Around line 69-75: The paragraph in messaging-channels.md overuses em dashes;
edit the text so it contains at most one em dash and convert other
dash-separated clauses into sentences or comma clauses, preserving meaning and
tokens like <sandbox>-wechat-bridge, openshell:resolve:env:WECHAT_BOT_TOKEN, and
the env var NEMOCLAW_WECHAT_QUIET; ensure the sentence about personal WeChat
(bot_type=3), the QR handshake flow that captures
token/accountId/baseUrl/userId, and the DM-only allowIdsMode: "dm" behavior are
preserved but reflowed with periods or commas instead of multiple em dashes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d02c8889-5eab-4400-8b6c-a6909797b79e

📥 Commits

Reviewing files that changed from the base of the PR and between e23486b and 3dd9e09.

📒 Files selected for processing (2)
  • docs/manage-sandboxes/messaging-channels.md
  • src/lib/onboard.ts

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (7)
docs/manage-sandboxes/messaging-channels.md (7)

213-213: ⚡ Quick win

Rewrite in active voice.

The phrase "are produced by" uses passive voice.

♻️ Suggested rewrite
-For WeChat specifically, the in-sandbox bridge emits a single `[wechat] [<accountId>] provider ready` line on stderr after the first successful iLink hit and an annotated line when the agent turn fails after the provider connected; both are produced by the diagnostics preload and are useful for telling "channel up, inference broken" apart from "channel never connected".
+For WeChat specifically, the in-sandbox bridge emits a single `[wechat] [<accountId>] provider ready` line on stderr after the first successful iLink hit and an annotated line when the agent turn fails after the provider connected; the diagnostics preload produces both and they help distinguish "channel up, inference broken" from "channel never connected".

As per coding guidelines: "Active voice required. Flag passive constructions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 213, Rewrite the passive
clause "are produced by the diagnostics preload" into active voice; locate the
sentence containing "[wechat] [<accountId>] provider ready" and change it to
something like "the diagnostics preload emits these lines" (or "the diagnostics
preload produces both lines") so the sentence reads actively and clearly
attributes the emission to diagnostics preload.

33-34: ⚡ Quick win

Split into one sentence per line.

The guidelines require one sentence per line in Markdown source to make diffs readable.
Break this into two lines:

-WeChat is an exception.
-It is wired up during `nemoclaw onboard` only — see [Add Channels After Onboarding](`#add-channels-after-onboarding`) for the limitation.
+WeChat is an exception.
+It is wired up during `nemoclaw onboard` only — see [Add Channels After Onboarding](`#add-channels-after-onboarding`) for the limitation.

As per coding guidelines: "One sentence per line in source (makes diffs readable). Flag paragraphs where multiple sentences appear on the same line."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` around lines 33 - 34, The
two-sentence paragraph ("WeChat is an exception. It is wired up during `nemoclaw
onboard` only — see [Add Channels After
Onboarding](`#add-channels-after-onboarding`) for the limitation.") should be
split so each sentence is on its own line in the Markdown source; update the
content in messaging-channels.md so the first line is "WeChat is an exception."
and the second line is "It is wired up during `nemoclaw onboard` only — see [Add
Channels After Onboarding](`#add-channels-after-onboarding`) for the limitation."

78-78: ⚡ Quick win

Rewrite in active voice.

The phrase "is added to" uses passive voice.

♻️ Suggested rewrite
-WeChat is DM-only (`allowIdsMode: "dm"`) — the operator who scanned the QR is added to `WECHAT_ALLOWED_IDS` automatically, and you can append more comma-separated WeChat user IDs through the same env var.
+WeChat is DM-only (`allowIdsMode: "dm"`) — NemoClaw adds the operator who scanned the QR to `WECHAT_ALLOWED_IDS` automatically, and you can append more comma-separated WeChat user IDs through the same env var.

As per coding guidelines: "Active voice required. Flag passive constructions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 78, Rewrite the passive
sentence into active voice: instead of "the operator who scanned the QR is added
to `WECHAT_ALLOWED_IDS` automatically," change it to state the system/action
actively adds the operator to `WECHAT_ALLOWED_IDS` (e.g., "the system
automatically adds the operator who scanned the QR to `WECHAT_ALLOWED_IDS`");
keep and reference `allowIdsMode: "dm"` and `WECHAT_ALLOWED_IDS` so the line
reads clearly like "With `allowIdsMode: "dm"`, the system automatically adds the
operator who scanned the QR to `WECHAT_ALLOWED_IDS`, and you can append more
comma-separated WeChat user IDs via the same env var."

77-77: ⚡ Quick win

Rewrite in active voice.

The sentence uses passive constructions ("is registered," "replaced").

♻️ Suggested rewrite
-The token is registered as the `<sandbox>-wechat-bridge` OpenShell provider and replaced inside the sandbox by the `openshell:resolve:env:WECHAT_BOT_TOKEN` placeholder, so it is never baked into the image or written to disk inside the running container.
+NemoClaw registers the token as the `<sandbox>-wechat-bridge` OpenShell provider and replaces it inside the sandbox with the `openshell:resolve:env:WECHAT_BOT_TOKEN` placeholder, so the token is never baked into the image or written to disk inside the running container.

As per coding guidelines: "Active voice required. Flag passive constructions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 77, Rewrite the sentence
in active voice: state that "we" (or the system) registers the token as the
`<sandbox>-wechat-bridge` OpenShell provider and that this registration replaces
the token inside the sandbox with the `openshell:resolve:env:WECHAT_BOT_TOKEN`
placeholder, ensuring the token is never baked into the image or written to disk
inside the running container; update the line to use that active-voice wording
and remove the passive phrases "is registered" and "replaced."

136-136: ⚡ Quick win

Reduce em dash usage.

This sentence contains two em dashes, which the guidelines flag as excessive.

♻️ Suggested rewrite
-The reason is that the host-side iLink QR handshake — which is the only way to obtain a WeChat bot token — does not fit the paste-prompt model the other channels use, and the `channels add` command has no slot for the per-account metadata (`accountId`, `baseUrl`, `userId`) that the in-sandbox bridge needs to start.
+The reason is that the host-side iLink QR handshake (the only way to obtain a WeChat bot token) does not fit the paste-prompt model the other channels use, and the `channels add` command has no slot for the per-account metadata (`accountId`, `baseUrl`, `userId`) that the in-sandbox bridge needs to start.

As per coding guidelines: "Excessive em dashes. One per paragraph is fine; multiple per paragraph or em dashes used instead of commas/periods should be flagged."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 136, The sentence in
docs/manage-sandboxes/messaging-channels.md overuses em dashes; rewrite it to
use at most one em dash or replace the clauses with commas/parentheses or a
period for clarity — e.g., explain that the host-side iLink QR handshake is the
only way to obtain a WeChat bot token and that it doesn't fit the paste-prompt
model used by other channels, and note that the `channels add` command has no
slot for the per-account metadata (`accountId`, `baseUrl`, `userId`) the
in-sandbox bridge needs to start.

105-105: ⚡ Quick win

Rewrite in active voice.

The phrase "cannot be configured" is passive.

♻️ Suggested rewrite
-WeChat cannot be configured non-interactively in this release because the iLink QR handshake requires a human to scan the QR on a paired phone.
+You cannot configure WeChat non-interactively in this release because the iLink QR handshake requires a human to scan the QR on a paired phone.

As per coding guidelines: "Active voice required. Flag passive constructions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 105, Rewrite the passive
sentence "WeChat cannot be configured non-interactively in this release because
the iLink QR handshake requires a human to scan the QR on a paired phone." into
active voice; replace it with an active construction such as "This release does
not support non-interactive configuration of WeChat because the iLink QR
handshake requires a human to scan the QR on a paired phone," updating the
sentence in the docs/managing-sandboxes messaging-channels content where the
original passive phrase appears.

54-54: ⚡ Quick win

Rewrite in active voice.

The phrase "Captured via host-side QR scan" uses passive voice.

♻️ Suggested rewrite
-| WeChat (personal) | Captured via host-side QR scan during `nemoclaw onboard` — no token to paste | `WECHAT_ALLOWED_IDS` for DM allowlisting (the WeChat user who scanned the QR is added automatically) |
+| WeChat (personal) | Host-side QR scan during `nemoclaw onboard` captures the token — no token to paste | `WECHAT_ALLOWED_IDS` for DM allowlisting (the WeChat user who scanned the QR is added automatically) |

As per coding guidelines: "Active voice required. Flag passive constructions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 54, The table row for
"WeChat (personal)" uses passive voice ("Captured via host-side QR scan");
change it to active voice by rephrasing the cell to something like "Host
captures it by scanning the QR during `nemoclaw onboard` — no token to paste"
(update the table row string containing "WeChat (personal) | Captured via
host-side QR scan during `nemoclaw onboard`" to the active phrasing and keep the
existing `WECHAT_ALLOWED_IDS` note unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@docs/manage-sandboxes/messaging-channels.md`:
- Line 213: Rewrite the passive clause "are produced by the diagnostics preload"
into active voice; locate the sentence containing "[wechat] [<accountId>]
provider ready" and change it to something like "the diagnostics preload emits
these lines" (or "the diagnostics preload produces both lines") so the sentence
reads actively and clearly attributes the emission to diagnostics preload.
- Around line 33-34: The two-sentence paragraph ("WeChat is an exception. It is
wired up during `nemoclaw onboard` only — see [Add Channels After
Onboarding](`#add-channels-after-onboarding`) for the limitation.") should be
split so each sentence is on its own line in the Markdown source; update the
content in messaging-channels.md so the first line is "WeChat is an exception."
and the second line is "It is wired up during `nemoclaw onboard` only — see [Add
Channels After Onboarding](`#add-channels-after-onboarding`) for the limitation."
- Line 78: Rewrite the passive sentence into active voice: instead of "the
operator who scanned the QR is added to `WECHAT_ALLOWED_IDS` automatically,"
change it to state the system/action actively adds the operator to
`WECHAT_ALLOWED_IDS` (e.g., "the system automatically adds the operator who
scanned the QR to `WECHAT_ALLOWED_IDS`"); keep and reference `allowIdsMode:
"dm"` and `WECHAT_ALLOWED_IDS` so the line reads clearly like "With
`allowIdsMode: "dm"`, the system automatically adds the operator who scanned the
QR to `WECHAT_ALLOWED_IDS`, and you can append more comma-separated WeChat user
IDs via the same env var."
- Line 77: Rewrite the sentence in active voice: state that "we" (or the system)
registers the token as the `<sandbox>-wechat-bridge` OpenShell provider and that
this registration replaces the token inside the sandbox with the
`openshell:resolve:env:WECHAT_BOT_TOKEN` placeholder, ensuring the token is
never baked into the image or written to disk inside the running container;
update the line to use that active-voice wording and remove the passive phrases
"is registered" and "replaced."
- Line 136: The sentence in docs/manage-sandboxes/messaging-channels.md overuses
em dashes; rewrite it to use at most one em dash or replace the clauses with
commas/parentheses or a period for clarity — e.g., explain that the host-side
iLink QR handshake is the only way to obtain a WeChat bot token and that it
doesn't fit the paste-prompt model used by other channels, and note that the
`channels add` command has no slot for the per-account metadata (`accountId`,
`baseUrl`, `userId`) the in-sandbox bridge needs to start.
- Line 105: Rewrite the passive sentence "WeChat cannot be configured
non-interactively in this release because the iLink QR handshake requires a
human to scan the QR on a paired phone." into active voice; replace it with an
active construction such as "This release does not support non-interactive
configuration of WeChat because the iLink QR handshake requires a human to scan
the QR on a paired phone," updating the sentence in the docs/managing-sandboxes
messaging-channels content where the original passive phrase appears.
- Line 54: The table row for "WeChat (personal)" uses passive voice ("Captured
via host-side QR scan"); change it to active voice by rephrasing the cell to
something like "Host captures it by scanning the QR during `nemoclaw onboard` —
no token to paste" (update the table row string containing "WeChat (personal) |
Captured via host-side QR scan during `nemoclaw onboard`" to the active phrasing
and keep the existing `WECHAT_ALLOWED_IDS` note unchanged).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 11d402e2-5109-40c5-a0e0-03844c947b25

📥 Commits

Reviewing files that changed from the base of the PR and between 3dd9e09 and 95626ae.

📒 Files selected for processing (2)
  • docs/manage-sandboxes/messaging-channels.md
  • src/lib/onboard.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/onboard.ts

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (3)
docs/manage-sandboxes/messaging-channels.md (3)

35-35: ⚡ Quick win

Use active voice.

The phrase "is generated at image build time" uses passive voice.

Suggested rewrite:
"Do not run openclaw channels add or openclaw channels remove inside the sandbox because the image build generates /sandbox/.openclaw/openclaw.json at build time and changes inside the running container do not persist across rebuilds."

As per coding guidelines: "Active voice required. Flag passive constructions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 35, The sentence
contains passive voice ("is generated at image build time") — update the line
mentioning `/sandbox/.openclaw/openclaw.json` so it uses active voice; replace
the passive clause with an active construction such as: "the image build
generates `/sandbox/.openclaw/openclaw.json` at build time" in the sentence that
starts "Do not run `openclaw channels add` or `openclaw channels remove` inside
the sandbox..." to produce the suggested rewrite and remove the passive
phrasing.

73-73: 💤 Low value

Remove unnecessary bold emphasis.

The bold formatting on "personal WeChat" is not necessary. The guidelines reserve bold for UI labels, parameter names, and genuine warnings. This mode name does not require emphasis beyond the surrounding context.

Suggested rewrite:
"The supported mode in this release is personal WeChat (bot_type=3)."

As per coding guidelines: "Unnecessary bold on routine instructions" is an LLM pattern to avoid, and "Bold is reserved for UI labels, parameter names, and genuine warnings."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 73, Remove the
unnecessary bold around "personal WeChat" in the sentence and replace it with
plain text so the line reads: The supported mode in this release is personal
WeChat (`bot_type=3`). Locate the string containing "**personal WeChat**
(`bot_type=3`)" and delete the bold markdown markers (**) while preserving the
inline code token `bot_type=3`.

74-74: ⚡ Quick win

Use active voice.

The phrase "are not wired up yet" uses passive voice.

Suggested rewrite:
"This release does not support WeChat Official Account or WeCom/Enterprise WeChat."

As per coding guidelines: "Active voice required. Flag passive constructions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 74, Replace the
passive-voice sentence "WeChat Official Account and WeCom/Enterprise WeChat are
not wired up yet." with an active-voice phrasing; for example change it to "This
release does not support WeChat Official Account or WeCom/Enterprise WeChat."
Update the string in the docs file where that exact sentence appears so the
documentation uses active voice.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@docs/manage-sandboxes/messaging-channels.md`:
- Line 35: The sentence contains passive voice ("is generated at image build
time") — update the line mentioning `/sandbox/.openclaw/openclaw.json` so it
uses active voice; replace the passive clause with an active construction such
as: "the image build generates `/sandbox/.openclaw/openclaw.json` at build time"
in the sentence that starts "Do not run `openclaw channels add` or `openclaw
channels remove` inside the sandbox..." to produce the suggested rewrite and
remove the passive phrasing.
- Line 73: Remove the unnecessary bold around "personal WeChat" in the sentence
and replace it with plain text so the line reads: The supported mode in this
release is personal WeChat (`bot_type=3`). Locate the string containing
"**personal WeChat** (`bot_type=3`)" and delete the bold markdown markers (**)
while preserving the inline code token `bot_type=3`.
- Line 74: Replace the passive-voice sentence "WeChat Official Account and
WeCom/Enterprise WeChat are not wired up yet." with an active-voice phrasing;
for example change it to "This release does not support WeChat Official Account
or WeCom/Enterprise WeChat." Update the string in the docs file where that exact
sentence appears so the documentation uses active voice.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: fc43dba4-5582-46d3-9f16-b89d914ed8f6

📥 Commits

Reviewing files that changed from the base of the PR and between 95626ae and 2ae23db.

📒 Files selected for processing (1)
  • docs/manage-sandboxes/messaging-channels.md

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/lib/onboard.ts (1)

7792-7848: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist and rehydrate the WeChat QR metadata, not just the token.

createSandbox() can only seed WeChat from WECHAT_ACCOUNT_ID / WECHAT_BASE_URL / WECHAT_USER_ID or session.wechatConfig. Here, a saved WECHAT_BOT_TOKEN skips the host-QR handler entirely, and a fresh host-QR login only leaves result.extraEnv in process.env until step 6 runs. A fresh onboard with a cached token, or an interruption after this step but before createSandbox(), will recreate the sandbox with the token but without the account metadata and force another QR login.

Suggested fix
-    if (getMessagingToken(ch.envKey)) {
+    if (getMessagingToken(ch.envKey)) {
+      if (ch.name === "wechat") {
+        const recorded = onboardSession.loadSession()?.wechatConfig;
+        if (recorded?.accountId && !process.env.WECHAT_ACCOUNT_ID) {
+          process.env.WECHAT_ACCOUNT_ID = recorded.accountId;
+        }
+        if (recorded?.baseUrl && !process.env.WECHAT_BASE_URL) {
+          process.env.WECHAT_BASE_URL = recorded.baseUrl;
+        }
+        if (recorded?.userId && !process.env.WECHAT_USER_ID) {
+          process.env.WECHAT_USER_ID = recorded.userId;
+        }
+      }
       console.log(`  ✓ ${ch.name} — already configured`);
     } else if (ch.loginMethod === "host-qr") {
@@
       if (result.extraEnv) {
         for (const [key, value] of Object.entries(result.extraEnv)) {
           process.env[key] = value;
         }
+        if (ch.name === "wechat") {
+          onboardSession.updateSession((current: Session) => {
+            current.wechatConfig = {
+              accountId: result.extraEnv.WECHAT_ACCOUNT_ID,
+              baseUrl: result.extraEnv.WECHAT_BASE_URL,
+              userId: result.extraEnv.WECHAT_USER_ID,
+            };
+            return current;
+          });
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard.ts` around lines 7792 - 7848, When a host-QR flow returns a
token you must also persist any non-secret metadata (result.extraEnv) so future
runs that skip the QR handler (via getMessagingToken(ch.envKey)) can rehydrate
those env keys before createSandbox runs; update the host-QR success path (the
block that calls saveCredential and sets process.env) to also store
result.extraEnv alongside the saved token (either by extending saveCredential to
accept metadata or adding a small saveMetadata helper) and ensure the early
branch where getMessagingToken(ch.envKey) is true rehydrates that stored
metadata into process.env (populate WECHAT_ACCOUNT_ID / WECHAT_BASE_URL /
WECHAT_USER_ID or session.wechatConfig equivalents) so createSandbox gets the
full WeChat config.
🧹 Nitpick comments (4)
docs/manage-sandboxes/messaging-channels.md (3)

72-72: ⚡ Quick win

Remove non-essential bold from routine text (LLM pattern detected).

Bold formatting in this sentence reads as emphasis without a warning/UI-label context.

As per coding guidelines, "Unnecessary bold on routine instructions ... should be flagged. Bold is reserved for UI labels, parameter names, and genuine warnings."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 72, The sentence uses
unnecessary bold around "personal WeChat"; update the sentence to remove the
bold formatting so it reads: personal WeChat (`bot_type=3`) instead of
**personal WeChat** (`bot_type=3`)—keep the inline code `bot_type=3` intact and
only remove the surrounding **...** around the phrase "personal WeChat".

191-192: ⚡ Quick win

Use WeChat casing in prose.

Both occurrences of wechat in prose should be WeChat (inline code uses are exempt).

As per coding guidelines, the word list requires "WeChat with a capital C," and code spans are the only exemption.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` around lines 191 - 192, Change
the lowercase "wechat" used in prose to the correct casing "WeChat" in the
sentence that describes `channels remove wechat` and the subsequent sentence
mentioning the `<sandbox>-wechat-bridge` provider and openclaw state files;
leave inline code spans (e.g., `channels remove wechat`,
`<sandbox>-wechat-bridge`, `openclaw.json`,
`/sandbox/.openclaw/openclaw-weixin/`) unchanged. Ensure both plain-text
occurrences are updated to "WeChat" to follow the style guide.

9-9: ⚡ Quick win

Use product-name casing in prose (NemoClaw).

nemoclaw appears in prose here and should be NemoClaw (keep lowercase only inside inline code).

As per coding guidelines, "NemoClaw is the correct form and nemoclaw in prose should be flagged."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manage-sandboxes/messaging-channels.md` at line 9, Replace the lowercase
product name "nemoclaw" used in prose with the correct product-name casing
"NemoClaw" in the sentence "Use when setting up messaging channels, chat
interfaces, or integrations without relying on nemoclaw tunnel start for
bridges."; leave lowercase only when it appears inside inline code (e.g.,
`nemoclaw`) and ensure other prose occurrences in this document use "NemoClaw"
consistently.
src/lib/actions/sandbox/policy-channel.ts (1)

14-17: ⚡ Quick win

This require() is still eager, so unrelated commands now load host-QR dependencies.

Because this runs at module import time, Slack/Telegram/policy commands now pull in ../../host-qr-handlers and its transitive QR/iLink deps too. That defeats the isolation the comment describes and widens the failure surface unnecessarily.

Suggested fix
-// Lazy-required: keeps qrcode-terminal + the iLink HTTP client out of the
-// import graph for non-host-qr channels-add calls.
-const { HOST_QR_LOGIN_HANDLERS } = require("../../host-qr-handlers") as typeof import("../../host-qr-handlers");
 const onboardSession = require("../../state/onboard-session") as typeof import("../../state/onboard-session");
   if (isNonInteractive()) {
     console.error(
       `  '${channelArg}' requires an interactive QR login; cannot run in non-interactive mode.`,
@@
-  const handler = HOST_QR_LOGIN_HANDLERS[channelArg];
+  const { HOST_QR_LOGIN_HANDLERS } =
+    require("../../host-qr-handlers") as typeof import("../../host-qr-handlers");
+  const handler = HOST_QR_LOGIN_HANDLERS[channelArg];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/actions/sandbox/policy-channel.ts` around lines 14 - 17, The
require() calls for HOST_QR_LOGIN_HANDLERS and onboardSession are executed at
module import time, pulling heavy host-QR deps into unrelated commands; change
them to be lazy-loaded where they are actually needed by moving the
require()/import() into the function(s) that use HOST_QR_LOGIN_HANDLERS and
onboardSession (e.g., inside the handler or helper that references those symbols
in policy-channel.ts) or use dynamic import() at the call site so these modules
are only loaded on-demand; ensure all references use the locally-imported values
and update any typings accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/seed-wechat-accounts.py`:
- Around line 155-170: account_id is used directly to build account_file and can
cause path-traversal; before constructing account_file (where account_id and
variables like plugin_dir and _state_dir() are used), validate/sanitize
account_id to allow only a safe pattern (e.g., alphanumeric plus - and _), or
reject/exit if it contains path separators, backrefs (".."), or is an absolute
path (starts with "/" or "\"), or alternatively replace it with a secure
basename and then confirm it equals the original; update the check around
account_id and fail/return non-zero if invalid so account_file = plugin_dir /
"accounts" / f"{account_id}.json" can never escape the intended directory.

In `@src/lib/actions/sandbox/policy-channel.ts`:
- Around line 463-467: The code currently returns immediately when
getCredential(channel.envKey) is truthy, which skips refreshing
session.wechatConfig / WECHAT_* when account metadata (accountId/baseUrl/userId)
is missing; change the logic so that after obtaining cached =
getCredential(channel.envKey) you validate that the associated account metadata
(check session.wechatConfig and the accountId, baseUrl, userId fields on the
channel/session) still exists and is complete — only if metadata is present do
you set acquired[channel.envKey] = cached and return; otherwise do not return so
the normal rebuild/refresh path that repopulates session.wechatConfig / WECHAT_*
runs (allowing seed-wechat-accounts.py to recreate plugin state).

In `@src/lib/actions/sandbox/rebuild.ts`:
- Around line 133-138: The code currently reads
onboardSession.loadSession()?.wechatConfig before verifying the session belongs
to the target sandbox, which can leak WECHAT_* from another session; change it
to first call const session = onboardSession.loadSession() and only read
session.wechatConfig and set process.env.WECHAT_* (and emit the log) when
session?.sandboxName === sandboxName; keep the same env variable assignments and
the log inside that conditional so metadata is only hydrated for the matching
sandbox session.

---

Duplicate comments:
In `@src/lib/onboard.ts`:
- Around line 7792-7848: When a host-QR flow returns a token you must also
persist any non-secret metadata (result.extraEnv) so future runs that skip the
QR handler (via getMessagingToken(ch.envKey)) can rehydrate those env keys
before createSandbox runs; update the host-QR success path (the block that calls
saveCredential and sets process.env) to also store result.extraEnv alongside the
saved token (either by extending saveCredential to accept metadata or adding a
small saveMetadata helper) and ensure the early branch where
getMessagingToken(ch.envKey) is true rehydrates that stored metadata into
process.env (populate WECHAT_ACCOUNT_ID / WECHAT_BASE_URL / WECHAT_USER_ID or
session.wechatConfig equivalents) so createSandbox gets the full WeChat config.

---

Nitpick comments:
In `@docs/manage-sandboxes/messaging-channels.md`:
- Line 72: The sentence uses unnecessary bold around "personal WeChat"; update
the sentence to remove the bold formatting so it reads: personal WeChat
(`bot_type=3`) instead of **personal WeChat** (`bot_type=3`)—keep the inline
code `bot_type=3` intact and only remove the surrounding **...** around the
phrase "personal WeChat".
- Around line 191-192: Change the lowercase "wechat" used in prose to the
correct casing "WeChat" in the sentence that describes `channels remove wechat`
and the subsequent sentence mentioning the `<sandbox>-wechat-bridge` provider
and openclaw state files; leave inline code spans (e.g., `channels remove
wechat`, `<sandbox>-wechat-bridge`, `openclaw.json`,
`/sandbox/.openclaw/openclaw-weixin/`) unchanged. Ensure both plain-text
occurrences are updated to "WeChat" to follow the style guide.
- Line 9: Replace the lowercase product name "nemoclaw" used in prose with the
correct product-name casing "NemoClaw" in the sentence "Use when setting up
messaging channels, chat interfaces, or integrations without relying on nemoclaw
tunnel start for bridges."; leave lowercase only when it appears inside inline
code (e.g., `nemoclaw`) and ensure other prose occurrences in this document use
"NemoClaw" consistently.

In `@src/lib/actions/sandbox/policy-channel.ts`:
- Around line 14-17: The require() calls for HOST_QR_LOGIN_HANDLERS and
onboardSession are executed at module import time, pulling heavy host-QR deps
into unrelated commands; change them to be lazy-loaded where they are actually
needed by moving the require()/import() into the function(s) that use
HOST_QR_LOGIN_HANDLERS and onboardSession (e.g., inside the handler or helper
that references those symbols in policy-channel.ts) or use dynamic import() at
the call site so these modules are only loaded on-demand; ensure all references
use the locally-imported values and update any typings accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3c225a0a-2e14-488a-9eb3-a8b92f61cda9

📥 Commits

Reviewing files that changed from the base of the PR and between 95626ae and 1850a0b.

📒 Files selected for processing (9)
  • docs/manage-sandboxes/messaging-channels.md
  • scripts/seed-wechat-accounts.py
  • src/lib/actions/sandbox/policy-channel.ts
  • src/lib/actions/sandbox/rebuild.ts
  • src/lib/onboard.ts
  • src/lib/state/sandbox.ts
  • test/security-sandbox-tar-traversal.test.ts
  • test/seed-wechat-accounts.test.ts
  • test/snapshot.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/seed-wechat-accounts.test.ts

Comment thread scripts/seed-wechat-accounts.py
Comment thread src/lib/actions/sandbox/policy-channel.ts Outdated
Comment thread src/lib/actions/sandbox/rebuild.ts Outdated
Comment thread src/lib/state/sandbox.ts Outdated
sandl99 added a commit to sandl99/NemoClaw that referenced this pull request May 11, 2026
Three open CodeRabbit threads on PR NVIDIA#3186:

- `channels add wechat` cached-token short-circuit now rehydrates
  session.wechatConfig (gated on session.sandboxName === sandboxName)
  before returning, and bails with a clear error if accountId/baseUrl/
  userId can't be recovered. Previous code silently rebuilt an image
  without per-account state files when the session had been cleared.

- rebuild.ts only stashes session.wechatConfig into process.env when the
  loaded session belongs to the target sandbox. Otherwise a stale session
  from a different sandbox could leak its accountId / baseUrl / userId
  into this image build.

- AUDIT_SYMLINK_WHITELIST is now Map<path, expectedTarget> instead of
  Set<path>. A compromised sandbox can rewrite the whitelisted symlinks
  under extensions/openclaw-weixin/node_modules/ at runtime; source-only
  matching let those tampered symlinks ride through both the pre-backup
  find and post-extraction audits. Both audits now compare the link
  target too — `find -printf` switched from `"%y %p\n"` to tab-separated
  `"%y\t%p\t%l\n"` to carry the target.

New tests:
- post-extraction audit rejects a tampered-target symlink at a
  whitelisted source path (security-sandbox-tar-traversal.test.ts)
- pre-backup audit rejects same (snapshot.test.ts)

Existing whitelist tests updated to use the tab-separated format and
include the expected link target.

Skipped: CodeRabbit also suggested validating accountId for path
traversal in seed-wechat-accounts.py. The JS-side normalizeWeixinAccountId
already strips @ and . before the value reaches the script; the
realistic attack surface is a tampered NEMOCLAW_WECHAT_CONFIG_B64 at
docker-build time, which the operator controls. Skipping per scope
discussion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
scripts/generate-openclaw-config.py (1)

660-679: 💤 Low value

Remove unused _seed_wechat_accounts() function and subprocess import.

_seed_wechat_accounts() is dead code—it's never called within this file and not imported by any other modules. The subprocess module (line 45) is used exclusively by this function. The private naming convention (_seed_wechat_accounts) suggests internal use only, yet the function has no callers. Remove both the function and the now-unneeded import, or if it should be invoked as part of the configuration pipeline, add an explicit call in main().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate-openclaw-config.py` around lines 660 - 679, The
_seed_wechat_accounts() function is unused and the subprocess import is only
referenced by it; remove dead code by deleting the _seed_wechat_accounts
function and the subprocess import (or alternatively, if seeding must run, add
an explicit invocation of _seed_wechat_accounts() inside main() and ensure
subprocess remains imported). Locate the function named _seed_wechat_accounts
and the top-level "import subprocess" and either delete both or add the call in
main() so the import is justified.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/generate-openclaw-config.py`:
- Around line 660-679: The _seed_wechat_accounts() function is unused and the
subprocess import is only referenced by it; remove dead code by deleting the
_seed_wechat_accounts function and the subprocess import (or alternatively, if
seeding must run, add an explicit invocation of _seed_wechat_accounts() inside
main() and ensure subprocess remains imported). Locate the function named
_seed_wechat_accounts and the top-level "import subprocess" and either delete
both or add the call in main() so the import is justified.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9c2d55b4-1922-464e-908d-d2a537271904

📥 Commits

Reviewing files that changed from the base of the PR and between 5f40307 and cf7edd9.

📒 Files selected for processing (3)
  • Dockerfile
  • Dockerfile.base
  • scripts/generate-openclaw-config.py
💤 Files with no reviewable changes (1)
  • Dockerfile.base
🚧 Files skipped from review as they are similar to previous changes (1)
  • Dockerfile

@cv cv closed this May 12, 2026
@cv cv reopened this May 12, 2026
sandl99 added a commit to sandl99/NemoClaw that referenced this pull request May 12, 2026
Three coupled changes on top of the prior WeChat work in this branch,
plus the CodeRabbit review feedback.

Build-time:
- Install @tencent-weixin/openclaw-weixin@2.4.2 in Dockerfile.base so
  onboarding never reaches npm for it. Activate the plugin entry from
  generate-openclaw-config.py (the base's openclaw.json gets rewritten
  wholesale on every onboard, so a base-set enable would not survive).
  Chain seed-wechat-accounts.py from that script via subprocess — the
  single image-build RUN now does plugin enable + per-account seeding,
  gated on whether an accountId was captured.
- Drop the NEMOCLAW_WECHAT_ENABLED build-arg gate everywhere; the seed
  script self-gates on accountId presence and is a silent no-op when
  the operator did not go through a host-side QR login.

Runtime:
- Guard 'channels add wechat' (and any other host-qr channel) — the
  paste-prompt path cannot capture WeChat credentials. The command
  exits with a clear message pointing to 'nemoclaw onboard'.
- Resume fallback in onboard.ts: when WECHAT_* env vars are absent
  (cached-token path skips the host-qr handler), hydrate wechatConfig
  from session.wechatConfig so the rebuild does not bake an empty
  NEMOCLAW_WECHAT_CONFIG_B64 and silently disable the bridge.

CodeRabbit fixes (PR NVIDIA#3186):
- sanitize WeChat env-line values against CR/LF/NUL injection
- widen wechat-diagnostics.js host match to *.wechat.com
- qrRefreshCount off-by-one in login.ts (was only 2/3 refreshes)
- AbortController + 10s timeout on fetchWechatQrSession
- validate parsed status against the WechatQrStatus union
- wrap host-qr handler require + call in try/catch
- drop unused _wechat_config decode in generate-openclaw-config.py

Hermes scope:
- Revert all WeChat-related additions under agents/hermes/ (build-env,
  hermes-config, messaging-config, manifest, policy-additions) plus
  the 3 hermes wechat tests. Hermes integration will ship in a
  follow-up PR.

Document NEMOCLAW_WECHAT_QUIET in docs/reference/commands.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: San Dang <sdang@nvidia.com>
sandl99 added a commit to sandl99/NemoClaw that referenced this pull request May 12, 2026
Three open CodeRabbit threads on PR NVIDIA#3186:

- `channels add wechat` cached-token short-circuit now rehydrates
  session.wechatConfig (gated on session.sandboxName === sandboxName)
  before returning, and bails with a clear error if accountId/baseUrl/
  userId can't be recovered. Previous code silently rebuilt an image
  without per-account state files when the session had been cleared.

- rebuild.ts only stashes session.wechatConfig into process.env when the
  loaded session belongs to the target sandbox. Otherwise a stale session
  from a different sandbox could leak its accountId / baseUrl / userId
  into this image build.

- AUDIT_SYMLINK_WHITELIST is now Map<path, expectedTarget> instead of
  Set<path>. A compromised sandbox can rewrite the whitelisted symlinks
  under extensions/openclaw-weixin/node_modules/ at runtime; source-only
  matching let those tampered symlinks ride through both the pre-backup
  find and post-extraction audits. Both audits now compare the link
  target too — `find -printf` switched from `"%y %p\n"` to tab-separated
  `"%y\t%p\t%l\n"` to carry the target.

New tests:
- post-extraction audit rejects a tampered-target symlink at a
  whitelisted source path (security-sandbox-tar-traversal.test.ts)
- pre-backup audit rejects same (snapshot.test.ts)

Existing whitelist tests updated to use the tab-separated format and
include the expected link target.

Skipped: CodeRabbit also suggested validating accountId for path
traversal in seed-wechat-accounts.py. The JS-side normalizeWeixinAccountId
already strips @ and . before the value reaches the script; the
realistic attack surface is a tampered NEMOCLAW_WECHAT_CONFIG_B64 at
docker-build time, which the operator controls. Skipping per scope
discussion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sandl99 sandl99 force-pushed the u/sdang/wechat-integration branch from cf7edd9 to 6058533 Compare May 12, 2026 04:00
sandl99 and others added 7 commits May 15, 2026 00:06
Three tests using the default 5s testTimeoutOptions() were occasionally
timing out under CI load. Each spawns the CLI as a child process and the
sibling tests in the same describe blocks already use 30s budgets, so
the smaller budget was an oversight.

- test/cli.test.ts:1310 `debug --sandbox skips stale default warning`
  joins its three sibling `debug --*` tests (lines 1247, 1279, 1295)
  which all use testTimeoutOptions(30_000).

- test/onboard.test.ts:3555 `prepares managed Model Router dependencies
  instead of using PATH when managed command is absent` — observed
  locally at ~7s wall time, which already exceeded the old 5s default.

- test/onboard.test.ts:3953 `refreshes stale managed Model Router
  command when source fingerprint changes` — same shape, prophylactic
  bump to match.

Adds the missing `testTimeoutOptions` import to test/onboard.test.ts.
No behavior change beyond raised budgets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ration

Three follow-ups raised on PR NVIDIA#3186:

- docs/manage-sandboxes/messaging-channels.md: wrap `nemoclaw tunnel
  start` in inline code formatting inside the YAML frontmatter `agent`
  description. The same command was already backticked everywhere
  else in the file; only the frontmatter line was missed.

- scripts/seed-wechat-accounts.py (_patch_openclaw_config): guard
  against a non-object openclaw.json. json.loads accepts arrays,
  scalars, and null — without the isinstance check, cfg.setdefault
  would AttributeError on those shapes. Bails with a clear message
  instead.

- scripts/seed-wechat-accounts.py (_wechat enabled-channels skip
  message): drop the unnecessary f-string prefixes — neither literal
  has interpolation placeholders. (Ruff F541)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`auditExtractedSymlinks` looks up entries in AUDIT_SYMLINK_WHITELIST by
the path relative to the extraction directory. The whitelist keys use
forward slashes, but `path.relative()` on Windows returns backslash-
separated paths, so the lookup misses and legitimate WeChat plugin
symlinks (extensions/openclaw-weixin/node_modules/.bin/qrcode-terminal,
…/node_modules/openclaw) fail validation — breaking snapshot/rebuild
on win32 hosts.

Normalize separators on the lookup key. The other whitelist site
(sandbox.ts:1063) consumes `find -printf` output produced inside the
Linux container and is already forward-slash regardless of host OS,
so no change needed there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…chat e2e

NVIDIA#3395 (merged) preserves messagingChannels and disabledChannels in the
registry across rebuild, so the session-based carry-across introduced in
961f222 is now redundant for the channels-start recovery side. The
channels-add policy auto-apply will land in a separate PR alongside the
global build-time disable filter fix.

Removed (defer to NVIDIA#3395 / separate PR):
- src/lib/state/onboard-session.ts: drop disabledChannels field from
  Session/SessionUpdates and its read/write/normalize sites.
- src/lib/actions/sandbox/rebuild.ts: drop preservedDisabledChannels
  snapshot and the s.disabledChannels session stash.
- src/lib/onboard.ts: drop sessionDisabledChannels read in createSandbox
  (back to registry.getDisabledChannels) and the session-clear after
  registry.setDefault.
- src/lib/actions/sandbox/policy-channel.ts: drop channels-add auto-apply
  policy preset and channels-remove "still applied" hint.
- src/lib/actions/inference-set.test.ts: drop disabledChannels: null from
  the test session fixture.

Added E2E coverage for non-interactive wechat onboarding
(test/e2e/test-messaging-providers.sh). Default fake env vars
(WECHAT_BOT_TOKEN, WECHAT_ACCOUNT_ID, WECHAT_BASE_URL, WECHAT_USER_ID,
WECHAT_ALLOWED_IDS) let onboard short-circuit HOST_QR_LOGIN_HANDLERS at
src/lib/onboard.ts:8433 so no QR scan is needed. New markers:
- M-W1: ${SANDBOX}-wechat-bridge provider registered in gateway.
- M-W3/3a-3d: host-side WECHAT_BOT_TOKEN must not leak into sandbox env,
  process list, or filesystem; placeholder is present.
- M-W8: openclaw.json registers channels.openclaw-weixin with the
  configured accountId enabled.
- M-W9: per-account credential file uses the L7-resolved placeholder
  (openshell:resolve:env:WECHAT_BOT_TOKEN), not the real token.
- M-W10: accounts.json index lists the configured accountId.

Fixes the cascade from inserting wechatConfig between telegramConfig and
darwinVmCompat in patchStagedDockerfile's signature: test/onboard.test.ts
and src/lib/onboard/dockerfile-patch.test.ts now pass {} for wechatConfig
before the darwinVmCompat boolean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…elpers

Brings src/lib/onboard.ts under the per-file growth budget enforced by
the source-shape check (net -93 lines vs origin/main on this PR; was
+129 before). New logic moved into focused modules under src/lib/onboard/
instead of the top-level entrypoint:

- onboard/host-qr-dispatch.ts: dispatchHostQrLogin() runs a channel's
  host-side QR handler and applies token + non-secret metadata side
  effects. Wraps the await in a real try/catch so a non-async handler
  that throws during setup still becomes a structured "error" outcome.
- onboard/wechat-config.ts: gatherWechatConfig() reads
  WECHAT_ACCOUNT_ID/_BASE_URL/_USER_ID from env with a session fallback;
  hasWechatConfigDrift() compares fresh env vs recorded session for
  resume-time drift detection; toSessionWechatConfig() builds the
  Session.wechatConfig payload (null-on-empty for parseWechatConfig
  compatibility).
- onboard/messaging-channel-setup.ts: setupSelectedMessagingChannels()
  drives the per-channel interactive loop (token / app token / server
  ID / mention mode / allowlist IDs). The host-qr branch routes through
  the dispatcher above.

src/lib/onboard.ts now references these via single-line require()s with
typeof import() types, matching the existing helper-import pattern.

No behavior change. 223/223 onboard + dockerfile-patch tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…og leak

Two CodeRabbit findings on the wechat-narrow + host-qr-extract pair:

- src/lib/onboard/host-qr-dispatch.ts: merge the scanned operator's id
  into the DM allowlist instead of only seeding when the env is empty.
  The channel's userIdHelp documents "added automatically; supply
  additional ids as a comma-separated list" — but the original code (now
  extracted from setupMessagingChannels) skipped the merge when the
  operator supplied their own list, locking the scanner out of DM
  access. Dedupe via Set; preserve the no-space comma format used in
  the rest of the stack.

- test/e2e/test-messaging-providers.sh: drop the WECHAT_TOKEN prefix
  from the Phase 0 info log. Fake tokens are harmless in CI, but if an
  operator passes a real WECHAT_BOT_TOKEN to override the default
  fake, ${WECHAT_TOKEN:0:10}... leaks 10 chars of secret material into
  artifact logs. Print length + account only, mirroring the Slack
  bot/app token lines which already do this.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e test

Why: rebasing onto main pulled in NVIDIA#3434's new
"can override the sandbox inference base URL" test, which was written
against the pre-WeChat 13-parameter signature of patchStagedDockerfile.
The WeChat branch added a wechatConfig: LooseObject parameter at slot
13, so the test's `false` (intended for darwinVmCompat) was landing in
wechatConfig and TypeScript flagged the boolean-to-LooseObject mismatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sandl99 sandl99 force-pushed the u/sdang/wechat-integration branch from 0739069 to f177e53 Compare May 14, 2026 17:34
sandl99 and others added 3 commits May 15, 2026 00:41
Why: rebasing onto main pulled in @earendil-works/pi-coding-agent and
its transitive AWS SDK / Anthropic SDK trees as a new devDependency,
which our branch's pre-rebase package-lock.json did not have. Running
npm install reconciled the lockfile so npm ci stays consistent with
the merged package.json (which also still carries our qrcode-terminal
addition for the WeChat work).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Why: rebasing onto main brought in new legacy E2E entrypoints, so the
generated parity inventory drifted and the
legacy-assertion-inventory-current assertion started failing. Re-ran
scripts/e2e/extract-legacy-assertions.ts to refresh the snapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Why: the WeChat work in test-messaging-providers.sh added 18 new
PASS/FAIL assertions (M-W1 provider check, M-W3/a/b/c/d token-leak
sweeps, M-W8 account-enabled, M-W9/W10 accounts.json shape checks)
that had no entries in parity-map.yaml. After the rebase regenerated
the inventory the e2e-coverage-report test started failing with 18
unmapped assertions, which is gated to zero.

Classification follows the existing Telegram/Discord patterns: 15
deferred with `secret_requirement: WeChat test credentials` for live
token-leak / provider behavior, and 3 retired for the placeholder-
confirmation lines (parallels M3 pass and M5d both polarities).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cv cv added v0.0.43 and removed v0.0.42 labels May 14, 2026
…ation

# Conflicts:
#	src/lib/actions/sandbox/policy-channel.ts
#	src/lib/onboard.ts
#	test/onboard.test.ts
@cv cv added v0.0.44 and removed v0.0.43 labels May 15, 2026
@sandl99 sandl99 force-pushed the u/sdang/wechat-integration branch from 98c343b to 4dd94f1 Compare May 15, 2026 04:04
@cv cv enabled auto-merge (squash) May 15, 2026 14:25
@cv cv merged commit 8bc805f into NVIDIA:main May 15, 2026
21 checks passed
@miyoungc miyoungc mentioned this pull request May 16, 2026
12 tasks
@wscurran wscurran added area: cli Command line interface, flags, terminal UX, or output feature PR adds or expands user-visible functionality and removed NemoClaw CLI labels Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli Command line interface, flags, terminal UX, or output feature PR adds or expands user-visible functionality integration: wechat WeChat integration behavior VRDC Issues and PRs submitted by NVIDIA VRDC test team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants