feat(messaging): add WeChat (personal) channel for OpenClaw#3186
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis 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. ChangesWeChat Messaging Channel Integration
🎯 4 (Complex) | ⏱️ ~60 minutes
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
✨ 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. |
|
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. |
27a041d to
a82508a
Compare
There was a problem hiding this comment.
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-e2erun 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 liftSplit 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 valueConsider removing unused
_wechat_configparsing or adding a validation check.The
_wechat_configvariable is decoded but never used in this file. While the comment block at lines 450-467 explains that WeChat channel configuration is handled byseed-wechat-accounts.py, parsing the config here without using it may cause confusion.Consider either:
- Removing the decode entirely since it's not needed here, or
- 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
📒 Files selected for processing (35)
Dockerfileagents/hermes/config/build-env.tsagents/hermes/config/hermes-config.tsagents/hermes/config/messaging-config.tsagents/hermes/generate-config.tsagents/hermes/manifest.yamlagents/hermes/policy-additions.yamlagents/openclaw/manifest.yamlnemoclaw-blueprint/policies/presets/wechat.yamlnemoclaw-blueprint/scripts/wechat-diagnostics.jsscripts/generate-openclaw-config.pyscripts/seed-wechat-accounts.pysrc/ext/wechat/login.test.tssrc/ext/wechat/login.tssrc/ext/wechat/qr.test.tssrc/ext/wechat/qr.tssrc/lib/agent/defs.test.tssrc/lib/credentials/store.tssrc/lib/host-qr-handlers.tssrc/lib/messaging-channel-config.test.tssrc/lib/messaging-conflict.test.tssrc/lib/messaging-conflict.tssrc/lib/onboard.tssrc/lib/sandbox-build-context.tssrc/lib/sandbox-channels.test.tssrc/lib/sandbox-channels.tssrc/lib/state/onboard-session.test.tssrc/lib/state/onboard-session.tstest/credentials.test.tstest/generate-hermes-config.test.tstest/generate-openclaw-config.test.tstest/policies.test.tstest/sandbox-provisioning.test.tstest/seed-wechat-accounts.test.tstest/wechat-diagnostics.test.ts
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>
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/lib/onboard.ts (1)
5486-5524:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPersist WeChat QR metadata somewhere durable, and merge it fill-only-if-missing.
This still only survives the current onboard session. A fresh
nemoclaw onboardcreates a new session before this code runs, so a savedWECHAT_BOT_TOKENcan still rebuild with noWECHAT_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
📒 Files selected for processing (14)
DockerfileDockerfile.basedocs/reference/commands.mdnemoclaw-blueprint/scripts/wechat-diagnostics.jsscripts/generate-openclaw-config.pyscripts/seed-wechat-accounts.pysrc/ext/wechat/login.tssrc/ext/wechat/qr.tssrc/lib/actions/sandbox/policy-channel.tssrc/lib/agent/defs.test.tssrc/lib/host-qr-handlers.tssrc/lib/onboard.tstest/generate-openclaw-config.test.tstest/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
There was a problem hiding this comment.
♻️ Duplicate comments (2)
src/lib/onboard.ts (2)
7834-7841:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPersist WeChat QR metadata before leaving step 5.
result.extraEnvonly lands inprocess.envhere. If onboarding is interrupted after messaging setup but beforecreateSandbox()updatescurrent.wechatConfig, resume keepsWECHAT_BOT_TOKENbut losesWECHAT_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 winWrap 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 beingasyncor 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 winReplace 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 winSplit
description.agentto 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 winReduce 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
📒 Files selected for processing (2)
docs/manage-sandboxes/messaging-channels.mdsrc/lib/onboard.ts
There was a problem hiding this comment.
🧹 Nitpick comments (7)
docs/manage-sandboxes/messaging-channels.md (7)
213-213: ⚡ Quick winRewrite 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 winSplit 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 winRewrite 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 winRewrite 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 winReduce 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 winRewrite 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 winRewrite 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
📒 Files selected for processing (2)
docs/manage-sandboxes/messaging-channels.mdsrc/lib/onboard.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/lib/onboard.ts
There was a problem hiding this comment.
🧹 Nitpick comments (3)
docs/manage-sandboxes/messaging-channels.md (3)
35-35: ⚡ Quick winUse active voice.
The phrase "is generated at image build time" uses passive voice.
Suggested rewrite:
"Do not runopenclaw channels addoropenclaw channels removeinside the sandbox because the image build generates/sandbox/.openclaw/openclaw.jsonat 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 valueRemove 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 winUse 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
📒 Files selected for processing (1)
docs/manage-sandboxes/messaging-channels.md
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
src/lib/onboard.ts (1)
7792-7848:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPersist and rehydrate the WeChat QR metadata, not just the token.
createSandbox()can only seed WeChat fromWECHAT_ACCOUNT_ID/WECHAT_BASE_URL/WECHAT_USER_IDorsession.wechatConfig. Here, a savedWECHAT_BOT_TOKENskips the host-QR handler entirely, and a fresh host-QR login only leavesresult.extraEnvinprocess.envuntil step 6 runs. A fresh onboard with a cached token, or an interruption after this step but beforecreateSandbox(), 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 winRemove 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 winUse
Both occurrences of
As per coding guidelines, the word list requires "
🤖 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 winUse product-name casing in prose (
NemoClaw).
nemoclawappears in prose here and should beNemoClaw(keep lowercase only inside inline code).As per coding guidelines, "
NemoClawis the correct form andnemoclawin 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 winThis
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-handlersand 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
📒 Files selected for processing (9)
docs/manage-sandboxes/messaging-channels.mdscripts/seed-wechat-accounts.pysrc/lib/actions/sandbox/policy-channel.tssrc/lib/actions/sandbox/rebuild.tssrc/lib/onboard.tssrc/lib/state/sandbox.tstest/security-sandbox-tar-traversal.test.tstest/seed-wechat-accounts.test.tstest/snapshot.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- test/seed-wechat-accounts.test.ts
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>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
scripts/generate-openclaw-config.py (1)
660-679: 💤 Low valueRemove 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. Thesubprocessmodule (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 inmain().🤖 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
📒 Files selected for processing (3)
DockerfileDockerfile.basescripts/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
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>
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>
cf7edd9 to
6058533
Compare
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>
0739069 to
f177e53
Compare
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>
…ation # Conflicts: # src/lib/actions/sandbox/policy-channel.ts # src/lib/onboard.ts # test/onboard.test.ts
98c343b to
4dd94f1
Compare
…ation # Conflicts: # src/lib/onboard.ts # src/lib/sandbox/build-context.ts
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 duringnemoclaw onboardso the in-sandbox pluginstarts 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)
wechatis listed as a supported messaging channel in CLI help/docs and channel listing outputnemoclaw onboard; existing sandbox via re-onboard. The post-onboardchannels add wechatlimitation is documented percriterion 5.)
nemoclaw onboard --freshis a known follow-up — see "Known follow-ups" below)piece — see "Known follow-ups" below)
Changes
Channel registry and CLI surface
nemoclaw <sandbox> channels listshows WeChat alongside Telegram, Discord, and Slack.HOST_QR_LOGIN_HANDLERSregistry (src/lib/host-qr-handlers.ts) — onboard dispatches byloginMethodso 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 documentediLink-App-Id/iLink-App-ClientVersionheaders, abort + 5xx fall-through as benignwait, and strict validationof the parsed
statusagainst theWechatQrStatusunion.src/ext/wechat/login.ts— orchestrator: 8-minute deadline, ≤3 QR refreshes, handlesscaned/scaned_but_redirect(rebinds the per-account base URL) /expired/confirmed, with a[wechat]diagnostic sink silenced viaNEMOCLAW_WECHAT_QUIET=1. Returns adiscriminated
WechatLoginResult.accountId,baseUrl, anduserIdcome out of the QR result; the operator user ID is auto-added toWECHAT_ALLOWED_IDS.Credential and secret handling
<sandbox>-wechat-bridgeOpenShell provider viaupsertMessagingProviders; SHA-256 recorded inregistry.<sandbox>.providerCredentialHashes.openshell:resolve:env:WECHAT_BOT_TOKENplaceholder substituted at egress by the L7 proxy — token is never on disk inside the running container.WECHAT_BOT_TOKENregistered inKNOWN_CREDENTIAL_ENV_KEYSfor redaction.nemoclaw-blueprint/scripts/wechat-diagnostics.jsstripsbot_token=,"bot_token":,Bearer, andapi_keypatterns from any stderr it routes.Sandbox image and config generation
Dockerfile.basepre-installs@tencent-weixin/openclaw-weixin@2.4.2 --pinas thesandboxuser so onboard never reaches the public npm registry for it.scripts/generate-openclaw-config.pyactivatesplugins.entries.openclaw-weixin.enabled=trueunconditionally and invokesscripts/seed-wechat-accounts.pyas a subprocess. The chicken-and-egg withopenclaw plugins installis gone — plugin install moved into the base.scripts/seed-wechat-accounts.pywrites 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), andpatches
channels.openclaw-weixin.accounts.<id>.enabled=trueintoopenclaw.json. Silent no-op when noaccountIdwas captured.NEMOCLAW_WECHAT_ENABLEDbuild-arg gate removed everywhere — the seed self-gates on accountId presence.Network policy
nemoclaw-blueprint/policies/presets/wechat.yamlopens*.wechat.com:443and*.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 wechatexits with an explicit error pointing tonemoclaw onboard. The paste-prompt path cannot capture a WeChat bot token (it only exists after the iLink QR handshake) and has no slot for theaccountId/baseUrl/userIdmetadata thebridge needs.
channels remove/start/stop wechatwork normally for existing WeChat-enabled sandboxes.Docs
docs/manage-sandboxes/messaging-channels.mdnow covers WeChat end-to-end: channel-requirements row, supported-mode statement, QR-login flow, OpenShell provider naming, DM-only model +WECHAT_ALLOWED_IDSauto-population,NEMOCLAW_WECHAT_QUIETknob, thechannels add wechatlimitation with reason and workaround, cross-sandboxaccountIduniqueness rule, and the third-party iLink ToS / data-residency note.docs/reference/commands.mdadds theNEMOCLAW_WECHAT_QUIETenv-var row.Tests (~11 files)
src/lib/messaging-conflict.test.ts.test/generate-openclaw-config.test.ts(plugin entry, seed chain, channels block).test/policies.test.ts(*.wechat.com+*.weixin.qq.com).test/credentials.test.ts,test/wechat-diagnostics.test.ts.src/lib/state/onboard-session.test.ts(wechatConfig parse/persist/round-trip).src/ext/wechat/login.test.ts,src/ext/wechat/qr.test.ts.src/lib/sandbox-channels.test.ts,src/lib/agent/defs.test.ts.Type of Change
Verification
npx prek run --all-filespassesnpm testpassesmake docsbuilds without warnings (doc changes only)Known follow-ups (not blocking #3006 closure):
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
Documentation
Infrastructure