From 66385670e43f955e61bbab36ffc517071a00fa37 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 18 Apr 2026 16:15:22 +0100 Subject: [PATCH] refactor: reduce unnecessary dynamic imports --- .../cli/browser-cli-actions-input/shared.ts | 2 +- .../browser/src/cli/browser-cli-inspect.ts | 2 +- extensions/feishu/src/channel.ts | 3 +- extensions/googlechat/src/monitor.ts | 2 +- extensions/lobster/src/lobster-runner.ts | 2 +- .../mattermost/src/mattermost/slash-state.ts | 2 +- .../msteams/src/feedback-reflection-store.ts | 8 +-- extensions/msteams/src/graph.ts | 4 +- extensions/msteams/src/monitor-handler.ts | 6 +- extensions/msteams/src/probe.ts | 3 +- extensions/msteams/src/setup-surface.ts | 3 +- extensions/qa-lab/src/docker-harness.ts | 2 +- extensions/signal/src/send.ts | 11 +--- extensions/voice-call/src/runtime.ts | 57 ++++++++++++++++--- src/agents/model-catalog.ts | 2 +- .../reply/get-reply-inline-actions.ts | 39 +++++++++++-- src/cli/capability-cli.ts | 6 +- src/cron/run-log.ts | 2 +- src/daemon/program-args.ts | 2 +- src/gateway/server-methods/usage.ts | 2 +- src/infra/fs-safe.ts | 5 +- src/plugin-sdk/browser-maintenance.ts | 14 ++--- 22 files changed, 114 insertions(+), 65 deletions(-) diff --git a/extensions/browser/src/cli/browser-cli-actions-input/shared.ts b/extensions/browser/src/cli/browser-cli-actions-input/shared.ts index 050f2ed7b9d..17dd630e1a3 100644 --- a/extensions/browser/src/cli/browser-cli-actions-input/shared.ts +++ b/extensions/browser/src/cli/browser-cli-actions-input/shared.ts @@ -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 { - const fs = await import("node:fs/promises"); return await fs.readFile(path, "utf8"); } diff --git a/extensions/browser/src/cli/browser-cli-inspect.ts b/extensions/browser/src/cli/browser-cli-inspect.ts index dd122269294..1366a40546f 100644 --- a/extensions/browser/src/cli/browser-cli-inspect.ts +++ b/extensions/browser/src/cli/browser-cli-inspect.ts @@ -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 { diff --git a/extensions/feishu/src/channel.ts b/extensions/feishu/src/channel.ts index 03809097518..d07d94cc0cb 100644 --- a/extensions/feishu/src/channel.ts +++ b/extensions/feishu/src/channel.ts @@ -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 { - 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 { - 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); diff --git a/extensions/msteams/src/graph.ts b/extensions/msteams/src/graph.ts index 1f4bbc6b247..08fcce4fce3 100644 --- a/extensions/msteams/src/graph.ts +++ b/extensions/msteams/src/graph.ts @@ -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, diff --git a/extensions/msteams/src/monitor-handler.ts b/extensions/msteams/src/monitor-handler.ts index 53c75f89a7c..1e69e38f87c 100644 --- a/extensions/msteams/src/monitor-handler.ts +++ b/extensions/msteams/src/monitor-handler.ts @@ -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 }); diff --git a/extensions/msteams/src/probe.ts b/extensions/msteams/src/probe.ts index 59fd31ee422..ab95e755ace 100644 --- a/extensions/msteams/src/probe.ts +++ b/extensions/msteams/src/probe.ts @@ -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 & { appId?: string; @@ -100,7 +100,6 @@ export async function probeMSTeams(cfg?: MSTeamsConfig): Promise { - 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) { diff --git a/extensions/signal/src/send.ts b/extensions/signal/src/send.ts index cb094e91efe..7947b4fdd2a 100644 --- a/extensions/signal/src/send.ts +++ b/extensions/signal/src/send.ts @@ -40,22 +40,13 @@ type SignalTarget = | { type: "group"; groupId: string } | { type: "username"; username: string }; -let signalConfigRuntimePromise: - | Promise - | undefined; - -async function loadSignalConfigRuntime() { - signalConfigRuntimePromise ??= import("openclaw/plugin-sdk/config-runtime"); - return await signalConfigRuntimePromise; -} - async function resolveSignalRpcAccountInfo( opts: Pick, ) { 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, diff --git a/extensions/voice-call/src/runtime.ts b/extensions/voice-call/src/runtime.ts index 5a944c3f76c..46d071212ff 100644 --- a/extensions/voice-call/src/runtime.ts +++ b/extensions/voice-call/src/runtime.ts @@ -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 | undefined; +let twilioProviderPromise: Promise | undefined; +let plivoProviderPromise: Promise | undefined; +let mockProviderPromise: Promise | undefined; +let realtimeVoiceRuntimePromise: Promise | undefined; +let realtimeHandlerPromise: Promise | undefined; + +function loadTelnyxProvider(): Promise { + telnyxProviderPromise ??= import("./providers/telnyx.js"); + return telnyxProviderPromise; +} + +function loadTwilioProvider(): Promise { + twilioProviderPromise ??= import("./providers/twilio.js"); + return twilioProviderPromise; +} + +function loadPlivoProvider(): Promise { + plivoProviderPromise ??= import("./providers/plivo.js"); + return plivoProviderPromise; +} + +function loadMockProvider(): Promise { + mockProviderPromise ??= import("./providers/mock.js"); + return mockProviderPromise; +} + +function loadRealtimeVoiceRuntime(): Promise { + realtimeVoiceRuntimePromise ??= import("./realtime-voice.runtime.js"); + return realtimeVoiceRuntimePromise; +} + +function loadRealtimeHandler(): Promise { + 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 { - 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, diff --git a/src/agents/model-catalog.ts b/src/agents/model-catalog.ts index e9ffbff06ae..75b5b18d319 100644 --- a/src/agents/model-catalog.ts +++ b/src/agents/model-catalog.ts @@ -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( diff --git a/src/auto-reply/reply/get-reply-inline-actions.ts b/src/auto-reply/reply/get-reply-inline-actions.ts index e11da667b9a..c21cc7bad85 100644 --- a/src/auto-reply/reply/get-reply-inline-actions.ts +++ b/src/auto-reply/reply/get-reply-inline-actions.ts @@ -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 | undefined; +let openClawToolsRuntimePromise: Promise | undefined; +let abortCutoffRuntimePromise: Promise | undefined; +let commandsRuntimePromise: Promise | undefined; let builtinSlashCommands: Set | null = null; +function loadSkillCommandsRuntime(): Promise { + skillCommandsRuntimePromise ??= import("../skill-commands.runtime.js"); + return skillCommandsRuntimePromise; +} + +function loadOpenClawToolsRuntime(): Promise { + openClawToolsRuntimePromise ??= import("../../agents/openclaw-tools.runtime.js"); + return openClawToolsRuntimePromise; +} + +function loadAbortCutoffRuntime(): Promise { + abortCutoffRuntimePromise ??= import("./abort-cutoff.runtime.js"); + return abortCutoffRuntimePromise; +} + +function loadCommandsRuntime(): Promise { + commandsRuntimePromise ??= import("./commands.runtime.js"); + return commandsRuntimePromise; +} + function getBuiltinSlashCommands(): Set { 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, diff --git a/src/cli/capability-cli.ts b/src/cli/capability-cli.ts index d4775bfc253..419722b11d1 100644 --- a/src/cli/capability-cli.ts +++ b/src/cli/capability-cli.ts @@ -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) || diff --git a/src/cron/run-log.ts b/src/cron/run-log.ts index d8898ce00dc..0a14a343238 100644 --- a/src/cron/run-log.ts +++ b/src/cron/run-log.ts @@ -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); diff --git a/src/daemon/program-args.ts b/src/daemon/program-args.ts index d435649fe60..b4148126954 100644 --- a/src/daemon/program-args.ts +++ b/src/daemon/program-args.ts @@ -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 { } async function resolveBinaryPath(binary: string): Promise { - const { execFileSync } = await import("node:child_process"); const cmd = process.platform === "win32" ? "where" : "which"; try { const output = execFileSync(cmd, [binary], { encoding: "utf8" }).trim(); diff --git a/src/gateway/server-methods/usage.ts b/src/gateway/server-methods/usage.ts index 4b159dae061..50e5cbbd4d2 100644 --- a/src/gateway/server-methods/usage.ts +++ b/src/gateway/server-methods/usage.ts @@ -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, diff --git a/src/infra/fs-safe.ts b/src/infra/fs-safe.ts index ea6fe2c7d16..46254451d87 100644 --- a/src/infra/fs-safe.ts +++ b/src/infra/fs-safe.ts @@ -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(() => {}); diff --git a/src/plugin-sdk/browser-maintenance.ts b/src/plugin-sdk/browser-maintenance.ts index 1d0afcb9663..ba2958e0b67 100644 --- a/src/plugin-sdk/browser-maintenance.ts +++ b/src/plugin-sdk/browser-maintenance.ts @@ -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 { - 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"), ]);