fix(feishu): support DM ACP binding placement

This commit is contained in:
Tak Hoffman
2026-03-14 23:19:24 -05:00
parent 372cd2b32c
commit 76c9094c8d
3 changed files with 59 additions and 14 deletions

View File

@@ -118,7 +118,7 @@ type FakeBinding = {
targetSessionKey: string;
targetKind: "subagent" | "session";
conversation: {
channel: "discord" | "telegram";
channel: "discord" | "telegram" | "feishu";
accountId: string;
conversationId: string;
parentConversationId?: string;
@@ -243,7 +243,7 @@ function createSessionBindingCapabilities() {
type AcpBindInput = {
targetSessionKey: string;
conversation: {
channel?: "discord" | "telegram";
channel?: "discord" | "telegram" | "feishu";
accountId: string;
conversationId: string;
};
@@ -256,21 +256,28 @@ function createAcpThreadBinding(input: AcpBindInput): FakeBinding {
input.placement === "child" ? "thread-created" : input.conversation.conversationId;
const boundBy = typeof input.metadata?.boundBy === "string" ? input.metadata.boundBy : "user-1";
const channel = input.conversation.channel ?? "discord";
return createSessionBinding({
targetSessionKey: input.targetSessionKey,
conversation:
channel === "discord"
const conversation =
channel === "discord"
? {
channel: "discord" as const,
accountId: input.conversation.accountId,
conversationId: nextConversationId,
parentConversationId: "parent-1",
}
: channel === "feishu"
? {
channel: "discord",
channel: "feishu" as const,
accountId: input.conversation.accountId,
conversationId: nextConversationId,
parentConversationId: "parent-1",
}
: {
channel: "telegram",
channel: "telegram" as const,
accountId: input.conversation.accountId,
conversationId: nextConversationId,
},
};
return createSessionBinding({
targetSessionKey: input.targetSessionKey,
conversation,
metadata: { boundBy, webhookId: "wh-1" },
});
}
@@ -350,6 +357,23 @@ async function runTelegramDmAcpCommand(commandBody: string, cfg: OpenClawConfig
return handleAcpCommand(createTelegramDmParams(commandBody, cfg), true);
}
function createFeishuDmParams(commandBody: string, cfg: OpenClawConfig = baseCfg) {
const params = buildCommandTestParams(commandBody, cfg, {
Provider: "feishu",
Surface: "feishu",
OriginatingChannel: "feishu",
OriginatingTo: "user:ou_sender_1",
AccountId: "default",
SenderId: "ou_sender_1",
});
params.command.senderId = "user-1";
return params;
}
async function runFeishuDmAcpCommand(commandBody: string, cfg: OpenClawConfig = baseCfg) {
return handleAcpCommand(createFeishuDmParams(commandBody, cfg), true);
}
describe("/acp command", () => {
beforeEach(() => {
acpManagerTesting.resetAcpSessionManagerForTests();
@@ -553,6 +577,23 @@ describe("/acp command", () => {
);
});
it("binds Feishu DM ACP spawns to the current DM conversation", async () => {
const result = await runFeishuDmAcpCommand("/acp spawn codex --thread here");
expect(result?.reply?.text).toContain("Spawned ACP session agent:codex:acp:");
expect(result?.reply?.text).toContain("Bound this thread to");
expect(hoisted.sessionBindingBindMock).toHaveBeenCalledWith(
expect.objectContaining({
placement: "current",
conversation: expect.objectContaining({
channel: "feishu",
accountId: "default",
conversationId: "ou_sender_1",
}),
}),
);
});
it("requires explicit ACP target when acp.defaultAgent is not configured", async () => {
const result = await runDiscordAcpCommand("/acp spawn");

View File

@@ -66,8 +66,7 @@ function resolveFeishuSenderScopedConversationId(params: {
.find((binding) => {
if (
binding.conversation.channel !== "feishu" ||
binding.conversation.accountId !== params.accountId ||
binding.conversation.parentConversationId !== parentConversationId
binding.conversation.accountId !== params.accountId
) {
return false;
}

View File

@@ -125,7 +125,7 @@ async function bindSpawnedAcpSessionToThread(params: {
const currentThreadId = bindingContext.threadId ?? "";
const currentConversationId = bindingContext.conversationId?.trim() || "";
const requiresThreadIdForHere = channel !== "telegram";
const requiresThreadIdForHere = channel !== "telegram" && channel !== "feishu";
if (
threadMode === "here" &&
((requiresThreadIdForHere && !currentThreadId) ||
@@ -137,7 +137,12 @@ async function bindSpawnedAcpSessionToThread(params: {
};
}
const placement = channel === "telegram" ? "current" : currentThreadId ? "current" : "child";
const placement =
channel === "telegram" || channel === "feishu"
? "current"
: currentThreadId
? "current"
: "child";
if (!capabilities.placements.includes(placement)) {
return {
ok: false,