feat: add telegram and whatsapp channels#554
Conversation
…p-channels # Conflicts: # apps/web/lib/api/sdk.gen.ts
Deploying nexu-docs with
|
| Latest commit: |
6969442
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://bed4906d.nexu-docs.pages.dev |
| Branch Preview URL: | https://feat-telegram-whatsapp-chann.nexu-docs.pages.dev |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds Telegram and WhatsApp channel support across the controller, gateway RPCs, config store, runtime plugins, SDK, web UI, and shared schemas — including Telegram bot-token connect and WhatsApp QR start/wait/connect flows, new ChannelService methods, and frontend setup components. Changes
Sequence Diagram(s)sequenceDiagram
participant User as Web Client
participant API as Controller API
participant ChannelSvc as ChannelService
participant Store as NexuConfigStore
User->>API: POST /api/v1/channels/telegram/connect { botToken, telegramBotId, ... }
API->>ChannelSvc: connectTelegram(input)
ChannelSvc->>Store: connectTelegram(input)
Store-->>ChannelSvc: ChannelResponse (status: connected, id, accountId, ...)
ChannelSvc-->>API: ChannelResponse
API-->>User: 200 ChannelResponse
sequenceDiagram
participant User as Web Client
participant API as Controller API
participant ChannelSvc as ChannelService
participant Gateway as OpenClawGatewayService
participant Store as NexuConfigStore
User->>API: POST /api/v1/channels/whatsapp/qr-start
API->>ChannelSvc: whatsappQrStart()
ChannelSvc->>Gateway: whatsappQrStart(accountId?)
Gateway-->>ChannelSvc: { qrDataUrl, accountId }
ChannelSvc-->>API: { qrDataUrl, accountId? }
API-->>User: 200 { qrDataUrl }
loop Polling
User->>API: POST /api/v1/channels/whatsapp/qr-wait { accountId }
API->>ChannelSvc: whatsappQrWait(accountId)
ChannelSvc->>Gateway: whatsappQrWait(accountId)
Gateway-->>ChannelSvc: { connected: false | true }
ChannelSvc-->>API: { connected, message }
API-->>User: 200 { connected }
end
User->>API: POST /api/v1/channels/whatsapp/connect { accountId }
API->>ChannelSvc: connectWhatsapp({ accountId })
ChannelSvc->>Store: connectWhatsapp({ accountId })
Store-->>ChannelSvc: ChannelResponse (status: connected, id, ...)
ChannelSvc-->>API: ChannelResponse
API-->>User: 200 ChannelResponse
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33bbb44008
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (3)
apps/controller/src/store/nexu-config-store.ts (1)
839-840: Use cuid2 for the new channel ids.These ids are exposed through
ChannelResponse, so the new Telegram/WhatsApp connect paths should follow the repo’s public-id rule instead of minting UUIDs here.As per coding guidelines,
Public IDs: use cuid2 (@paralleldrive/cuid2), never expose pk.Also applies to: 879-880
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/src/store/nexu-config-store.ts` around lines 839 - 840, The code creates new channel IDs using crypto.randomUUID() for the ChannelResponse objects (variable channel) — replace those calls with the cuid2 generator from `@paralleldrive/cuid2` (e.g., createId()) so the exposed ChannelResponse IDs follow the repo’s public-id rule; add the import for createId and update both occurrences (the channel construction at the current snippet and the other occurrence around lines 879-880) to use createId() instead of crypto.randomUUID().apps/controller/static/runtime-plugins/whatsapp/src/channel.ts (1)
439-441: Use structured fields for the startup log.Interpolating the account metadata into one string makes this harder to query and parse in log sinks. Emit fields and a short message instead.
As per coding guidelines, "Logging: structured (pino or console JSON), never log credentials".🪵 Suggested refactor
- ctx.log?.info(`[${account.accountId}] starting provider (${identity})`); + ctx.log?.info( + { channel: "whatsapp", accountId: account.accountId, identity }, + "starting provider", + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/static/runtime-plugins/whatsapp/src/channel.ts` around lines 439 - 441, Current startup log interpolates account metadata into a single string; change it to emit structured fields and a short message instead. In the block using getWhatsAppRuntime().channel.whatsapp.readWebSelfId(account.authDir) and ctx.log?.info, build a log call that passes an object with accountId, e164, jid (omit sensitive values if they are credentials) and a concise message like "starting provider" rather than embedding them in the message string; update the call site where account.accountId, e164, and jid are used (and ensure you never log secrets) so downstream log consumers can query fields instead of parsing the interpolated string.apps/web/lib/api/types.gen.ts (1)
1-2: Note: Auto-generated file has style deviations from coding guidelines.This file uses 4-space indentation and single quotes, while the coding guidelines specify 2-space indentation and double quotes. Since this is auto-generated by
@hey-api/openapi-ts, these style choices should be addressed in the generator configuration rather than manually edited in the generated output.As per coding guidelines, TypeScript files should use 2-space indent and double quotes. Consider updating the generator config to align with project standards.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/lib/api/types.gen.ts` around lines 1 - 2, The generated types file produced by `@hey-api/openapi-ts` uses 4-space indentation and single quotes; update the generator configuration for `@hey-api/openapi-ts` to emit double quotes and a 2-space indent (e.g., set quote style to double and tabWidth/indentation to 2 in the generator’s options or template/prettier settings), then regenerate the output (the header comment "// This file is auto-generated by `@hey-api/openapi-ts`" identifies the generated artifact to target).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/controller/openapi.json`:
- Around line 3359-3376: The OpenAPI request bodies for the new onboarding POST
endpoints currently omit requestBody.required making the entire body optional;
update the OpenAPI generation (the POST route specs that include a requestBody
with properties like "botToken" and "accountId") to set requestBody.required:
true so the body itself is required while keeping the inner property
"botToken"/"accountId" required as-is; apply the same change for the other two
occurrences referenced (the blocks around the "botToken" schema and the ones at
the other two ranges) so all POST onboarding routes enforce a required request
body.
In `@apps/controller/src/services/channel-service.ts`:
- Around line 469-485: fetchWechatQrCode uses a bare fetch so a stalled upstream
can hang wechatQrStart; add an AbortController-based timeout: create an
AbortController inside fetchWechatQrCode, start a timer (e.g., 10s or use a
configurable constant) that calls controller.abort(), pass controller.signal to
fetch(url.toString(), { signal }), and clear the timer after fetch completes;
ensure the function throws a clear timeout/abort error so callers (like
wechatQrStart) can handle it.
- Around line 681-690: The polling loop in channel-service.ts that calls
pollWechatQrStatus (using DEFAULT_WECHAT_BASE_URL and activeLogin.qrcode) spins
with no delay when status is "wait" or "scaned"; add a short awaitable backoff
inside that branch (e.g., await sleep(500ms) with optional exponential/backoff
capped at ~5s and jitter) so the loop yields between requests and reduces
hammering the WeChat API, leaving the existing deadline logic intact; implement
a small helper like sleep(ms) and apply it whenever status is "wait" or "scaned"
before continuing.
In `@apps/controller/src/store/nexu-config-store.ts`:
- Around line 852-868: The update block in this.store.update that replaces
channels by channelType+accountId leaves orphaned secrets keyed by the previous
channel.id (e.g., channel:<oldId>:botToken or channel:<oldId>:authDir) causing
unreachable secrets after reconnect; modify the update logic in the
channels-replacement blocks (the this.store.update call that builds channels and
secrets using channel and input) to detect any previous channel with the same
channelType+accountId, and remove its secrets from the secrets map (delete keys
like `channel:${oldId}:botToken` and `channel:${oldId}:authDir`) while adding
the new `channel:${channel.id}:...` entries; apply the same pruning change to
the second occurrence of this pattern (the other this.store.update block around
the 892-910 region).
In `@apps/controller/static/runtime-plugins/whatsapp/package.json`:
- Around line 7-10: The package.json "openclaw.extensions" currently points to
"./index.ts" but the plugin isn't compiled, so Node will fail to load it; add a
build step to transpile TypeScript to JavaScript (e.g., add a "build" script
using tsc or an equivalent bundler), ensure the compiled output includes an
"index.js" at the same path, and update package.json or the
"openclaw.extensions" entry if you change the output path; specifically modify
the plugin's package.json to add a "scripts.build" (and run it in CI) and ensure
the source entry referenced by openclaw (the "extensions" array) points to the
produced index.js rather than index.ts.
In `@apps/controller/static/runtime-plugins/whatsapp/src/channel.ts`:
- Around line 246-264: listActions currently may add "poll" to the returned set
via createActionGate(cfg.channels.whatsapp.actions) but supportsAction and
handleAction only accept "react", causing a mismatch; either remove "poll" from
listActions or implement poll support. Fix by updating listActions in the
whatsapp channel to not add "poll" (remove gate("poll") / actions.add("poll"))
if you do not intend to support polls, or by extending supportsAction to return
true for "poll" and adding a poll handling branch in handleAction (similar to
the existing react branch) that validates params, performs the poll action
against the provider, and throws a clear error including meta.id on unsupported
action. Ensure consistency between listActions, supportsAction, and handleAction
for ChannelMessageActionName values so "poll" is only present if fully
supported.
- Around line 407-427: buildAccountSnapshot currently forces configured: true
which contradicts the linked check and makes resolveAccountState report "linked"
for unlinked accounts; change buildAccountSnapshot (the function that calls
getWhatsAppRuntime().channel.whatsapp.webAuthExists and sets linked) to set
configured to the actual link status (e.g., configured: !!linked or configured:
linked) or otherwise derive it from account.authDir/webAuthExists instead of
hardcoding true so resolveAccountState correctly reflects the account's real
auth state.
In `@apps/desktop/main/services/launchd-bootstrap.ts`:
- Around line 686-702: Currently openclawBinPath and openclawExtensionsDir point
at .tmp/sidecars/openclaw while openclawPath still points at
openclaw-runtime/node_modules, causing mismatched installs; unify by deriving
all OpenClaw locations from a single sidecar root variable (e.g., const
openclawSidecarRoot = path.join(repoRoot, ".tmp", "sidecars", "openclaw")) and
then set openclawBinPath, openclawExtensionsDir and openclawPath from that root
(e.g., openclawPath -> path.join(openclawSidecarRoot, "node_modules",
"openclaw", "openclaw.mjs")), and replace existing hardcoded paths in the return
object so controller and gateway use the same OpenClaw install.
- Around line 656-662: resolveLaunchdPaths() is computing openclawSidecarRoot
from ~/.nexu which differs from the runtime manifest resolver; update
resolveLaunchdPaths to accept (or derive) the same runtime/userData root used by
the manifest resolver (the same runtimeRoot value) and build openclawBinPath and
openclawExtensionsDir from that shared root instead of openclawSidecarRoot
derived from ~/.nexu; adjust the function signature/call sites to pass through
the runtimeRoot so openclawBinPath and openclawExtensionsDir reference the exact
packaged sidecar the manifests.ts resolver uses.
In `@apps/web/src/components/channel-setup/telegram-setup-view.tsx`:
- Around line 22-23: The hard-coded English strings in telegram-setup-view.tsx
(all toast messages, headings, helper text, step descriptions, labels, and
button text used in the Telegram setup UI and in functions that call
toast.error) must be moved to i18n translation keys: wrap the component with
react-i18next's useTranslation and replace every literal string (including the
toast.error("Telegram bot token is required") call and all UI text in the
component) with t('channelSetup.telegram.<descriptive_key>') lookups; add
corresponding keys/values to the appropriate locale resource files, and ensure
the toast calls use those translated strings (e.g.,
toast.error(t('channelSetup.telegram.tokenRequired'))), so the entire onboarding
experience is localized.
- Around line 32-37: The toast currently displays raw error.message from the
Telegram connect flow (in telegram-setup-view.tsx around the if (error || !data)
block), which can leak bot tokens or upstream URLs; change this to always show a
generic user-facing message (e.g. "Failed to connect to Telegram") or map only
from a strict allowlist of safe error keys before touching the toast, and ensure
any full error details are only logged to a secure developer log (not shown to
the user) or sent to server-side telemetry instead; update the logic that
computes message (the variable currently derived from error.message) and the
toast.error call accordingly while keeping any sensitive error content out of
the UI.
In `@apps/web/src/components/channel-setup/whatsapp-setup-view.tsx`:
- Around line 44-73: finalizeConnect currently treats a non-terminal `/qr-wait`
response as an immediate error and lets its async work continue after the modal
is closed; update finalizeConnect (and the related polling flow around
postApiV1ChannelsWhatsappConnect) to treat responses where connected === false
as a cancellable polling step (do not setPhase("error") or drop QR prematurely),
implement cancellation via an AbortController or by checking mountedRef.current
before any state mutations, and ensure any pending promise early-returns when
cancelled; specifically, modify the code path that inspects the API
error/response from postApiV1ChannelsWhatsappConnect to continue polling on
qr-wait responses, only transition to "error" on real terminal errors, and guard
all setPhase/setQrUrl/onConnected calls with mountedRef.current or abort checks
to prevent background updates after the modal is closed.
In `@apps/web/src/pages/channels.tsx`:
- Around line 452-475: The Telegram card renders several hard-coded English
strings; update the JSX block that checks platform === "telegram" and
telegramBotUrl to use the i18n translator (t(...)) for all user-facing text
(title "Open in Telegram", description lines, button label "Open Bot") instead
of raw strings, keeping the same structure and components (ExternalLink, anchor
props, classNames, etc.), and add corresponding translation keys to the locale
files used by the app so the strings are available in other locales.
- Around line 334-337: The inline Telegram URL construction using
channel.botUserId should be replaced with the shared helper getChannelChatUrl to
preserve the existing Telegram Web fallback and handle missing botUserId; find
the telegramBotUrl declaration (currently using platform === "telegram" &&
channel.botUserId) and set telegramBotUrl = getChannelChatUrl(...) using the
helper's expected parameters (pass platform and/or channel object as the helper
requires) so the helper centralizes the URL logic for Telegram channels.
In `@apps/web/src/pages/home.tsx`:
- Around line 1120-1178: TelegramModal and WhatsappModal are missing the
accessibility contract that WechatQrModal uses; update both components to behave
like real dialogs by adding role="dialog" and aria-modal="true" on the outer
dialog container, give the title element a unique id and reference it with
aria-labelledby, add an accessible label (e.g. aria-label="Close dialog") to the
icon-only close button, and implement Escape key and backdrop-click handlers to
call onClose (and ensure focus is moved into and trapped within the modal on
open and restored on close similar to WechatQrModal). Use the same identifying
symbols (TelegramModal, WhatsappModal, the close button and the title div /
TelegramSetupView / WhatsappSetupView) so the changes mirror the existing
WechatQrModal accessibility behavior.
---
Nitpick comments:
In `@apps/controller/src/store/nexu-config-store.ts`:
- Around line 839-840: The code creates new channel IDs using
crypto.randomUUID() for the ChannelResponse objects (variable channel) — replace
those calls with the cuid2 generator from `@paralleldrive/cuid2` (e.g.,
createId()) so the exposed ChannelResponse IDs follow the repo’s public-id rule;
add the import for createId and update both occurrences (the channel
construction at the current snippet and the other occurrence around lines
879-880) to use createId() instead of crypto.randomUUID().
In `@apps/controller/static/runtime-plugins/whatsapp/src/channel.ts`:
- Around line 439-441: Current startup log interpolates account metadata into a
single string; change it to emit structured fields and a short message instead.
In the block using
getWhatsAppRuntime().channel.whatsapp.readWebSelfId(account.authDir) and
ctx.log?.info, build a log call that passes an object with accountId, e164, jid
(omit sensitive values if they are credentials) and a concise message like
"starting provider" rather than embedding them in the message string; update the
call site where account.accountId, e164, and jid are used (and ensure you never
log secrets) so downstream log consumers can query fields instead of parsing the
interpolated string.
In `@apps/web/lib/api/types.gen.ts`:
- Around line 1-2: The generated types file produced by `@hey-api/openapi-ts` uses
4-space indentation and single quotes; update the generator configuration for
`@hey-api/openapi-ts` to emit double quotes and a 2-space indent (e.g., set quote
style to double and tabWidth/indentation to 2 in the generator’s options or
template/prettier settings), then regenerate the output (the header comment "//
This file is auto-generated by `@hey-api/openapi-ts`" identifies the generated
artifact to target).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: f8c5a64a-7061-4846-abc4-79f48521016b
📒 Files selected for processing (35)
apps/controller/openapi.jsonapps/controller/src/app/container.tsapps/controller/src/app/env.tsapps/controller/src/lib/channel-binding-compiler.tsapps/controller/src/routes/channel-routes.tsapps/controller/src/runtime/openclaw-runtime-plugin-writer.tsapps/controller/src/runtime/sessions-runtime.tsapps/controller/src/services/analytics-service.tsapps/controller/src/services/channel-service.tsapps/controller/src/services/openclaw-gateway-service.tsapps/controller/src/store/nexu-config-store.tsapps/controller/static/runtime-plugins/openclaw-weixin/src/channel.tsapps/controller/static/runtime-plugins/whatsapp/index.tsapps/controller/static/runtime-plugins/whatsapp/openclaw.plugin.jsonapps/controller/static/runtime-plugins/whatsapp/package.jsonapps/controller/static/runtime-plugins/whatsapp/src/channel.outbound.test.tsapps/controller/static/runtime-plugins/whatsapp/src/channel.test.tsapps/controller/static/runtime-plugins/whatsapp/src/channel.tsapps/controller/static/runtime-plugins/whatsapp/src/resolve-target.test.tsapps/controller/static/runtime-plugins/whatsapp/src/runtime.tsapps/controller/tests/openclaw-runtime-plugin-writer.test.tsapps/desktop/main/services/launchd-bootstrap.tsapps/desktop/main/services/plist-generator.tsapps/web/lib/api/sdk.gen.tsapps/web/lib/api/types.gen.tsapps/web/src/components/channel-setup/telegram-setup-view.tsxapps/web/src/components/channel-setup/wechat-setup-view.tsxapps/web/src/components/channel-setup/whatsapp-setup-view.tsxapps/web/src/i18n/locales/en.tsapps/web/src/i18n/locales/zh-CN.tsapps/web/src/lib/channel-links.tsapps/web/src/pages/channels.tsxapps/web/src/pages/home.tsxpackages/shared/src/schemas/channel.tspackages/shared/src/schemas/openclaw-config.ts
💤 Files with no reviewable changes (1)
- apps/controller/static/runtime-plugins/openclaw-weixin/src/channel.ts
…p-channels # Conflicts: # apps/desktop/main/services/launchd-bootstrap.ts # apps/desktop/main/services/plist-generator.ts # apps/web/lib/api/sdk.gen.ts
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/controller/openapi.json (1)
2840-2847:⚠️ Potential issue | 🟠 MajorKeep the shared
channelTypeschema in sync with this contract.This spec now publishes
telegramandpackages/shared/src/schemas/channel.ts:1-12andpackages/shared/src/schemas/channel.ts:82-104still constrainchannelTypetoslack | discord | feishu | wechat. Any path still validating or persisting through that shared schema will reject the new channel types.Representative source fix
export const channelTypeSchema = z.enum([ "slack", "discord", "feishu", "wechat", + "telegram", + "whatsapp", ]);After that, regenerate the OpenAPI and SDK artifacts so the contract and shared validators match again.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/openapi.json` around lines 2840 - 2847, The shared channelType schema is out of sync with the OpenAPI contract — update the channelType enum in packages/shared/src/schemas/channel.ts (both the schema definition and the exported TypeScript union/type locations referencing channelType) to include "telegram" and "whatsapp" alongside the existing values, ensure any validators/types that reference channelType are updated accordingly, then regenerate the OpenAPI and SDK artifacts so the spec and shared validators/types match.
♻️ Duplicate comments (3)
apps/controller/static/runtime-plugins/whatsapp/src/channel.ts (2)
407-427:⚠️ Potential issue | 🟠 MajorUse the real link status for
configured.
buildAccountSnapshot()computeslinkedand then hardcodesconfigured: true. That makesresolveAccountState()report"linked"for accounts whose auth state is actually missing.Representative fix
- const linked = await getWhatsAppRuntime().channel.whatsapp.webAuthExists(account.authDir); + const linked = account.authDir + ? await getWhatsAppRuntime().channel.whatsapp.webAuthExists(account.authDir) + : false; return { accountId: account.accountId, name: account.name, enabled: account.enabled, - configured: true, + configured: linked, linked,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/static/runtime-plugins/whatsapp/src/channel.ts` around lines 407 - 427, In buildAccountSnapshot, configured is hardcoded to true which misleads resolveAccountState; change configured to reflect the actual link status (use the computed linked value or an explicit check like webAuthExists result) so buildAccountSnapshot returns configured: linked (or equivalent) and let resolveAccountState correctly return "linked" or "not linked".
246-264:⚠️ Potential issue | 🟡 MinorDon't advertise
pollas a message action yet.
listActions()can return"poll", butsupportsAction()andhandleAction()still reject anything except"react". That leaves callers with an action the plugin immediately errors on.Representative fix
if (gate("reactions")) { actions.add("react"); } - if (gate("polls")) { - actions.add("poll"); - } return Array.from(actions);If
pollis meant to be a real message action here,supportsAction()andhandleAction()need the matching branch instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/static/runtime-plugins/whatsapp/src/channel.ts` around lines 246 - 264, listActions returns "poll" but supportsAction and handleAction only accept "react", causing callers to receive an advertised action that will throw; either stop advertising "poll" or implement poll handling. Update either listActions (in the same channel.ts code) to omit adding "poll" from the actions Set when gate("polls") is true, or modify supportsAction to return action === "react" || action === "poll" and extend handleAction to branch on action === "poll" (or delegate to a new handlePoll function) and avoid throwing for poll; reference the functions listActions, supportsAction, handleAction and the meta.id error message to keep behavior consistent.apps/controller/openapi.json (1)
3359-3376:⚠️ Potential issue | 🟠 MajorMake these new POST bodies required in the spec.
Omitting
requestBody.requiredhere makes the entire JSON body optional, even thoughbotToken/accountIdinside it are mandatory. Please fix the source that generatesopenapi.json, then regenerate.Representative contract fix
"requestBody": { + "required": true, "content": {In OpenAPI 3.1, what is the default value of `requestBody.required`, and does omitting it make the entire request body optional?Also applies to: 3703-3720, 3774-3791
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/openapi.json` around lines 3359 - 3376, The OpenAPI fragment omits requestBody.required which leaves the entire JSON body optional despite properties like "botToken" and "accountId" being required; update the OpenAPI generation logic to set requestBody.required = true for the affected POST operations (the requestBody objects that contain "botToken" and "accountId" schemas), regenerate openapi.json, and ensure the regenerated spec includes requestBody.required: true for the entries around the duplicated ranges (the ones shown with "botToken" and the other POST bodies at the noted ranges).
🧹 Nitpick comments (1)
apps/controller/src/store/nexu-config-store.ts (1)
844-844: Consider using cuid2 for public channel IDs.The coding guidelines specify using
@paralleldrive/cuid2for public IDs. Both new methods usecrypto.randomUUID(), which is consistent with all other connect methods in this file but technically violates the guideline. If alignment with guidelines is desired, this would be a file-wide refactor affecting all ID generation.As per coding guidelines: "Public IDs: use cuid2 (
@paralleldrive/cuid2), never exposepk".Also applies to: 894-894
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/src/store/nexu-config-store.ts` at line 844, The code is using crypto.randomUUID() for public channel IDs (e.g. the object property "id: crypto.randomUUID()") which conflicts with the guideline to use `@paralleldrive/cuid2` for public IDs; replace the crypto.randomUUID() calls used to generate public IDs with the cuid2 generator (import { createId } from '@paralleldrive/cuid2') and use createId() wherever "id: crypto.randomUUID()" appears (and the other similar occurrences around the second instance) so public channel IDs are generated with cuid2; ensure imports are added and remove or keep crypto import only if still needed for private/internal IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/controller/static/runtime-plugins/whatsapp/src/channel.ts`:
- Around line 437-448: The startup log in startAccount leaks WhatsApp
identifiers by logging e164 and jid returned from
getWhatsAppRuntime().channel.whatsapp.readWebSelfId(account.authDir); update the
ctx.log?.info invocation in startAccount to omit e164 and jid and log only
non-identifying context (e.g., channel: "whatsapp" and accountId:
account.accountId) so self IDs are not written to controller logs.
---
Outside diff comments:
In `@apps/controller/openapi.json`:
- Around line 2840-2847: The shared channelType schema is out of sync with the
OpenAPI contract — update the channelType enum in
packages/shared/src/schemas/channel.ts (both the schema definition and the
exported TypeScript union/type locations referencing channelType) to include
"telegram" and "whatsapp" alongside the existing values, ensure any
validators/types that reference channelType are updated accordingly, then
regenerate the OpenAPI and SDK artifacts so the spec and shared validators/types
match.
---
Duplicate comments:
In `@apps/controller/openapi.json`:
- Around line 3359-3376: The OpenAPI fragment omits requestBody.required which
leaves the entire JSON body optional despite properties like "botToken" and
"accountId" being required; update the OpenAPI generation logic to set
requestBody.required = true for the affected POST operations (the requestBody
objects that contain "botToken" and "accountId" schemas), regenerate
openapi.json, and ensure the regenerated spec includes requestBody.required:
true for the entries around the duplicated ranges (the ones shown with
"botToken" and the other POST bodies at the noted ranges).
In `@apps/controller/static/runtime-plugins/whatsapp/src/channel.ts`:
- Around line 407-427: In buildAccountSnapshot, configured is hardcoded to true
which misleads resolveAccountState; change configured to reflect the actual link
status (use the computed linked value or an explicit check like webAuthExists
result) so buildAccountSnapshot returns configured: linked (or equivalent) and
let resolveAccountState correctly return "linked" or "not linked".
- Around line 246-264: listActions returns "poll" but supportsAction and
handleAction only accept "react", causing callers to receive an advertised
action that will throw; either stop advertising "poll" or implement poll
handling. Update either listActions (in the same channel.ts code) to omit adding
"poll" from the actions Set when gate("polls") is true, or modify supportsAction
to return action === "react" || action === "poll" and extend handleAction to
branch on action === "poll" (or delegate to a new handlePoll function) and avoid
throwing for poll; reference the functions listActions, supportsAction,
handleAction and the meta.id error message to keep behavior consistent.
---
Nitpick comments:
In `@apps/controller/src/store/nexu-config-store.ts`:
- Line 844: The code is using crypto.randomUUID() for public channel IDs (e.g.
the object property "id: crypto.randomUUID()") which conflicts with the
guideline to use `@paralleldrive/cuid2` for public IDs; replace the
crypto.randomUUID() calls used to generate public IDs with the cuid2 generator
(import { createId } from '@paralleldrive/cuid2') and use createId() wherever
"id: crypto.randomUUID()" appears (and the other similar occurrences around the
second instance) so public channel IDs are generated with cuid2; ensure imports
are added and remove or keep crypto import only if still needed for
private/internal IDs.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: fca8a926-439f-4532-b697-b1b297920fa1
📒 Files selected for processing (9)
apps/controller/openapi.jsonapps/controller/src/app/container.tsapps/controller/src/store/nexu-config-store.tsapps/controller/static/runtime-plugins/whatsapp/src/channel.tsapps/desktop/main/services/launchd-bootstrap.tsapps/web/lib/api/sdk.gen.tsapps/web/lib/api/types.gen.tsapps/web/src/i18n/locales/en.tsapps/web/src/i18n/locales/zh-CN.ts
✅ Files skipped from review due to trivial changes (2)
- apps/web/src/i18n/locales/en.ts
- apps/web/lib/api/types.gen.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/desktop/main/services/launchd-bootstrap.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
apps/controller/src/services/channel-service.ts (2)
889-960: Minor:while (true)loop relies on internal exits.The loop has proper timeout handling via
Promise.racewith a deadline, and all paths eventually return. The logic correctly handles 401 (logged out) and 515 (retry once) error codes. The structure is sound, thoughwhile (Date.now() < deadline)would make the exit condition more explicit.♻️ Optional: explicit loop condition
- while (true) { + while (Date.now() < deadline) { const remaining = deadline - Date.now(); - if (remaining <= 0) { - return { - connected: false, - message: - "Still waiting for the QR scan. Let me know when you've scanned it.", - accountId, - }; - }Then add a fallback return after the loop for clarity.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/src/services/channel-service.ts` around lines 889 - 960, The infinite loop uses while (true) and relies on internal returns; change it to an explicit time-bound loop like while (Date.now() < deadline) around the block that awaits Promise.race so the exit condition is clear, keep the existing Promise.race/timeout logic and all existing handling for login.error (including checks against WHATSAPP_LOGGED_OUT_STATUS, calls to rmSync(login.authDir...), resetActiveWhatsappLogin(accountId, ...), restartWhatsappLoginSocket(login, runtime) and isWhatsappLoginFresh(login)), preserve the login.connected branch (setting login.preserveAuthDirOnReset), and add a single fallback return after the loop that returns { connected: false, message: "Still waiting for the QR scan. Let me know when you've scanned it.", accountId } to cover any fallthrough.
778-866: Add a code comment explaining why auth is reset on every QR start.The logging at line 1078–1081 is properly structured, but the code lacks a comment explaining the design rationale. The method unconditionally calls
resetWhatsAppDefaultLoginStatewhich deletes the auth directory if it exists, forcing a fresh QR scan. Consider adding a comment clarifying whether this is intentional (e.g., to handle stale/corrupt sessions) or if users should reconnect without re-scanning.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/src/services/channel-service.ts` around lines 778 - 866, The method whatsappQrStart currently calls resetWhatsAppDefaultLoginState(...) unconditionally which deletes existing auth and forces a fresh QR; add a concise code comment above that call (in whatsappQrStart) explaining the rationale (e.g., to avoid stale or corrupted credential state, ensure clean authDir before creating a new login session, and prevent mismatched socket/auth state) and note any user-facing consequence (users must re-scan) or future TODO if you intend to allow reuse; reference resetWhatsAppDefaultLoginState, DEFAULT_WHATSAPP_ACCOUNT_ID, and the authDir/login session logic so reviewers can see why the reset is necessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/controller/src/routes/channel-routes.ts`:
- Around line 324-358: The leading comment "WeChat QR login flow" above the
WhatsApp endpoint is stale and misleading; update or replace it to accurately
describe the code that follows (e.g., "WhatsApp QR login flow") where the
app.openapi(createRoute(...)) defines the POST route
"/api/v1/channels/whatsapp/qr-start" (and any adjacent WhatsApp routes),
ensuring nearby comments reflect that the WeChat flow actually begins later
around the WeChat handlers.
In `@apps/controller/src/services/channel-service.ts`:
- Around line 443-468: The loadWhatsappRuntimeModules function currently assumes
minified export names (sessionModule.t/i/r/n) exist; add runtime validation
after importing sessionModule to verify each expected export exists and is of
the correct type (e.g., typeof sessionModule.t === "function" for
createWaSocket, likewise for i, r, n) and if any check fails throw a clear,
descriptive error that includes the actual keys present on the imported module
and which expected export(s) were missing or invalid; reference sessionModule
and the mapping to createWaSocket/waitForWaConnection/getStatusCode/formatError
so reviewers can locate and update the validation and error message.
---
Nitpick comments:
In `@apps/controller/src/services/channel-service.ts`:
- Around line 889-960: The infinite loop uses while (true) and relies on
internal returns; change it to an explicit time-bound loop like while
(Date.now() < deadline) around the block that awaits Promise.race so the exit
condition is clear, keep the existing Promise.race/timeout logic and all
existing handling for login.error (including checks against
WHATSAPP_LOGGED_OUT_STATUS, calls to rmSync(login.authDir...),
resetActiveWhatsappLogin(accountId, ...), restartWhatsappLoginSocket(login,
runtime) and isWhatsappLoginFresh(login)), preserve the login.connected branch
(setting login.preserveAuthDirOnReset), and add a single fallback return after
the loop that returns { connected: false, message: "Still waiting for the QR
scan. Let me know when you've scanned it.", accountId } to cover any
fallthrough.
- Around line 778-866: The method whatsappQrStart currently calls
resetWhatsAppDefaultLoginState(...) unconditionally which deletes existing auth
and forces a fresh QR; add a concise code comment above that call (in
whatsappQrStart) explaining the rationale (e.g., to avoid stale or corrupted
credential state, ensure clean authDir before creating a new login session, and
prevent mismatched socket/auth state) and note any user-facing consequence
(users must re-scan) or future TODO if you intend to allow reuse; reference
resetWhatsAppDefaultLoginState, DEFAULT_WHATSAPP_ACCOUNT_ID, and the
authDir/login session logic so reviewers can see why the reset is necessary.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 19fb15a1-ec2f-4e48-b0f5-435646358671
📒 Files selected for processing (12)
apps/controller/openapi.jsonapps/controller/src/routes/channel-routes.tsapps/controller/src/services/channel-service.tsapps/controller/static/runtime-plugins/whatsapp/src/channel.tsapps/web/lib/api/sdk.gen.tsapps/web/lib/api/types.gen.tsapps/web/src/components/channel-setup/telegram-setup-view.tsxapps/web/src/components/channel-setup/whatsapp-setup-view.tsxapps/web/src/i18n/locales/en.tsapps/web/src/i18n/locales/zh-CN.tsapps/web/src/pages/channels.tsxapps/web/src/pages/home.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
- apps/web/src/components/channel-setup/telegram-setup-view.tsx
- apps/web/src/pages/channels.tsx
- apps/web/src/pages/home.tsx
- apps/web/src/i18n/locales/en.ts
- apps/web/src/components/channel-setup/whatsapp-setup-view.tsx
- apps/web/lib/api/sdk.gen.ts
- apps/controller/openapi.json
- apps/controller/static/runtime-plugins/whatsapp/src/channel.ts
- apps/web/lib/api/types.gen.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
apps/controller/src/services/channel-service.ts (3)
1066-1090: Consider extracting magic numbers to constants.Lines 1067 and 1083 use inline numeric values. For consistency with the other timeout constants defined at the top of the file, consider extracting these:
const WHATSAPP_READINESS_TIMEOUT_MS = 45_000; const WHATSAPP_READINESS_POLL_INTERVAL_MS = 1_500;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/src/services/channel-service.ts` around lines 1066 - 1090, Extract the magic numbers in waitForWhatsappReady into named constants: replace 45_000 with WHATSAPP_READINESS_TIMEOUT_MS and 1_500 with WHATSAPP_READINESS_POLL_INTERVAL_MS (define them near the other timeout constants at top of the file), then update the function (method waitForWhatsappReady) to use these constants for deadline calculation and the sleep(...) call to keep consistency with existing timeout configuration.
911-911: Consider caching WhatsApp runtime modules.
loadWhatsappRuntimeModulesinvolves file system operations and dynamic imports. IfwhatsappQrWaitis called multiple times (e.g., client polling), this redundant I/O could impact performance. Consider caching the loaded modules at the service instance level.Suggested approach
// Add private field private whatsappRuntime: WhatsappRuntimeModules | null = null; private async getWhatsappRuntime(): Promise<WhatsappRuntimeModules> { if (!this.whatsappRuntime) { this.whatsappRuntime = await loadWhatsappRuntimeModules(this.env); } return this.whatsappRuntime; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/src/services/channel-service.ts` at line 911, The call to loadWhatsappRuntimeModules in whatsappQrWait causes repeated FS and dynamic import work; add an instance-level cache and accessor to avoid reloading: add a private field (e.g., whatsappRuntime: WhatsappRuntimeModules | null = null) on the service, implement a private async getWhatsappRuntime() that sets whatsappRuntime = await loadWhatsappRuntimeModules(this.env) the first time and returns the cached object thereafter, and update whatsappQrWait (and any other places calling loadWhatsappRuntimeModules) to call getWhatsappRuntime() instead of loadWhatsappRuntimeModules(this.env).
555-561: Add tracing annotations for methods with monitoring value.Per coding guidelines, methods with monitoring value should use
@Trace/@Spanannotations. The channel connection methods (connectTelegram,connectWhatsapp,whatsappQrStart,whatsappQrWait, etc.) involve external API calls and would benefit from distributed tracing for debugging and performance monitoring.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/src/services/channel-service.ts` around lines 555 - 561, The channel connection methods lack tracing annotations; add `@Trace` or `@Span` decorators to the public methods that perform external calls (e.g., connectTelegram, connectWhatsapp, whatsappQrStart, whatsappQrWait and any other channel connect methods in ChannelService) so they emit distributed-tracing spans; import the tracing decorators from the project's tracing/tracer module, place an appropriate `@Trace/`@Span on each method, include contextual attributes (channel type, channelId or phone) and ensure exceptions are recorded on the span so errors are visible in monitoring.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/controller/src/services/channel-service.ts`:
- Line 716: The polling deadline currently uses a hardcoded 500_000ms (deadline
= Date.now() + 500_000) which exceeds the QR code TTL; change the deadline
computation to use the WECHAT_LOGIN_TTL_MS constant (e.g., deadline = Date.now()
+ WECHAT_LOGIN_TTL_MS) in the function in channel-service.ts so polling stops
when the QR TTL elapses (or use Math.min if you need an upper bound), ensuring
the loop’s timeout aligns with WECHAT_LOGIN_TTL_MS.
---
Nitpick comments:
In `@apps/controller/src/services/channel-service.ts`:
- Around line 1066-1090: Extract the magic numbers in waitForWhatsappReady into
named constants: replace 45_000 with WHATSAPP_READINESS_TIMEOUT_MS and 1_500
with WHATSAPP_READINESS_POLL_INTERVAL_MS (define them near the other timeout
constants at top of the file), then update the function (method
waitForWhatsappReady) to use these constants for deadline calculation and the
sleep(...) call to keep consistency with existing timeout configuration.
- Line 911: The call to loadWhatsappRuntimeModules in whatsappQrWait causes
repeated FS and dynamic import work; add an instance-level cache and accessor to
avoid reloading: add a private field (e.g., whatsappRuntime:
WhatsappRuntimeModules | null = null) on the service, implement a private async
getWhatsappRuntime() that sets whatsappRuntime = await
loadWhatsappRuntimeModules(this.env) the first time and returns the cached
object thereafter, and update whatsappQrWait (and any other places calling
loadWhatsappRuntimeModules) to call getWhatsappRuntime() instead of
loadWhatsappRuntimeModules(this.env).
- Around line 555-561: The channel connection methods lack tracing annotations;
add `@Trace` or `@Span` decorators to the public methods that perform external calls
(e.g., connectTelegram, connectWhatsapp, whatsappQrStart, whatsappQrWait and any
other channel connect methods in ChannelService) so they emit
distributed-tracing spans; import the tracing decorators from the project's
tracing/tracer module, place an appropriate `@Trace/`@Span on each method, include
contextual attributes (channel type, channelId or phone) and ensure exceptions
are recorded on the span so errors are visible in monitoring.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b8235dcc-4807-4c2c-ba13-c0f8b493e76a
📒 Files selected for processing (2)
apps/controller/src/routes/channel-routes.tsapps/controller/src/services/channel-service.ts
✅ Files skipped from review due to trivial changes (1)
- apps/controller/src/routes/channel-routes.ts
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
apps/controller/src/services/channel-service.ts (1)
708-717:⚠️ Potential issue | 🟡 MinorCap WeChat polling to the QR's actual TTL.
This starts a fresh 500s deadline even when the session is already partway through its 5-minute lifetime, so
wechatQrWait()can keep polling after the QR should already be expired. UseactiveLogin.startedAt + WECHAT_LOGIN_TTL_MSinstead.🕒 Proposed fix
- const deadline = Date.now() + 500_000; + const deadline = activeLogin.startedAt + WECHAT_LOGIN_TTL_MS;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/src/services/channel-service.ts` around lines 708 - 717, The polling deadline currently resets to Date.now() + 500_000 which can extend polling past the QR TTL; change the deadline calculation in the wechat QR wait loop to use the QR's actual expiry (activeLogin.startedAt + WECHAT_LOGIN_TTL_MS) and ensure you clamp it to at least the current time (so you don't loop if already expired). Update the variable referenced as deadline (used in the while loop that polls) to compare against activeLogin.startedAt + WECHAT_LOGIN_TTL_MS instead of Date.now() + 500_000, keeping use of activeWechatLogins.delete(sessionKey) and the existing early-return logic intact.
🧹 Nitpick comments (1)
apps/controller/src/services/channel-service.ts (1)
676-695: Use cuid2 for the public WeChat session key.
sessionKeyis returned to clients and is part of the controller's public ID surface. WhilerandomUUID()is cryptographically sound, it diverges from the repo's established convention of using cuid2 (@paralleldrive/cuid2) for public IDs in this package.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/src/services/channel-service.ts` around lines 676 - 695, The public sessionKey in wechatQrStart is generated with randomUUID(), but this repo standard uses cuid2 for public IDs; replace randomUUID() with the cuid2 generator (import createId from '@paralleldrive/cuid2') and use createId() to produce sessionKey in the wechatQrStart function, update the import list at the top of the file to include createId, and keep the sessionKey variable name and usage unchanged so activeWechatLogins and the returned object continue to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/controller/src/services/channel-service.ts`:
- Around line 828-834: The code creates a temporary login session directory via
resolveWhatsAppLoginSessionDir and then uses its nested authDir (authDir,
loginSessionId, mkdirSync) as the channel's persistent auth state, which leaves
orphaned credentials when replaced; change this to persist a stable per-account
auth path (e.g., derive a deterministic auth path using the channel/account id
instead of the transient loginSessionId) or, if you must replace the stored
authDir in connectWhatsapp(), delete the previous authDir recursively before
assigning the new one (and update resetWhatsAppDefaultLoginState() to clear the
whole stored tree), referencing resolveWhatsAppLoginSessionDir,
connectWhatsapp(), resetWhatsAppDefaultLoginState(), authDir and loginSessionId
to locate the affected code.
- Around line 971-972: connectWhatsapp() must take responsibility for preserving
and rolling back auth-dir state: when preparing to store login.authDir
(including when login.connected is already true), set
login.preserveAuthDirOnReset = true before touching authDir and ensure you clear
it and call resetActiveWhatsappLogin() on failure; wrap the critical section
(including waitForWhatsappReady()) in try/catch/finally so any thrown error
triggers the rollback (unset preserveAuthDirOnReset and
resetActiveWhatsappLogin()), and remove reliance on whatsappQrWait() having set
preserveAuthDirOnReset first. Ensure the same change is applied to the other
block around lines 988-1008.
- Around line 736-748: The code uses status.ilink_bot_id without verifying
normalizeAccountId(status.ilink_bot_id) returns a non-empty string, so values
like "---" produce an empty normalizedAccountId and create a bad file; before
calling writeWeChatAccount and registerWeChatAccount, compute
normalizedAccountId = normalizeAccountId(status.ilink_bot_id) and skip (or
return/log) if normalizedAccountId is empty (e.g., === "" or falsy), ensuring
you only write/register when normalizedAccountId is a valid non-empty string.
---
Duplicate comments:
In `@apps/controller/src/services/channel-service.ts`:
- Around line 708-717: The polling deadline currently resets to Date.now() +
500_000 which can extend polling past the QR TTL; change the deadline
calculation in the wechat QR wait loop to use the QR's actual expiry
(activeLogin.startedAt + WECHAT_LOGIN_TTL_MS) and ensure you clamp it to at
least the current time (so you don't loop if already expired). Update the
variable referenced as deadline (used in the while loop that polls) to compare
against activeLogin.startedAt + WECHAT_LOGIN_TTL_MS instead of Date.now() +
500_000, keeping use of activeWechatLogins.delete(sessionKey) and the existing
early-return logic intact.
---
Nitpick comments:
In `@apps/controller/src/services/channel-service.ts`:
- Around line 676-695: The public sessionKey in wechatQrStart is generated with
randomUUID(), but this repo standard uses cuid2 for public IDs; replace
randomUUID() with the cuid2 generator (import createId from
'@paralleldrive/cuid2') and use createId() to produce sessionKey in the
wechatQrStart function, update the import list at the top of the file to include
createId, and keep the sessionKey variable name and usage unchanged so
activeWechatLogins and the returned object continue to work.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 180e5f39-fe3d-4af9-815b-0262a2717677
📒 Files selected for processing (4)
apps/controller/src/runtime/openclaw-runtime-plugin-writer.tsapps/controller/src/services/channel-service.tsapps/controller/tests/openclaw-runtime-plugin-writer.test.tsapps/desktop/main/index.ts
lefarcen
left a comment
There was a problem hiding this comment.
Code Review
P0 — Must fix
1. Accidental openapi.json title change
"Nexu Controller API" → "nexu Controller API" — looks like an unintended side-effect of pnpm generate-types. Please revert.
2. WhatsApp login management complexity in channel-service.ts
The controller gained ~900 lines of low-level WhatsApp baileys socket management (activeWhatsappLogins Map, QR polling, disconnect/reconnect, session directories). This turns the stateless controller into a stateful connection manager.
The WhatsApp runtime plugin already declares gatewayMethods: ["web.login.start", "web.login.wait"] — which means the OpenClaw gateway exposes these operations over its websocket RPC. The controller should call gatewayService.request("web.login.start", ...) / gatewayService.request("web.login.wait", ...) instead of reimplementing baileys connection logic.
Current approach:
- Breaks the controller's stateless design (global
Mapof active sockets) - Duplicates logic already in the OpenClaw WhatsApp plugin
- Creates two competing connection managers if the gateway is also running WhatsApp
3. WeChat QR flow moved from gatewayService to channelService with hardcoded iLink API
const DEFAULT_WECHAT_BASE_URL = "https://ilinkai.weixin.qq.com";The route handler was changed from container.gatewayService.wechatQrStart() to container.channelService.wechatQrStart(), bypassing the openclaw-weixin plugin entirely. This leaks iLink API implementation details (iLink-App-ClientVersion header, QR polling protocol) into the controller. What motivated this change? If the gateway was unreliable, a fallback with clear comments would be better than a full reimplementation.
4. isQrImageSource in whatsapp-setup-view.tsx allows arbitrary HTTP URLs as <img src>
trimmed.startsWith("http://") || trimmed.startsWith("https://")The WeChat version only allows data:image/, which is correct. Allowing arbitrary URLs opens an XSS/tracking vector if the QR data is ever influenced by external input. Restrict to data:image/ only, or validate against a known origin.
5. Telegram botToken in request URL — verify logging
https://api.telegram.org/bot${token}/getMe is the standard Telegram pattern, but make sure the controller's HTTP request logging (pino, fetch interceptors) does not log the full URL, or the token will appear in plaintext logs.
P1 — Strongly recommended
6. 500-second HTTP request timeout for QR wait endpoints
Both whatsappQrWait (timeoutMs: 500_000) and wechatQrWait (deadline = Date.now() + 500_000) hold an HTTP connection open for ~8 minutes. Most reverse proxies and browsers time out at 30–120s. Either shorten to ~60s or switch to a short-polling pattern (frontend polls every few seconds).
7. No Telegram runtime plugin
WhatsApp has a full static/runtime-plugins/whatsapp/ plugin, but Telegram has none. The channel-binding-compiler generates a telegram channel config block, but does OpenClaw have a built-in Telegram extension to consume it? If not, connecting Telegram will appear to succeed but the bot won't actually respond. Please clarify.
8. connectWhatsapp store method doesn't persist authDir
configStore.connectWhatsapp({ accountId }) stores no authDir, but channel-binding-compiler reads secret("authDir"). Confirm the final version writes authDir into secrets — otherwise WhatsApp config will have authDir: undefined and the runtime won't find the session.
P2 — Suggestions
9. Platform order change — WhatsApp is now first
PLATFORMS array puts WhatsApp before WeChat. Given the primary user base is Chinese, WeChat probably should remain the default selection.
10. Telegram and WhatsApp setup views have no i18n
All copy is hardcoded English ("Connect Telegram Bot", "Scan WhatsApp QR", etc.). The project has a full i18n system with en.ts / zh-CN.ts — these views should use useTranslation() and locale keys like the other channel setup views.
11. TelegramModal / WhatsappModal in home.tsx duplicate WechatQrModal
All three modals share the same shell (overlay + close button + title). Extract a generic ChannelSetupModal wrapper to reduce repetition.
12. Session channel detection relies on fragile string matching
combined.includes("@g.us")@g.us is very short and could false-match in non-WhatsApp contexts. Consider a more specific check (e.g., require the string to end with @g.us or be preceded by digits).
|
Thanks, this is helpful. Point-by-point:
I will follow up on (1) and (4), and verify (5). |
Relates to #439
Summary
Testing
Summary by CodeRabbit
New Features
Enhanced Channel Management
API & SDK
Localization & Tests