mirror of https://github.com/openclaw/openclaw.git
refactor(discord): lazy-load subagent hooks
This commit is contained in:
parent
0ecf84524d
commit
690c58baa2
|
|
@ -1,16 +1,37 @@
|
|||
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { discordPlugin } from "./src/channel.js";
|
||||
import { setDiscordRuntime } from "./src/runtime.js";
|
||||
import { registerDiscordSubagentHooks } from "./src/subagent-hooks.js";
|
||||
|
||||
export { discordPlugin } from "./src/channel.js";
|
||||
export { setDiscordRuntime } from "./src/runtime.js";
|
||||
|
||||
type DiscordSubagentHooksModule = typeof import("./src/subagent-hooks.js");
|
||||
|
||||
let discordSubagentHooksPromise: Promise<DiscordSubagentHooksModule> | null = null;
|
||||
|
||||
function loadDiscordSubagentHooksModule() {
|
||||
discordSubagentHooksPromise ??= import("./src/subagent-hooks.js");
|
||||
return discordSubagentHooksPromise;
|
||||
}
|
||||
|
||||
export default defineChannelPluginEntry({
|
||||
id: "discord",
|
||||
name: "Discord",
|
||||
description: "Discord channel plugin",
|
||||
plugin: discordPlugin,
|
||||
setRuntime: setDiscordRuntime,
|
||||
registerFull: registerDiscordSubagentHooks,
|
||||
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);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,137 +16,173 @@ function summarizeError(err: unknown): string {
|
|||
return "error";
|
||||
}
|
||||
|
||||
export function registerDiscordSubagentHooks(api: OpenClawPluginApi) {
|
||||
const resolveThreadBindingFlags = (accountId?: string) => {
|
||||
const account = resolveDiscordAccount({
|
||||
cfg: api.config,
|
||||
accountId,
|
||||
});
|
||||
const baseThreadBindings = api.config.channels?.discord?.threadBindings;
|
||||
const accountThreadBindings =
|
||||
api.config.channels?.discord?.accounts?.[account.accountId]?.threadBindings;
|
||||
return {
|
||||
enabled:
|
||||
accountThreadBindings?.enabled ??
|
||||
baseThreadBindings?.enabled ??
|
||||
api.config.session?.threadBindings?.enabled ??
|
||||
true,
|
||||
spawnSubagentSessions:
|
||||
accountThreadBindings?.spawnSubagentSessions ??
|
||||
baseThreadBindings?.spawnSubagentSessions ??
|
||||
false,
|
||||
};
|
||||
type DiscordSubagentSpawningEvent = {
|
||||
threadRequested?: boolean;
|
||||
requester?: {
|
||||
channel?: string;
|
||||
accountId?: string;
|
||||
to?: string;
|
||||
threadId?: string | number;
|
||||
};
|
||||
childSessionKey: string;
|
||||
agentId?: string;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
api.on("subagent_spawning", async (event) => {
|
||||
if (!event.threadRequested) {
|
||||
return;
|
||||
}
|
||||
const channel = event.requester?.channel?.trim().toLowerCase();
|
||||
if (channel !== "discord") {
|
||||
// Ignore non-Discord channels so channel-specific plugins can handle
|
||||
// their own thread/session provisioning without Discord blocking them.
|
||||
return;
|
||||
}
|
||||
const threadBindingFlags = resolveThreadBindingFlags(event.requester?.accountId);
|
||||
if (!threadBindingFlags.enabled) {
|
||||
return {
|
||||
status: "error" as const,
|
||||
error:
|
||||
"Discord thread bindings are disabled (set channels.discord.threadBindings.enabled=true to override for this account, or session.threadBindings.enabled=true globally).",
|
||||
};
|
||||
}
|
||||
if (!threadBindingFlags.spawnSubagentSessions) {
|
||||
return {
|
||||
status: "error" as const,
|
||||
error:
|
||||
"Discord thread-bound subagent spawns are disabled for this account (set channels.discord.threadBindings.spawnSubagentSessions=true to enable).",
|
||||
};
|
||||
}
|
||||
try {
|
||||
const binding = await autoBindSpawnedDiscordSubagent({
|
||||
accountId: event.requester?.accountId,
|
||||
channel: event.requester?.channel,
|
||||
to: event.requester?.to,
|
||||
threadId: event.requester?.threadId,
|
||||
childSessionKey: event.childSessionKey,
|
||||
agentId: event.agentId,
|
||||
label: event.label,
|
||||
boundBy: "system",
|
||||
});
|
||||
if (!binding) {
|
||||
return {
|
||||
status: "error" as const,
|
||||
error:
|
||||
"Unable to create or bind a Discord thread for this subagent session. Session mode is unavailable for this target.",
|
||||
};
|
||||
}
|
||||
return { status: "ok" as const, threadBindingReady: true };
|
||||
} catch (err) {
|
||||
return {
|
||||
status: "error" as const,
|
||||
error: `Discord thread bind failed: ${summarizeError(err)}`,
|
||||
};
|
||||
}
|
||||
type DiscordSubagentEndedEvent = {
|
||||
targetSessionKey: string;
|
||||
accountId?: string;
|
||||
targetKind?: string;
|
||||
reason?: string;
|
||||
sendFarewell?: boolean;
|
||||
};
|
||||
|
||||
type DiscordSubagentDeliveryTargetEvent = {
|
||||
expectsCompletionMessage?: boolean;
|
||||
childSessionKey: string;
|
||||
requesterOrigin?: {
|
||||
channel?: string;
|
||||
accountId?: string;
|
||||
threadId?: string | number;
|
||||
};
|
||||
};
|
||||
|
||||
function resolveThreadBindingFlags(api: OpenClawPluginApi, accountId?: string) {
|
||||
const account = resolveDiscordAccount({
|
||||
cfg: api.config,
|
||||
accountId,
|
||||
});
|
||||
const baseThreadBindings = api.config.channels?.discord?.threadBindings;
|
||||
const accountThreadBindings =
|
||||
api.config.channels?.discord?.accounts?.[account.accountId]?.threadBindings;
|
||||
return {
|
||||
enabled:
|
||||
accountThreadBindings?.enabled ??
|
||||
baseThreadBindings?.enabled ??
|
||||
api.config.session?.threadBindings?.enabled ??
|
||||
true,
|
||||
spawnSubagentSessions:
|
||||
accountThreadBindings?.spawnSubagentSessions ??
|
||||
baseThreadBindings?.spawnSubagentSessions ??
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
api.on("subagent_ended", (event) => {
|
||||
unbindThreadBindingsBySessionKey({
|
||||
targetSessionKey: event.targetSessionKey,
|
||||
accountId: event.accountId,
|
||||
targetKind: event.targetKind,
|
||||
reason: event.reason,
|
||||
sendFarewell: event.sendFarewell,
|
||||
});
|
||||
});
|
||||
|
||||
api.on("subagent_delivery_target", (event) => {
|
||||
if (!event.expectsCompletionMessage) {
|
||||
return;
|
||||
}
|
||||
const requesterChannel = event.requesterOrigin?.channel?.trim().toLowerCase();
|
||||
if (requesterChannel !== "discord") {
|
||||
return;
|
||||
}
|
||||
const requesterAccountId = event.requesterOrigin?.accountId?.trim();
|
||||
const requesterThreadId =
|
||||
event.requesterOrigin?.threadId != null && event.requesterOrigin.threadId !== ""
|
||||
? String(event.requesterOrigin.threadId).trim()
|
||||
: "";
|
||||
const bindings = listThreadBindingsBySessionKey({
|
||||
targetSessionKey: event.childSessionKey,
|
||||
...(requesterAccountId ? { accountId: requesterAccountId } : {}),
|
||||
targetKind: "subagent",
|
||||
});
|
||||
if (bindings.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let binding: (typeof bindings)[number] | undefined;
|
||||
if (requesterThreadId) {
|
||||
binding = bindings.find((entry) => {
|
||||
if (entry.threadId !== requesterThreadId) {
|
||||
return false;
|
||||
}
|
||||
if (requesterAccountId && entry.accountId !== requesterAccountId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (!binding && bindings.length === 1) {
|
||||
binding = bindings[0];
|
||||
}
|
||||
if (!binding) {
|
||||
return;
|
||||
}
|
||||
export async function handleDiscordSubagentSpawning(
|
||||
api: OpenClawPluginApi,
|
||||
event: DiscordSubagentSpawningEvent,
|
||||
) {
|
||||
if (!event.threadRequested) {
|
||||
return;
|
||||
}
|
||||
const channel = event.requester?.channel?.trim().toLowerCase();
|
||||
if (channel !== "discord") {
|
||||
return;
|
||||
}
|
||||
const threadBindingFlags = resolveThreadBindingFlags(api, event.requester?.accountId);
|
||||
if (!threadBindingFlags.enabled) {
|
||||
return {
|
||||
origin: {
|
||||
channel: "discord",
|
||||
accountId: binding.accountId,
|
||||
to: `channel:${binding.threadId}`,
|
||||
threadId: binding.threadId,
|
||||
},
|
||||
status: "error" as const,
|
||||
error:
|
||||
"Discord thread bindings are disabled (set channels.discord.threadBindings.enabled=true to override for this account, or session.threadBindings.enabled=true globally).",
|
||||
};
|
||||
}
|
||||
if (!threadBindingFlags.spawnSubagentSessions) {
|
||||
return {
|
||||
status: "error" as const,
|
||||
error:
|
||||
"Discord thread-bound subagent spawns are disabled for this account (set channels.discord.threadBindings.spawnSubagentSessions=true to enable).",
|
||||
};
|
||||
}
|
||||
try {
|
||||
const binding = await autoBindSpawnedDiscordSubagent({
|
||||
accountId: event.requester?.accountId,
|
||||
channel: event.requester?.channel,
|
||||
to: event.requester?.to,
|
||||
threadId: event.requester?.threadId,
|
||||
childSessionKey: event.childSessionKey,
|
||||
agentId: event.agentId,
|
||||
label: event.label,
|
||||
boundBy: "system",
|
||||
});
|
||||
if (!binding) {
|
||||
return {
|
||||
status: "error" as const,
|
||||
error:
|
||||
"Unable to create or bind a Discord thread for this subagent session. Session mode is unavailable for this target.",
|
||||
};
|
||||
}
|
||||
return { status: "ok" as const, threadBindingReady: true };
|
||||
} catch (err) {
|
||||
return {
|
||||
status: "error" as const,
|
||||
error: `Discord thread bind failed: ${summarizeError(err)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function handleDiscordSubagentEnded(event: DiscordSubagentEndedEvent) {
|
||||
unbindThreadBindingsBySessionKey({
|
||||
targetSessionKey: event.targetSessionKey,
|
||||
accountId: event.accountId,
|
||||
targetKind: event.targetKind,
|
||||
reason: event.reason,
|
||||
sendFarewell: event.sendFarewell,
|
||||
});
|
||||
}
|
||||
|
||||
export function handleDiscordSubagentDeliveryTarget(event: DiscordSubagentDeliveryTargetEvent) {
|
||||
if (!event.expectsCompletionMessage) {
|
||||
return;
|
||||
}
|
||||
const requesterChannel = event.requesterOrigin?.channel?.trim().toLowerCase();
|
||||
if (requesterChannel !== "discord") {
|
||||
return;
|
||||
}
|
||||
const requesterAccountId = event.requesterOrigin?.accountId?.trim();
|
||||
const requesterThreadId =
|
||||
event.requesterOrigin?.threadId != null && event.requesterOrigin.threadId !== ""
|
||||
? String(event.requesterOrigin.threadId).trim()
|
||||
: "";
|
||||
const bindings = listThreadBindingsBySessionKey({
|
||||
targetSessionKey: event.childSessionKey,
|
||||
...(requesterAccountId ? { accountId: requesterAccountId } : {}),
|
||||
targetKind: "subagent",
|
||||
});
|
||||
if (bindings.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let binding: (typeof bindings)[number] | undefined;
|
||||
if (requesterThreadId) {
|
||||
binding = bindings.find((entry) => {
|
||||
if (entry.threadId !== requesterThreadId) {
|
||||
return false;
|
||||
}
|
||||
if (requesterAccountId && entry.accountId !== requesterAccountId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (!binding && bindings.length === 1) {
|
||||
binding = bindings[0];
|
||||
}
|
||||
if (!binding) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
origin: {
|
||||
channel: "discord" as const,
|
||||
accountId: binding.accountId,
|
||||
to: `channel:${binding.threadId}`,
|
||||
threadId: binding.threadId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue