Skip to content

feat(channels/yuanbao): add Yuanbao channel provider#2494

Closed
lrt4836 wants to merge 8 commits into
tinyhumansai:mainfrom
YuanbaoTeam:feat/yuanbao-channel
Closed

feat(channels/yuanbao): add Yuanbao channel provider#2494
lrt4836 wants to merge 8 commits into
tinyhumansai:mainfrom
YuanbaoTeam:feat/yuanbao-channel

Conversation

@lrt4836
Copy link
Copy Markdown
Contributor

@lrt4836 lrt4836 commented May 22, 2026

Summary

  • New Yuanbao channel provider (Tencent 元宝 robot logic v5) under src/openhuman/channels/providers/yuanbao/, wired into the channel registry + startup loop and exposed to the UI through the existing ChannelSetupModal flow.
  • Outbound: HMAC-SHA256 sign-token client (sign.rs) with cached tokens, message splitting (splitter.rs), and a COS uploader (cos.rs) for media.
  • Inbound: framed protobuf WebSocket client (connection.rs + wire.rs + proto_biz.rs) with bounded reconnect, single-flight token refresh on code=10099, prompt shutdown response, and bounded length-delimited parsing.
  • Frontend: YuanbaoConfig.tsx form (i18n-keyed validation messages), YuanbaoIcon, and ChannelSetupModal integration for icon parity with ChannelSelector.
  • Backend storage hygiene: app_secret is verified at connect time via the live sign-token endpoint, persisted only in the encrypted credentials store (never in plaintext config.toml), and hydrated at startup with a stale-app_key guard.
  • Vitest coverage added for the new frontend surface (YuanbaoIcon, YuanbaoConfig, ChannelSetupModal, channelConnectionsSlice) — diff-cover on changed lines: 13% → 84%, well above the ≥80% gate.

Problem

Yuanbao was not a supported channel; users on Tencent Yuanbao bots had no first-class way to wire OpenHuman to their robot. The 1st-round CodeRabbit review additionally flagged:

  • Yuanbao icon missing in ChannelSetupModal header.
  • Hardcoded Chinese validation copy.
  • connect_channel returning connected even when the upstream sign-token call failed.
  • app_secret mirrored in plaintext config.toml.
  • Raw inbound payload bytes logged on parse failure.
  • WS reconnect loop honoured shutdown with a one-backoff delay.
  • Unguarded as i32 / as u32 varint conversions in protobuf decoding.
  • Unchecked length arithmetic in wire::parse_fields.

2nd-round CodeRabbit then flagged three follow-ups on the above fixes:

  • Preflight verification rebuilt the config from TOML alone, ignoring client-supplied env / api_domain / route_env overrides — a pre connect could verify against PROD then fail after restart.
  • The route-env header wiremock test was bound only to the header, not to UPLOAD_INFO_PATH.
  • resolve_yuanbao_app_secret copied the stored secret even when yb_cfg.app_key had been changed — pairing a fresh key with a stale secret on next startup.

Solution

  • controllers/ops.rs: extracted build_effective_yuanbao_config that overlays client overrides on top of base TOML and calls apply_env_defaults. The verifier consumes the resulting YuanbaoConfig, and the same config is reused for persistence — verify and persist can no longer diverge.
  • providers/yuanbao/cos.rs: bound the route-env wiremock matcher to UPLOAD_INFO_PATH as well.
  • runtime/startup.rs: resolve_yuanbao_app_secret now compares profile.metadata.get("app_key") against yb_cfg.app_key before copying; on mismatch it logs both keys and leaves app_secret empty so YuanbaoChannel::new's validate() step fails loudly.
  • providers/yuanbao/proto_biz.rs: introduced varint_to_i32 / varint_to_u32 helpers that return YuanbaoError::ProtoDecode on overflow.
  • providers/yuanbao/wire.rs: usize::try_from(len) + pos.checked_add(len_usize) in the WT_LEN branch.
  • providers/yuanbao/connection.rs: added an early *shutdown.borrow() check after connect_once() so the loop exits without a backoff delay.
  • providers/yuanbao/channel.rs: drop the hex preview of biz_body on PipelineOutcome::Failed; log only biz_body.len().
  • i18n: three new channels.yuanbao.* keys (connecting, fieldRequired, unexpectedStatus) added to en.ts + zh-CN-3.ts (translated) and the other 12 locale chunks (English placeholders).
  • Removed two pre-existing duplicate key blocks in de-3.ts / de-5.ts that were tripping TS1117 in Build E2E app.

Submission Checklist

If a section does not apply to this change, mark the item as N/A with a one-line reason. Do not delete items.

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by .github/workflows/coverage.yml. Run pnpm test:coverage and pnpm test:rust locally; PRs below 80% on changed lines will not merge.
  • Coverage matrix updated — added/removed/renamed feature rows in docs/TEST-COVERAGE-MATRIX.md reflect this change (or N/A: behaviour-only change)
  • All affected feature IDs from the matrix are listed in the PR description under ## Related
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • N/A — this PR does not touch release-cut surfaces (installer / code signing / OS permission prompts / DMG / Gatekeeper / accessibility). Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md)
  • N/A — no GitHub issue tracker entry for this feature; this PR introduces the channel directly. Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Desktop only. No Tauri shell changes; the channel runs entirely in the in-process Rust core. Added Rust deps were already present in the repo (reqwest, tungstenite, hmac/sha2). No web / mobile / CLI behavior changes outside the channel surface.
  • Security: app_secret never lands in plaintext config.toml; runtime loads it from the encrypted credentials store at startup. Stale-app_key profiles will no longer be paired with the new key.
  • Performance: sign-token cache + single-flight refresh; bounded WS backoff with prompt-exit on shutdown. No new periodic timers beyond the existing channel supervisor.
  • Migration: app_secret written by a prior (pre-merge) version of this branch into config.toml is intentionally not auto-migrated; on first launch the runtime warns yuanbao credentials missing — connect the channel again from the UI. Users reconnect once to populate the encrypted store.
  • Protocol robustness: Protobuf decoders now return structured ProtoDecode errors instead of silently truncating overflowing varints or panicking on oversized length-delimited fields.

Related

  • Matrix feature IDs touched by this PR: 10.1.5 Yuanbao Connection (added; layer RU, status 🟡).
  • Closes: N/A — no prior tracker issue.
  • Follow-up PR(s)/TODOs:
    • WDIO spec for Yuanbao connect-flow (would raise 10.1.5 from 🟡 to ✅).
    • Translate the three channels.yuanbao.* strings in the 12 non-en / non-zh-CN locales (placeholders only today).

AI Authored PR Metadata (required for Codex/Linear PRs)

Keep this section for AI-authored PRs. For human-only PRs, mark each field N/A.

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: YuanbaoTeam:feat/yuanbao-channel
  • Commit SHA: 426263a9 (HEAD at time of writing)

Validation Run

  • pnpm --filter openhuman-app format:check
  • pnpm typecheck
  • Focused tests: cargo test --lib -- yuanbao → 190 passed, 0 failed; Vitest YuanbaoIcon 3/3, YuanbaoConfig 13/13, ChannelSetupModal 4/4, channelConnectionsSlice 10/10 — all passing. Local diff-cover on changed lines = 84%.
  • Rust fmt/check (if changed): cargo fmt --check → exit 0
  • N/A — no app/src-tauri/** changes in this PR. Tauri fmt/check (if changed):

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: Introduces the Yuanbao channel provider end-to-end (connect, verify, persist, hydrate, send, receive, reconnect, shutdown).
  • User-visible effect: New "Yuanbao" entry in the channel setup modal; users can paste app_key + app_secret (+ optional env / api_domain / ws_domain / route_env overrides) and the channel goes live after a restart.

Parity Contract

  • Legacy behavior preserved: existing channels (Telegram, Discord, Slack, WhatsApp, iMessage, …) are untouched; the new branch sits alongside them in connect_channel / disconnect_channel / channel_status.
  • Guard/fallback/dispatch parity checks: def.validate_credentials runs first as for every other channel; verify_yuanbao_credentials is yuanbao-specific and short-circuits on upstream API error before any persistence; startup hydration is best-effort (warn-only) and never panics.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): None known.
  • Canonical PR: This PR (tinyhumansai/openhuman#2494).
  • Resolution (closed/superseded/updated): N/A.

@lrt4836 lrt4836 requested a review from a team May 22, 2026 14:30
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

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
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a complete Yuanbao (元宝) channel provider to OpenHuman, including frontend UI components, Redux state management, a full WebSocket-based backend provider with protocol codecs, message pipelines, media handling, token signing, and comprehensive tests across 15+ languages.

Changes

Yuanbao Channel Integration

Layer / File(s) Summary
Channel type and configuration foundation
app/src/types/channels.ts, src/openhuman/channels/providers/yuanbao/config.rs, src/openhuman/config/schema/channels.rs, src/openhuman/channels/controllers/definitions.rs
ChannelType union extended with 'yuanbao'; YuanbaoConfig struct defined with credentials, endpoint overrides, access policies, and limits; ChannelsConfig schema updated to include optional yuanbao field; channel definition added to registry with API-key auth and capabilities (SendText, ReceiveText, Typing).
Frontend UI components and state management
app/src/components/channels/YuanbaoIcon.tsx, app/src/components/channels/YuanbaoConfig.tsx, app/src/components/channels/ChannelSelector.tsx, app/src/components/channels/ChannelSetupModal.tsx, app/src/components/skills/skillIcons.tsx, app/src/store/channelConnectionsSlice.ts
New YuanbaoIcon SVG component and YuanbaoConfig credential form; channel selector/modal refactored to use shared renderChannelIcon helper; Redux state updated with ensureChannelModes helper and migration for yuanbao/lark/dingtalk/mcp channel initialization.
Backend provider core structures
src/openhuman/channels/providers/yuanbao/errors.rs, src/openhuman/channels/providers/yuanbao/types.rs, src/openhuman/channels/providers/yuanbao/channel.rs, src/openhuman/channels/providers/mod.rs, src/openhuman/channels/mod.rs
YuanbaoError enum with error classification constants; domain types (ConnFrame, InboundMessage, MsgBodyElement, MessageKind, Source, GroupInfo, Account, ConnectionState); YuanbaoChannel implementing the Channel trait with chunked send, draft support, listen loop, typing, and inbound dispatch; module exports.
Protocol wire format and message codecs
src/openhuman/channels/providers/yuanbao/wire.rs, src/openhuman/channels/providers/yuanbao/proto_constants.rs, src/openhuman/channels/providers/yuanbao/proto.rs, src/openhuman/channels/providers/yuanbao/proto_biz.rs
Hand-rolled protobuf wire-format with varint/field encoders and parser; protocol constants (cmd types, module names, biz commands, heartbeat codes, TIM element types, reconnect/ping/timeout tuning); ConnMsg framing, auth-bind, ping, push-ack encoders; TIM message serialization; inbound/outbound biz-layer C2C/group send, heartbeat, query codecs.
WebSocket connection, auth, and token signing
src/openhuman/channels/providers/yuanbao/connection.rs, src/openhuman/channels/providers/yuanbao/sign.rs
YuanbaoConnection with reconnect loop (exponential backoff, shutdown watch, no-retry codes), auth-bind handshake within timeout, read loop (inbound event muxing, pending request correlation, push-ack), periodic ping heartbeats; SignManager for token caching per app_key with coalesced refresh, HMAC-SHA256 signing, nonce/timestamp generation, retry on code=10099.
Inbound pipeline, outbound sender, and media
src/openhuman/channels/providers/yuanbao/inbound.rs, src/openhuman/channels/providers/yuanbao/outbound.rs, src/openhuman/channels/providers/yuanbao/splitter.rs, src/openhuman/channels/providers/yuanbao/media.rs, src/openhuman/channels/providers/yuanbao/ids.rs, src/openhuman/channels/providers/yuanbao/cos.rs
InboundPipeline with 17 ordered middleware stages (recall, dedup, self-filter, routing, access-control, home-chat, extraction, placeholder-filter, owner-command, mention enforcement, stripping, classification); OutboundSender for text/image/file sends (DM and group), heartbeats, group queries; fence-aware markdown splitter for message chunking; MIME guessing, image dimension parsing (PNG/JPEG/GIF/WebP), media download; ID shortening for filesystem safety; COS signature generation and upload with temporary credentials.
Connect/disconnect operations and startup wiring
src/openhuman/channels/controllers/ops.rs, src/openhuman/channels/runtime/startup.rs, src/openhuman/channels/commands.rs
connect_channel validates credentials via SignManager::get_token, builds effective config from TOML + endpoint overrides, persists config with cleared app_secret; disconnect_channel clears config; connected_channel_slugs includes yuanbao when configured; resolve_yuanbao_app_secret hydrates from credential store when TOML is empty; startup wires channel when config present; health-check doctor constructs channel on demand.
Integration tests and i18n translations
src/openhuman/channels/controllers/ops_tests.rs, app/src/lib/i18n/chunks/{ar,bn,de,en,es,fr,hi,id,it,ko,pt,ru,zh-CN}-3.ts, app/src/lib/i18n/{en,ko}.ts
Four WireMock-backed tests verify: credential rejection, persistence with restart requirement, override verification, env/route-env persistence; 15 language translation chunks for connecting state, required-field validation, and unexpected status error messages.
Cargo dependency
Cargo.toml
Add sha1 = "0.10" for Tencent COS HMAC-SHA1 signing (legacy, not for new security-sensitive work).

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

This PR introduces a substantial, intricate new channel provider with high-density logic across multiple integration layers: a WebSocket protocol implementation with reconnect, auth, and message correlation; a 17-stage inbound middleware pipeline; media handling with COS integration; token signing with cache coalescing; and comprehensive protocol codecs. The changes span heterogeneous concerns (frontend, state, backend architecture, protocols, operations, tests, i18n) requiring separate reasoning for each layer. The density of new types, state machines, async flows, and protocol-specific logic drives complexity.

Suggested reviewers

  • senamakel
  • graycyrus

🐰 A brand new Yuanbao channel hops in,
With WebSocket whispers and middleware spins,
From frontend to protocol, image uploads too,
Token signing and COS—oh what a brew!
The pipeline flows smooth through dedup and recall,
This comprehensive integration's a triumph for all! 🎉

@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label May 22, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (3)
app/src/components/channels/YuanbaoConfig.tsx (1)

68-71: ⚡ Quick win

Use namespaced debug instead of console.* lifecycle logs in this component.

Please replace console.log/warn/error with the existing debug('channels:yuanbao') flow (and keep verbose detail dev-only) to match frontend logging conventions.

As per coding guidelines "Follow existing patterns for debug logging (e.g. the debug npm package with a namespace per area) plus dev-only detail where useful... Never log secrets, raw JWTs, API keys, or full PII."

Also applies to: 82-87, 100-110, 116-128, 142-143, 154-157, 167-168, 177-178

🤖 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 `@app/src/components/channels/YuanbaoConfig.tsx` around lines 68 - 71, Replace
all console.log/console.warn/console.error calls in the YuanbaoConfig component
(particularly inside the handleConnect function and other lifecycle/debugging
spots) with the project's namespaced debug logger using
debug('channels:yuanbao') so logs follow the existing debug flow; ensure
messages preserve the same informative text but are emitted via the debug
instance and gated for dev-only verbosity, and remove any raw sensitive values
(JWTs, API keys, full PII) from logged output before sending to debug. Locate
the debug replacements in the YuanbaoConfig component (e.g., the handleConnect
function and surrounding blocks where console.* is used) and update each call to
use the debug namespace and sanitized message formatting consistent with other
components.
src/openhuman/channels/providers/yuanbao/connection.rs (1)

451-459: ⚡ Quick win

Use debug/trace for per-frame diagnostics instead of info.

These messages are high-frequency diagnostic logs; keeping them at info will create noisy production logs.

🔧 Suggested fix
-use tracing::{error, info, warn};
+use tracing::{debug, error, info, warn};
...
-        info!(
+        debug!(
             "[yuanbao] rx cmd={} module={} cmd_type={} seq={} msg_id={} data_len={}",
             frame.cmd,
             frame.module,
             frame.cmd_type,
             frame.seq_no,
             frame.msg_id,
             frame.data.len()
         );
...
-        info!(
+        debug!(
             "[yuanbao] push forwarded to listener cmd={} module={} seq={}",
             frame.cmd, frame.module, frame.seq_no
         );

As per coding guidelines src/**/*.rs: “Use log or tracing crate at debug or trace level for Rust diagnostic logs.”

