Skip to content

Add Feishu and Lark platform support for thread bridge#208

Open
SoyaLeaf wants to merge 19 commits into
friuns2:mainfrom
SoyaLeaf:feishu
Open

Add Feishu and Lark platform support for thread bridge#208
SoyaLeaf wants to merge 19 commits into
friuns2:mainfrom
SoyaLeaf:feishu

Conversation

@SoyaLeaf

@SoyaLeaf SoyaLeaf commented Jul 9, 2026

Copy link
Copy Markdown

Summary

Add Feishu Thread Bridge support for both domestic Feishu and international Lark.

This lets users choose which platform their bot belongs to, so the bridge can connect through either the Feishu API domain or the Lark international API domain.

What Changed

  • Added a Feishu / Lark platform selector in the app settings panel.
  • Added domain to the Feishu bridge config API, persisted config, and status response.
  • Defaulted existing/missing configs to feishu for backward compatibility.
  • Added FEISHU_DOMAIN environment support for startup configuration.
  • Configured both the Feishu REST client and WebSocket client with the selected SDK domain.
  • Restarted the bridge client when App ID, App Secret, or platform changes, so switching between Feishu and Lark takes effect immediately after saving.

Compatibility

Existing domestic Feishu setups continue to work without config changes because missing domain values default to feishu.

Performance Notes

This change only adds a small config normalization path and passes the selected domain into SDK client construction. It does not add duplicate requests, polling, large payloads, or new fanout. The bridge only reconnects when credentials or platform change.

Summary by CodeRabbit

  • New Features
    • Added optional Feishu/Lark bot bridging for bidirectional messaging.
    • Added Feishu bridge controls to the settings UI, including domain selection, app credentials, allowed-user configuration, and save flow.
    • Added bridge status reporting and command support for interacting with threads and history.
  • Documentation
    • Updated the README with Feishu Bot Bridge (Optional) setup instructions, permissions/events, required environment variables, and supported commands.

SoyaLeaf added 18 commits July 9, 2026 14:46
- Bidirectional messaging via Feishu bot
- Same command set as Telegram bridge
- Interactive card buttons for thread picker
- turn/completed notification forwarding
- Add feishuBridge to SharedBridgeState
- Add /codex-api/feishu/configure-bot, /feishu/config, /feishu/status routes
- Persist config to ~/.codex/feishu-bridge.json
- Auto-start on middleware creation, stop on dispose
- FeishuStatus, FeishuConfig types
- configureFeishuBot, getFeishuConfig, getFeishuStatus functions
- App ID, App Secret, allowed user open_ids fields
- Status display and save functionality
- Positioned alongside existing Telegram config
- Threads grouped under project folder headers
- Divider between groups
- Indigo card header style
- Each button in its own action row for vertical layout
- Limit to 6 threads per project group
- Show remaining count when truncated
- Fetch all threads with cursor pagination instead of limit 20
- Ensures no projects are missing from the thread picker
- Replace plain text messages with markdown card format
- Add wathet header with Codex branding
- Preserve code blocks and markdown formatting
- Replace plain text history with per-message interactive cards
- User messages: wathet header with 👤 User
- Assistant messages: grey header with 🤖 Assistant
- Merge consecutive same-role messages into one card
- Full text without truncation
- Thread selection shows last 2 messages, /history shows all
- Pass cwd to thread/list API to only show project-local threads
- Fixes thread not found error when selecting cross-project threads
- Show ⏳ Thinking... immediately when user sends a message
- Edit same message with actual reply when turn completes
- Reuse existing thinking message on consecutive sends
- Update thinking message with error on failure
- Remove cwd filter to show threads from all projects
- Show clear message when thread belongs to different project
- Store thread cwd from thread picker button value
- Pass stored cwd to turn/start RPC call
- Fixes thread not found error for cross-project threads
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: be6bd901-bcb8-4766-a648-5abeeeb4d0ec

📥 Commits

Reviewing files that changed from the base of the PR and between 55c814d and 050d3d3.

📒 Files selected for processing (1)
  • src/server/feishuThreadBridge.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/server/feishuThreadBridge.ts

📝 Walkthrough

Walkthrough

This PR adds a Feishu/Lark bot bridge with server-side bridge logic, configuration/status APIs, a frontend settings panel, SDK integration, and README documentation.

Changes

Feishu Bot Bridge

Layer / File(s) Summary
Feishu thread bridge core implementation
src/server/feishuThreadBridge.ts
New FeishuThreadBridge class handling Lark SDK lifecycle, allowlist/dedup, command routing, chat↔thread mapping, turn-completed forwarding, and history rendering.
Server bridge wiring and HTTP endpoints
src/server/codexAppServerBridge.ts
Persists Feishu config, wires feishuBridge into shared state/startup, adds configure-bot/config/status endpoints, and stops the bridge on disposal.
Client gateway API helpers for Feishu
src/api/codexGateway.ts
Adds FeishuDomain, FeishuStatus, FeishuConfig types and configureFeishuBot, getFeishuConfig, getFeishuStatus functions.
Feishu settings panel and state management
src/App.vue
Adds sidebar Feishu toggle/config panel, reactive drafts/status state, status text formatter, mount-time refresh calls, and save/refresh logic.
Dependency and documentation updates
package.json, README.md
Adds @larksuiteoapi/node-sdk and documents the Feishu bridge feature, setup steps, environment variables, and commands.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FeishuUser
  participant FeishuThreadBridge
  participant AppServer
  participant CodexAppServerBridge

  FeishuUser->>FeishuThreadBridge: send message or command
  FeishuThreadBridge->>FeishuThreadBridge: validate allowlist and dedupe
  FeishuThreadBridge->>AppServer: forward text to thread
  AppServer-->>CodexAppServerBridge: turn/completed notification
  CodexAppServerBridge-->>FeishuThreadBridge: notify completion
  FeishuThreadBridge->>AppServer: read latest assistant reply
  FeishuThreadBridge->>FeishuUser: send reply or update thinking message
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding Feishu and Lark support for the thread bridge.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add Feishu/Lark domain selection for Feishu thread bridge

✨ Enhancement ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add Feishu settings UI with platform (Feishu/Lark) selector and allowlist management.
• Persist domain in Feishu bridge config and expose new configure/config/status API routes.
• Initialize and restart the Feishu WebSocket bridge using the selected SDK domain.
Diagram

graph TD
  UI["Settings UI"] --> GW["codexGateway"] --> API["Feishu API routes"] --> BR["FeishuThreadBridge"] --> EXT{{"Feishu/Lark SDK"}}
  API --> CFG[("feishu-bridge.json")]

  subgraph Legend
    direction LR
    _ui["UI/module"] ~~~ _svc["Server/service"] ~~~ _file[("Config file")] ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Factor a shared BaseThreadBridge (Telegram/Feishu)
  • ➕ Reduces duplicated logic (allowlist normalization, thread mapping, turn completion forwarding, message splitting).
  • ➕ Easier to add future chat platforms with consistent behavior.
  • ➖ Bigger refactor; higher merge risk vs. shipping Feishu/Lark support now.
  • ➖ May over-generalize two bridges that differ in formatting and event models.
2. Persist platform as a full SDK domain/base URL
  • ➕ More flexible for future regional endpoints or testing against mocks.
  • ➕ Avoids adding more enum values if additional variants appear.
  • ➖ More validation/footguns (invalid URLs, mixed schemes).
  • ➖ Current Feishu/Lark enum is simpler and aligns with SDK Domain enum.

Recommendation: Current approach (explicit feishu/lark enum + normalization + restart-on-change) is appropriate for correctness and immediate usability. Consider a follow-up refactor to share common bridge mechanics with Telegram once the Feishu bridge stabilizes.

Files changed (6) +1503 / -2

Enhancement (4) +1466 / -2
App.vueAdd Feishu/Lark configuration panel in sidebar settings +188/-1

Add Feishu/Lark configuration panel in sidebar settings

• Adds a Feishu settings section with platform selector (Feishu vs Lark), App ID/Secret inputs, allowlist textarea parsing, status text rendering, and save/refresh flows via new gateway APIs.

src/App.vue

codexGateway.tsAdd Feishu config/status types and gateway functions +100/-0

Add Feishu config/status types and gateway functions

• Defines 'FeishuDomain', 'FeishuConfig', and 'FeishuStatus', and adds 'configureFeishuBot', 'getFeishuConfig', and 'getFeishuStatus' to call the new server endpoints with defensive payload parsing and domain defaulting.

src/api/codexGateway.ts

codexAppServerBridge.tsPersist Feishu bridge config and expose configure/config/status routes +153/-1

Persist Feishu bridge config and expose configure/config/status routes

• Adds Feishu bridge state to the shared middleware, reads/writes '~/.codex/feishu-bridge.json', normalizes config (including defaulting missing domains to 'feishu'), auto-starts the bridge on startup, and adds '/codex-api/feishu/configure-bot', '/codex-api/feishu/config', and '/codex-api/feishu/status' routes. Ensures bridge shutdown on middleware dispose.

src/server/codexAppServerBridge.ts

feishuThreadBridge.tsImplement FeishuThreadBridge with Feishu/Lark domain-aware WebSocket client +1025/-0

Implement FeishuThreadBridge with Feishu/Lark domain-aware WebSocket client

• Introduces a new bridge that uses the LarkSuite SDK (REST + WS long connection) to map Feishu chats to Codex threads, enforce an open_id allowlist, provide bot commands and interactive thread picker cards, and forward assistant replies on 'turn/completed'. Supports 'FEISHU_DOMAIN' env and restarts clients when credentials/domain change.

src/server/feishuThreadBridge.ts

Documentation (1) +36 / -0
README.mdDocument optional Feishu bot bridge setup and commands +36/-0

Document optional Feishu bot bridge setup and commands

• Adds README documentation for the Feishu WebSocket long-connection bridge, including setup steps, required permissions/events, environment variables, and supported bot commands.

README.md

Other (1) +1 / -0
package.jsonAdd LarkSuite OpenAPI Node SDK dependency +1/-0

Add LarkSuite OpenAPI Node SDK dependency

• Introduces '@larksuiteoapi/node-sdk' to support Feishu/Lark REST + WebSocket clients.

package.json

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Feishu manual tests missing 📘 Rule violation ⚙ Maintainability
Description
The PR adds a new Feishu/Lark thread bridge feature and related settings, but does not add/update
domain-scoped manual test documentation under tests/<domain>/ (and keeps tests.md as only an
index). Without a new Feishu/Lark manual test section following the required template (prereqs,
steps, expected results, rollback), the change is not reproducibly testable.
Code

src/App.vue[R478-555]

