Skip to content

Commit 05b84e7

Browse files
committed
fix(feishu): preserve explicit target routing hints (openclaw#31594) (thanks @liuxiaopai-ai)
1 parent 07b419a commit 05b84e7

File tree

4 files changed

+85
-2
lines changed

4 files changed

+85
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Docs: https://docs.openclaw.ai
8585
- Cron/Delivery: disable the agent messaging tool when `delivery.mode` is `"none"` so cron output is not sent to Telegram or other channels. (#21808) Thanks @lailoo.
8686
- CLI/Cron: clarify `cron list` output by renaming `Agent` to `Agent ID` and adding a `Model` column for isolated agent-turn jobs. (#26259) Thanks @openperf.
8787
- Feishu/Reply media attachments: send Feishu reply `mediaUrl`/`mediaUrls` payloads as attachments alongside text/streamed replies in the reply dispatcher, including legacy fallback when `mediaUrls` is empty. (#28959) Thanks @icesword0760.
88+
- Feishu/Send target prefixes: normalize explicit `group:`/`dm:` send targets and preserve explicit receive-id routing hints when resolving outbound Feishu targets. (#31594) Thanks @liuxiaopai-ai.
8889
- Slack/User-token resolution: normalize Slack account user-token sourcing through resolved account metadata (`SLACK_USER_TOKEN` env + config) so monitor reads, Slack actions, directory lookups, onboarding allow-from resolution, and capabilities probing consistently use the effective user token. (#28103) Thanks @Glucksberg.
8990
- Slack/Channel message subscriptions: register explicit `message.channels` and `message.groups` monitor handlers (alongside generic `message`) so channel/group event subscriptions are consumed even when Slack dispatches typed message event names. Fixes #31674.
9091
- Feishu/Outbound session routing: stop assuming bare `oc_` identifiers are always group chats, honor explicit `dm:`/`group:` prefixes for `oc_` chat IDs, and default ambiguous bare `oc_` targets to direct routing to avoid DM session misclassification. (#10407) Thanks @Bermudarat.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
2+
import { beforeEach, describe, expect, it, vi } from "vitest";
3+
import { resolveFeishuSendTarget } from "./send-target.js";
4+
5+
const resolveFeishuAccountMock = vi.hoisted(() => vi.fn());
6+
const createFeishuClientMock = vi.hoisted(() => vi.fn());
7+
8+
vi.mock("./accounts.js", () => ({
9+
resolveFeishuAccount: resolveFeishuAccountMock,
10+
}));
11+
12+
vi.mock("./client.js", () => ({
13+
createFeishuClient: createFeishuClientMock,
14+
}));
15+
16+
describe("resolveFeishuSendTarget", () => {
17+
const cfg = {} as ClawdbotConfig;
18+
const client = { id: "client" };
19+
20+
beforeEach(() => {
21+
resolveFeishuAccountMock.mockReset().mockReturnValue({
22+
accountId: "default",
23+
enabled: true,
24+
configured: true,
25+
});
26+
createFeishuClientMock.mockReset().mockReturnValue(client);
27+
});
28+
29+
it("keeps explicit group targets as chat_id even when ID shape is ambiguous", () => {
30+
const result = resolveFeishuSendTarget({
31+
cfg,
32+
to: "feishu:group:group_room_alpha",
33+
});
34+
35+
expect(result.receiveId).toBe("group_room_alpha");
36+
expect(result.receiveIdType).toBe("chat_id");
37+
expect(result.client).toBe(client);
38+
});
39+
40+
it("maps dm-prefixed open IDs to open_id", () => {
41+
const result = resolveFeishuSendTarget({
42+
cfg,
43+
to: "lark:dm:ou_123",
44+
});
45+
46+
expect(result.receiveId).toBe("ou_123");
47+
expect(result.receiveIdType).toBe("open_id");
48+
});
49+
50+
it("maps dm-prefixed non-open IDs to user_id", () => {
51+
const result = resolveFeishuSendTarget({
52+
cfg,
53+
to: " feishu:dm:user_123 ",
54+
});
55+
56+
expect(result.receiveId).toBe("user_123");
57+
expect(result.receiveIdType).toBe("user_id");
58+
});
59+
60+
it("throws when target account is not configured", () => {
61+
resolveFeishuAccountMock.mockReturnValue({
62+
accountId: "default",
63+
enabled: true,
64+
configured: false,
65+
});
66+
67+
expect(() =>
68+
resolveFeishuSendTarget({
69+
cfg,
70+
to: "feishu:group:oc_123",
71+
}),
72+
).toThrow('Feishu account "default" not configured');
73+
});
74+
});

extensions/feishu/src/send-target.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@ export function resolveFeishuSendTarget(params: {
88
to: string;
99
accountId?: string;
1010
}) {
11+
const target = params.to.trim();
1112
const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
1213
if (!account.configured) {
1314
throw new Error(`Feishu account "${account.accountId}" not configured`);
1415
}
1516
const client = createFeishuClient(account);
16-
const receiveId = normalizeFeishuTarget(params.to);
17+
const receiveId = normalizeFeishuTarget(target);
1718
if (!receiveId) {
1819
throw new Error(`Invalid Feishu target: ${params.to}`);
1920
}
21+
// Preserve explicit routing prefixes (chat/group/user/dm/open_id) when present.
22+
// normalizeFeishuTarget strips these prefixes, so infer type from the raw target first.
23+
const withoutProviderPrefix = target.replace(/^(feishu|lark):/i, "");
2024
return {
2125
client,
2226
receiveId,
23-
receiveIdType: resolveReceiveIdType(receiveId),
27+
receiveIdType: resolveReceiveIdType(withoutProviderPrefix),
2428
};
2529
}

extensions/feishu/src/targets.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ describe("resolveReceiveIdType", () => {
1717
it("treats explicit group targets as chat_id", () => {
1818
expect(resolveReceiveIdType("group:oc_123")).toBe("chat_id");
1919
});
20+
21+
it("treats dm-prefixed open IDs as open_id", () => {
22+
expect(resolveReceiveIdType("dm:ou_123")).toBe("open_id");
23+
});
2024
});
2125

2226
describe("normalizeFeishuTarget", () => {

0 commit comments

Comments
 (0)