Also applies to: 503-506

🤖 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/openhuman/channels/providers/yuanbao/connection.rs` around lines 451 -
459, The log here uses info! for high-frequency per-frame diagnostics: change
the info! macro call that logs "[yuanbao] rx cmd={} module={} cmd_type={} seq={}
msg_id={} data_len={}" (which references frame.cmd, frame.module,
frame.cmd_type, frame.seq_no, frame.msg_id, frame.data.len()) to debug! (or
trace! if you want even lower noise) so these messages are emitted at the
appropriate diagnostic level; apply the same change to the similar logging call
around the other occurrence that logs the same frame fields (the block noted at
lines 503-506) so all per-frame diagnostics use debug/trace instead of info.
src/openhuman/channels/providers/yuanbao/channel.rs (1)

268-274: ⚡ Quick win

Downgrade per-event diagnostic logs from info to debug/trace.

These branches are diagnostic/high-frequency paths; logging them at info will be noisy in production.

As per coding guidelines: src/**/*.rs: “Use log or tracing crate at debug or trace level for Rust diagnostic logs.”

Also applies to: 346-361

🤖 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/openhuman/channels/providers/yuanbao/channel.rs` around lines 268 - 274,
The per-event diagnostic logs inside the Yuanbao channel listen loop should be
downgraded from info! to debug! or trace! — locate the listen loop that creates
shutdown_rx2 (the tokio::select! block in the channel's listen/loop function)
and replace the high-frequency info! calls (e.g., "[yuanbao] channel listening —
pipeline ready" and "[yuanbao] listen loop received shutdown") with debug! or
trace! as appropriate; apply the same change to the other diagnostic branches
referenced (the similar log statements around the other select branches
~346-361) while keeping only true errors or important lifecycle messages at info
level.
🤖 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 `@app/src/components/channels/ChannelSetupModal.tsx`:
- Around line 68-69: The modal header icon is empty because CHANNEL_ICONS lacks
a 'yuanbao' entry so emojiIcon = CHANNEL_ICONS[definition.icon] ?? '' returns
''. Update ChannelSetupModal to handle the yuanbao icon by either adding a
'yuanbao' key to CHANNEL_ICONS mapped to the correct emoji string, or
special-case render YuanbaoIcon when definition.icon === 'yuanbao' (use the
existing YuanbaoIcon component instead of emojiIcon) so the header shows the
correct icon; update both places where emojiIcon is used (including the header
render and the other occurrence around line 92).

In `@app/src/components/channels/YuanbaoConfig.tsx`:
- Line 78: The validation message in YuanbaoConfig uses a hardcoded Chinese
string; replace the literal `${field.label} 不能为空` with the app's i18n call (the
same translate/util used elsewhere in this component) so the message is
localized—e.g. build the message via the translation function with field.label
or a keyed template, and assign that to errors[field.key] instead of the
hardcoded string; update any tests/usage that expect the previous literal if
present.
- Around line 112-117: The handler handleConnect currently marks Yuanbao as
connected unconditionally after calling channelConnectionsApi.connectChannel;
change it to first inspect the returned result.status (from connectChannel) and
only dispatch the connected action when result.status === 'connected', otherwise
handle other statuses (e.g., 'pending' or 'error') by logging the result and
dispatching an appropriate non-connected/failed action or showing UI feedback;
keep references to result, channelConnectionsApi.connectChannel, and the
existing log/console.log calls to aid locating the mutation and ensure no
optimistic connected dispatch occurs before status validation.

In `@src/openhuman/channels/controllers/ops.rs`:
- Around line 405-407: The code is persisting the raw Yuanbao secret by
assigning yb_config.app_secret and saving persisted.channels_config.yuanbao;
instead, stop writing the plaintext secret into persisted config and persist
only a credential reference (e.g., a credential_id or key name). Store the
actual secret in the secure credential store / vault or OS keyring and update
the flow where yb_config is constructed to load the secret from that store at
runtime using the credential reference; remove any assignment to
yb_config.app_secret that would persist the secret and ensure
persisted.channels_config.yuanbao contains only non-sensitive fields and the
credential reference.
- Around line 399-407: When updating the yuanbao config you only set
app_key/app_secret and drop any custom endpoint selection; preserve the client's
chosen endpoint by also copying the env and api_domain into yb_config before
assigning it back to persisted.channels_config.yuanbao. Concretely, read the
existing or incoming endpoint fields (env and api_domain) into yb_config
(alongside app_key and app_secret) so yb_config.env and yb_config.api_domain are
set appropriately before persisted.channels_config.yuanbao = Some(yb_config).

In `@src/openhuman/channels/providers/yuanbao/channel.rs`:
- Around line 408-417: The current pipeline error log computes and emits a hex
preview of the inbound payload (biz_body via preview_len and hex) which may leak
PII; change the warn! call to stop logging raw bytes—remove the hex variable and
its formatting, and instead log only safe metadata such as biz_body.len() and a
redacted marker (e.g. "<redacted>") or a non-reversible digest of the preview
(hash of biz_body[..preview_len]) if you need a correlatable fingerprint; update
the warn! invocation that references preview_len and hex accordingly so no raw
payload bytes are ever written to logs.

In `@src/openhuman/channels/providers/yuanbao/connection.rs`:
- Around line 217-234: The reconnect loop sleeps with backoff even when shutdown
already fired; after the cleanup block (where run() calls self.set_state, clears
sender/pending and increments attempt) detect that shutdown is already signaled
and return immediately instead of awaiting the backoff. Concretely, in the
run()/reconnect loop (the code around connect_once, backoff_seconds, and the
tokio::select) check the shutdown watcher’s current value (e.g. inspect
shutdown.borrow() or use the watch receiver’s immediate-check helper) right
after the cleanup and short-circuit by calling self.shutdown().await; return so
you skip the time::sleep path and avoid delayed shutdown.

In `@src/openhuman/channels/providers/yuanbao/proto_biz.rs`:
- Around line 247-248: The decoder currently narrows get_varint(&fields, ...)
(u64) with as i32/as u32 causing silent truncation; change each cast site (e.g.,
where populating fields like code, member_count, next_offset, role, join_time)
to use i32::try_from(...) or u32::try_from(...), map Err to return
YuanbaoError::ProtoDecode(...) with a clear message, and propagate the error
from the surrounding decoder function instead of using as-casts; locate usages
of get_varint in the decoder functions and replace the direct casts with
try_from conversions and early returns on failure.

In `@src/openhuman/channels/providers/yuanbao/wire.rs`:
- Around line 101-111: The arithmetic that computes end = pos + len as usize can
overflow and panic when a crafted large varint length is returned by
decode_varint; update the decode path in the function using decode_varint so you
perform checked conversions and additions (e.g., convert len to usize safely
with a bounds/try_from or checked_cast and use pos.checked_add(len_usize)) and
validate that pos <= data.len() before slicing, returning
YuanbaoError::ProtoDecode with a clear message on any overflow/invalid length;
ensure the same guarded logic applies to computing remaining bytes
(data.len().checked_sub(pos)) before constructing FieldValue::Bytes and pushing
to out.

---

Nitpick comments:
In `@app/src/components/channels/YuanbaoConfig.tsx`:
- Around line 68-71: Replace all console.log/console.warn/console.error calls in
the YuanbaoConfig component (particularly inside the handleConnect function and
other lifecycle/debugging spots) with the project's namespaced debug logger
using debug('channels:yuanbao') so logs follow the existing debug flow; ensure
messages preserve the same informative text but are emitted via the debug
instance and gated for dev-only verbosity, and remove any raw sensitive values
(JWTs, API keys, full PII) from logged output before sending to debug. Locate
the debug replacements in the YuanbaoConfig component (e.g., the handleConnect
function and surrounding blocks where console.* is used) and update each call to
use the debug namespace and sanitized message formatting consistent with other
components.

In `@src/openhuman/channels/providers/yuanbao/channel.rs`:
- Around line 268-274: The per-event diagnostic logs inside the Yuanbao channel
listen loop should be downgraded from info! to debug! or trace! — locate the
listen loop that creates shutdown_rx2 (the tokio::select! block in the channel's
listen/loop function) and replace the high-frequency info! calls (e.g.,
"[yuanbao] channel listening — pipeline ready" and "[yuanbao] listen loop
received shutdown") with debug! or trace! as appropriate; apply the same change
to the other diagnostic branches referenced (the similar log statements around
the other select branches ~346-361) while keeping only true errors or important
lifecycle messages at info level.

In `@src/openhuman/channels/providers/yuanbao/connection.rs`:
- Around line 451-459: The log here uses info! for high-frequency per-frame
diagnostics: change the info! macro call that logs "[yuanbao] rx cmd={}
module={} cmd_type={} seq={} msg_id={} data_len={}" (which references frame.cmd,
frame.module, frame.cmd_type, frame.seq_no, frame.msg_id, frame.data.len()) to
debug! (or trace! if you want even lower noise) so these messages are emitted at
the appropriate diagnostic level; apply the same change to the similar logging
call around the other occurrence that logs the same frame fields (the block
noted at lines 503-506) so all per-frame diagnostics use debug/trace instead of
info.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ac2efc8b-d851-4e20-8009-d0b91d05d476

📥 Commits

Reviewing files that changed from the base of the PR and between ed3e453 and 26537ca.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • app/src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (33)
  • Cargo.toml
  • app/src/components/channels/ChannelSelector.tsx
  • app/src/components/channels/ChannelSetupModal.tsx
  • app/src/components/channels/YuanbaoConfig.tsx
  • app/src/components/channels/YuanbaoIcon.tsx
  • app/src/components/skills/skillIcons.tsx
  • app/src/store/channelConnectionsSlice.ts
  • app/src/types/channels.ts
  • src/openhuman/channels/commands.rs
  • src/openhuman/channels/controllers/definitions.rs
  • src/openhuman/channels/controllers/ops.rs
  • src/openhuman/channels/controllers/ops_tests.rs
  • src/openhuman/channels/mod.rs
  • src/openhuman/channels/providers/mod.rs
  • src/openhuman/channels/providers/yuanbao/channel.rs
  • src/openhuman/channels/providers/yuanbao/config.rs
  • src/openhuman/channels/providers/yuanbao/connection.rs
  • src/openhuman/channels/providers/yuanbao/cos.rs
  • src/openhuman/channels/providers/yuanbao/errors.rs
  • src/openhuman/channels/providers/yuanbao/ids.rs
  • src/openhuman/channels/providers/yuanbao/inbound.rs
  • src/openhuman/channels/providers/yuanbao/media.rs
  • src/openhuman/channels/providers/yuanbao/mod.rs
  • src/openhuman/channels/providers/yuanbao/outbound.rs
  • src/openhuman/channels/providers/yuanbao/proto.rs
  • src/openhuman/channels/providers/yuanbao/proto_biz.rs
  • src/openhuman/channels/providers/yuanbao/proto_constants.rs
  • src/openhuman/channels/providers/yuanbao/sign.rs
  • src/openhuman/channels/providers/yuanbao/splitter.rs
  • src/openhuman/channels/providers/yuanbao/types.rs
  • src/openhuman/channels/providers/yuanbao/wire.rs
  • src/openhuman/channels/runtime/startup.rs
  • src/openhuman/config/schema/channels.rs

Comment thread app/src/components/channels/ChannelSetupModal.tsx
Comment thread app/src/components/channels/YuanbaoConfig.tsx Outdated
Comment thread app/src/components/channels/YuanbaoConfig.tsx
Comment thread src/openhuman/channels/controllers/ops.rs
Comment thread src/openhuman/channels/controllers/ops.rs Outdated
Comment thread src/openhuman/channels/providers/yuanbao/channel.rs Outdated
Comment thread src/openhuman/channels/providers/yuanbao/connection.rs
Comment thread src/openhuman/channels/providers/yuanbao/proto_biz.rs Outdated
Comment thread src/openhuman/channels/providers/yuanbao/wire.rs
@senamakel senamakel self-assigned this May 23, 2026
lrt4836 added a commit to YuanbaoTeam/openhuman that referenced this pull request May 23, 2026
Resolve all 9 actionable comments from the CodeRabbit review.

Frontend:
- ChannelSetupModal: render branded YuanbaoIcon for the yuanbao channel
  instead of falling through CHANNEL_ICONS to an empty emoji.
- YuanbaoConfig: route required-field validation and the connecting
  spinner copy through i18n; branch on connectChannel's reported status
  so non-"connected" results surface as errors instead of being silently
  treated as success.
- i18n: add 3 new keys (fieldRequired / connecting / unexpectedStatus)
  to en, ko, and all chunk-3 files (zh-CN translated; other locales use
  English placeholders to keep `pnpm i18n:check` green).

Backend (Rust):
- ops.rs: stop persisting `app_secret` in plaintext config.toml — the
  encrypted credentials store already holds it. Also persist the optional
  endpoint overrides (env / api_domain / ws_domain / route_env) so a
  non-default cluster selection survives restart.
- startup.rs: extract `resolve_yuanbao_app_secret` and load the secret
  from the credentials store at startup when TOML is empty. Pre-existing
  TOML values still win so manually-installed deployments don't break.
- channel.rs: drop the hex preview of inbound biz payloads from the
  pipeline-error log path; user content / PII must not leak to logs.
- connection.rs: re-check `*shutdown.borrow()` after connect_once
  returns. Without this, a shutdown signal observed inside connect_once
  consumes the `changed()` notification, leaving the outer
  `tokio::select!` to wait through the full reconnect backoff before
  exiting.
- proto_biz.rs: replace `as i32` / `as u32` casts on decoded varints
  with `varint_to_i32` / `varint_to_u32` helpers backed by `try_from`,
  so oversized fields surface a structured `ProtoDecode` error instead
  of silently truncating `code`, `member_count`, `role`, `join_time`,
  and `next_offset`.
- wire.rs: guard the `WT_LEN` arithmetic with `usize::try_from(len)` and
  `pos.checked_add(...)`, eliminating the slice-panic path on
  adversarial length-delimited fields.

Tests (+6 net):
- proto_biz: `decode_biz_rsp_code_rejects_varint_out_of_i32_range`,
  `decode_group_member_list_rejects_varint_out_of_u32_range`.
- wire: `parse_fields_oversize_len_field_errors_without_panic`.
- ops: updated `connect_yuanbao_persists_when_credentials_valid` to
  assert TOML has no plaintext secret and the store has both fields;
  new `connect_yuanbao_persists_env_override` covering the endpoint
  round-trip.
- startup: three tests around `resolve_yuanbao_app_secret` (load from
  credentials, prefer existing TOML, gracefully return empty).
- connection: `run_exits_promptly_after_shutdown_signal` regression
  guard against backoff-blocked shutdown.

cargo test --lib -- yuanbao: 188 passed (was 182).
pnpm i18n:check: green.
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 23, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/openhuman/channels/providers/yuanbao/media.rs (1)

206-215: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize the response MIME type before returning it.

is_image() and image_format_code() both compare MIME values case-sensitively. If a server returns a valid header like Image/PNG, this path preserves that casing and downstream code will treat it as a non-image / unknown format.

💡 Suggested fix
     let ct = resp
         .headers()
         .get(reqwest::header::CONTENT_TYPE)
         .and_then(|v| v.to_str().ok())
         .unwrap_or("")
         .split(';')
         .next()
         .unwrap_or("")
         .trim()
+        .to_ascii_lowercase()
         .to_string();
🤖 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/openhuman/channels/providers/yuanbao/media.rs` around lines 206 - 215,
The extracted Content-Type string stored in ct should be normalized (e.g., to
lowercase and trimmed) before being returned so downstream checks like
is_image() and image_format_code()—which perform case-sensitive comparisons—see
a canonical MIME type; update the ct construction (the block that reads
headers().get(CONTENT_TYPE) and produces ct) to convert the final MIME token to
a normalized form (lowercase) and preserve trimming so is_image() and
image_format_code() will match correctly.
🧹 Nitpick comments (1)
app/src/components/channels/YuanbaoConfig.tsx (1)

50-64: ⚡ Quick win

YuanbaoConfig: reconsider dispatching reset-from-mount for persisted “connecting”

  • The repo’s ESLint config sets react-hooks/set-state-in-effect to warn with “Allow initialization in effects”, so this may be treated as acceptable rehydration rather than a hard lint violation.
  • Still, this mount effect (lines 50-64) can produce a brief stale “connecting” UI before the correction; consider normalizing the persisted connection status earlier and deriving the effective status in render instead of dispatching on mount.
🤖 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 `@app/src/components/channels/YuanbaoConfig.tsx` around lines 50 - 64, The
useEffect in YuanbaoConfig currently dispatches setChannelConnectionStatus on
mount to clear a persisted "connecting" state (it references
channelConnections.connections.yuanbao and spec.mode); instead, remove that
mount-time dispatch and instead derive an effective status at render time (e.g.,
compute effectiveStatus from channelConnections.connections.yuanbao?.[spec.mode]
and fall back to 'disconnected' when it equals 'connecting' or is stale) so the
UI never briefly shows stale "connecting"; alternatively, perform this
normalization during rehydration/initialization in the store reducer where
channelConnections is restored rather than in the component's useEffect.
🤖 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 `@src/openhuman/channels/controllers/ops.rs`:
- Around line 402-416: The preflight verifier is using the base Yuanbao config
instead of the user-supplied overrides parsed by opt_string (env_override,
api_domain_override, ws_domain_override, route_env_override), so
verify_yuanbao_credentials may test the wrong endpoint; fix by constructing the
effective Yuanbao config right after those opt_string calls (apply non-empty
overrides onto the original credentials/config), pass that effective config into
verify_yuanbao_credentials, and then persist the same effective config when
saving credentials so the runtime uses the identical endpoint on restart.

In `@src/openhuman/channels/providers/yuanbao/cos.rs`:
- Around line 476-484: The test
get_cos_credentials_sends_route_env_header_when_non_empty should also assert the
request path equals UPLOAD_INFO_PATH so it fails if the POST goes to the wrong
endpoint; update the wiremock expectation in that test to include a path matcher
for UPLOAD_INFO_PATH (use the UPLOAD_INFO_PATH symbol from the module) alongside
the existing header matcher so the mock only matches POSTs to the correct
endpoint with X-Route-Env: canary.

In `@src/openhuman/channels/runtime/startup.rs`:
- Around line 650-654: The code hydrates yb_cfg.app_secret from any stored
profile returned by auth.get_profile without verifying it matches the currently
configured yb_cfg.app_key; change the logic to only set yb_cfg.app_secret when
the stored profile's app_key equals the configured yb_cfg.app_key (e.g., check
profile.metadata.get("app_key") == Some(&yb_cfg.app_key) before assigning), or
alternatively query the encrypted store with a key that includes yb_cfg.app_key
so you cannot load a secret for a different app_key; update the branch around
auth.get_profile, the profile.metadata access, and the yb_cfg.app_secret
assignment to enforce this check.

---

Outside diff comments:
In `@src/openhuman/channels/providers/yuanbao/media.rs`:
- Around line 206-215: The extracted Content-Type string stored in ct should be
normalized (e.g., to lowercase and trimmed) before being returned so downstream
checks like is_image() and image_format_code()—which perform case-sensitive
comparisons—see a canonical MIME type; update the ct construction (the block
that reads headers().get(CONTENT_TYPE) and produces ct) to convert the final
MIME token to a normalized form (lowercase) and preserve trimming so is_image()
and image_format_code() will match correctly.

---

Nitpick comments:
In `@app/src/components/channels/YuanbaoConfig.tsx`:
- Around line 50-64: The useEffect in YuanbaoConfig currently dispatches
setChannelConnectionStatus on mount to clear a persisted "connecting" state (it
references channelConnections.connections.yuanbao and spec.mode); instead,
remove that mount-time dispatch and instead derive an effective status at render
time (e.g., compute effectiveStatus from
channelConnections.connections.yuanbao?.[spec.mode] and fall back to
'disconnected' when it equals 'connecting' or is stale) so the UI never briefly
shows stale "connecting"; alternatively, perform this normalization during
rehydration/initialization in the store reducer where channelConnections is
restored rather than in the component's useEffect.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38946162-7261-49db-b5f2-7303b7e0457f

📥 Commits

Reviewing files that changed from the base of the PR and between 26537ca and a6820d3.

📒 Files selected for processing (30)
  • app/src/components/channels/ChannelSetupModal.tsx
  • app/src/components/channels/YuanbaoConfig.tsx
  • app/src/lib/i18n/chunks/ar-3.ts
  • app/src/lib/i18n/chunks/bn-3.ts
  • app/src/lib/i18n/chunks/de-3.ts
  • app/src/lib/i18n/chunks/en-3.ts
  • app/src/lib/i18n/chunks/es-3.ts
  • app/src/lib/i18n/chunks/fr-3.ts
  • app/src/lib/i18n/chunks/hi-3.ts
  • app/src/lib/i18n/chunks/id-3.ts
  • app/src/lib/i18n/chunks/it-3.ts
  • app/src/lib/i18n/chunks/ko-3.ts
  • app/src/lib/i18n/chunks/pt-3.ts
  • app/src/lib/i18n/chunks/ru-3.ts
  • app/src/lib/i18n/chunks/zh-CN-3.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/ko.ts
  • src/openhuman/channels/controllers/ops.rs
  • src/openhuman/channels/controllers/ops_tests.rs
  • src/openhuman/channels/providers/yuanbao/channel.rs
  • src/openhuman/channels/providers/yuanbao/connection.rs
  • src/openhuman/channels/providers/yuanbao/cos.rs
  • src/openhuman/channels/providers/yuanbao/media.rs
  • src/openhuman/channels/providers/yuanbao/outbound.rs
  • src/openhuman/channels/providers/yuanbao/proto.rs
  • src/openhuman/channels/providers/yuanbao/proto_biz.rs
  • src/openhuman/channels/providers/yuanbao/sign.rs
  • src/openhuman/channels/providers/yuanbao/types.rs
  • src/openhuman/channels/providers/yuanbao/wire.rs
  • src/openhuman/channels/runtime/startup.rs
✅ Files skipped from review due to trivial changes (7)
  • app/src/lib/i18n/chunks/en-3.ts
  • app/src/lib/i18n/chunks/pt-3.ts
  • app/src/lib/i18n/chunks/ko-3.ts
  • app/src/lib/i18n/chunks/fr-3.ts
  • app/src/lib/i18n/chunks/it-3.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/chunks/ru-3.ts

Comment thread src/openhuman/channels/controllers/ops.rs Outdated
Comment thread src/openhuman/channels/providers/yuanbao/cos.rs
Comment thread src/openhuman/channels/runtime/startup.rs
lrt4836 added a commit to YuanbaoTeam/openhuman that referenced this pull request May 23, 2026
…humansai#2494

Three CodeRabbit-flagged issues from the follow-up review:

1) ops.rs — apply endpoint overrides BEFORE preflight verification.

   The previous flow rebuilt YuanbaoConfig from `config.channels_config.yuanbao`
   alone inside `verify_yuanbao_credentials`, so client-supplied
   `env` / `api_domain` / `ws_domain` / `route_env` overrides were ignored
   at verify-time but applied at persistence-time. A user submitting
   `env = "pre"` could pass verification against PROD's sign-token
   cluster and then fail auth after restart when the persisted override
   took effect.

   The verifier now consumes a pre-built effective `YuanbaoConfig`. A new
   `build_effective_yuanbao_config` helper overlays the overrides on top
   of the existing TOML, calls `apply_env_defaults`, and produces the
   single config used for BOTH verify and persistence — they can no
   longer diverge.

2) cos.rs — tighten `get_cos_credentials_sends_route_env_header_when_non_empty`.

   The wiremock matcher only checked for `X-Route-Env: canary`, so a
   future refactor routing the call to the wrong endpoint would still
   pass the test as long as some POST carried that header. Bind the
   matcher to `UPLOAD_INFO_PATH` as well.

3) startup.rs — don't hydrate `app_secret` from a stale profile.

   `resolve_yuanbao_app_secret` used to copy whatever secret was in the
   encrypted store, even if `yb_cfg.app_key` had been edited in
   `config.toml`. That would silently pair a new key with an old secret
   on next startup and the channel would fail auth until the user
   reconnected manually.

   Now compare the stored profile's `app_key` metadata to `yb_cfg.app_key`
   before copying. On mismatch, log a warning naming both keys and leave
   `app_secret` empty so `YuanbaoChannel::new`'s `validate()` step fails
   loudly instead of attempting auth with a stale value.

Tests added:
- ops_tests: `connect_yuanbao_verifies_against_overridden_api_domain`
  proves the verifier hits the override URI even when base TOML
  `api_domain` points at a black hole (`http://127.0.0.1:1`), and that
  the same override is the one persisted.
- startup tests: `skips_hydration_when_stored_profile_has_different_app_key`
  proves we leave `app_secret` empty when the store profile is keyed
  to a different `app_key`.

cargo test -- yuanbao: 190 passed, 0 failed.
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 23, 2026
@lrt4836 lrt4836 force-pushed the feat/yuanbao-channel branch from 36a290e to b2f3d21 Compare May 24, 2026 05:03
lrt4836 added a commit to YuanbaoTeam/openhuman that referenced this pull request May 24, 2026
Resolve all 9 actionable comments from the CodeRabbit review.

Frontend:
- ChannelSetupModal: render branded YuanbaoIcon for the yuanbao channel
  instead of falling through CHANNEL_ICONS to an empty emoji.
- YuanbaoConfig: route required-field validation and the connecting
  spinner copy through i18n; branch on connectChannel's reported status
  so non-"connected" results surface as errors instead of being silently
  treated as success.
- i18n: add 3 new keys (fieldRequired / connecting / unexpectedStatus)
  to en, ko, and all chunk-3 files (zh-CN translated; other locales use
  English placeholders to keep `pnpm i18n:check` green).

Backend (Rust):
- ops.rs: stop persisting `app_secret` in plaintext config.toml — the
  encrypted credentials store already holds it. Also persist the optional
  endpoint overrides (env / api_domain / ws_domain / route_env) so a
  non-default cluster selection survives restart.
- startup.rs: extract `resolve_yuanbao_app_secret` and load the secret
  from the credentials store at startup when TOML is empty. Pre-existing
  TOML values still win so manually-installed deployments don't break.
- channel.rs: drop the hex preview of inbound biz payloads from the
  pipeline-error log path; user content / PII must not leak to logs.
- connection.rs: re-check `*shutdown.borrow()` after connect_once
  returns. Without this, a shutdown signal observed inside connect_once
  consumes the `changed()` notification, leaving the outer
  `tokio::select!` to wait through the full reconnect backoff before
  exiting.
- proto_biz.rs: replace `as i32` / `as u32` casts on decoded varints
  with `varint_to_i32` / `varint_to_u32` helpers backed by `try_from`,
  so oversized fields surface a structured `ProtoDecode` error instead
  of silently truncating `code`, `member_count`, `role`, `join_time`,
  and `next_offset`.
- wire.rs: guard the `WT_LEN` arithmetic with `usize::try_from(len)` and
  `pos.checked_add(...)`, eliminating the slice-panic path on
  adversarial length-delimited fields.

Tests (+6 net):
- proto_biz: `decode_biz_rsp_code_rejects_varint_out_of_i32_range`,
  `decode_group_member_list_rejects_varint_out_of_u32_range`.
- wire: `parse_fields_oversize_len_field_errors_without_panic`.
- ops: updated `connect_yuanbao_persists_when_credentials_valid` to
  assert TOML has no plaintext secret and the store has both fields;
  new `connect_yuanbao_persists_env_override` covering the endpoint
  round-trip.
- startup: three tests around `resolve_yuanbao_app_secret` (load from
  credentials, prefer existing TOML, gracefully return empty).
- connection: `run_exits_promptly_after_shutdown_signal` regression
  guard against backoff-blocked shutdown.

cargo test --lib -- yuanbao: 188 passed (was 182).
pnpm i18n:check: green.
lrt4836 added a commit to YuanbaoTeam/openhuman that referenced this pull request May 24, 2026
…humansai#2494

Three CodeRabbit-flagged issues from the follow-up review:

1) ops.rs — apply endpoint overrides BEFORE preflight verification.

   The previous flow rebuilt YuanbaoConfig from `config.channels_config.yuanbao`
   alone inside `verify_yuanbao_credentials`, so client-supplied
   `env` / `api_domain` / `ws_domain` / `route_env` overrides were ignored
   at verify-time but applied at persistence-time. A user submitting
   `env = "pre"` could pass verification against PROD's sign-token
   cluster and then fail auth after restart when the persisted override
   took effect.

   The verifier now consumes a pre-built effective `YuanbaoConfig`. A new
   `build_effective_yuanbao_config` helper overlays the overrides on top
   of the existing TOML, calls `apply_env_defaults`, and produces the
   single config used for BOTH verify and persistence — they can no
   longer diverge.

2) cos.rs — tighten `get_cos_credentials_sends_route_env_header_when_non_empty`.

   The wiremock matcher only checked for `X-Route-Env: canary`, so a
   future refactor routing the call to the wrong endpoint would still
   pass the test as long as some POST carried that header. Bind the
   matcher to `UPLOAD_INFO_PATH` as well.

3) startup.rs — don't hydrate `app_secret` from a stale profile.

   `resolve_yuanbao_app_secret` used to copy whatever secret was in the
   encrypted store, even if `yb_cfg.app_key` had been edited in
   `config.toml`. That would silently pair a new key with an old secret
   on next startup and the channel would fail auth until the user
   reconnected manually.

   Now compare the stored profile's `app_key` metadata to `yb_cfg.app_key`
   before copying. On mismatch, log a warning naming both keys and leave
   `app_secret` empty so `YuanbaoChannel::new`'s `validate()` step fails
   loudly instead of attempting auth with a stale value.

Tests added:
- ops_tests: `connect_yuanbao_verifies_against_overridden_api_domain`
  proves the verifier hits the override URI even when base TOML
  `api_domain` points at a black hole (`http://127.0.0.1:1`), and that
  the same override is the one persisted.
- startup tests: `skips_hydration_when_stored_profile_has_different_app_key`
  proves we leave `app_secret` empty when the store profile is keyed
  to a different `app_key`.