+              <button class="sidebar-settings-row" type="button" aria-live="polite" @click="isFeishuConfigOpen = !isFeishuConfigOpen">
+                <span class="sidebar-settings-label">{{ t('Feishu') }}</span>
+                <span class="sidebar-settings-value">{{ feishuStatusText }}</span>
+              </button>
+              <div v-if="isFeishuConfigOpen" class="sidebar-settings-telegram-panel">
+                <div class="sidebar-settings-row sidebar-settings-row--select" style="padding: 0">
+                  <span class="sidebar-settings-label">{{ t('Platform') }}</span>
+                  <div class="sidebar-settings-segmented" role="group" :aria-label="t('Feishu platform')">
+                    <button
+                      type="button"
+                      class="sidebar-settings-segmented-option"
+                      :class="{ 'is-active': feishuDomainDraft === 'feishu' }"
+                      :disabled="isFeishuSaving"
+                      @click="feishuDomainDraft = 'feishu'"
+                    >
+                      Feishu
+                    </button>
+                    <button
+                      type="button"
+                      class="sidebar-settings-segmented-option"
+                      :class="{ 'is-active': feishuDomainDraft === 'lark' }"
+                      :disabled="isFeishuSaving"
+                      @click="feishuDomainDraft = 'lark'"
+                    >
+                      Lark
+                    </button>
+                  </div>
+                </div>
+                <label class="sidebar-settings-field">
+                  <span class="sidebar-settings-field-label">{{ t('App ID') }}</span>
+                  <input
+                    v-model="feishuAppIdDraft"
+                    class="sidebar-settings-input"
+                    type="text"
+                    placeholder="cli_xxxxxxxxxx"
+                    autocomplete="off"
+                    spellcheck="false"
+                  >
+                </label>
+                <label class="sidebar-settings-field">
+                  <span class="sidebar-settings-field-label">{{ t('App Secret') }}</span>
+                  <input
+                    v-model="feishuAppSecretDraft"
+                    class="sidebar-settings-input"
+                    type="password"
+                    placeholder="xxxxxxxxxxxxxxxxxxxxxxxx"
+                    autocomplete="off"
+                    spellcheck="false"
+                  >
+                </label>
+                <label class="sidebar-settings-field">
+                  <span class="sidebar-settings-field-label">{{ t('Allowed Feishu user open_ids') }}</span>
+                  <textarea
+                    v-model="feishuAllowedUserIdsDraft"
+                    class="sidebar-settings-textarea"
+                    rows="3"
+                    placeholder="ou_xxxxxxxxxx&#10;ou_yyyyyyyyyy"
+                    spellcheck="false"
+                  />
+                </label>
+                <div class="sidebar-settings-field-help">
+                  {{ t('Put one Feishu open_id per line or separate them with commas. Use `*` to allow all Feishu users. You can find open_id in the bot rejection message.') }}
+                </div>
+                <div v-if="feishuConfigError" class="sidebar-settings-telegram-error">
+                  <span>{{ feishuConfigError }}</span>
+                  <a class="visible-error-feedback" :href="feedbackMailto" @click="prepareFeedbackLink($event, feishuConfigError)">{{ t('Send feedback') }}</a>
+                </div>
+                <div class="sidebar-settings-telegram-actions">
+                  <button
+                    class="sidebar-settings-telegram-save"
+                    type="button"
+                    :disabled="isFeishuSaving"
+                    @click="saveFeishuConfig"
+                  >
+                    {{ isFeishuSaving ? t('Saving…') : t('Save Feishu config') }}
+                  </button>
+                </div>
+              </div>
Evidence
The diff adds new Feishu/Lark UI configuration and README documentation for the Feishu bridge,
indicating a feature change needing manual verification. However, the manual test index describes
that detailed steps must live under tests/ and the integrations domain index currently lists
Telegram-related sections but no Feishu/Lark section; the manual test template specifies required
fields that are currently not provided for this new feature.

AGENTS.md: Manual test documentation must be updated under tests/<domain>/ and tests.md must remain an index
AGENTS.md: Manual test entries must include required details (actions, expected results, prerequisites, and rollback notes when applicable)
src/App.vue[478-555]
README.md[178-212]
tests.md[1-13]
tests/skills-plugins-integrations/index.md[7-14]
tests/template.md[7-20]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
This PR introduces a Feishu/Lark platform selector + Feishu bot bridge configuration and server support, but no new/updated manual test documentation was added under `tests/<domain>/`, and therefore there is no reproducible manual test section for validating the new behavior.

## Issue Context
Repository policy requires feature work to be accompanied by domain-scoped manual test docs under `tests/<domain>/`, while keeping `tests.md` as an index. Manual test entries must include: feature name, prerequisites, exact steps, expected results, and rollback/cleanup notes.

## Fix Focus Areas
- tests/skills-plugins-integrations/index.md[7-14]
- tests/template.md[7-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Env config not started 🐞 Bug ≡ Correctness
Description
FeishuThreadBridge reads FEISHU_* env vars, but createCodexBridgeMiddleware only calls
feishuBridge.start() when feishu-bridge.json contains appId/appSecret, so env-only deployments never
activate the Feishu WebSocket client. This breaks the documented “use environment variables” setup
and leaves the bridge offline despite valid FEISHU_* values.
Code

src/server/codexAppServerBridge.ts[R7554-7560]

+  void readFeishuBridgeConfig()
+    .then((config) => {
+      if (!config.appId || !config.appSecret) return
+      feishuBridge.configureApp(config.appId, config.appSecret, config.domain)
+      feishuBridge.configureAllowedUserIds(config.allowedUserIds)
+      feishuBridge.start()
+    })
Evidence
The bridge is constructed with credentials from FEISHU_* env vars, but the startup code only starts
Feishu when the persisted JSON config has appId/appSecret; therefore env-only configuration cannot
make the bridge active.

src/server/feishuThreadBridge.ts[203-220]
src/server/codexAppServerBridge.ts[7554-7561]
README.md[145-154]
README.md[191-197]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
FeishuThreadBridge initializes credentials and defaults from environment variables, but the server only starts the Feishu bridge if the persisted `feishu-bridge.json` contains `appId/appSecret`. As a result, a user who follows the README and sets only `FEISHU_APP_ID/FEISHU_APP_SECRET/FEISHU_ALLOWED_USER_IDS` will have a configured-but-inactive bridge.

## Issue Context
- `FeishuThreadBridge` constructor reads `process.env.FEISHU_*`.
- `createCodexBridgeMiddleware()` only calls `feishuBridge.start()` inside the `readFeishuBridgeConfig().then(...)` block, and returns early when the config file lacks credentials.

## Fix Focus Areas
- src/server/codexAppServerBridge.ts[7545-7561]
- src/server/feishuThreadBridge.ts[203-257]

## Implementation notes
- After reading persisted config, if it’s missing credentials, fall back to the bridge’s current in-memory configuration (from env) and start when `getStatus().configured` is true.
- Optionally also require an allowlist to be present before starting: `status.allowAllUsers || status.allowedUsers > 0`, consistent with the security expectations in the README.
- Consider merging precedence: persisted config overrides env when present; otherwise env drives startup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Feishu config API leaks secrets 🐞 Bug ⛨ Security
Description
The new Feishu routes are unauthenticated and include a GET endpoint that returns appSecret, so
any client that can reach the server can exfiltrate Feishu credentials and/or reconfigure the
allowlist. This is especially risky because the app is explicitly LAN-accessible and these routes
lack the localhost-only guard used elsewhere in the same middleware.
Code

src/server/codexAppServerBridge.ts[R9683-9732]

+      if (req.method === 'POST' && url.pathname === '/codex-api/feishu/configure-bot') {
+        const payload = asRecord(await readJsonBody(req))
+        const appId = typeof payload?.appId === 'string' ? payload.appId.trim() : ''
+        const appSecret = typeof payload?.appSecret === 'string' ? payload.appSecret.trim() : ''
+        const domain = normalizeFeishuDomain(payload?.domain)
+        const rawAllowedUserIds = Array.isArray(payload?.allowedUserIds) ? payload.allowedUserIds : []
+        if (!appId) {
+          setJson(res, 400, { error: 'Missing appId' })
+          return
+        }
+        if (!appSecret) {
+          setJson(res, 400, { error: 'Missing appSecret' })
+          return
+        }
+        const config = normalizeFeishuBridgeConfig({
+          appId,
+          appSecret,
+          domain,
+          allowedUserIds: rawAllowedUserIds,
+        })
+        if (config.allowedUserIds.length === 0) {
+          setJson(res, 400, { error: 'At least one allowed Feishu user ID is required' })
+          return
+        }
+
+        feishuBridge.configureApp(config.appId, config.appSecret, config.domain)
+        feishuBridge.configureAllowedUserIds(config.allowedUserIds)
+        feishuBridge.start()
+        const existingConfig = await readFeishuBridgeConfig()
+        await writeFeishuBridgeConfig({
+          appId: config.appId,
+          appSecret: config.appSecret,
+          domain: config.domain,
+          chatIds: existingConfig.chatIds,
+          allowedUserIds: config.allowedUserIds,
+        })
+        setJson(res, 200, { ok: true })
+        return
+      }
+
+      if (req.method === 'GET' && url.pathname === '/codex-api/feishu/config') {
+        const config = await readFeishuBridgeConfig()
+        setJson(res, 200, {
+          data: {
+            appId: config.appId,
+            appSecret: config.appSecret,
+            domain: config.domain,
+            allowedUserIds: config.allowedUserIds,
+          },
+        })
Evidence
The Feishu configure/config routes are reachable without any loopback/auth check and the GET config
route explicitly returns the stored appSecret; the repo also advertises LAN-friendly access, so a
LAN client can steal or tamper with these credentials.

src/server/codexAppServerBridge.ts[9683-9733]
src/server/codexAppServerBridge.ts[7613-7617]
README.md[132-134]
src/server/codexAppServerBridge.ts[6184-6193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`/codex-api/feishu/config` returns `appSecret` and `/codex-api/feishu/configure-bot` allows arbitrary reconfiguration, but neither endpoint is protected by a localhost check or any authentication. Since the server is intended to be reachable on the LAN, this enables credential theft and bridge takeover.

## Issue Context
- The middleware already enforces a localhost-only check for some sensitive endpoints (e.g., zen proxy), but not for Feishu.
- Feishu credentials are also persisted to disk in plaintext JSON.

## Fix Focus Areas
- src/server/codexAppServerBridge.ts[9683-9733]
- src/server/codexAppServerBridge.ts[7613-7617]
- src/server/codexAppServerBridge.ts[6184-6193]
- src/App.vue[2474-2526]
- src/api/codexGateway.ts[3424-3475]

## Implementation notes
- Add an access-control guard for Feishu config routes at minimum (e.g., `isLoopbackRemoteAddress(req.socket.remoteAddress)`), consistent with other sensitive endpoints.
- Stop returning secrets from GET `/codex-api/feishu/config` (return a boolean like `hasAppSecret` and/or a masked placeholder).
- Update POST `/codex-api/feishu/configure-bot` to allow `appSecret` to be omitted/empty to mean “keep existing secret” (so the UI doesn’t need to re-fetch or re-send the secret).
- When writing `feishu-bridge.json`, consider restricting file permissions (e.g., mode `0o600`) since it contains an app secret.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/App.vue
Comment on lines +478 to +555
<button class="sidebar-settings-row" type="button" aria-live="polite" @click="isFeishuConfigOpen = !isFeishuConfigOpen">
<span class="sidebar-settings-label">{{ t('Feishu') }}</span>
<span class="sidebar-settings-value">{{ feishuStatusText }}</span>
</button>
<div v-if="isFeishuConfigOpen" class="sidebar-settings-telegram-panel">
<div class="sidebar-settings-row sidebar-settings-row--select" style="padding: 0">
<span class="sidebar-settings-label">{{ t('Platform') }}</span>
<div class="sidebar-settings-segmented" role="group" :aria-label="t('Feishu platform')">
<button
type="button"
class="sidebar-settings-segmented-option"
:class="{ 'is-active': feishuDomainDraft === 'feishu' }"
:disabled="isFeishuSaving"
@click="feishuDomainDraft = 'feishu'"
>
Feishu
</button>
<button
type="button"
class="sidebar-settings-segmented-option"
:class="{ 'is-active': feishuDomainDraft === 'lark' }"
:disabled="isFeishuSaving"
@click="feishuDomainDraft = 'lark'"
>
Lark
</button>
</div>
</div>
<label class="sidebar-settings-field">
<span class="sidebar-settings-field-label">{{ t('App ID') }}</span>
<input
v-model="feishuAppIdDraft"
class="sidebar-settings-input"
type="text"
placeholder="cli_xxxxxxxxxx"
autocomplete="off"
spellcheck="false"
>
</label>
<label class="sidebar-settings-field">
<span class="sidebar-settings-field-label">{{ t('App Secret') }}</span>
<input
v-model="feishuAppSecretDraft"
class="sidebar-settings-input"
type="password"
placeholder="xxxxxxxxxxxxxxxxxxxxxxxx"
autocomplete="off"
spellcheck="false"
>
</label>
<label class="sidebar-settings-field">
<span class="sidebar-settings-field-label">{{ t('Allowed Feishu user open_ids') }}</span>
<textarea
v-model="feishuAllowedUserIdsDraft"
class="sidebar-settings-textarea"
rows="3"
placeholder="ou_xxxxxxxxxx&#10;ou_yyyyyyyyyy"
spellcheck="false"
/>
</label>
<div class="sidebar-settings-field-help">
{{ t('Put one Feishu open_id per line or separate them with commas. Use `*` to allow all Feishu users. You can find open_id in the bot rejection message.') }}
</div>
<div v-if="feishuConfigError" class="sidebar-settings-telegram-error">
<span>{{ feishuConfigError }}</span>
<a class="visible-error-feedback" :href="feedbackMailto" @click="prepareFeedbackLink($event, feishuConfigError)">{{ t('Send feedback') }}</a>
</div>
<div class="sidebar-settings-telegram-actions">
<button
class="sidebar-settings-telegram-save"
type="button"
:disabled="isFeishuSaving"
@click="saveFeishuConfig"
>
{{ isFeishuSaving ? t('Saving…') : t('Save Feishu config') }}
</button>
</div>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Feishu manual tests missing 📘 Rule violation ⚙ Maintainability

The PR adds a new Feishu/Lark thread bridge feature and related settings, but does not add/update
domain-scoped manual test documentation under tests/<domain>/ (and keeps tests.md as only an
index). Without a new Feishu/Lark manual test section following the required template (prereqs,
steps, expected results, rollback), the change is not reproducibly testable.
Agent Prompt
## Issue description
This PR introduces a Feishu/Lark platform selector + Feishu bot bridge configuration and server support, but no new/updated manual test documentation was added under `tests/<domain>/`, and therefore there is no reproducible manual test section for validating the new behavior.

## Issue Context
Repository policy requires feature work to be accompanied by domain-scoped manual test docs under `tests/<domain>/`, while keeping `tests.md` as an index. Manual test entries must include: feature name, prerequisites, exact steps, expected results, and rollback/cleanup notes.

## Fix Focus Areas
- tests/skills-plugins-integrations/index.md[7-14]
- tests/template.md[7-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +7554 to +7560
void readFeishuBridgeConfig()
.then((config) => {
if (!config.appId || !config.appSecret) return
feishuBridge.configureApp(config.appId, config.appSecret, config.domain)
feishuBridge.configureAllowedUserIds(config.allowedUserIds)
feishuBridge.start()
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Env config not started 🐞 Bug ≡ Correctness

FeishuThreadBridge reads FEISHU_* env vars, but createCodexBridgeMiddleware only calls
feishuBridge.start() when feishu-bridge.json contains appId/appSecret, so env-only deployments never
activate the Feishu WebSocket client. This breaks the documented “use environment variables” setup
and leaves the bridge offline despite valid FEISHU_* values.
Agent Prompt
## Issue description
FeishuThreadBridge initializes credentials and defaults from environment variables, but the server only starts the Feishu bridge if the persisted `feishu-bridge.json` contains `appId/appSecret`. As a result, a user who follows the README and sets only `FEISHU_APP_ID/FEISHU_APP_SECRET/FEISHU_ALLOWED_USER_IDS` will have a configured-but-inactive bridge.

## Issue Context
- `FeishuThreadBridge` constructor reads `process.env.FEISHU_*`.
- `createCodexBridgeMiddleware()` only calls `feishuBridge.start()` inside the `readFeishuBridgeConfig().then(...)` block, and returns early when the config file lacks credentials.

## Fix Focus Areas
- src/server/codexAppServerBridge.ts[7545-7561]
- src/server/feishuThreadBridge.ts[203-257]

## Implementation notes
- After reading persisted config, if it’s missing credentials, fall back to the bridge’s current in-memory configuration (from env) and start when `getStatus().configured` is true.
- Optionally also require an allowlist to be present before starting: `status.allowAllUsers || status.allowedUsers > 0`, consistent with the security expectations in the README.
- Consider merging precedence: persisted config overrides env when present; otherwise env drives startup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +9683 to +9732
if (req.method === 'POST' && url.pathname === '/codex-api/feishu/configure-bot') {
const payload = asRecord(await readJsonBody(req))
const appId = typeof payload?.appId === 'string' ? payload.appId.trim() : ''
const appSecret = typeof payload?.appSecret === 'string' ? payload.appSecret.trim() : ''
const domain = normalizeFeishuDomain(payload?.domain)
const rawAllowedUserIds = Array.isArray(payload?.allowedUserIds) ? payload.allowedUserIds : []
if (!appId) {
setJson(res, 400, { error: 'Missing appId' })
return
}
if (!appSecret) {
setJson(res, 400, { error: 'Missing appSecret' })
return
}
const config = normalizeFeishuBridgeConfig({
appId,
appSecret,
domain,
allowedUserIds: rawAllowedUserIds,
})
if (config.allowedUserIds.length === 0) {
setJson(res, 400, { error: 'At least one allowed Feishu user ID is required' })
return
}

feishuBridge.configureApp(config.appId, config.appSecret, config.domain)
feishuBridge.configureAllowedUserIds(config.allowedUserIds)
feishuBridge.start()
const existingConfig = await readFeishuBridgeConfig()
await writeFeishuBridgeConfig({
appId: config.appId,
appSecret: config.appSecret,
domain: config.domain,
chatIds: existingConfig.chatIds,
allowedUserIds: config.allowedUserIds,
})
setJson(res, 200, { ok: true })
return
}

if (req.method === 'GET' && url.pathname === '/codex-api/feishu/config') {
const config = await readFeishuBridgeConfig()
setJson(res, 200, {
data: {
appId: config.appId,
appSecret: config.appSecret,
domain: config.domain,
allowedUserIds: config.allowedUserIds,
},
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Feishu config api leaks secrets 🐞 Bug ⛨ Security

The new Feishu routes are unauthenticated and include a GET endpoint that returns appSecret, so
any client that can reach the server can exfiltrate Feishu credentials and/or reconfigure the
allowlist. This is especially risky because the app is explicitly LAN-accessible and these routes
lack the localhost-only guard used elsewhere in the same middleware.
Agent Prompt
## Issue description
`/codex-api/feishu/config` returns `appSecret` and `/codex-api/feishu/configure-bot` allows arbitrary reconfiguration, but neither endpoint is protected by a localhost check or any authentication. Since the server is intended to be reachable on the LAN, this enables credential theft and bridge takeover.

## Issue Context
- The middleware already enforces a localhost-only check for some sensitive endpoints (e.g., zen proxy), but not for Feishu.
- Feishu credentials are also persisted to disk in plaintext JSON.

## Fix Focus Areas
- src/server/codexAppServerBridge.ts[9683-9733]
- src/server/codexAppServerBridge.ts[7613-7617]
- src/server/codexAppServerBridge.ts[6184-6193]
- src/App.vue[2474-2526]
- src/api/codexGateway.ts[3424-3475]

## Implementation notes
- Add an access-control guard for Feishu config routes at minimum (e.g., `isLoopbackRemoteAddress(req.socket.remoteAddress)`), consistent with other sensitive endpoints.
- Stop returning secrets from GET `/codex-api/feishu/config` (return a boolean like `hasAppSecret` and/or a masked placeholder).
- Update POST `/codex-api/feishu/configure-bot` to allow `appSecret` to be omitted/empty to mean “keep existing secret” (so the UI doesn’t need to re-fetch or re-send the secret).
- When writing `feishu-bridge.json`, consider restricting file permissions (e.g., mode `0o600`) since it contains an app secret.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/server/feishuThreadBridge.ts (2)

341-361: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Misleading name: sendTextMessage sends an interactive card.

sendTextMessage builds an interactive card with a header, not a plain text message. It's used for the "⏳ Thinking..." placeholder and its returned message_id is later patched. The behavior is fine, but the name obscures intent; consider renaming (e.g. sendCardTextMessage) for clarity.

🤖 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/server/feishuThreadBridge.ts` around lines 341 - 361, The helper named
sendTextMessage is misleading because it sends an interactive card, not plain
text. Rename the method to reflect its actual behavior, such as
sendCardTextMessage or similar, and update its call sites in FeishuThreadBridge
so the purpose is clear while keeping the existing message creation and returned
message_id behavior unchanged.

683-733: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unbounded thread accumulation in the picker path.

listRecentThreadGroups loops thread/list (limit 100) until nextCursor is exhausted, pushing every thread into allRows before grouping. On workspaces with many threads this loads the entire thread universe into memory on each /threads or /start command, even though only MAX_THREADS_PER_GROUP (6) per group are rendered. Consider capping total pages/threads fetched (the picker only shows a small slice anyway).

As per coding guidelines: "For startup, thread loading, ... explicitly check ... unbounded fanout, large payloads."

🤖 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/server/feishuThreadBridge.ts` around lines 683 - 733, The picker path is
fetching every thread page in listRecentThreadGroups, accumulating them all in
allRows before grouping, which creates unbounded memory and RPC fanout on large
workspaces. Update listRecentThreadGroups to stop after a bounded number of
pages or threads, keeping only enough recent results for the picker and
respecting the existing MAX_THREADS_PER_GROUP rendering limit. Make the cap
explicit in the pagination loop that calls this.appServer.rpc('thread/list') and
in the thread/group assembly logic so /threads and /start cannot load the entire
thread universe.

Source: Coding guidelines

🤖 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 `@README.md`:
- Around line 184-188: The README setup steps are missing the interactive card
subscription needed by the handler in FeishuThreadBridge. Update the required
Feishu Event Subscription list to include card.action.trigger alongside
im.message.receive_v1, so users configuring the app know to enable both events
for the thread-picker/card flow to work.
- Around line 189-196: The startup environment variables section is missing the
FEISHU_DOMAIN setting even though feishuThreadBridge uses it to switch between
Feishu and Lark domains. Update the README env var block to document
FEISHU_DOMAIN alongside FEISHU_APP_ID, FEISHU_APP_SECRET, and
FEISHU_ALLOWED_USER_IDS, and note its purpose so users configuring the app via
CLI/bootstrap can discover the international-domain path.

In `@src/App.vue`:
- Line 1752: `feishuConfigError` is defined in `App.vue` but is missing from
`visibleFeedbackErrors`, so Feishu bridge failures are not picked up by the
failure watcher or `hasVisibleFeedbackError`. Update the `visibleFeedbackErrors`
array in the `App` component to include `feishuConfigError`, matching how
`telegramConfigError` is registered, so Feishu errors are auto-recorded
consistently.

In `@src/server/feishuThreadBridge.ts`:
- Around line 218-257: The start() flow in FeishuThreadBridge marks the bridge
active before WSClient startup, so a thrown error leaves the instance stuck in
an active state. Move the this.active = true assignment to after
this.wsClient.start({ eventDispatcher }) completes successfully, or clear
this.active inside the failure path; use start(), wsClient.start(), and active
as the key points to update.

---

Nitpick comments:
In `@src/server/feishuThreadBridge.ts`:
- Around line 341-361: The helper named sendTextMessage is misleading because it
sends an interactive card, not plain text. Rename the method to reflect its
actual behavior, such as sendCardTextMessage or similar, and update its call
sites in FeishuThreadBridge so the purpose is clear while keeping the existing
message creation and returned message_id behavior unchanged.
- Around line 683-733: The picker path is fetching every thread page in
listRecentThreadGroups, accumulating them all in allRows before grouping, which
creates unbounded memory and RPC fanout on large workspaces. Update
listRecentThreadGroups to stop after a bounded number of pages or threads,
keeping only enough recent results for the picker and respecting the existing
MAX_THREADS_PER_GROUP rendering limit. Make the cap explicit in the pagination
loop that calls this.appServer.rpc('thread/list') and in the thread/group
assembly logic so /threads and /start cannot load the entire thread universe.
🪄 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 Plus

Run ID: 5a8ea43e-25db-48c1-975c-1d1f49cda202

📥 Commits

Reviewing files that changed from the base of the PR and between fac2291 and 55c814d.

📒 Files selected for processing (6)
  • README.md
  • package.json
  • src/App.vue
  • src/api/codexGateway.ts
  • src/server/codexAppServerBridge.ts
  • src/server/feishuThreadBridge.ts

Comment thread README.md
Comment on lines +184 to +188
1. Create a self-built app at [Feishu Open Platform](https://open.feishu.cn)
2. Enable bot capability
3. Under Event Subscription, select **Long Connection** mode
4. Subscribe to `im.message.receive_v1` event
5. Add permission `im:message:send_as_bot`

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add card.action.trigger to the required Feishu subscriptions.

The bridge also registers a handler for card.action.trigger in src/server/feishuThreadBridge.ts; documenting only im.message.receive_v1 leaves the interactive card/thread-picker path broken for users who follow the README.

Suggested doc fix
-4. Subscribe to `im.message.receive_v1` event
+4. Subscribe to `im.message.receive_v1` and `card.action.trigger`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
1. Create a self-built app at [Feishu Open Platform](https://open.feishu.cn)
2. Enable bot capability
3. Under Event Subscription, select **Long Connection** mode
4. Subscribe to `im.message.receive_v1` event
5. Add permission `im:message:send_as_bot`
1. Create a self-built app at [Feishu Open Platform](https://open.feishu.cn)
2. Enable bot capability
3. Under Event Subscription, select **Long Connection** mode
4. Subscribe to `im.message.receive_v1` and `card.action.trigger`
5. Add permission `im:message:send_as_bot`
🤖 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 `@README.md` around lines 184 - 188, The README setup steps are missing the
interactive card subscription needed by the handler in FeishuThreadBridge.
Update the required Feishu Event Subscription list to include
card.action.trigger alongside im.message.receive_v1, so users configuring the
app know to enable both events for the thread-picker/card flow to work.

Comment thread README.md
Comment on lines +189 to +196
6. Configure in the codexapp sidebar settings (App ID, App Secret, allowed user open_ids), or use environment variables:

```bash
export FEISHU_APP_ID="<your-feishu-app-id>"
export FEISHU_APP_SECRET="<your-feishu-app-secret>"
export FEISHU_ALLOWED_USER_IDS="<your-open-id>,<optional-second-id>"
export FEISHU_DEFAULT_CWD="$PWD" # optional, defaults to current working directory
npx codexapp

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document FEISHU_DOMAIN in the startup env vars block.

src/server/feishuThreadBridge.ts reads process.env.FEISHU_DOMAIN to choose the Feishu vs. Lark domain, but this README section doesn’t mention it. That hides the new international-domain startup path from CLI/bootstrap users.

Suggested doc fix
 export FEISHU_APP_ID="<your-feishu-app-id>"
 export FEISHU_APP_SECRET="<your-feishu-app-secret>"
+export FEISHU_DOMAIN="feishu|lark"
 export FEISHU_ALLOWED_USER_IDS="<your-open-id>,<optional-second-id>"
 export FEISHU_DEFAULT_CWD="$PWD" # optional, defaults to current working directory
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
6. Configure in the codexapp sidebar settings (App ID, App Secret, allowed user open_ids), or use environment variables:
```bash
export FEISHU_APP_ID="<your-feishu-app-id>"
export FEISHU_APP_SECRET="<your-feishu-app-secret>"
export FEISHU_ALLOWED_USER_IDS="<your-open-id>,<optional-second-id>"
export FEISHU_DEFAULT_CWD="$PWD" # optional, defaults to current working directory
npx codexapp
6. Configure in the codexapp sidebar settings (App ID, App Secret, allowed user open_ids), or use environment variables:
🤖 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 `@README.md` around lines 189 - 196, The startup environment variables section
is missing the FEISHU_DOMAIN setting even though feishuThreadBridge uses it to
switch between Feishu and Lark domains. Update the README env var block to
document FEISHU_DOMAIN alongside FEISHU_APP_ID, FEISHU_APP_SECRET, and
FEISHU_ALLOWED_USER_IDS, and note its purpose so users configuring the app via
CLI/bootstrap can discover the international-domain path.

Comment thread src/App.vue
const feishuAppIdDraft = ref('')
const feishuAppSecretDraft = ref('')
const feishuAllowedUserIdsDraft = ref('')
const feishuConfigError = ref('')

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Register feishuConfigError in visibleFeedbackErrors.

Unlike telegramConfigError (added at Line 1787), feishuConfigError is not included in the visibleFeedbackErrors array (Lines 1780-1791). As a result, Feishu bridge errors are not auto-recorded by the failure watcher (Line 2260) and don't contribute to hasVisibleFeedbackError, so they're only captured if the user clicks the inline "Send feedback" link.

🔧 Proposed fix
   telegramConfigError,
+  feishuConfigError,
   createFolderError,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const feishuConfigError = ref('')
telegramConfigError,
feishuConfigError,
createFolderError,
🤖 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/App.vue` at line 1752, `feishuConfigError` is defined in `App.vue` but is
missing from `visibleFeedbackErrors`, so Feishu bridge failures are not picked
up by the failure watcher or `hasVisibleFeedbackError`. Update the
`visibleFeedbackErrors` array in the `App` component to include
`feishuConfigError`, matching how `telegramConfigError` is registered, so Feishu
errors are auto-recorded consistently.

Comment on lines +218 to +257
start(): void {
if (!this.appId || !this.appSecret || this.active) return
this.active = true
const domain = getLarkSdkDomain(this.domain)

this.client = new lark.Client({
appId: this.appId,
appSecret: this.appSecret,
appType: lark.AppType.SelfBuild,
domain,
})

const eventDispatcher = new lark.EventDispatcher({}).register({
'im.message.receive_v1': async (data: unknown) => {
await this.handleMessageEvent(data).catch((error) => {
this.lastError = getErrorMessage(error, 'Failed to handle message event')
})
},
'card.action.trigger': async (data: unknown) => {
await this.handleCardAction(data).catch((error) => {
this.lastError = getErrorMessage(error, 'Failed to handle card action')
})
},
})

this.wsClient = new lark.WSClient({
appId: this.appId,
appSecret: this.appSecret,
domain,
loggerLevel: lark.LoggerLevel.warn,
})

this.wsClient.start({ eventDispatcher })

this.unsubscribeNotifications = this.appServer.onNotification((notification) => {
void this.handleNotification(notification).catch(() => {})
})

void this.notifyOnlineForKnownChats().catch(() => {})
}

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

@larksuiteoapi/node-sdk WSClient close or stop method 1.70

💡 Result:

In the @larksuiteoapi/node-sdk, the WSClient does not expose a standard.stop or.close method for direct termination of the WebSocket connection as of recent versions [1]. For managing or closing a WebSocket connection, the recommended approach involves manually handling the underlying WebSocket instance or using the higher-level Channel abstraction [1][2]. 1. Manual Management via WSClient: To shut down a WSClient connection, you must prevent auto-reconnection and then close the underlying WebSocket instance directly [1]: // 1. Disable auto-reconnect to prevent the client from restarting the connection wsClient.wsConfig.updateWs({autoReconnect: false}); // 2. Retrieve the underlying WebSocket instance and close it const instance = wsClient.wsConfig.getWSInstance; instance.close; instance.terminate; 2. Using Channel Abstraction: If you are using the high-level Channel module, it provides a built-in.disconnect method, which is the preferred way to handle connection lifecycle management [2]: await channel.disconnect; Development history indicates that internal improvements (such as commit 965cdcb) have focused on ensuring proper resource cleanup, including timers, during connection close events [3]. Always check the specific version of the SDK you are using, as implementation details for internal cleanup continue to evolve [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file, then inspect the relevant sections around start/stop/status.
ast-grep outline src/server/feishuThreadBridge.ts --view expanded || true

printf '\n--- start/stop/status sections ---\n'
sed -n '200,310p' src/server/feishuThreadBridge.ts | cat -n

printf '\n--- getStatus / active references ---\n'
rg -n "active|start\\(|stop\\(|getStatus|wsClient\\.close|unsubscribeNotifications|lastError" src/server/feishuThreadBridge.ts

Repository: friuns2/codex-mobile

Length of output: 11628


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect nearby methods to understand whether failures can leave the bridge active.
sed -n '200,310p' src/server/feishuThreadBridge.ts | cat -n

printf '\n--- surrounding status/teardown references ---\n'
sed -n '1,220p' src/server/feishuThreadBridge.ts | cat -n | sed -n '1,220p'

Repository: friuns2/codex-mobile

Length of output: 14242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether the SDK docs/types are present in the repo for direct evidence of WSClient lifecycle APIs.
rg -n "class WSClient|interface WSClient|close\\(|stop\\(" . -g '!node_modules' -g '!dist' -g '!build' -g '!coverage'

Repository: friuns2/codex-mobile

Length of output: 2826


🌐 Web query:

@larksuiteoapi/node-sdk 1.70.0 WSClient close start lifecycle docs

💡 Result:

In the @larksuiteoapi/node-sdk, the WSClient lifecycle is managed primarily through the.start and.close methods. Lifecycle Overview: 1. Initialization: The WSClient is instantiated with configuration parameters, including credentials and optional settings like autoReconnect [1][2]. 2. Start: The.start method is called to initiate the WebSocket connection and register an event dispatcher to handle incoming messages [1][2]. 3. Operation: Once connected, the client maintains a full-duplex channel. If autoReconnect is enabled, the SDK attempts to automatically re-establish the connection if it drops [1]. 4. Close: The.close method is used to gracefully terminate the WebSocket connection [1][3]. Closing the Connection: While older versions of the SDK required manual intervention (such as accessing the underlying WebSocket instance via internal methods) to close the connection [3], modern versions of the @larksuiteoapi/node-sdk support the.close method directly on the WSClient instance [1]. To perform a force close, you can typically pass an options object to the.close method, such as wsClient.close({ force: true }) [1]. Note on Reliability: - If you are using the Channel module (a higher-level abstraction over WSClient), you should manage the lifecycle using channel.connect and channel.disconnect instead [4]. - For robust production deployments, ensure that wsConfig.pingTimeout is configured (e.g., set to 15 seconds) to handle scenarios where the underlying socket connection may become stale or dead, such as after system sleep/wake cycles [5]. - Be aware of known issues in older versions regarding timer leaks during reconnection loops; ensure you are using a stable, up-to-date version of the SDK [6].

Citations:


🌐 Web query:

site:github.com/larksuite/node-sdk WSClient close force start 1.70.0

💡 Result:

In the context of the larksuite/node-sdk, the request to add a way to properly close a WebSocket connection was addressed in response to Issue #173 [1]. The feature, which allows for the explicit closing of a WSClient connection, was implemented around January 2026 [2]. Regarding the specific usage of closing a WSClient, official recommendations (and community workarounds prior to the feature release) emphasize ensuring that auto-reconnect functionality is disabled before calling the underlying WebSocket's close or terminate methods [1]. If you are encountering issues related to reconnect loops or resource cleanup, please note that recent updates, such as those in mid-2026 (e.g., commit 965cdcb), have focused on fixing timer leaks and ensuring proper cleanup of internal caches and timers when the client is closed [3]. If you are experiencing memory growth or unbound reconnect loops, ensure you are using the latest version of the SDK, as these issues have been documented and patched in subsequent releases [4][3]. For high-level implementations, the SDK's Channel module provides a built-in disconnect method which is the recommended way to handle graceful shutdowns [5].

Citations:


Move active = true after wsClient.start() succeeds
If wsClient.start() throws, the bridge stays marked active and later start() calls will no-op. Set the flag only after startup completes, or clear it in the failure path.

🤖 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/server/feishuThreadBridge.ts` around lines 218 - 257, The start() flow in
FeishuThreadBridge marks the bridge active before WSClient startup, so a thrown
error leaves the instance stuck in an active state. Move the this.active = true
assignment to after this.wsClient.start({ eventDispatcher }) completes
successfully, or clear this.active inside the failure path; use start(),
wsClient.start(), and active as the key points to update.

Source: Coding guidelines

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