feat(lark): surface installation region in settings UI

Read the per-installation region off the listings response: build the
"Manage in Lark" dev-console host from it (open.feishu.cn vs
open.larksuite.com instead of a hardcoded mainland host) and render a
Feishu / Lark badge on each connected bot. The field is optional and
defaults to Feishu when an older server omits it (API-compat). Adds the
region_feishu / region_lark labels to all four locales.

MUL-3083

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
J
2026-06-05 15:26:54 +08:00
parent c1b236f9cf
commit faa018fd70
7 changed files with 57 additions and 7 deletions

View File

@@ -13,6 +13,12 @@ export interface LarkInstallation {
bot_open_id: string;
installer_user_id: string;
status: "active" | "revoked" | string;
/** Which Lark cloud the bot lives on: "feishu" (mainland) or "lark"
* (international). Auto-detected at install time. Optional so an older
* desktop build parsing a newer server — or a newer build hitting a
* server that predates the field — defaults to Feishu in the UI
* (see CLAUDE.md → API Response Compatibility). */
region?: "feishu" | "lark" | string;
installed_at: string;
created_at: string;
updated_at: string;

View File

@@ -233,6 +233,8 @@
"empty_description_cta": "Bind to Lark",
"empty_description_suffix": "to install a Bot for it.",
"revoked_badge": "revoked",
"region_feishu": "Feishu",
"region_lark": "Lark",
"installed_at_label": "Installed {{when}}",
"disconnect": "Disconnect",
"disconnecting": "Disconnecting…",

View File

@@ -233,6 +233,8 @@
"empty_description_cta": "Lark に接続",
"empty_description_suffix": "をクリックすると、対応するボットを設置できます。",
"revoked_badge": "取り消し済み",
"region_feishu": "Feishu",
"region_lark": "Lark",
"installed_at_label": "{{when}} に設置",
"disconnect": "切断",
"disconnecting": "切断しています…",

View File

@@ -310,6 +310,8 @@
"empty_description_cta": "Lark에 연결",
"empty_description_suffix": "을(를) 클릭해 봇을 설치하세요.",
"revoked_badge": "취소됨",
"region_feishu": "Feishu",
"region_lark": "Lark",
"installed_at_label": "{{when}} 설치됨",
"disconnect": "연결 해제",
"disconnecting": "연결을 해제하는 중…",

View File

@@ -233,6 +233,8 @@
"empty_description_cta": "绑定到飞书",
"empty_description_suffix": "即可为它安装 Bot。",
"revoked_badge": "已撤销",
"region_feishu": "飞书",
"region_lark": "Lark",
"installed_at_label": "安装于 {{when}}",
"disconnect": "断开连接",
"disconnecting": "正在断开…",

View File

@@ -251,6 +251,32 @@ describe("LarkAgentBindButton (CTA gate)", () => {
expect(link.rel).toContain("noopener");
});
it("points the Manage link at open.larksuite.com for a Lark-international (region=lark) installation", () => {
// Dual-region: a bot installed against the Lark international cloud
// must manage at open.larksuite.com, not the Feishu default. The
// region rides on the listings response, auto-detected at install.
installationsRef.current.installations = [
{
id: "inst-lark",
workspace_id: "ws-1",
agent_id: "agent-1",
app_id: "cli_lark_app",
bot_open_id: "ou_lark_bot",
installer_user_id: "user-1",
status: "active",
region: "lark",
installed_at: "2026-06-03T00:00:00Z",
created_at: "2026-06-03T00:00:00Z",
updated_at: "2026-06-03T00:00:00Z",
},
];
render(<LarkAgentBindButton agentId="agent-1" agentName="Bot" />, {
wrapper: I18nWrapper,
});
const link = screen.getByRole("link", { name: /Manage in Lark/i }) as HTMLAnchorElement;
expect(link.href).toBe("https://open.larksuite.com/app/cli_lark_app");
});
it("still shows the bind CTA when an installation exists for a DIFFERENT agent (per-agent scoping)", () => {
installationsRef.current.installations = [
{

View File

@@ -232,6 +232,11 @@ function InstallationRow({
<div className="space-y-1">
<p className="text-sm font-medium">
{agentName}
<span className="ml-2 rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">
{installation.region === "lark"
? t(($) => $.lark.region_lark)
: t(($) => $.lark.region_feishu)}
</span>
{!isActive && (
<span className="ml-2 rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">
{t(($) => $.lark.revoked_badge)}
@@ -359,12 +364,17 @@ export function LarkAgentBindButton({
// a new tab so the user can manage scopes / display name / additional
// permissions without re-scanning the QR.
//
// The dev console URL host follows the same default as the backend's
// LARK_BASE_URL (open.feishu.cn for mainland Lark). Operators on the
// Lark international tenant currently see the wrong host; future-
// proofing requires the backend to surface a per-installation
// `dev_console_url` on the listings response. Tracked separately.
const LARK_DEV_CONSOLE_HOST = "https://open.feishu.cn";
// The dev-console host depends on which Lark cloud the bot lives on:
// Feishu (mainland) bots are managed at open.feishu.cn, Lark
// (international) bots at open.larksuite.com. The region is auto-detected
// at install time and surfaced per installation on the listings
// response; an older server that omits `region` defaults to Feishu
// (API-compat — see CLAUDE.md).
function larkDevConsoleHost(region?: string): string {
return region === "lark"
? "https://open.larksuite.com"
: "https://open.feishu.cn";
}
function LarkAgentBotConnectedBadge({
installation,
@@ -374,7 +384,7 @@ function LarkAgentBotConnectedBadge({
className?: string;
}) {
const { t } = useT("settings");
const manageHref = `${LARK_DEV_CONSOLE_HOST}/app/${encodeURIComponent(installation.app_id)}`;
const manageHref = `${larkDevConsoleHost(installation.region)}/app/${encodeURIComponent(installation.app_id)}`;
return (
<div className={className} data-testid="lark-agent-bot-connected">
<span className="inline-flex items-center gap-2 text-xs text-muted-foreground">