feat(channels/yuanbao): add Yuanbao channel provider#2600
Conversation
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.
# Conflicts: # app/src/lib/i18n/chunks/ar-3.ts # app/src/lib/i18n/chunks/ar-5.ts # app/src/lib/i18n/chunks/bn-3.ts # app/src/lib/i18n/chunks/bn-5.ts # app/src/lib/i18n/chunks/de-3.ts # app/src/lib/i18n/chunks/de-5.ts # app/src/lib/i18n/chunks/es-3.ts # app/src/lib/i18n/chunks/es-5.ts # app/src/lib/i18n/chunks/fr-3.ts # app/src/lib/i18n/chunks/fr-5.ts # app/src/lib/i18n/chunks/hi-3.ts # app/src/lib/i18n/chunks/hi-5.ts # app/src/lib/i18n/chunks/id-3.ts # app/src/lib/i18n/chunks/id-5.ts # app/src/lib/i18n/chunks/it-3.ts # app/src/lib/i18n/chunks/it-5.ts # app/src/lib/i18n/chunks/ko-3.ts # app/src/lib/i18n/chunks/ko-5.ts # app/src/lib/i18n/chunks/pt-3.ts # app/src/lib/i18n/chunks/pt-5.ts # app/src/lib/i18n/chunks/ru-3.ts # app/src/lib/i18n/chunks/ru-5.ts # app/src/lib/i18n/chunks/zh-CN-3.ts # app/src/lib/i18n/chunks/zh-CN-5.ts # app/src/lib/i18n/ko.ts
…s, remove expect() panic
- Replace 14 unconditional console.log/warn/error calls in YuanbaoConfig.tsx
with the namespaced debug() logger to avoid leaking credential key names
in production devtools.
- Use dedicated channels.yuanbao.{connect,reconnect,savedRestartRequired}
i18n keys instead of borrowing from channels.telegram.*.
- Add the 3 new keys to en.ts, en-3.ts, and all 12 locale -3.ts chunks.
- Replace expect() with ok_or_else()? in ops.rs to avoid panicking the
core process if prebuilt_yuanbao_config is unexpectedly None.
- Use definition.id instead of definition.icon for the yuanbao check in
ChannelSetupModal to avoid breakage if icon name diverges from id.
- Remove duplicate de-5.ts keys that caused TS1117 errors.
|
Caution Review failedFailed to post review comments 📝 WalkthroughWalkthroughAdds a complete Yuanbao channel provider: frontend UI (icons, ChannelSetupModal, YuanbaoConfig), Redux/store updates, i18n strings, extensive Rust backend provider (config, SignManager, ops verification, WebSocket connection, inbound pipeline, outbound sender), protocol codecs, media/COS upload support, tests, and module wiring. ChangesYuanbao Channel Integration
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Suggested labels
Suggested reviewers
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (5)
src/openhuman/channels/providers/yuanbao/config.rs (1)
143-161: 💤 Low valueConsider documenting the expected call order for
apply_env_defaultsandvalidate.
validate()requiresws_domainandapi_domain(when no token) to be non-empty, butapply_env_defaults()fills these fromenv. If a caller validates before applying defaults, validation fails unexpectedly. The tests show the intended pattern (defaults applied first), but the API doesn't enforce or document it.A defensive option would be to call
apply_env_defaultsinternally at the start ofvalidate, or add a doc comment clarifying the expected order.🤖 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/config.rs` around lines 143 - 161, The validate() method on the Yuanbao config currently fails if env-derived fields (ws_domain, api_domain when token is absent) haven't been populated; update validate to defensively call apply_env_defaults() at the start (or ensure it returns/propagates any error) so env defaults are applied before checks, then perform the existing checks and return YuanbaoError::Config as before; alternatively, if you prefer not to mutate state during validation, add a clear doc comment on validate mentioning callers must call apply_env_defaults() first and reference the apply_env_defaults and validate functions in the docstring.src/openhuman/channels/controllers/ops.rs (1)
180-194: 💤 Low valueMinor: Consider reusing an existing HTTP client.
verify_yuanbao_credentialscreates a newreqwest::Clienton each call. While acceptable for a one-time connect operation, the existing pattern elsewhere in the codebase typically shares a client for connection pooling benefits.This is fine for now since credential verification is infrequent.
🤖 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/controllers/ops.rs` around lines 180 - 194, verify_yuanbao_credentials creates a fresh reqwest::Client for each call which prevents reuse of connection pooling; change the function signature to accept a shared reqwest::Client (or obtain the existing shared client) and pass that into SignManager::new instead of constructing reqwest::Client::new() so verify_yuanbao_credentials uses the shared client instance used elsewhere in the codebase (refer to verify_yuanbao_credentials and SignManager::new(...) in the diff).src/openhuman/channels/providers/yuanbao/sign.rs (1)
261-261: 💤 Low valueConsider explicit handling when
codefield is missing.At line 261, if the response JSON lacks a
codefield, it defaults to0, which means the code proceeds as if successful. The subsequent check fordataat line 263-270 provides a safety net, but an explicit check for a missingcodefield would make the intent clearer and fail faster with a more descriptive error.This is a minor robustness improvement since the current flow still fails correctly.
🤖 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/sign.rs` at line 261, The current extraction let code = json.get("code").and_then(|c| c.as_i64()).unwrap_or(0) masks a missing "code" field by treating it as success; change the logic in the handler (where variable code is created) to explicitly check whether json.get("code") is Some and return a clear Err (or Err variant/early return) when it's absent or not an integer, rather than falling back to 0, so callers of this function (the response parsing around code and data) get a descriptive failure instead of proceeding as if successful.app/src/components/channels/__tests__/YuanbaoConfig.test.tsx (1)
82-95: ⚡ Quick winAvoid className-coupled validation assertions.
This assertion depends on
text-coral, so harmless style refactors can break the test. Assert the validation message count/content directly instead of CSS classes.As per coding guidelines: “Prefer testing behavior over implementation details.”
🤖 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/__tests__/YuanbaoConfig.test.tsx` around lines 82 - 95, The test currently filters elements by className ('text-coral') to detect validation errors; update the assertions to avoid coupling to CSS by checking the validation text/content directly: use screen.getAllByText(/AppID/) or screen.queryAllByText(/AppID/) and assert on the returned array length (e.g., toBeGreaterThan(0) before input and toHaveLength(0) or toBe(0) after firing fireEvent.change on the '元宝开放平台 AppID' field) and keep the expect that channelConnectionsApi.connectChannel was not called; locate and modify the assertions around screen.getAllByText, screen.queryAllByText, and the fireEvent.change usage in YuanbaoConfig.test.tsx.src/openhuman/channels/providers/yuanbao/splitter.rs (1)
6-7: 💤 Low valueDocumentation mentions table-row handling but implementation doesn't track table state.
The module doc mentions "a Markdown table row (lines starting with
|)" butsafe_to_breakonly checks fence state. If table preservation was intentional, the state tracking is missing; otherwise, consider updating the doc comment.Also applies to: 81-83
🤖 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/splitter.rs` around lines 6 - 7, The module doc claims splitter handles "a Markdown table row (lines starting with `|`)" but the implementation (see function safe_to_break and surrounding state) never tracks table state; either remove/update that sentence in the module comment or implement table detection: add a boolean field (e.g., in the same state struct used by safe_to_break) that is set when a line begins with '|' (and cleared on blank lines or other block boundaries) and then update safe_to_break to return false while in-table so table rows are preserved; pick one approach and ensure you reference/modify the state struct and the safe_to_break function 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 `@app/src/components/channels/YuanbaoConfig.tsx`:
- Around line 67-93: Add a synchronous re-entrancy guard to prevent rapid
repeated clicks from enqueueing duplicate actions: at the very start of
handleConnect check the local busy flag and return immediately if busy; do the
same for the other action handler (the disconnect/restart handler around lines
186–208). Keep the existing setBusy(true) usage but ensure the early busy check
is present before any async work or dispatch (and ensure setBusy(false) is
restored on completion/error so the guard is cleared).
In `@src/openhuman/channels/providers/yuanbao/channel.rs`:
- Around line 94-100: PipelineState is being created with config.bot_id at
startup so when config.bot_id is empty the echo-guard (SkipSelfMw) cannot match
self-sent messages after sign-token/auth-bind resolves the canonical bot id;
locate where sign-token/auth-bind yields the resolved bot id and update the
inbound pipeline with it by either calling a setter on the existing
PipelineState (e.g., PipelineState::set_from_account or set_bot_id) or by
replacing the pipeline with a new InboundPipeline(PipelineState::new(&config,
resolved_bot_id)); ensure the same PipelineState instance used by
InboundPipeline is updated (or pipeline swapped) after auth so SkipSelfMw can
match outbound echoes.
- Around line 294-296: After sending the shutdown signal via shutdown_tx, do not
immediately abort conn_task; instead await the connection task to allow
YuanbaoConnection::shutdown() to run and perform its cleanup so sender, pending,
and is_connected are updated; replace conn_task.abort() with awaiting the task's
JoinHandle (optionally with a timeout) and handle its Result, ensuring listen()
returns only after the task finishes.
In `@src/openhuman/channels/providers/yuanbao/connection.rs`:
- Around line 207-243: The reconnect attempt counter is never reset so
intermittent clean disconnects will exhaust max_attempts; after a successful
session (when self.connect_once(...) returns Ok(...) in the match) reset attempt
to 0 instead of only incrementing later. Locate the match on outcome in the
connection loop (connect_once, outcome, NO_RECONNECT_CLOSE_CODES) and set
attempt = 0 in the Ok arms (except where you return for no-reconnect close
codes) so cleanly closed/authenticated sessions restore the reconnect budget
before the backoff and subsequent sleep logic.
In `@src/openhuman/channels/providers/yuanbao/outbound.rs`:
- Around line 23-27: The send_* functions (e.g., send_group_message and
send_c2c_message) currently treat any frame with a matching msg_id as success;
after calling send_and_wait() they must inspect the business-layer response
payload (resp.data) and verify the inner response code/message before returning
success. Fix: after send_and_wait() in send_group_message and send_c2c_message,
decode or parse resp.data to extract the business fields (e.g., code and
message) using the appropriate decoder for that response (or parse JSON/proto
from resp.data), and if the inner code != 0 return/propagate an error containing
that code/message instead of success; apply the same check to the
heartbeat/send_*_heartbeat paths mentioned. Ensure you reference and use
send_and_wait, send_group_message, send_c2c_message (and the heartbeat send
functions) so the logic always validates resp.data.code before signaling
success.
In `@src/openhuman/channels/providers/yuanbao/wire.rs`:
- Around line 42-55: The varint decoder loop in wire.rs currently allows a
malformed 10th-byte varint to overflow/truncate; update the loop in the varint
decoding function (the block using variables i, pos, shift, byte, value and
returning YuanbaoError::ProtoDecode) to reject a 10th byte with any payload bits
other than the low bit: specifically, when shift == 63 (i.e. handling the 10th
byte) check if (byte & 0xFE) != 0 and return
Err(YuanbaoError::ProtoDecode("varint too long".into())) instead of shifting and
accepting those bits; keep the existing checks for truncated inputs and shift >=
64.
---
Nitpick comments:
In `@app/src/components/channels/__tests__/YuanbaoConfig.test.tsx`:
- Around line 82-95: The test currently filters elements by className
('text-coral') to detect validation errors; update the assertions to avoid
coupling to CSS by checking the validation text/content directly: use
screen.getAllByText(/AppID/) or screen.queryAllByText(/AppID/) and assert on the
returned array length (e.g., toBeGreaterThan(0) before input and toHaveLength(0)
or toBe(0) after firing fireEvent.change on the '元宝开放平台 AppID' field) and keep
the expect that channelConnectionsApi.connectChannel was not called; locate and
modify the assertions around screen.getAllByText, screen.queryAllByText, and the
fireEvent.change usage in YuanbaoConfig.test.tsx.
In `@src/openhuman/channels/controllers/ops.rs`:
- Around line 180-194: verify_yuanbao_credentials creates a fresh
reqwest::Client for each call which prevents reuse of connection pooling; change
the function signature to accept a shared reqwest::Client (or obtain the
existing shared client) and pass that into SignManager::new instead of
constructing reqwest::Client::new() so verify_yuanbao_credentials uses the
shared client instance used elsewhere in the codebase (refer to
verify_yuanbao_credentials and SignManager::new(...) in the diff).
In `@src/openhuman/channels/providers/yuanbao/config.rs`:
- Around line 143-161: The validate() method on the Yuanbao config currently
fails if env-derived fields (ws_domain, api_domain when token is absent) haven't
been populated; update validate to defensively call apply_env_defaults() at the
start (or ensure it returns/propagates any error) so env defaults are applied
before checks, then perform the existing checks and return YuanbaoError::Config
as before; alternatively, if you prefer not to mutate state during validation,
add a clear doc comment on validate mentioning callers must call
apply_env_defaults() first and reference the apply_env_defaults and validate
functions in the docstring.
In `@src/openhuman/channels/providers/yuanbao/sign.rs`:
- Line 261: The current extraction let code = json.get("code").and_then(|c|
c.as_i64()).unwrap_or(0) masks a missing "code" field by treating it as success;
change the logic in the handler (where variable code is created) to explicitly
check whether json.get("code") is Some and return a clear Err (or Err
variant/early return) when it's absent or not an integer, rather than falling
back to 0, so callers of this function (the response parsing around code and
data) get a descriptive failure instead of proceeding as if successful.
In `@src/openhuman/channels/providers/yuanbao/splitter.rs`:
- Around line 6-7: The module doc claims splitter handles "a Markdown table row
(lines starting with `|`)" but the implementation (see function safe_to_break
and surrounding state) never tracks table state; either remove/update that
sentence in the module comment or implement table detection: add a boolean field
(e.g., in the same state struct used by safe_to_break) that is set when a line
begins with '|' (and cleared on blank lines or other block boundaries) and then
update safe_to_break to return false while in-table so table rows are preserved;
pick one approach and ensure you reference/modify the state struct and the
safe_to_break function 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 29cac06e-2dc3-404c-8717-90802e523b0f
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.lockapp/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (65)
Cargo.tomlapp/src/components/channels/ChannelSelector.tsxapp/src/components/channels/ChannelSetupModal.tsxapp/src/components/channels/YuanbaoConfig.tsxapp/src/components/channels/YuanbaoIcon.tsxapp/src/components/channels/__tests__/ChannelSetupModal.test.tsxapp/src/components/channels/__tests__/YuanbaoConfig.test.tsxapp/src/components/channels/__tests__/YuanbaoIcon.test.tsxapp/src/components/skills/skillIcons.tsxapp/src/lib/i18n/chunks/ar-3.tsapp/src/lib/i18n/chunks/ar-5.tsapp/src/lib/i18n/chunks/bn-3.tsapp/src/lib/i18n/chunks/bn-5.tsapp/src/lib/i18n/chunks/de-3.tsapp/src/lib/i18n/chunks/de-5.tsapp/src/lib/i18n/chunks/en-3.tsapp/src/lib/i18n/chunks/en-5.tsapp/src/lib/i18n/chunks/es-3.tsapp/src/lib/i18n/chunks/es-5.tsapp/src/lib/i18n/chunks/fr-3.tsapp/src/lib/i18n/chunks/fr-5.tsapp/src/lib/i18n/chunks/hi-3.tsapp/src/lib/i18n/chunks/hi-5.tsapp/src/lib/i18n/chunks/id-3.tsapp/src/lib/i18n/chunks/id-5.tsapp/src/lib/i18n/chunks/it-3.tsapp/src/lib/i18n/chunks/it-5.tsapp/src/lib/i18n/chunks/ko-3.tsapp/src/lib/i18n/chunks/ko-5.tsapp/src/lib/i18n/chunks/pt-3.tsapp/src/lib/i18n/chunks/pt-5.tsapp/src/lib/i18n/chunks/ru-3.tsapp/src/lib/i18n/chunks/ru-5.tsapp/src/lib/i18n/chunks/zh-CN-3.tsapp/src/lib/i18n/chunks/zh-CN-5.tsapp/src/lib/i18n/en.tsapp/src/store/__tests__/channelConnectionsSlice.test.tsapp/src/store/channelConnectionsSlice.tsapp/src/types/channels.tsdocs/TEST-COVERAGE-MATRIX.mdsrc/openhuman/channels/commands.rssrc/openhuman/channels/controllers/definitions.rssrc/openhuman/channels/controllers/ops.rssrc/openhuman/channels/controllers/ops_tests.rssrc/openhuman/channels/mod.rssrc/openhuman/channels/providers/mod.rssrc/openhuman/channels/providers/yuanbao/channel.rssrc/openhuman/channels/providers/yuanbao/config.rssrc/openhuman/channels/providers/yuanbao/connection.rssrc/openhuman/channels/providers/yuanbao/cos.rssrc/openhuman/channels/providers/yuanbao/errors.rssrc/openhuman/channels/providers/yuanbao/ids.rssrc/openhuman/channels/providers/yuanbao/inbound.rssrc/openhuman/channels/providers/yuanbao/media.rssrc/openhuman/channels/providers/yuanbao/mod.rssrc/openhuman/channels/providers/yuanbao/outbound.rssrc/openhuman/channels/providers/yuanbao/proto.rssrc/openhuman/channels/providers/yuanbao/proto_biz.rssrc/openhuman/channels/providers/yuanbao/proto_constants.rssrc/openhuman/channels/providers/yuanbao/sign.rssrc/openhuman/channels/providers/yuanbao/splitter.rssrc/openhuman/channels/providers/yuanbao/types.rssrc/openhuman/channels/providers/yuanbao/wire.rssrc/openhuman/channels/runtime/startup.rssrc/openhuman/config/schema/channels.rs
…own, reconnect, varint - Add synchronous `if (busy) return` guard in both handleConnect and handleDisconnect to prevent duplicate calls from rapid double-clicks. - Give conn_task a 2s timeout to run its own shutdown cleanup before force-aborting, so sender/pending/is_connected state stays consistent. - Reset reconnect attempt counter after a successful session so intermittent disconnects don't permanently exhaust the budget. - Reject invalid 10-byte varints (10th byte > 1) instead of silently truncating, preventing malformed frames from decoding to wrong values.
…ct resolution The 3 keys (customGifError, customGifHeading, customGifLabel) at their original position (~line 243) were mistakenly removed as duplicates during merge conflict resolution. They are the canonical copies on main; the later occurrence (from a different merge state) was the duplicate. Restore the originals and remove the actual duplicates so CI's merge-with-main produces the correct key count.
# Conflicts: # src/openhuman/channels/controllers/ops_tests.rs
The 10th-byte overflow check (added for CodeRabbit) fires before the "too long" guard when all bytes are 0x80. Update the test assertion to accept either error message.
Summary
Yuanbaochannel provider (Tencent 元宝 robot logic v5) undersrc/openhuman/channels/providers/yuanbao/, wired into the channel registry + startup loop and exposed to the UI through the existingChannelSetupModalflow.sign.rs) with cached tokens, message splitting (splitter.rs), and a COS uploader (cos.rs) for media.connection.rs+wire.rs+proto_biz.rs) with bounded reconnect, single-flight token refresh oncode=10099, prompt shutdown response, and bounded length-delimited parsing.YuanbaoConfig.tsxform (i18n-keyed validation messages),YuanbaoIcon, andChannelSetupModalintegration for icon parity withChannelSelector.app_secretis verified at connect time via the live sign-token endpoint, persisted only in the encrypted credentials store (never in plaintextconfig.toml), and hydrated at startup with a stale-app_keyguard.Problem
Yuanbao was not a supported channel; users on Tencent Yuanbao bots had no first-class way to wire OpenHuman to their robot.
Solution
This PR supersedes #2494 (from
YuanbaoTeam/openhuman) because that PR did not have "Allow edits from maintainers" enabled and we could not push fixes directly. This version includes the original implementation plus:main(25 i18n chunk files).console.log/warn/errorcalls inYuanbaoConfig.tsxthat leaked credential key names in production devtools — now uses the namespaceddebug('channels:yuanbao')logger.channels.yuanbao.{connect,reconnect,savedRestartRequired}keys instead of borrowing fromchannels.telegram.*.expect()panic inops.rswith proper error propagation viaok_or_else()?.definition.idinstead ofdefinition.iconfor Yuanbao detection inChannelSetupModal.de-5.tscausing TS1117 compile errors.Submission Checklist
diff-cover) meet the gate enforced by.github/workflows/coverage.yml. Runpnpm test:coverageandpnpm test:rustlocally; PRs below 80% on changed lines will not merge.docs/TEST-COVERAGE-MATRIX.mdreflect this change (orN/A: behaviour-only change)## RelatedImpact
app_secretnever lands in plaintextconfig.toml; runtime loads it from the encrypted credentials store at startup. Stale-app_keyprofiles will no longer be paired with the new key.Related
10.1.5 Yuanbao Connection(added; layerRU, status 🟡)Summary by CodeRabbit
Release Notes
New Features
Tests