refactor: reduce unnecessary dynamic imports

This commit is contained in:
Peter Steinberger
2026-04-18 16:15:22 +01:00
parent 3f2e73b723
commit 66385670e4
22 changed files with 114 additions and 65 deletions

View File

@@ -1,3 +1,4 @@
import fs from "node:fs/promises";
import type { Command } from "commander";
import { callBrowserRequest, type BrowserParentOpts } from "../browser-cli-shared.js";
import {
@@ -63,7 +64,6 @@ export function requireRef(ref: string | undefined) {
}
async function readFile(path: string): Promise<string> {
const fs = await import("node:fs/promises");
return await fs.readFile(path, "utf8");
}

View File

@@ -1,3 +1,4 @@
import fs from "node:fs/promises";
import type { Command } from "commander";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js";
@@ -101,7 +102,6 @@ export function registerBrowserInspectCommands(
);
if (opts.out) {
const fs = await import("node:fs/promises");
if (result.format === "ai") {
await fs.writeFile(opts.out, result.snapshot, "utf8");
} else {

View File

@@ -67,7 +67,7 @@ import { collectFeishuSecurityAuditFindings } from "./security-audit.js";
import { resolveFeishuSessionConversation } from "./session-conversation.js";
import { resolveFeishuOutboundSessionRoute } from "./session-route.js";
import { feishuSetupAdapter } from "./setup-core.js";
import { feishuSetupWizard } from "./setup-surface.js";
import { feishuSetupWizard, runFeishuLogin } from "./setup-surface.js";
import { looksLikeFeishuId, normalizeFeishuTarget } from "./targets.js";
import type { FeishuConfig, FeishuProbeResult, ResolvedFeishuAccount } from "./types.js";
@@ -1096,7 +1096,6 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount, FeishuProbeResul
const { createClackPrompter } = await import("openclaw/plugin-sdk/setup-runtime");
const { writeConfigFile } = await import("openclaw/plugin-sdk/config-runtime");
const prompter = createClackPrompter();
const { runFeishuLogin } = await import("./setup-surface.js");
const nextCfg = await runFeishuLogin({ cfg, prompter });
if (nextCfg !== cfg) {
await writeConfigFile(nextCfg);

View File

@@ -14,6 +14,7 @@ import {
downloadGoogleChatMedia,
deleteGoogleChatMessage,
sendGoogleChatMessage,
uploadGoogleChatAttachment,
updateGoogleChatMessage,
} from "./api.js";
import { type GoogleChatAudienceType } from "./auth.js";
@@ -451,7 +452,6 @@ async function uploadAttachmentForReply(params: {
filename: string;
}) {
const { account, spaceId, buffer, contentType, filename } = params;
const { uploadGoogleChatAttachment } = await import("./api.js");
return await uploadGoogleChatAttachment({
account,
space: spaceId,

View File

@@ -1,3 +1,4 @@
import { stat } from "node:fs/promises";
import path from "node:path";
import { Readable, Writable } from "node:stream";
import {
@@ -177,7 +178,6 @@ function throwOnErrorEnvelope(envelope: LobsterEnvelope): Extract<LobsterEnvelop
}
async function resolveWorkflowFile(candidate: string, cwd: string) {
const { stat } = await import("node:fs/promises");
const resolved = path.isAbsolute(candidate) ? candidate : path.resolve(cwd, candidate);
const fileStat = await stat(resolved);
if (!fileStat.isFile()) {

View File

@@ -10,6 +10,7 @@
*/
import type { IncomingMessage, ServerResponse } from "node:http";
import { Readable } from "node:stream";
import type { MattermostConfig } from "../types.js";
import type { ResolvedMattermostAccount } from "./accounts.js";
import type { OpenClawPluginApi } from "./runtime-api.js";
@@ -284,7 +285,6 @@ export function registerSlashCommandRoute(api: OpenClawPluginApi) {
const matchedHandler = match.handler!;
// Replay: create a synthetic readable that re-emits the buffered body
const { Readable } = await import("node:stream");
const syntheticReq = new Readable({
read() {
this.push(Buffer.from(bodyStr, "utf8"));

View File

@@ -1,3 +1,6 @@
import fs from "node:fs/promises";
import path from "node:path";
/** Default cooldown between reflections per session (5 minutes). */
export const DEFAULT_COOLDOWN_MS = 300_000;
@@ -26,8 +29,6 @@ function resolveLegacyLearningsFilePath(storePath: string, sessionKey: string):
async function readLearningsFile(
filePath: string,
): Promise<{ exists: boolean; learnings: string[] }> {
const fs = await import("node:fs/promises");
try {
const content = await fs.readFile(filePath, "utf-8");
const parsed = JSON.parse(content);
@@ -77,9 +78,6 @@ export async function storeSessionLearning(params: {
sessionKey: string;
learning: string;
}): Promise<void> {
const fs = await import("node:fs/promises");
const path = await import("node:path");
const learningsFile = resolveLearningsFilePath(params.storePath, params.sessionKey);
const legacyLearningsFile = resolveLegacyLearningsFilePath(params.storePath, params.sessionKey);
const { exists, learnings: existingLearnings } = await readLearningsFile(learningsFile);

View File

@@ -4,7 +4,7 @@ import { GRAPH_ROOT } from "./attachments/shared.js";
const GRAPH_BETA = "https://graph.microsoft.com/beta";
import { createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js";
import { readAccessToken } from "./token-response.js";
import { resolveMSTeamsCredentials } from "./token.js";
import { resolveDelegatedAccessToken, resolveMSTeamsCredentials } from "./token.js";
import { buildUserAgent } from "./user-agent.js";
export type GraphUser = {
@@ -199,8 +199,6 @@ export async function resolveGraphToken(
// Try delegated token if requested and configured
if (options?.preferDelegated && msteamsCfg?.delegatedAuth?.enabled && creds.type === "secret") {
// Dynamic import to avoid circular dependency (token.ts imports from graph.ts indirectly)
const { resolveDelegatedAccessToken } = await import("./token.js");
const delegated = await resolveDelegatedAccessToken({
tenantId: creds.tenantId,
clientId: creds.appId,

View File

@@ -1,3 +1,5 @@
import fs from "node:fs/promises";
import path from "node:path";
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import { formatUnknownError } from "./errors.js";
@@ -388,10 +390,8 @@ async function handleFeedbackInvoke(
const storePath = core.channel.session.resolveStorePath(deps.cfg.session?.store, {
agentId: route.agentId,
});
const fs = await import("node:fs/promises");
const pathMod = await import("node:path");
const safeKey = route.sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_");
const transcriptFile = pathMod.join(storePath, `${safeKey}.jsonl`);
const transcriptFile = path.join(storePath, `${safeKey}.jsonl`);
await fs.appendFile(transcriptFile, JSON.stringify(feedbackEvent) + "\n", "utf-8").catch(() => {
// Best effort — transcript dir may not exist yet
});

View File

@@ -6,7 +6,7 @@ import {
import { formatUnknownError } from "./errors.js";
import { createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js";
import { readAccessToken } from "./token-response.js";
import { resolveMSTeamsCredentials } from "./token.js";
import { loadDelegatedTokens, resolveMSTeamsCredentials } from "./token.js";
export type ProbeMSTeamsResult = BaseProbeResult<string> & {
appId?: string;
@@ -100,7 +100,6 @@ export async function probeMSTeams(cfg?: MSTeamsConfig): Promise<ProbeMSTeamsRes
let delegatedAuth: ProbeMSTeamsResult["delegatedAuth"];
if (cfg?.delegatedAuth?.enabled) {
try {
const { loadDelegatedTokens } = await import("./token.js");
const tokens = loadDelegatedTokens();
if (tokens) {
const isExpired = tokens.expiresAt <= Date.now();

View File

@@ -18,7 +18,7 @@ import {
resolveMSTeamsUserAllowlist,
} from "./resolve-allowlist.js";
import { createMSTeamsSetupWizardBase, msteamsSetupAdapter } from "./setup-core.js";
import { resolveMSTeamsCredentials } from "./token.js";
import { resolveMSTeamsCredentials, saveDelegatedTokens } from "./token.js";
const channel = "msteams" as const;
const setMSTeamsAllowFrom = createTopLevelChannelAllowFromSetter({
@@ -279,7 +279,6 @@ export const msteamsSetupWizard: ChannelSetupWizard = {
};
try {
const { loginMSTeamsDelegated } = await import("./oauth.js");
const { saveDelegatedTokens } = await import("./token.js");
const { shouldUseManualOAuthFlow } = await import("./oauth.flow.js");
const isRemote = Boolean(process.env.SSH_TTY || process.env.SSH_CONNECTION);
const progress = params.prompter.progress("MSTeams Delegated OAuth");

View File

@@ -1,3 +1,4 @@
import { execFile } from "node:child_process";
import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
@@ -352,7 +353,6 @@ export async function buildQaDockerHarnessImage(
const runCommand =
deps?.runCommand ??
(async (command: string, args: string[], cwd: string) => {
const { execFile } = await import("node:child_process");
return await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
execFile(command, args, { cwd }, (error, stdout, stderr) => {
if (error) {

View File

@@ -40,22 +40,13 @@ type SignalTarget =
| { type: "group"; groupId: string }
| { type: "username"; username: string };
let signalConfigRuntimePromise:
| Promise<typeof import("openclaw/plugin-sdk/config-runtime")>
| undefined;
async function loadSignalConfigRuntime() {
signalConfigRuntimePromise ??= import("openclaw/plugin-sdk/config-runtime");
return await signalConfigRuntimePromise;
}
async function resolveSignalRpcAccountInfo(
opts: Pick<SignalSendOpts, "cfg" | "baseUrl" | "account" | "accountId">,
) {
if (opts.baseUrl?.trim() && opts.account?.trim()) {
return undefined;
}
const cfg = opts.cfg ?? (await loadSignalConfigRuntime()).loadConfig();
const cfg = opts.cfg ?? loadConfig();
return resolveSignalAccount({
cfg,
accountId: opts.accountId,

View File

@@ -39,6 +39,50 @@ type ResolvedRealtimeProvider = {
providerConfig: RealtimeVoiceProviderConfig;
};
type TelnyxProviderModule = typeof import("./providers/telnyx.js");
type TwilioProviderModule = typeof import("./providers/twilio.js");
type PlivoProviderModule = typeof import("./providers/plivo.js");
type MockProviderModule = typeof import("./providers/mock.js");
type RealtimeVoiceRuntimeModule = typeof import("./realtime-voice.runtime.js");
type RealtimeHandlerModule = typeof import("./webhook/realtime-handler.js");
let telnyxProviderPromise: Promise<TelnyxProviderModule> | undefined;
let twilioProviderPromise: Promise<TwilioProviderModule> | undefined;
let plivoProviderPromise: Promise<PlivoProviderModule> | undefined;
let mockProviderPromise: Promise<MockProviderModule> | undefined;
let realtimeVoiceRuntimePromise: Promise<RealtimeVoiceRuntimeModule> | undefined;
let realtimeHandlerPromise: Promise<RealtimeHandlerModule> | undefined;
function loadTelnyxProvider(): Promise<TelnyxProviderModule> {
telnyxProviderPromise ??= import("./providers/telnyx.js");
return telnyxProviderPromise;
}
function loadTwilioProvider(): Promise<TwilioProviderModule> {
twilioProviderPromise ??= import("./providers/twilio.js");
return twilioProviderPromise;
}
function loadPlivoProvider(): Promise<PlivoProviderModule> {
plivoProviderPromise ??= import("./providers/plivo.js");
return plivoProviderPromise;
}
function loadMockProvider(): Promise<MockProviderModule> {
mockProviderPromise ??= import("./providers/mock.js");
return mockProviderPromise;
}
function loadRealtimeVoiceRuntime(): Promise<RealtimeVoiceRuntimeModule> {
realtimeVoiceRuntimePromise ??= import("./realtime-voice.runtime.js");
return realtimeVoiceRuntimePromise;
}
function loadRealtimeHandler(): Promise<RealtimeHandlerModule> {
realtimeHandlerPromise ??= import("./webhook/realtime-handler.js");
return realtimeHandlerPromise;
}
function createRuntimeResourceLifecycle(params: {
config: VoiceCallConfig;
webhookServer: VoiceCallWebhookServer;
@@ -97,7 +141,7 @@ async function resolveProvider(config: VoiceCallConfig): Promise<VoiceCallProvid
switch (config.provider) {
case "telnyx": {
const { TelnyxProvider } = await import("./providers/telnyx.js");
const { TelnyxProvider } = await loadTelnyxProvider();
return new TelnyxProvider(
{
apiKey: config.telnyx?.apiKey,
@@ -110,7 +154,7 @@ async function resolveProvider(config: VoiceCallConfig): Promise<VoiceCallProvid
);
}
case "twilio": {
const { TwilioProvider } = await import("./providers/twilio.js");
const { TwilioProvider } = await loadTwilioProvider();
return new TwilioProvider(
{
accountSid: config.twilio?.accountSid,
@@ -126,7 +170,7 @@ async function resolveProvider(config: VoiceCallConfig): Promise<VoiceCallProvid
);
}
case "plivo": {
const { PlivoProvider } = await import("./providers/plivo.js");
const { PlivoProvider } = await loadPlivoProvider();
return new PlivoProvider(
{
authId: config.plivo?.authId,
@@ -141,7 +185,7 @@ async function resolveProvider(config: VoiceCallConfig): Promise<VoiceCallProvid
);
}
case "mock": {
const { MockProvider } = await import("./providers/mock.js");
const { MockProvider } = await loadMockProvider();
return new MockProvider();
}
default:
@@ -153,8 +197,7 @@ async function resolveRealtimeProvider(params: {
config: VoiceCallConfig;
fullConfig: OpenClawConfig;
}): Promise<ResolvedRealtimeProvider> {
const { getRealtimeVoiceProvider, listRealtimeVoiceProviders } =
await import("./realtime-voice.runtime.js");
const { getRealtimeVoiceProvider, listRealtimeVoiceProviders } = await loadRealtimeVoiceRuntime();
const resolution = resolveConfiguredCapabilityProvider({
configuredProviderId: params.config.realtime.provider,
providerConfigs: params.config.realtime.providers,
@@ -236,7 +279,7 @@ export async function createVoiceCallRuntime(params: {
agentRuntime,
);
if (realtimeProvider) {
const { RealtimeCallHandler } = await import("./webhook/realtime-handler.js");
const { RealtimeCallHandler } = await loadRealtimeHandler();
webhookServer.setRealtimeHandler(
new RealtimeCallHandler(
config.realtime,

View File

@@ -1,3 +1,4 @@
import { join } from "node:path";
import { loadConfig } from "../config/config.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
@@ -116,7 +117,6 @@ export async function loadModelCatalog(params?: {
const agentDir = resolveOpenClawAgentDir();
const { shouldSuppressBuiltInModel } = await loadModelSuppression();
logStage("catalog-deps-ready");
const { join } = await import("node:path");
const authStorage = piSdk.discoverAuthStorage(agentDir);
logStage("auth-storage-ready");
const registry = instantiatePiModelRegistry(

View File

@@ -35,8 +35,37 @@ import type { createModelSelectionState } from "./model-selection.js";
import { extractInlineSimpleCommand } from "./reply-inline.js";
import type { TypingController } from "./typing.js";
type SkillCommandsRuntime = typeof import("../skill-commands.runtime.js");
type OpenClawToolsRuntime = typeof import("../../agents/openclaw-tools.runtime.js");
type AbortCutoffRuntime = typeof import("./abort-cutoff.runtime.js");
type CommandsRuntime = typeof import("./commands.runtime.js");
let skillCommandsRuntimePromise: Promise<SkillCommandsRuntime> | undefined;
let openClawToolsRuntimePromise: Promise<OpenClawToolsRuntime> | undefined;
let abortCutoffRuntimePromise: Promise<AbortCutoffRuntime> | undefined;
let commandsRuntimePromise: Promise<CommandsRuntime> | undefined;
let builtinSlashCommands: Set<string> | null = null;
function loadSkillCommandsRuntime(): Promise<SkillCommandsRuntime> {
skillCommandsRuntimePromise ??= import("../skill-commands.runtime.js");
return skillCommandsRuntimePromise;
}
function loadOpenClawToolsRuntime(): Promise<OpenClawToolsRuntime> {
openClawToolsRuntimePromise ??= import("../../agents/openclaw-tools.runtime.js");
return openClawToolsRuntimePromise;
}
function loadAbortCutoffRuntime(): Promise<AbortCutoffRuntime> {
abortCutoffRuntimePromise ??= import("./abort-cutoff.runtime.js");
return abortCutoffRuntimePromise;
}
function loadCommandsRuntime(): Promise<CommandsRuntime> {
commandsRuntimePromise ??= import("./commands.runtime.js");
return commandsRuntimePromise;
}
function getBuiltinSlashCommands(): Set<string> {
if (builtinSlashCommands) {
return builtinSlashCommands;
@@ -205,7 +234,7 @@ export async function handleInlineActions(params: {
shouldLoadSkillCommands && params.skillCommands
? params.skillCommands
: shouldLoadSkillCommands
? (await import("../skill-commands.runtime.js")).listSkillCommandsForWorkspace({
? (await loadSkillCommandsRuntime()).listSkillCommandsForWorkspace({
workspaceDir,
cfg,
agentId,
@@ -237,7 +266,7 @@ export async function handleInlineActions(params: {
resolveGatewayMessageChannel(ctx.Provider) ??
undefined;
const { createOpenClawTools } = await import("../../agents/openclaw-tools.runtime.js");
const { createOpenClawTools } = await loadOpenClawToolsRuntime();
const tools = createOpenClawTools({
agentSessionKey: sessionKey,
agentChannel: channel,
@@ -326,7 +355,7 @@ export async function handleInlineActions(params: {
}
if (cutoff) {
await (
await import("./abort-cutoff.runtime.js")
await loadAbortCutoffRuntime()
).clearAbortCutoffInSessionRuntime({
sessionEntry: targetSessionEntry,
sessionStore,
@@ -358,7 +387,7 @@ export async function handleInlineActions(params: {
}) && inlineStatusRequested;
let didSendInlineStatus = false;
if (handleInlineStatus) {
const { buildStatusReply } = await import("./commands.runtime.js");
const { buildStatusReply } = await loadCommandsRuntime();
const inlineStatusReply = await buildStatusReply({
cfg,
command,
@@ -385,7 +414,7 @@ export async function handleInlineActions(params: {
}
const runCommands = async (commandInput: typeof command) => {
const { handleCommands } = await import("./commands.runtime.js");
const { handleCommands } = await loadCommandsRuntime();
return handleCommands({
// Pass sessionCtx so command handlers can mutate stripped body for same-turn continuation.
ctx: sessionCtx,

View File

@@ -1,5 +1,8 @@
import { createWriteStream } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
import type { Command } from "commander";
import { agentCommand } from "../agents/agent-command.js";
import { resolveAgentDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
@@ -826,9 +829,6 @@ async function runVideoGenerate(params: { prompt: string; model?: string; output
throw new Error(`Failed to download video from ${video.url}: ${response.status}`);
}
if (params.output && response.body) {
const { pipeline } = await import("node:stream/promises");
const { Readable } = await import("node:stream");
const { createWriteStream } = await import("node:fs");
const mimeType = normalizeMimeType(video.mimeType);
const ext =
extensionForMime(mimeType) ||

View File

@@ -1,3 +1,4 @@
import { randomBytes } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { parseByteSize } from "../cli/parse-bytes.js";
@@ -135,7 +136,6 @@ async function pruneIfNeeded(filePath: string, opts: { maxBytes: number; keepLin
.map((l) => l.trim())
.filter(Boolean);
const kept = lines.slice(Math.max(0, lines.length - opts.keepLines));
const { randomBytes } = await import("node:crypto");
const tmp = `${filePath}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
await fs.writeFile(tmp, `${kept.join("\n")}\n`, { encoding: "utf-8", mode: 0o600 });
await setSecureFileMode(tmp);

View File

@@ -1,3 +1,4 @@
import { execFileSync } from "node:child_process";
import fs from "node:fs/promises";
import path from "node:path";
import {
@@ -157,7 +158,6 @@ async function resolveNodePath(): Promise<string> {
}
async function resolveBinaryPath(binary: string): Promise<string> {
const { execFileSync } = await import("node:child_process");
const cmd = process.platform === "win32" ? "where" : "which";
try {
const output = execFileSync(cmd, [binary], { encoding: "utf8" }).trim();

View File

@@ -15,6 +15,7 @@ import type {
} from "../../infra/session-cost-usage.js";
import {
loadCostUsageSummary,
loadSessionLogs,
loadSessionCostSummary,
loadSessionUsageTimeSeries,
discoverAllSessions,
@@ -880,7 +881,6 @@ export const usageHandlers: GatewayRequestHandlers = {
}
const { config, entry, agentId, sessionId, sessionFile } = resolved;
const { loadSessionLogs } = await import("../../infra/session-cost-usage.js");
const logs = await loadSessionLogs({
sessionId,
sessionEntry: entry,

View File

@@ -5,6 +5,7 @@ import type { FileHandle } from "node:fs/promises";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { pipeline } from "node:stream/promises";
import { logWarn } from "../logger.js";
import { resolveBoundaryPath } from "./boundary-path.js";
import { sameFileIdentity } from "./file-identity.js";
@@ -1107,9 +1108,7 @@ async function copyFileWithinRootLegacy(
targetStream.once("close", () => {
tempClosedByStream = true;
});
await import("node:stream/promises").then(({ pipeline }) =>
pipeline(sourceStream, targetStream),
);
await pipeline(sourceStream, targetStream);
const writtenStat = await fs.stat(tempPath);
if (!tempClosedByStream) {
await tempHandle.close().catch(() => {});

View File

@@ -1,3 +1,6 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js";
type CloseTrackedBrowserTabsParams = {
@@ -43,16 +46,7 @@ export async function closeTrackedBrowserTabsForSessions(
}
export async function movePathToTrash(targetPath: string): Promise<string> {
const [
{ default: fs },
{ default: os },
{ default: path },
{ generateSecureToken },
{ runExec },
] = await Promise.all([
import("node:fs"),
import("node:os"),
import("node:path"),
const [{ generateSecureToken }, { runExec }] = await Promise.all([
import("../infra/secure-random.js"),
import("../process/exec.js"),
]);