Compare commits

...

1 Commits

Author SHA1 Message Date
J
b436af2aa8 fix(lark): hide only the Lark (international) connect entry; keep Feishu
Mainland Feishu binding works; only the newly-added Lark (international,
open.larksuite.com) install path is unreliable — some Lark installs
complete on Lark's side but never persist a lark_installation row (no WS,
no inbound, no task). Hide just the "Bind to Lark" CTA behind a single
LARK_INTL_CONNECT_ENABLED flag and leave the "Bind to Feishu" entry, the
settings panel, and all existing-installation management untouched.

Flip LARK_INTL_CONNECT_ENABLED back to true to restore the Lark CTA;
nothing else changes. Temporary measure while the Lark install-landing
bug is investigated.

- LarkAgentBindButton: the Lark button is gated by the flag; the Feishu
  button and the Connected badge / Manage / Disconnect are unchanged.
- Tests: the CTA tests assert Feishu shown + Lark hidden; the Feishu
  click-to-begin (region=feishu) test stays; the Lark click test was
  removed (no button) and noted for restore; the dialog polling-error
  tests open via the Feishu CTA.

MUL-3083

Co-authored-by: multica-agent <github@multica.ai>
2026-06-05 19:00:24 +08:00
2 changed files with 52 additions and 54 deletions

View File

@@ -187,28 +187,24 @@ function resetFixtures() {
describe("LarkAgentBindButton (CTA gate)", () => {
beforeEach(resetFixtures);
it("renders both Feishu and Lark bind CTAs when the viewer is a workspace owner and install is supported", () => {
// The CTA was split into two explicit entry points — one per cloud
// — so the begin POST hits the right accounts host up front (no
// tenant-brand mid-poll auto-switch from a Feishu-first start) and
// the QR / dialog copy reflects the cloud the user picked. Both
// buttons must mount side by side for owners/admins; either one
// alone would re-introduce the "Lark user has to scan a Feishu QR"
// confusion this split is meant to remove (MUL-3083 follow-up).
it("shows the Feishu bind CTA but hides the Lark CTA for an owner (MUL-3083)", () => {
// Mainland Feishu binding stays available; the Lark (international)
// entry is temporarily hidden via LARK_INTL_CONNECT_ENABLED while its
// install→inbound pipeline is stabilized (MUL-3083).
render(<LarkAgentBindButton agentId="agent-1" agentName="Bot" />, {
wrapper: I18nWrapper,
});
expect(screen.getByRole("button", { name: /Bind to Feishu/i })).toBeTruthy();
expect(screen.getByRole("button", { name: /Bind to Lark/i })).toBeTruthy();
expect(screen.queryByRole("button", { name: /Bind to Lark/i })).toBeNull();
});
it("renders both bind CTAs when the viewer is a workspace admin", () => {
it("shows the Feishu bind CTA but hides the Lark CTA for an admin (MUL-3083)", () => {
membersRef.current = [{ user_id: "user-1", role: "admin" }];
render(<LarkAgentBindButton agentId="agent-1" agentName="Bot" />, {
wrapper: I18nWrapper,
});
expect(screen.getByRole("button", { name: /Bind to Feishu/i })).toBeTruthy();
expect(screen.getByRole("button", { name: /Bind to Lark/i })).toBeTruthy();
expect(screen.queryByRole("button", { name: /Bind to Lark/i })).toBeNull();
});
it("hides both bind CTAs for a non-admin agent owner (matches backend admin gate)", () => {
@@ -258,28 +254,12 @@ describe("LarkAgentBindButton (CTA gate)", () => {
);
});
it("clicking Bind to Lark begins an install with region='lark'", async () => {
const user = userEvent.setup();
mockBeginInstall.mockResolvedValue({
session_id: "sess-lark",
qr_code_url: "https://accounts.larksuite.com/oauth/v1/device?u=lark",
expires_in_seconds: 300,
poll_interval_seconds: 2,
});
mockGetStatus.mockResolvedValue({ status: "pending" });
render(<LarkAgentBindButton agentId="agent-1" agentName="Bot" />, {
wrapper: I18nWrapper,
});
await user.click(screen.getByRole("button", { name: /Bind to Lark/i }));
await waitFor(() => {
expect(mockBeginInstall).toHaveBeenCalledTimes(1);
});
expect(mockBeginInstall).toHaveBeenCalledWith(
"workspace-1",
"agent-1",
"lark",
);
});
// NOTE (MUL-3083): the "clicking Bind to Lark begins an install with
// region='lark'" test was removed alongside the temporarily-hidden Lark
// (international) CTA — there is no Lark button to click while
// LARK_INTL_CONNECT_ENABLED is false. The Feishu region routing is still
// pinned by the "clicking Bind to Feishu …" test above; restore the Lark
// case when the entry is re-enabled.
it("swaps the bind CTAs for a 'Connected + Manage in Lark' badge when this agent already has an active installation", () => {
// Anti-zombie guard: re-scanning the same agent upserts the row
@@ -352,7 +332,7 @@ describe("LarkAgentBindButton (CTA gate)", () => {
expect(link.href).toBe("https://open.larksuite.com/app/cli_lark_app");
});
it("still shows both bind CTAs when an installation exists for a DIFFERENT agent (per-agent scoping)", () => {
it("shows the Feishu CTA (Lark hidden) for an agent without its own installation, per-agent scoping (MUL-3083)", () => {
installationsRef.current.installations = [
{
id: "inst-other",
@@ -371,7 +351,7 @@ describe("LarkAgentBindButton (CTA gate)", () => {
wrapper: I18nWrapper,
});
expect(screen.getByRole("button", { name: /Bind to Feishu/i })).toBeTruthy();
expect(screen.getByRole("button", { name: /Bind to Lark/i })).toBeTruthy();
expect(screen.queryByRole("button", { name: /Bind to Lark/i })).toBeNull();
});
it("keeps the Connected + Manage badge for an already-installed agent even when new installs are unavailable (install_supported=false)", () => {
@@ -413,7 +393,7 @@ describe("LarkAgentBindButton (CTA gate)", () => {
).toBeTruthy();
});
it("still shows both bind CTAs when this agent's only installation is revoked (treat as not-installed for re-bind)", () => {
it("shows the Feishu CTA (Lark hidden) when this agent's only installation is revoked (MUL-3083)", () => {
installationsRef.current.installations = [
{
id: "inst-revoked",
@@ -432,7 +412,7 @@ describe("LarkAgentBindButton (CTA gate)", () => {
wrapper: I18nWrapper,
});
expect(screen.getByRole("button", { name: /Bind to Feishu/i })).toBeTruthy();
expect(screen.getByRole("button", { name: /Bind to Lark/i })).toBeTruthy();
expect(screen.queryByRole("button", { name: /Bind to Lark/i })).toBeNull();
});
});
@@ -599,7 +579,9 @@ describe("LarkInstallDialog (polling terminal errors)", () => {
render(<LarkAgentBindButton agentId="agent-1" agentName="Bot" />, {
wrapper: I18nWrapper,
});
await user.click(screen.getByRole("button", { name: /Bind to Lark/i }));
// The Lark CTA is hidden (MUL-3083); open the dialog via the Feishu CTA
// — the polling-error behavior under test is region-agnostic.
await user.click(screen.getByRole("button", { name: /Bind to Feishu/i }));
// Let the begin-session promise resolve and the QR render.
await waitFor(() => {
expect(screen.getByTestId("qr-code")).toBeTruthy();
@@ -673,7 +655,9 @@ describe("LarkInstallDialog (polling terminal errors)", () => {
render(<LarkAgentBindButton agentId="agent-1" agentName="Bot" />, {
wrapper: StrictModeWrapper,
});
await user.click(screen.getByRole("button", { name: /Bind to Lark/i }));
// The Lark CTA is hidden (MUL-3083); the StrictMode regression is about
// the dialog mount cycle, so open it via the Feishu CTA.
await user.click(screen.getByRole("button", { name: /Bind to Feishu/i }));
// The QR must appear even though the dialog mounted, unmounted, and
// mounted again under StrictMode. The previous bug left the body

View File

@@ -43,6 +43,15 @@ import type { LarkInstallation, LarkInstallStatusResponse } from "@multica/core/
import { ActorAvatar } from "../../common/actor-avatar";
import { useT } from "../../i18n";
// MUL-3083: the Lark (international, open.larksuite.com) "connect a Bot"
// entry is temporarily hidden while its install → inbound pipeline is
// stabilized — some Lark installs complete on Lark's side but never land a
// `lark_installation` row, so the Bot silently can't receive messages.
// Mainland Feishu is unaffected and keeps its bind entry. Existing
// installations (either cloud) stay fully manageable. Flip this back to
// `true` to restore the "Bind to Lark" CTA; nothing else needs to change.
const LARK_INTL_CONNECT_ENABLED: boolean = false;
// LarkTab is the workspace settings panel for Lark Bot installations.
// Listing is member-visible; the disconnect action is admin-only (the
// backend enforces it; the UI hides the button for non-admins to match).
@@ -389,21 +398,26 @@ export function LarkAgentBindButton({
<ExternalLink className="h-3 w-3" />
{t(($) => $.lark.bind_button_feishu)}
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setDialogRegion("lark")}
disabled={!agentId}
title={
agentName
? t(($) => $.lark.bind_button_lark_title, { agent: agentName })
: undefined
}
data-testid="lark-agent-bind-lark"
>
<ExternalLink className="h-3 w-3" />
{t(($) => $.lark.bind_button_lark)}
</Button>
{/* MUL-3083: Lark (international) bind entry is temporarily hidden —
see LARK_INTL_CONNECT_ENABLED. Mainland Feishu (above) is
unaffected. */}
{LARK_INTL_CONNECT_ENABLED && (
<Button
variant="outline"
size="sm"
onClick={() => setDialogRegion("lark")}
disabled={!agentId}
title={
agentName
? t(($) => $.lark.bind_button_lark_title, { agent: agentName })
: undefined
}
data-testid="lark-agent-bind-lark"
>
<ExternalLink className="h-3 w-3" />
{t(($) => $.lark.bind_button_lark)}
</Button>
)}
</div>
{dialogRegion && (
<LarkInstallDialog