cargo test -- yuanbao: 190 passed, 0 failed.
lrt4836 added a commit to YuanbaoTeam/openhuman that referenced this pull request May 24, 2026
The Coverage Gate (diff-cover ≥ 80%) job was failing on PR tinyhumansai#2494
at 13% because the yuanbao channel surface lacked Vitest coverage.
Add targeted unit tests that lift diff coverage on the touched
files well above the 80% threshold:

- YuanbaoIcon: 33% → 100% (default/custom className, unique
  clipPath ids per instance for collision-free dual rendering)
- YuanbaoConfig: 2% → 95.1% (renders fields, returns null with
  no auth modes, inline validation + clear-on-input, all four
  connect outcomes — connected/restart_required/restart-fails/
  non-connected/connect-throws — disconnect success+failure,
  stale-connecting reset on mount, lastError rendering)
- ChannelSetupModal: 60% → 100% (yuanbao SVG branch + yuanbao
  switch case + emoji branch + fallback message + Escape close)
- channelConnectionsSlice: 87.5% → 100% (ensureChannelModes
  lazy-init when persisted state is missing the yuanbao key)

Diff-cover total on changed lines: 13% → 84%.
lrt4836 added 8 commits May 25, 2026 10:07
Adds a new Yuanbao channel provider so OpenHuman can talk to the
Yuanbao bot service over signed WebSocket + HTTPS.

- Backend: full provider under `src/openhuman/channels/providers/yuanbao/`
  (signed WS connection, inbound/outbound pipelines, COS media upload,
  protobuf wire codec, ID shortening for conversation-store filenames),
  wired through channel config schema, registry, runtime startup,
  controllers, and CLI.
- Frontend: dedicated `YuanbaoConfig` form + icon, hooked into the
  channel selector, setup modal, and connection slice using the
  existing per-channel definition pattern.
- Endpoint config: prod / pre-release defaults selectable via `env`
  field; no test credentials in code.
Resolve all 9 actionable comments from the CodeRabbit review.

Frontend:
- ChannelSetupModal: render branded YuanbaoIcon for the yuanbao channel
  instead of falling through CHANNEL_ICONS to an empty emoji.
- YuanbaoConfig: route required-field validation and the connecting
  spinner copy through i18n; branch on connectChannel's reported status
  so non-"connected" results surface as errors instead of being silently
  treated as success.
- i18n: add 3 new keys (fieldRequired / connecting / unexpectedStatus)
  to en, ko, and all chunk-3 files (zh-CN translated; other locales use
  English placeholders to keep `pnpm i18n:check` green).

Backend (Rust):
- ops.rs: stop persisting `app_secret` in plaintext config.toml — the
  encrypted credentials store already holds it. Also persist the optional
  endpoint overrides (env / api_domain / ws_domain / route_env) so a
  non-default cluster selection survives restart.
- startup.rs: extract `resolve_yuanbao_app_secret` and load the secret
  from the credentials store at startup when TOML is empty. Pre-existing
  TOML values still win so manually-installed deployments don't break.
- channel.rs: drop the hex preview of inbound biz payloads from the
  pipeline-error log path; user content / PII must not leak to logs.
- connection.rs: re-check `*shutdown.borrow()` after connect_once
  returns. Without this, a shutdown signal observed inside connect_once
  consumes the `changed()` notification, leaving the outer
  `tokio::select!` to wait through the full reconnect backoff before
  exiting.
- proto_biz.rs: replace `as i32` / `as u32` casts on decoded varints
  with `varint_to_i32` / `varint_to_u32` helpers backed by `try_from`,
  so oversized fields surface a structured `ProtoDecode` error instead
  of silently truncating `code`, `member_count`, `role`, `join_time`,
  and `next_offset`.
- wire.rs: guard the `WT_LEN` arithmetic with `usize::try_from(len)` and
  `pos.checked_add(...)`, eliminating the slice-panic path on
  adversarial length-delimited fields.

Tests (+6 net):
- proto_biz: `decode_biz_rsp_code_rejects_varint_out_of_i32_range`,
  `decode_group_member_list_rejects_varint_out_of_u32_range`.
- wire: `parse_fields_oversize_len_field_errors_without_panic`.
- ops: updated `connect_yuanbao_persists_when_credentials_valid` to
  assert TOML has no plaintext secret and the store has both fields;
  new `connect_yuanbao_persists_env_override` covering the endpoint
  round-trip.
- startup: three tests around `resolve_yuanbao_app_secret` (load from
  credentials, prefer existing TOML, gracefully return empty).
- connection: `run_exits_promptly_after_shutdown_signal` regression
  guard against backoff-blocked shutdown.

cargo test --lib -- yuanbao: 188 passed (was 182).
pnpm i18n:check: green.
…humansai#2494

Three CodeRabbit-flagged issues from the follow-up review:

1) ops.rs — apply endpoint overrides BEFORE preflight verification.

   The previous flow rebuilt YuanbaoConfig from `config.channels_config.yuanbao`
   alone inside `verify_yuanbao_credentials`, so client-supplied
   `env` / `api_domain` / `ws_domain` / `route_env` overrides were ignored
   at verify-time but applied at persistence-time. A user submitting
   `env = "pre"` could pass verification against PROD's sign-token
   cluster and then fail auth after restart when the persisted override
   took effect.

   The verifier now consumes a pre-built effective `YuanbaoConfig`. A new
   `build_effective_yuanbao_config` helper overlays the overrides on top
   of the existing TOML, calls `apply_env_defaults`, and produces the
   single config used for BOTH verify and persistence — they can no
   longer diverge.

2) cos.rs — tighten `get_cos_credentials_sends_route_env_header_when_non_empty`.

   The wiremock matcher only checked for `X-Route-Env: canary`, so a
   future refactor routing the call to the wrong endpoint would still
   pass the test as long as some POST carried that header. Bind the
   matcher to `UPLOAD_INFO_PATH` as well.

3) startup.rs — don't hydrate `app_secret` from a stale profile.

   `resolve_yuanbao_app_secret` used to copy whatever secret was in the
   encrypted store, even if `yb_cfg.app_key` had been edited in
   `config.toml`. That would silently pair a new key with an old secret
   on next startup and the channel would fail auth until the user
   reconnected manually.

   Now compare the stored profile's `app_key` metadata to `yb_cfg.app_key`
   before copying. On mismatch, log a warning naming both keys and leave
   `app_secret` empty so `YuanbaoChannel::new`'s `validate()` step fails
   loudly instead of attempting auth with a stale value.

Tests added:
- ops_tests: `connect_yuanbao_verifies_against_overridden_api_domain`
  proves the verifier hits the override URI even when base TOML
  `api_domain` points at a black hole (`http://127.0.0.1:1`), and that
  the same override is the one persisted.
- startup tests: `skips_hydration_when_stored_profile_has_different_app_key`
  proves we leave `app_secret` empty when the store profile is keyed
  to a different `app_key`.

cargo test -- yuanbao: 190 passed, 0 failed.
Per the matrix update contract in `docs/TEST-COVERAGE-MATRIX.md` — any
PR that adds, removes, or changes a feature leaf must update the
matrix in the same change. This PR introduces the Yuanbao channel
provider, so add the leaf row pointing at the RU test paths landed in
this PR (sign-token preflight, credentials store hydration including
the stale-app_key guard, WS reconnect/shutdown).

Status 🟡 not ✅: there is no dedicated WDIO spec for Yuanbao yet —
the connect-flow UI is rendered via the generic `ChannelSetupModal`
that is already covered by other channel flow specs.
The Coverage Gate (diff-cover ≥ 80%) job was failing on PR tinyhumansai#2494
at 13% because the yuanbao channel surface lacked Vitest coverage.
Add targeted unit tests that lift diff coverage on the touched
files well above the 80% threshold:

- YuanbaoIcon: 33% → 100% (default/custom className, unique
  clipPath ids per instance for collision-free dual rendering)
- YuanbaoConfig: 2% → 95.1% (renders fields, returns null with
  no auth modes, inline validation + clear-on-input, all four
  connect outcomes — connected/restart_required/restart-fails/
  non-connected/connect-throws — disconnect success+failure,
  stale-connecting reset on mount, lastError rendering)
- ChannelSetupModal: 60% → 100% (yuanbao SVG branch + yuanbao
  switch case + emoji branch + fallback message + Escape close)
- channelConnectionsSlice: 87.5% → 100% (ensureChannelModes
  lazy-init when persisted state is missing the yuanbao key)

Diff-cover total on changed lines: 13% → 84%.
Upstream refactored `getChannelIcons()` in `skills/skillIcons.tsx` to
take a `t()` translator and read each entry's aria-label from
`skills.channelIcon.<channel>`. Add the matching `yuanbao` entry to
`en.ts` and all 13 locale chunks so the yuanbao branch (resolved
during rebase onto upstream/main) does not break `pnpm i18n:check`.

zh-CN translated as 元宝; other locales carry the same `Yuanbao`
placeholder as the existing un-translated `discord`/`telegram` keys.
@lrt4836 lrt4836 force-pushed the feat/yuanbao-channel branch from 426263a to 2fd5746 Compare May 25, 2026 02:30
@senamakel
Copy link
Copy Markdown
Member

Post-merge-with-main fixes (from senamakel/openhuman:pr/2494-fixes)

I resolved the merge conflicts with current main and applied the following fixes:

Merge conflict resolution

  • Resolved 25 conflicted i18n chunk files — kept main's proper locale translations + added Yuanbao-specific keys from this PR.
  • ko.ts: kept main's chunked approach (Yuanbao keys are in the chunk files).

Code fixes applied

  1. Remove production console.log calls (YuanbaoConfig.tsx) — replaced 14 unconditional console.log/warn/error calls (which leak credential key names in devtools) with the existing debug('channels:yuanbao') namespaced logger.
  2. Fix wrong i18n keyschannels.telegram.{connect,reconnect,savedRestartRequired} → dedicated channels.yuanbao.* keys (added to en.ts + all 13 locale chunk files).
  3. Remove expect() panic (ops.rs:442) — replaced with ok_or_else()? to avoid crashing the core process.
  4. Fix icon check (ChannelSetupModal.tsx:73) — use definition.id instead of definition.icon for Yuanbao detection.
  5. Remove duplicate keys in de-5.ts — fixed TS1117 compile errors.

How to apply

git remote add senamakel https://github.com/senamakel/openhuman.git
git fetch senamakel pr/2494-fixes
git cherry-pick senamakel/pr/2494-fixes~1..senamakel/pr/2494-fixes
# Or: merge the branch

Alternatively, please enable "Allow edits from maintainers" on this PR so we can push directly.

Remaining items (deferred to contributor)

  • heartbeat_tasks has no capacity cap and tasks aren't aborted on Drop — medium-lift fix.
  • ops.rs is 1,283 lines (>500 line preference) — consider extracting yuanbao logic.
  • connection.rs:59 uses unbounded mpsc channel — no backpressure under load.
  • connect_id logged at info level (line 445) — consider downgrading to debug.

@senamakel
Copy link
Copy Markdown
Member

senamakel commented May 25, 2026

Redoing this PR in #2600

This PR did not have "Allow edits from maintainers" enabled, so I could not push the necessary fixes (merge conflict resolution + code improvements) directly to YuanbaoTeam/openhuman:feat/yuanbao-channel.

The work has been carried forward in #2600, which includes:

  • All commits from this PR
  • Merge conflict resolution with current main
  • Security fix (console.log → debug logger)
  • i18n fix (dedicated yuanbao keys)
  • Stability fix (expect → proper error propagation)
  • Build fix (duplicate TS keys)

Thank you @lrt4836 for the contribution! The channel implementation is solid and all original CodeRabbit findings were properly addressed.

You'll get the attribution. well done!

@senamakel senamakel closed this May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants