diff --git a/extensions/feishu/index.ts b/extensions/feishu/index.ts index 492fca0af5c..2eae5ce5e68 100644 --- a/extensions/feishu/index.ts +++ b/extensions/feishu/index.ts @@ -6,7 +6,6 @@ import { registerFeishuDocTools } from "./src/docx.js"; import { registerFeishuDriveTools } from "./src/drive.js"; import { registerFeishuPermTools } from "./src/perm.js"; import { setFeishuRuntime } from "./src/runtime.js"; -import { registerFeishuSubagentHooks } from "./src/subagent-hooks.js"; import { registerFeishuWikiTools } from "./src/wiki.js"; export { feishuPlugin } from "./src/channel.js"; @@ -46,14 +45,21 @@ export { } from "./src/mention.js"; type MonitorFeishuProvider = typeof import("./src/monitor.js").monitorFeishuProvider; +type FeishuSubagentHooksModule = typeof import("./src/subagent-hooks.js"); let feishuMonitorPromise: Promise | null = null; +let feishuSubagentHooksPromise: Promise | null = null; function loadFeishuMonitorModule() { feishuMonitorPromise ??= import("./src/monitor.js"); return feishuMonitorPromise; } +function loadFeishuSubagentHooksModule() { + feishuSubagentHooksPromise ??= import("./src/subagent-hooks.js"); + return feishuSubagentHooksPromise; +} + export async function monitorFeishuProvider( ...args: Parameters ): ReturnType { @@ -68,7 +74,18 @@ export default defineChannelPluginEntry({ plugin: feishuPlugin, setRuntime: setFeishuRuntime, registerFull(api) { - registerFeishuSubagentHooks(api); + api.on("subagent_spawning", async (event, ctx) => { + const { handleFeishuSubagentSpawning } = await loadFeishuSubagentHooksModule(); + return await handleFeishuSubagentSpawning(event, ctx); + }); + api.on("subagent_delivery_target", async (event) => { + const { handleFeishuSubagentDeliveryTarget } = await loadFeishuSubagentHooksModule(); + return await handleFeishuSubagentDeliveryTarget(event); + }); + api.on("subagent_ended", async (event) => { + const { handleFeishuSubagentEnded } = await loadFeishuSubagentHooksModule(); + await handleFeishuSubagentEnded(event); + }); registerFeishuDocTools(api); registerFeishuChatTools(api); registerFeishuWikiTools(api); diff --git a/extensions/feishu/src/subagent-hooks.ts b/extensions/feishu/src/subagent-hooks.ts index c6d14240160..39f6b102128 100644 --- a/extensions/feishu/src/subagent-hooks.ts +++ b/extensions/feishu/src/subagent-hooks.ts @@ -232,110 +232,151 @@ function resolveMatchingChildBinding(params: { return childBindings.length === 1 ? childBindings[0] : null; } -export function registerFeishuSubagentHooks(api: OpenClawPluginApi) { - api.on("subagent_spawning", async (event, ctx) => { - if (!event.threadRequested) { - return; - } - const requesterChannel = event.requester?.channel?.trim().toLowerCase(); - if (requesterChannel !== "feishu") { - return; - } +type FeishuSubagentContext = { + requesterSessionKey?: string; +}; - const manager = getFeishuThreadBindingManager(event.requester?.accountId); - if (!manager) { - return { - status: "error" as const, - error: - "Feishu current-conversation binding is unavailable because the Feishu account monitor is not active.", - }; - } +type FeishuSubagentSpawningEvent = { + threadRequested?: boolean; + requester?: { + channel?: string; + accountId?: string; + to?: string; + threadId?: string | number; + }; + childSessionKey: string; + agentId?: string; + label?: string; +}; - const conversation = resolveFeishuRequesterConversation({ - accountId: event.requester?.accountId, - to: event.requester?.to, - threadId: event.requester?.threadId, - requesterSessionKey: ctx.requesterSessionKey, - }); - if (!conversation) { - return { - status: "error" as const, - error: - "Feishu current-conversation binding is only available in direct messages or topic conversations.", - }; - } +type FeishuSubagentDeliveryTargetEvent = { + expectsCompletionMessage?: boolean; + requesterOrigin?: { + channel?: string; + accountId?: string; + to?: string; + threadId?: string | number; + }; + childSessionKey: string; + requesterSessionKey?: string; +}; - try { - const binding = manager.bindConversation({ - conversationId: conversation.conversationId, - parentConversationId: conversation.parentConversationId, - targetKind: "subagent", - targetSessionKey: event.childSessionKey, - metadata: { - agentId: event.agentId, - label: event.label, - boundBy: "system", - deliveryTo: event.requester?.to, - deliveryThreadId: - event.requester?.threadId != null && event.requester.threadId !== "" - ? String(event.requester.threadId) - : undefined, - }, - }); - if (!binding) { - return { - status: "error" as const, - error: - "Unable to bind this Feishu conversation to the spawned subagent session. Session mode is unavailable for this target.", - }; - } - return { - status: "ok" as const, - threadBindingReady: true, - }; - } catch (err) { - return { - status: "error" as const, - error: `Feishu conversation bind failed: ${summarizeError(err)}`, - }; - } +type FeishuSubagentEndedEvent = { + accountId?: string; + targetSessionKey: string; +}; + +export async function handleFeishuSubagentSpawning( + event: FeishuSubagentSpawningEvent, + ctx: FeishuSubagentContext, +) { + if (!event.threadRequested) { + return; + } + const requesterChannel = event.requester?.channel?.trim().toLowerCase(); + if (requesterChannel !== "feishu") { + return; + } + + const manager = getFeishuThreadBindingManager(event.requester?.accountId); + if (!manager) { + return { + status: "error" as const, + error: + "Feishu current-conversation binding is unavailable because the Feishu account monitor is not active.", + }; + } + + const conversation = resolveFeishuRequesterConversation({ + accountId: event.requester?.accountId, + to: event.requester?.to, + threadId: event.requester?.threadId, + requesterSessionKey: ctx.requesterSessionKey, }); + if (!conversation) { + return { + status: "error" as const, + error: + "Feishu current-conversation binding is only available in direct messages or topic conversations.", + }; + } - api.on("subagent_delivery_target", (event) => { - if (!event.expectsCompletionMessage) { - return; - } - const requesterChannel = event.requesterOrigin?.channel?.trim().toLowerCase(); - if (requesterChannel !== "feishu") { - return; - } - - const binding = resolveMatchingChildBinding({ - accountId: event.requesterOrigin?.accountId, - childSessionKey: event.childSessionKey, - requesterSessionKey: event.requesterSessionKey, - requesterOrigin: { - to: event.requesterOrigin?.to, - threadId: event.requesterOrigin?.threadId, + try { + const binding = manager.bindConversation({ + conversationId: conversation.conversationId, + parentConversationId: conversation.parentConversationId, + targetKind: "subagent", + targetSessionKey: event.childSessionKey, + metadata: { + agentId: event.agentId, + label: event.label, + boundBy: "system", + deliveryTo: event.requester?.to, + deliveryThreadId: + event.requester?.threadId != null && event.requester.threadId !== "" + ? String(event.requester.threadId) + : undefined, }, }); if (!binding) { - return; + return { + status: "error" as const, + error: + "Unable to bind this Feishu conversation to the spawned subagent session. Session mode is unavailable for this target.", + }; } - return { - origin: resolveFeishuDeliveryOrigin({ - conversationId: binding.conversationId, - parentConversationId: binding.parentConversationId, - accountId: binding.accountId, - deliveryTo: binding.deliveryTo, - deliveryThreadId: binding.deliveryThreadId, - }), + status: "ok" as const, + threadBindingReady: true, }; - }); - - api.on("subagent_ended", (event) => { - const manager = getFeishuThreadBindingManager(event.accountId); - manager?.unbindBySessionKey(event.targetSessionKey); - }); + } catch (err) { + return { + status: "error" as const, + error: `Feishu conversation bind failed: ${summarizeError(err)}`, + }; + } +} + +export function handleFeishuSubagentDeliveryTarget(event: FeishuSubagentDeliveryTargetEvent) { + if (!event.expectsCompletionMessage) { + return; + } + const requesterChannel = event.requesterOrigin?.channel?.trim().toLowerCase(); + if (requesterChannel !== "feishu") { + return; + } + + const binding = resolveMatchingChildBinding({ + accountId: event.requesterOrigin?.accountId, + childSessionKey: event.childSessionKey, + requesterSessionKey: event.requesterSessionKey, + requesterOrigin: { + to: event.requesterOrigin?.to, + threadId: event.requesterOrigin?.threadId, + }, + }); + if (!binding) { + return; + } + + return { + origin: resolveFeishuDeliveryOrigin({ + conversationId: binding.conversationId, + parentConversationId: binding.parentConversationId, + accountId: binding.accountId, + deliveryTo: binding.deliveryTo, + deliveryThreadId: binding.deliveryThreadId, + }), + }; +} + +export function handleFeishuSubagentEnded(event: FeishuSubagentEndedEvent) { + const manager = getFeishuThreadBindingManager(event.accountId); + manager?.unbindBySessionKey(event.targetSessionKey); +} + +export function registerFeishuSubagentHooks(api: OpenClawPluginApi) { + api.on("subagent_spawning", (event, ctx) => handleFeishuSubagentSpawning(event, ctx)); + api.on("subagent_delivery_target", (event) => handleFeishuSubagentDeliveryTarget(event)); + api.on("subagent_ended", (event) => handleFeishuSubagentEnded(event)); }