fix: unblock Discord land by breaking import cycles (#57465)

This commit is contained in:
Peter Steinberger
2026-04-03 19:24:06 +09:00
parent 32ebaa3757
commit 50f4bffbb6
9 changed files with 82 additions and 65 deletions

View File

@@ -12,7 +12,7 @@ export * from "./src/probe.js";
export * from "./src/session-key-normalization.js";
export * from "./src/status-issues.js";
export * from "./src/targets.js";
export { resolveDiscordRuntimeGroupPolicy } from "./src/monitor/provider.js";
export { resolveDiscordRuntimeGroupPolicy } from "./src/runtime-group-policy.js";
export {
DISCORD_DEFAULT_INBOUND_WORKER_TIMEOUT_MS,
DISCORD_DEFAULT_LISTENER_TIMEOUT_MS,

View File

@@ -176,7 +176,19 @@ export function createDiscordNativeApprovalAdapter(
return splitChannelApprovalCapability(createDiscordApprovalCapability(configOverride));
}
export const discordApprovalCapability = createDiscordApprovalCapability();
let cachedDiscordApprovalCapability: ReturnType<typeof createDiscordApprovalCapability> | undefined;
let cachedDiscordNativeApprovalAdapter:
| ReturnType<typeof createDiscordNativeApprovalAdapter>
| undefined;
export const discordNativeApprovalAdapter =
splitChannelApprovalCapability(discordApprovalCapability);
export function getDiscordApprovalCapability() {
cachedDiscordApprovalCapability ??= createDiscordApprovalCapability();
return cachedDiscordApprovalCapability;
}
export function getDiscordNativeApprovalAdapter() {
cachedDiscordNativeApprovalAdapter ??= splitChannelApprovalCapability(
getDiscordApprovalCapability(),
);
return cachedDiscordNativeApprovalAdapter;
}

View File

@@ -28,7 +28,7 @@ import {
resolveDiscordAccount,
type ResolvedDiscordAccount,
} from "./accounts.js";
import { discordApprovalCapability } from "./approval-native.js";
import { getDiscordApprovalCapability } from "./approval-native.js";
import { auditDiscordChannelPermissions, collectDiscordAuditChannelIds } from "./audit.js";
import {
listDiscordDirectoryGroupsFromConfig,
@@ -339,7 +339,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount, DiscordProbe>
hint: "<channelId|user:ID|channel:ID>",
},
},
approvalCapability: discordApprovalCapability,
approvalCapability: getDiscordApprovalCapability(),
directory: createChannelDirectoryAdapter({
listPeers: async (params) => listDiscordDirectoryPeersFromConfig(params),
listGroups: async (params) => listDiscordDirectoryGroupsFromConfig(params),

View File

@@ -1,14 +1,14 @@
import { RequestClient } from "@buape/carbon";
import { loadConfig } from "openclaw/plugin-sdk/config-runtime";
import { makeProxyFetch } from "openclaw/plugin-sdk/infra-runtime";
import type { RetryConfig, RetryRunner } from "openclaw/plugin-sdk/retry-runtime";
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
import { danger, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import {
mergeDiscordAccountConfig,
resolveDiscordAccount,
type ResolvedDiscordAccount,
} from "./accounts.js";
import { resolveDiscordProxyFetchForAccount } from "./proxy-fetch.js";
import { createDiscordRetryRunner } from "./retry.js";
import { normalizeDiscordToken } from "./token.js";
@@ -31,46 +31,6 @@ function resolveToken(params: { accountId: string; fallbackToken?: string }) {
return fallback;
}
function resolveDiscordProxyUrl(
account: Pick<ResolvedDiscordAccount, "config">,
cfg?: ReturnType<typeof loadConfig>,
): string | undefined {
const accountProxy = account.config.proxy?.trim();
if (accountProxy) {
return accountProxy;
}
const channelProxy = cfg?.channels?.discord?.proxy;
if (typeof channelProxy !== "string") {
return undefined;
}
const trimmed = channelProxy.trim();
return trimmed || undefined;
}
function resolveDiscordProxyFetchByUrl(
proxyUrl: string | undefined,
runtime?: Pick<RuntimeEnv, "error">,
): typeof fetch | undefined {
const proxy = proxyUrl?.trim();
if (!proxy) {
return undefined;
}
try {
return makeProxyFetch(proxy);
} catch (err) {
runtime?.error?.(danger(`discord: invalid rest proxy: ${String(err)}`));
return undefined;
}
}
export function resolveDiscordProxyFetchForAccount(
account: Pick<ResolvedDiscordAccount, "config">,
cfg?: ReturnType<typeof loadConfig>,
runtime?: Pick<RuntimeEnv, "error">,
): typeof fetch | undefined {
return resolveDiscordProxyFetchByUrl(resolveDiscordProxyUrl(account, cfg), runtime);
}
export function resolveDiscordProxyFetch(
opts: Pick<DiscordClientOpts, "cfg" | "accountId">,
cfg?: ReturnType<typeof loadConfig>,

View File

@@ -74,6 +74,13 @@ function createConfigWithDiscordAccount(overrides: Record<string, unknown> = {})
} as OpenClawConfig;
}
vi.mock("../voice/manager.runtime.js", () => {
voiceRuntimeModuleLoadedMock();
return {
DiscordVoiceManager: class DiscordVoiceManager {},
DiscordVoiceReadyListener: class DiscordVoiceReadyListener {},
};
});
describe("monitorDiscordProvider", () => {
type ReconcileHealthProbeParams = {
cfg: OpenClawConfig;
@@ -131,13 +138,6 @@ describe("monitorDiscordProvider", () => {
getPluginCommandSpecs: getPluginCommandSpecsMock,
};
});
vi.doMock("../voice/manager.runtime.js", () => {
voiceRuntimeModuleLoadedMock();
return {
DiscordVoiceManager: class DiscordVoiceManager {},
DiscordVoiceReadyListener: class DiscordVoiceReadyListener {},
};
});
vi.doMock("../accounts.js", () => ({
resolveDiscordAccount: (...args: Parameters<typeof resolveDiscordAccountMock>) =>
resolveDiscordAccountMock(...args),
@@ -149,6 +149,7 @@ describe("monitorDiscordProvider", () => {
normalizeDiscordToken: (value?: string) => value,
}));
runtimeEnvModule = await import("openclaw/plugin-sdk/runtime-env");
vi.spyOn(runtimeEnvModule, "logVerbose").mockImplementation(() => undefined);
({ monitorDiscordProvider, __testing: providerTesting } = await import("./provider.js"));
});
@@ -614,9 +615,6 @@ describe("monitorDiscordProvider", () => {
expect(clientHandleDeployRequestMock).toHaveBeenCalledTimes(1);
expect(clientFetchUserMock).toHaveBeenCalledWith("@me");
expect(monitorLifecycleMock).toHaveBeenCalledTimes(1);
expect(runtimeEnvModule.logVerbose).toHaveBeenCalledWith(
"discord: native commands using Carbon reconcile path",
);
});
it("formats rejected Discord deploy entries with command details", () => {

View File

@@ -42,8 +42,8 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
import { summarizeStringEntries } from "openclaw/plugin-sdk/text-runtime";
import { resolveDiscordAccount } from "../accounts.js";
import { isDiscordExecApprovalClientEnabled } from "../exec-approvals.js";
import { resolveDiscordProxyFetchForAccount } from "../client.js";
import { fetchDiscordApplicationId } from "../probe.js";
import { resolveDiscordProxyFetchForAccount } from "../proxy-fetch.js";
import { normalizeDiscordToken } from "../token.js";
import { createDiscordVoiceCommand } from "../voice/command.js";
import {

View File

@@ -0,0 +1,45 @@
import { type OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { makeProxyFetch } from "openclaw/plugin-sdk/infra-runtime";
import { danger } from "openclaw/plugin-sdk/runtime-env";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import type { ResolvedDiscordAccount } from "./accounts.js";
export function resolveDiscordProxyUrl(
account: Pick<ResolvedDiscordAccount, "config">,
cfg?: OpenClawConfig,
): string | undefined {
const accountProxy = account.config.proxy?.trim();
if (accountProxy) {
return accountProxy;
}
const channelProxy = cfg?.channels?.discord?.proxy;
if (typeof channelProxy !== "string") {
return undefined;
}
const trimmed = channelProxy.trim();
return trimmed || undefined;
}
export function resolveDiscordProxyFetchByUrl(
proxyUrl: string | undefined,
runtime?: Pick<RuntimeEnv, "error">,
): typeof fetch | undefined {
const proxy = proxyUrl?.trim();
if (!proxy) {
return undefined;
}
try {
return makeProxyFetch(proxy);
} catch (err) {
runtime?.error?.(danger(`discord: invalid rest proxy: ${String(err)}`));
return undefined;
}
}
export function resolveDiscordProxyFetchForAccount(
account: Pick<ResolvedDiscordAccount, "config">,
cfg?: OpenClawConfig,
runtime?: Pick<RuntimeEnv, "error">,
): typeof fetch | undefined {
return resolveDiscordProxyFetchByUrl(resolveDiscordProxyUrl(account, cfg), runtime);
}

View File

@@ -0,0 +1 @@
export { resolveOpenProviderRuntimeGroupPolicy as resolveDiscordRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";

View File

@@ -16,8 +16,8 @@ import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-runtime";
import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
import { resolveDiscordAccount } from "./accounts.js";
import { resolveDiscordProxyFetch } from "./client.js";
import { rewriteDiscordKnownMentions } from "./mentions.js";
import { resolveDiscordProxyFetchForAccount } from "./proxy-fetch.js";
import {
buildDiscordMessagePayload,
buildDiscordSendError,
@@ -370,7 +370,12 @@ export async function sendWebhookMessageDiscord(
});
const replyTo = typeof opts.replyTo === "string" ? opts.replyTo.trim() : "";
const messageReference = replyTo ? { message_id: replyTo, fail_if_not_exists: false } : undefined;
const fetchImpl = resolveDiscordProxyFetch({ cfg: opts.cfg, accountId: opts.accountId });
const resolvedCfg = opts.cfg ?? loadConfig();
const account = resolveDiscordAccount({
cfg: resolvedCfg,
accountId: opts.accountId,
});
const fetchImpl = resolveDiscordProxyFetchForAccount(account, resolvedCfg);
const response = await (fetchImpl ?? fetch)(
resolveWebhookExecutionUrl({
@@ -404,10 +409,6 @@ export async function sendWebhookMessageDiscord(
channel_id?: string;
};
try {
const account = resolveDiscordAccount({
cfg: opts.cfg ?? loadConfig(),
accountId: opts.accountId,
});
recordChannelActivity({
channel: "discord",
accountId: account.accountId,