feat(feishu): support sender/topic-scoped group session routing (openclaw#17798) thanks @yfge

Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: yfge <1186273+yfge@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
拐爷&&老拐瘦
2026-02-28 09:26:36 +08:00
committed by GitHub
parent ed51796d97
commit 36d69d05e2
5 changed files with 185 additions and 22 deletions

View File

@@ -10,6 +10,7 @@ const {
mockGetMessageFeishu,
mockDownloadMessageResourceFeishu,
mockCreateFeishuClient,
mockResolveAgentRoute,
} = vi.hoisted(() => ({
mockCreateFeishuReplyDispatcher: vi.fn(() => ({
dispatcher: vi.fn(),
@@ -24,6 +25,12 @@ const {
fileName: "clip.mp4",
}),
mockCreateFeishuClient: vi.fn(),
mockResolveAgentRoute: vi.fn(() => ({
agentId: "main",
accountId: "default",
sessionKey: "agent:main:feishu:dm:ou-attacker",
matchedBy: "default",
})),
}));
vi.mock("./reply-dispatcher.js", () => ({
@@ -120,6 +127,12 @@ describe("handleFeishuMessage command authorization", () => {
beforeEach(() => {
vi.clearAllMocks();
mockResolveAgentRoute.mockReturnValue({
agentId: "main",
accountId: "default",
sessionKey: "agent:main:feishu:dm:ou-attacker",
matchedBy: "default",
});
mockCreateFeishuClient.mockReturnValue({
contact: {
user: {
@@ -133,12 +146,7 @@ describe("handleFeishuMessage command authorization", () => {
},
channel: {
routing: {
resolveAgentRoute: vi.fn(() => ({
agentId: "main",
accountId: "default",
sessionKey: "agent:main:feishu:dm:ou-attacker",
matchedBy: "default",
})),
resolveAgentRoute: mockResolveAgentRoute,
},
reply: {
resolveEnvelopeFormatOptions: vi.fn(() => ({ template: "channel+name+time" })),
@@ -540,4 +548,117 @@ describe("handleFeishuMessage command authorization", () => {
}),
);
});
it("routes group sessions by sender when groupSessionScope=group_sender", async () => {
mockShouldComputeCommandAuthorized.mockReturnValue(false);
const cfg: ClawdbotConfig = {
channels: {
feishu: {
groups: {
"oc-group": {
requireMention: false,
groupSessionScope: "group_sender",
},
},
},
},
} as ClawdbotConfig;
const event: FeishuMessageEvent = {
sender: { sender_id: { open_id: "ou-scope-user" } },
message: {
message_id: "msg-scope-group-sender",
chat_id: "oc-group",
chat_type: "group",
message_type: "text",
content: JSON.stringify({ text: "group sender scope" }),
},
};
await dispatchMessage({ cfg, event });
expect(mockResolveAgentRoute).toHaveBeenCalledWith(
expect.objectContaining({
peer: { kind: "group", id: "oc-group:sender:ou-scope-user" },
parentPeer: null,
}),
);
});
it("routes topic sessions and parentPeer when groupSessionScope=group_topic_sender", async () => {
mockShouldComputeCommandAuthorized.mockReturnValue(false);
const cfg: ClawdbotConfig = {
channels: {
feishu: {
groups: {
"oc-group": {
requireMention: false,
groupSessionScope: "group_topic_sender",
},
},
},
},
} as ClawdbotConfig;
const event: FeishuMessageEvent = {
sender: { sender_id: { open_id: "ou-topic-user" } },
message: {
message_id: "msg-scope-topic-sender",
chat_id: "oc-group",
chat_type: "group",
root_id: "om_root_topic",
message_type: "text",
content: JSON.stringify({ text: "topic sender scope" }),
},
};
await dispatchMessage({ cfg, event });
expect(mockResolveAgentRoute).toHaveBeenCalledWith(
expect.objectContaining({
peer: { kind: "group", id: "oc-group:topic:om_root_topic:sender:ou-topic-user" },
parentPeer: { kind: "group", id: "oc-group" },
}),
);
});
it("maps legacy topicSessionMode=enabled to group_topic routing", async () => {
mockShouldComputeCommandAuthorized.mockReturnValue(false);
const cfg: ClawdbotConfig = {
channels: {
feishu: {
topicSessionMode: "enabled",
groups: {
"oc-group": {
requireMention: false,
},
},
},
},
} as ClawdbotConfig;
const event: FeishuMessageEvent = {
sender: { sender_id: { open_id: "ou-legacy" } },
message: {
message_id: "msg-legacy-topic-mode",
chat_id: "oc-group",
chat_type: "group",
root_id: "om_root_legacy",
message_type: "text",
content: JSON.stringify({ text: "legacy topic mode" }),
},
};
await dispatchMessage({ cfg, event });
expect(mockResolveAgentRoute).toHaveBeenCalledWith(
expect.objectContaining({
peer: { kind: "group", id: "oc-group:topic:om_root_legacy" },
parentPeer: { kind: "group", id: "oc-group" },
}),
);
});
});