mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
refactor(hooks): centralize bundled subagent hook wiring
This commit is contained in:
@@ -1,13 +1,5 @@
|
||||
import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
|
||||
|
||||
type DiscordSubagentHooksModule = typeof import("./subagent-hooks-api.js");
|
||||
|
||||
let discordSubagentHooksPromise: Promise<DiscordSubagentHooksModule> | null = null;
|
||||
|
||||
function loadDiscordSubagentHooksModule() {
|
||||
discordSubagentHooksPromise ??= import("./subagent-hooks-api.js");
|
||||
return discordSubagentHooksPromise;
|
||||
}
|
||||
import { registerDiscordSubagentHooks } from "./subagent-hooks-api.js";
|
||||
|
||||
export default defineBundledChannelEntry({
|
||||
id: "discord",
|
||||
@@ -27,17 +19,6 @@ export default defineBundledChannelEntry({
|
||||
exportName: "inspectDiscordReadOnlyAccount",
|
||||
},
|
||||
registerFull(api) {
|
||||
api.on("subagent_spawning", async (event) => {
|
||||
const { handleDiscordSubagentSpawning } = await loadDiscordSubagentHooksModule();
|
||||
return await handleDiscordSubagentSpawning(api, event);
|
||||
});
|
||||
api.on("subagent_ended", async (event) => {
|
||||
const { handleDiscordSubagentEnded } = await loadDiscordSubagentHooksModule();
|
||||
handleDiscordSubagentEnded(event);
|
||||
});
|
||||
api.on("subagent_delivery_target", async (event) => {
|
||||
const { handleDiscordSubagentDeliveryTarget } = await loadDiscordSubagentHooksModule();
|
||||
return handleDiscordSubagentDeliveryTarget(event);
|
||||
});
|
||||
registerDiscordSubagentHooks(api);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ const hookMocks = vi.hoisted(() => ({
|
||||
unbindThreadBindingsBySessionKey: vi.fn(() => []),
|
||||
}));
|
||||
|
||||
let registerDiscordSubagentHooks: typeof import("./subagent-hooks.js").registerDiscordSubagentHooks;
|
||||
let registerDiscordSubagentHooks: typeof import("../subagent-hooks-api.js").registerDiscordSubagentHooks;
|
||||
|
||||
vi.mock("./accounts.js", () => ({
|
||||
resolveDiscordAccount: hookMocks.resolveDiscordAccount,
|
||||
@@ -66,7 +66,7 @@ function registerHandlersForTest(
|
||||
});
|
||||
}
|
||||
|
||||
function resolveSubagentDeliveryTargetForTest(requesterOrigin: {
|
||||
async function resolveSubagentDeliveryTargetForTest(requesterOrigin: {
|
||||
channel: string;
|
||||
accountId: string;
|
||||
to: string;
|
||||
@@ -74,7 +74,7 @@ function resolveSubagentDeliveryTargetForTest(requesterOrigin: {
|
||||
}) {
|
||||
const handlers = registerHandlersForTest();
|
||||
const handler = getRequiredHookHandler(handlers, "subagent_delivery_target");
|
||||
return handler(
|
||||
return await handler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:child",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
@@ -167,7 +167,7 @@ async function expectSubagentSpawningError(params?: {
|
||||
|
||||
describe("discord subagent hook handlers", () => {
|
||||
beforeAll(async () => {
|
||||
({ registerDiscordSubagentHooks } = await import("./subagent-hooks.js"));
|
||||
({ registerDiscordSubagentHooks } = await import("../subagent-hooks-api.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -303,11 +303,11 @@ describe("discord subagent hook handlers", () => {
|
||||
expect(errorText).toMatch(/unable to create or bind/i);
|
||||
});
|
||||
|
||||
it("unbinds thread routing on subagent_ended", () => {
|
||||
it("unbinds thread routing on subagent_ended", async () => {
|
||||
const handlers = registerHandlersForTest();
|
||||
const handler = getRequiredHookHandler(handlers, "subagent_ended");
|
||||
|
||||
handler(
|
||||
await handler(
|
||||
{
|
||||
targetSessionKey: "agent:main:subagent:child",
|
||||
targetKind: "subagent",
|
||||
@@ -328,11 +328,11 @@ describe("discord subagent hook handlers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves delivery target from matching bound thread", () => {
|
||||
it("resolves delivery target from matching bound thread", async () => {
|
||||
hookMocks.listThreadBindingsBySessionKey.mockReturnValueOnce([
|
||||
{ accountId: "work", threadId: "777" },
|
||||
]);
|
||||
const result = resolveSubagentDeliveryTargetForTest({
|
||||
const result = await resolveSubagentDeliveryTargetForTest({
|
||||
channel: "discord",
|
||||
accountId: "work",
|
||||
to: "channel:123",
|
||||
@@ -354,12 +354,12 @@ describe("discord subagent hook handlers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps original routing when delivery target is ambiguous", () => {
|
||||
it("keeps original routing when delivery target is ambiguous", async () => {
|
||||
hookMocks.listThreadBindingsBySessionKey.mockReturnValueOnce([
|
||||
{ accountId: "work", threadId: "777" },
|
||||
{ accountId: "work", threadId: "888" },
|
||||
]);
|
||||
const result = resolveSubagentDeliveryTargetForTest({
|
||||
const result = await resolveSubagentDeliveryTargetForTest({
|
||||
channel: "discord",
|
||||
accountId: "work",
|
||||
to: "channel:123",
|
||||
|
||||
@@ -212,9 +212,3 @@ export function handleDiscordSubagentDeliveryTarget(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function registerDiscordSubagentHooks(api: OpenClawPluginApi) {
|
||||
api.on("subagent_spawning", (event) => handleDiscordSubagentSpawning(api, event));
|
||||
api.on("subagent_ended", (event) => handleDiscordSubagentEnded(event));
|
||||
api.on("subagent_delivery_target", (event) => handleDiscordSubagentDeliveryTarget(event));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
// Subagent hooks live behind a dedicated barrel so the bundled entry can lazy
|
||||
// load only the handlers it needs.
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contract";
|
||||
|
||||
type DiscordSubagentHooksModule = typeof import("./src/subagent-hooks.js");
|
||||
|
||||
let discordSubagentHooksPromise: Promise<DiscordSubagentHooksModule> | null = null;
|
||||
|
||||
function loadDiscordSubagentHooksModule() {
|
||||
discordSubagentHooksPromise ??= import("./src/subagent-hooks.js");
|
||||
return discordSubagentHooksPromise;
|
||||
}
|
||||
|
||||
// Subagent hooks live behind a dedicated barrel so the bundled entry can
|
||||
// register one stable hook wiring path while keeping the handler module lazy.
|
||||
export function registerDiscordSubagentHooks(api: OpenClawPluginApi): void {
|
||||
api.on("subagent_spawning", async (event) => {
|
||||
const { handleDiscordSubagentSpawning } = await loadDiscordSubagentHooksModule();
|
||||
return await handleDiscordSubagentSpawning(api, event);
|
||||
});
|
||||
api.on("subagent_ended", async (event) => {
|
||||
const { handleDiscordSubagentEnded } = await loadDiscordSubagentHooksModule();
|
||||
handleDiscordSubagentEnded(event);
|
||||
});
|
||||
api.on("subagent_delivery_target", async (event) => {
|
||||
const { handleDiscordSubagentDeliveryTarget } = await loadDiscordSubagentHooksModule();
|
||||
return handleDiscordSubagentDeliveryTarget(event);
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
handleDiscordSubagentDeliveryTarget,
|
||||
handleDiscordSubagentEnded,
|
||||
|
||||
@@ -3,15 +3,7 @@ import {
|
||||
loadBundledEntryExportSync,
|
||||
} from "openclaw/plugin-sdk/channel-entry-contract";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contract";
|
||||
|
||||
type FeishuSubagentHooksModule = typeof import("./api.js");
|
||||
|
||||
let feishuSubagentHooksPromise: Promise<FeishuSubagentHooksModule> | null = null;
|
||||
|
||||
function loadFeishuSubagentHooksModule() {
|
||||
feishuSubagentHooksPromise ??= import("./api.js");
|
||||
return feishuSubagentHooksPromise;
|
||||
}
|
||||
import { registerFeishuSubagentHooks } from "./subagent-hooks-api.js";
|
||||
|
||||
function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, {
|
||||
@@ -79,18 +71,7 @@ export default defineBundledChannelEntry({
|
||||
exportName: "setFeishuRuntime",
|
||||
},
|
||||
registerFull(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 handleFeishuSubagentDeliveryTarget(event);
|
||||
});
|
||||
api.on("subagent_ended", async (event) => {
|
||||
const { handleFeishuSubagentEnded } = await loadFeishuSubagentHooksModule();
|
||||
handleFeishuSubagentEnded(event);
|
||||
});
|
||||
registerFeishuSubagentHooks(api);
|
||||
registerFeishuDocTools(api);
|
||||
registerFeishuChatTools(api);
|
||||
registerFeishuWikiTools(api);
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
registerHookHandlersForTest,
|
||||
} from "../../../test/helpers/plugins/subagent-hooks.js";
|
||||
import type { ClawdbotConfig, OpenClawPluginApi } from "../runtime-api.js";
|
||||
import { registerFeishuSubagentHooks } from "./subagent-hooks.js";
|
||||
import { registerFeishuSubagentHooks } from "../subagent-hooks-api.js";
|
||||
import {
|
||||
createFeishuThreadBindingManager,
|
||||
__testing as threadBindingTesting,
|
||||
@@ -51,7 +51,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
expect(result).toEqual({ status: "ok", threadBindingReady: true });
|
||||
|
||||
const deliveryTargetHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
|
||||
expect(
|
||||
await expect(
|
||||
deliveryTargetHandler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:child",
|
||||
@@ -65,7 +65,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toEqual({
|
||||
).resolves.toEqual({
|
||||
origin: {
|
||||
channel: "feishu",
|
||||
accountId: "work",
|
||||
@@ -89,7 +89,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
deliveryHandler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:chat-dm-child",
|
||||
@@ -103,7 +103,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toEqual({
|
||||
).resolves.toEqual({
|
||||
origin: {
|
||||
channel: "feishu",
|
||||
accountId: "work",
|
||||
@@ -136,7 +136,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
);
|
||||
|
||||
expect(result).toEqual({ status: "ok", threadBindingReady: true });
|
||||
expect(
|
||||
await expect(
|
||||
deliveryHandler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:topic-child",
|
||||
@@ -151,7 +151,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toEqual({
|
||||
).resolves.toEqual({
|
||||
origin: {
|
||||
channel: "feishu",
|
||||
accountId: "work",
|
||||
@@ -205,7 +205,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
parentConversationId: "oc_group_chat",
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
await expect(
|
||||
deliveryHandler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:sender-child",
|
||||
@@ -220,7 +220,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toEqual({
|
||||
).resolves.toEqual({
|
||||
origin: {
|
||||
channel: "feishu",
|
||||
accountId: "work",
|
||||
@@ -267,7 +267,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
deliveryHandler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:shared",
|
||||
@@ -281,7 +281,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toEqual({
|
||||
).resolves.toEqual({
|
||||
origin: {
|
||||
channel: "feishu",
|
||||
accountId: "work",
|
||||
@@ -335,7 +335,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
error: expect.stringContaining("direct messages or topic conversations"),
|
||||
});
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
deliveryHandler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:ambiguous-child",
|
||||
@@ -350,7 +350,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toBeUndefined();
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("fails closed when both topic-level and sender-scoped requester bindings exist", async () => {
|
||||
@@ -398,7 +398,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
error: expect.stringContaining("direct messages or topic conversations"),
|
||||
});
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
deliveryHandler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:mixed-topic-child",
|
||||
@@ -413,7 +413,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toBeUndefined();
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("no-ops for non-Feishu channels and non-threaded spawns", async () => {
|
||||
@@ -456,7 +456,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
deliveryHandler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:child",
|
||||
@@ -470,9 +470,9 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toBeUndefined();
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
endedHandler(
|
||||
{
|
||||
targetSessionKey: "agent:main:subagent:child",
|
||||
@@ -482,7 +482,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toBeUndefined();
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns an error for unsupported non-topic Feishu group conversations", async () => {
|
||||
@@ -532,7 +532,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
endedHandler(
|
||||
await endedHandler(
|
||||
{
|
||||
targetSessionKey: "agent:main:subagent:child",
|
||||
targetKind: "subagent",
|
||||
@@ -542,7 +542,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
deliveryHandler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:child",
|
||||
@@ -556,7 +556,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toBeUndefined();
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("fails closed when the Feishu monitor-owned binding manager is unavailable", async () => {
|
||||
@@ -584,7 +584,7 @@ describe("feishu subagent hook handlers", () => {
|
||||
error: expect.stringContaining("monitor is not active"),
|
||||
});
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
deliveryHandler(
|
||||
{
|
||||
childSessionKey: "agent:main:subagent:no-manager",
|
||||
@@ -598,6 +598,6 @@ describe("feishu subagent hook handlers", () => {
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toBeUndefined();
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { OpenClawPluginApi } from "../runtime-api.js";
|
||||
import { buildFeishuConversationId, parseFeishuConversationId } from "./conversation-id.js";
|
||||
import { normalizeFeishuTarget } from "./targets.js";
|
||||
import { getFeishuThreadBindingManager } from "./thread-bindings.js";
|
||||
@@ -396,9 +395,3 @@ 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));
|
||||
}
|
||||
|
||||
31
extensions/feishu/subagent-hooks-api.ts
Normal file
31
extensions/feishu/subagent-hooks-api.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contract";
|
||||
|
||||
type FeishuSubagentHooksModule = typeof import("./src/subagent-hooks.js");
|
||||
|
||||
let feishuSubagentHooksPromise: Promise<FeishuSubagentHooksModule> | null = null;
|
||||
|
||||
function loadFeishuSubagentHooksModule() {
|
||||
feishuSubagentHooksPromise ??= import("./src/subagent-hooks.js");
|
||||
return feishuSubagentHooksPromise;
|
||||
}
|
||||
|
||||
export function registerFeishuSubagentHooks(api: OpenClawPluginApi): void {
|
||||
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 handleFeishuSubagentDeliveryTarget(event);
|
||||
});
|
||||
api.on("subagent_ended", async (event) => {
|
||||
const { handleFeishuSubagentEnded } = await loadFeishuSubagentHooksModule();
|
||||
handleFeishuSubagentEnded(event);
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
handleFeishuSubagentDeliveryTarget,
|
||||
handleFeishuSubagentEnded,
|
||||
handleFeishuSubagentSpawning,
|
||||
} from "./src/subagent-hooks.js";
|
||||
Reference in New Issue
Block a user