Skip to content

Added a WeChat bridge leveraging Feishu and Tencent OpenClaw.#3206

Open
VincentCorleone wants to merge 1 commit into
Hmbown:mainfrom
VincentCorleone:main
Open

Added a WeChat bridge leveraging Feishu and Tencent OpenClaw.#3206
VincentCorleone wants to merge 1 commit into
Hmbown:mainfrom
VincentCorleone:main

Conversation

@VincentCorleone

Copy link
Copy Markdown

Summary

To use CodeWhale from the WeChat infrastructure I already use every day, I added a WeChat bridge leveraging the existing Feishu Bridge in this repo and Tencent OpenClaw on npm.

Testing

image

I ran an end-to-end test (WeChat → CodeWhale) and confirmed it works: I send a message to CodeWhale on WeChat, and it replies with context-aware responses based on the working directory.

Checklist

image

22 of 24 CI checks have passed. One was skipped, which is not a concern. The remaining failure is caused by a missing CNB_GIT_TOKEN; since this token is configured in the upstream repository (Hunter Bown's), this check is expected to resolve automatically upon merge.

@VincentCorleone VincentCorleone requested a review from Hmbown as a code owner June 14, 2026 03:30

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@github-actions

Copy link
Copy Markdown

Thanks @VincentCorleone for taking the time to contribute.

This repository is observing a maintainer-managed PR intake gate in dry-run mode, so this pull request is staying open. This note helps maintainers prepare the allowlist before any enforcement is considered.

Please read CONTRIBUTING.md for the expected contribution shape. A maintainer can grant recurring PR access by commenting /lgtm on a pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the weixin-bridge integration, allowing personal WeChat accounts to control a local CodeWhale runtime via the Tencent iLink Bot protocol. The feedback focuses on improving robustness and responsiveness: specifically, avoiding blocking the long-polling loop by running command handlers asynchronously, wrapping JSON parsing in try-catch blocks to prevent crashes on non-JSON responses, ensuring timeouts are always cleared to prevent leaks, and adding defensive checks for potentially null or undefined objects like response.body and the long-polling response.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +861 to +867
const command = parseCommand(text);
await handleCommand(fromUser, command).catch((error) => {
console.error(
`failed to handle command from=${fromUser} text=${text.slice(0, 100)}`,
error
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Awaiting handleCommand inside the message processing loop blocks the entire long-polling cycle. If a turn takes a long time to execute, the bridge will be completely unresponsive to other users, and control commands like /interrupt will not be received or processed until the active turn completes. Removing the await allows the command handler to run asynchronously, keeping the long-polling loop active and responsive.

        // 命令路由
        const command = parseCommand(text);
        handleCommand(fromUser, command).catch((error) => {
          console.error(
            `failed to handle command from=${fromUser} text=${text.slice(0, 100)}`,
            error
          );
        });

continue;
}

const data = JSON.parse(raw);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If apiGet returns a non-JSON string (such as "timeout" or gateway HTML error pages on 524), JSON.parse(raw) will throw a SyntaxError and crash the bridge. Wrapping the parsing in a try-catch block ensures that non-JSON responses are safely caught, allowing the polling loop to wait and retry.

    let data;
    try {
      data = JSON.parse(raw);
    } catch {
      // If the response is not valid JSON (e.g. "timeout" or HTML error), wait and retry
      await new Promise((r) => setTimeout(r, 1000));
      continue;
    }", edge_cases

Comment on lines +770 to +778
const resp = await getUpdates({
baseUrl,
token,
get_updates_buf: getUpdatesBuf,
timeoutMs: nextTimeoutMs,
signal: abortController.signal,
});

clearTimeout(timer);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If getUpdates throws an error, clearTimeout(timer) is skipped, which leaks the timeout. Wrapping the call in a try...finally block ensures that the timer is always cleared, preventing potential memory leaks over time.

      let resp;
      try {
        resp = await getUpdates({
          baseUrl,
          token,
          get_updates_buf: getUpdatesBuf,
          timeoutMs: nextTimeoutMs,
          signal: abortController.signal,
        });
      } finally {
        clearTimeout(timer);
      }

Comment on lines +251 to +253
async function* readSse(response) {
let buffer = "";
for await (const chunk of response.body) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If response.body is null (e.g., due to an empty response or network issue), attempting to iterate over it will throw a TypeError. Adding a defensive check for response.body prevents potential crashes.

Suggested change
async function* readSse(response) {
let buffer = "";
for await (const chunk of response.body) {
async function* readSse(response) {
if (!response.body) return;
let buffer = "";
for await (const chunk of response.body) {

Comment on lines +780 to +788
if (resp.longpolling_timeout_ms) {
nextTimeoutMs = resp.longpolling_timeout_ms;
}

// 检查错误
const isApiError =
(resp.ret !== undefined && resp.ret !== 0) ||
(resp.errcode !== undefined && resp.errcode !== 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If resp is null or undefined, accessing its properties will throw a TypeError and crash the loop. Using optional chaining and defensively checking resp prevents these crashes.

Suggested change
if (resp.longpolling_timeout_ms) {
nextTimeoutMs = resp.longpolling_timeout_ms;
}
// 检查错误
const isApiError =
(resp.ret !== undefined && resp.ret !== 0) ||
(resp.errcode !== undefined && resp.errcode !== 0);
if (resp?.longpolling_timeout_ms) {
nextTimeoutMs = resp.longpolling_timeout_ms;
}
// 检查错误
const isApiError =
!resp ||
(resp.ret !== undefined && resp.ret !== 0) ||
(resp.errcode !== undefined && resp.errcode !== 0);

Comment on lines +807 to +813
if (resp.get_updates_buf) {
getUpdatesBuf = resp.get_updates_buf;
await saveSyncBuf(config.stateDir, getUpdatesBuf);
}

// 处理消息
const msgs = resp.msgs || [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If resp is null or undefined, accessing resp.get_updates_buf or resp.msgs will throw a TypeError. Using optional chaining ensures safe property access.

Suggested change
if (resp.get_updates_buf) {
getUpdatesBuf = resp.get_updates_buf;
await saveSyncBuf(config.stateDir, getUpdatesBuf);
}
// 处理消息
const msgs = resp.msgs || [];
if (resp?.get_updates_buf) {
getUpdatesBuf = resp.get_updates_buf;
await saveSyncBuf(config.stateDir, getUpdatesBuf);
}
// 处理消息
const msgs = resp?.msgs || [];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant