diff --git a/extensions/feishu/src/thread-bindings.test.ts b/extensions/feishu/src/thread-bindings.test.ts index fa10730247b..d02b392d8f4 100644 --- a/extensions/feishu/src/thread-bindings.test.ts +++ b/extensions/feishu/src/thread-bindings.test.ts @@ -91,4 +91,53 @@ describe("Feishu thread bindings", () => { }), ).toBeNull(); }); + + it("preserves delivery routing metadata when rebinding the same conversation", async () => { + const manager = createFeishuThreadBindingManager({ cfg: baseCfg, accountId: "default" }); + + manager.bindConversation({ + conversationId: "oc_group_chat:topic:om_topic_root:sender:ou_sender_1", + parentConversationId: "oc_group_chat", + targetKind: "subagent", + targetSessionKey: "agent:main:subagent:child", + metadata: { + agentId: "codex", + label: "child", + boundBy: "system", + deliveryTo: "user:ou_sender_1", + deliveryThreadId: "om_topic_root", + }, + }); + + await getSessionBindingService().bind({ + targetSessionKey: "agent:main:subagent:child", + targetKind: "subagent", + conversation: { + channel: "feishu", + accountId: "default", + conversationId: "oc_group_chat:topic:om_topic_root:sender:ou_sender_1", + parentConversationId: "oc_group_chat", + }, + placement: "current", + metadata: { + label: "child", + }, + }); + + expect( + getSessionBindingService().resolveByConversation({ + channel: "feishu", + accountId: "default", + conversationId: "oc_group_chat:topic:om_topic_root:sender:ou_sender_1", + }), + ).toMatchObject({ + metadata: expect.objectContaining({ + agentId: "codex", + label: "child", + boundBy: "system", + deliveryTo: "user:ou_sender_1", + deliveryThreadId: "om_topic_root", + }), + }); + }); }); diff --git a/extensions/feishu/src/thread-bindings.ts b/extensions/feishu/src/thread-bindings.ts index bc3661ac085..3b976a7c668 100644 --- a/extensions/feishu/src/thread-bindings.ts +++ b/extensions/feishu/src/thread-bindings.ts @@ -159,36 +159,41 @@ export function createFeishuThreadBindingManager(params: { metadata, }) => { const normalizedConversationId = conversationId.trim(); - if (!normalizedConversationId || !targetSessionKey.trim()) { + const normalizedTargetSessionKey = targetSessionKey.trim(); + if (!normalizedConversationId || !normalizedTargetSessionKey) { return null; } + const existing = getState().bindingsByAccountConversation.get( + resolveBindingKey({ accountId, conversationId: normalizedConversationId }), + ); const now = Date.now(); const record: FeishuThreadBindingRecord = { accountId, conversationId: normalizedConversationId, - parentConversationId: normalizeOptionalString(parentConversationId), + parentConversationId: + normalizeOptionalString(parentConversationId) ?? existing?.parentConversationId, deliveryTo: typeof metadata?.deliveryTo === "string" && metadata.deliveryTo.trim() ? metadata.deliveryTo.trim() - : undefined, + : existing?.deliveryTo, deliveryThreadId: typeof metadata?.deliveryThreadId === "string" && metadata.deliveryThreadId.trim() ? metadata.deliveryThreadId.trim() - : undefined, + : existing?.deliveryThreadId, targetKind: toFeishuTargetKind(targetKind), - targetSessionKey: targetSessionKey.trim(), + targetSessionKey: normalizedTargetSessionKey, agentId: typeof metadata?.agentId === "string" && metadata.agentId.trim() ? metadata.agentId.trim() - : resolveAgentIdFromSessionKey(targetSessionKey), + : (existing?.agentId ?? resolveAgentIdFromSessionKey(normalizedTargetSessionKey)), label: typeof metadata?.label === "string" && metadata.label.trim() ? metadata.label.trim() - : undefined, + : existing?.label, boundBy: typeof metadata?.boundBy === "string" && metadata.boundBy.trim() ? metadata.boundBy.trim() - : undefined, + : existing?.boundBy, boundAt: now, lastActivityAt: now, };