From 59fb9e5ca7fe47d3e45413596524a7d19a43bdb4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 10:55:52 +0100 Subject: [PATCH] refactor: unify lazy import loaders --- src/agents/agent-command.ts | 102 ++++++++++-------- src/agents/auth-profiles/session-override.ts | 10 +- src/agents/bash-tools.exec.ts | 8 +- src/agents/command/session-store.ts | 11 +- src/agents/context-runtime-state.ts | 7 +- src/agents/context.ts | 3 +- src/agents/harness/tool-result-middleware.ts | 17 +-- src/agents/model-catalog.ts | 8 +- src/agents/model-fallback.ts | 8 +- .../pi-embedded-runner/compact.runtime.ts | 6 +- .../moonshot-thinking-stream-wrappers.ts | 6 +- .../pi-embedded-subscribe.handlers.tools.ts | 29 ++--- src/agents/pi-tools.ts | 8 +- src/agents/skills-install-download.ts | 6 +- src/agents/subagent-announce.ts | 10 +- src/agents/subagent-control.ts | 9 +- src/agents/subagent-registry-lifecycle.ts | 8 +- src/agents/subagent-registry.ts | 55 +++++----- src/agents/tools/session-status-tool.ts | 9 +- src/agents/tools/sessions-spawn-tool.ts | 8 +- src/agents/tools/web-fetch.ts | 15 +-- .../tools/web-search-provider-common.ts | 11 +- src/auto-reply/reply/agent-runner-memory.ts | 8 +- src/auto-reply/reply/agent-runner-payloads.ts | 10 +- src/auto-reply/reply/body.ts | 10 +- src/auto-reply/reply/commands-acp.ts | 22 ++-- src/auto-reply/reply/commands-compact.ts | 6 +- src/auto-reply/reply/commands-core.ts | 9 +- src/auto-reply/reply/commands-reset-hooks.ts | 6 +- src/auto-reply/reply/commands-subagents.ts | 82 +++++++------- .../reply/dispatch-acp-attachments.ts | 10 +- src/auto-reply/reply/dispatch-acp-delivery.ts | 31 +++--- src/auto-reply/reply/dispatch-acp.runtime.ts | 16 +-- src/auto-reply/reply/dispatch-acp.ts | 36 +++---- src/auto-reply/reply/dispatch-from-config.ts | 38 +++---- .../reply/get-reply-directives-apply.ts | 35 +++--- src/auto-reply/reply/get-reply-directives.ts | 14 +-- .../reply/get-reply-inline-actions.ts | 29 ++--- src/auto-reply/reply/get-reply-run.ts | 31 +++--- src/auto-reply/reply/get-reply.ts | 61 +++++------ src/auto-reply/reply/groups.ts | 6 +- src/auto-reply/reply/model-selection.ts | 19 ++-- src/auto-reply/reply/route-reply.ts | 10 +- src/auto-reply/reply/session-fork.ts | 6 +- src/auto-reply/reply/session.ts | 10 +- src/cli/channels-cli.ts | 8 +- src/cli/command-bootstrap.ts | 6 +- src/cli/daemon-cli/probe.ts | 6 +- .../daemon-cli/register-service-commands.ts | 16 ++- src/cli/daemon-cli/status.gather.ts | 37 +++---- src/cli/gateway-cli/register.ts | 77 ++++++------- src/cli/gateway-cli/run-loop.ts | 8 +- src/cli/gateway-rpc.ts | 8 +- src/cli/logs-cli.ts | 8 +- src/cli/nodes-cli/rpc.ts | 8 +- src/cli/plugin-registry-loader.ts | 6 +- src/cli/program/routed-command-definitions.ts | 20 ++-- src/cli/proxy-cli.ts | 8 +- .../isolated-agent/channel-output-policy.ts | 8 +- src/cron/isolated-agent/delivery-dispatch.ts | 49 ++++----- src/cron/isolated-agent/delivery-target.ts | 28 +++-- .../isolated-agent/run-execution.runtime.ts | 10 +- src/cron/isolated-agent/run-executor.ts | 15 +-- src/cron/isolated-agent/run.ts | 61 +++++------ src/cron/isolated-agent/skills-snapshot.ts | 10 +- src/plugins/loader.ts | 2 +- 66 files changed, 642 insertions(+), 616 deletions(-) diff --git a/src/agents/agent-command.ts b/src/agents/agent-command.ts index a5d30a634fe..1e2919832ea 100644 --- a/src/agents/agent-command.ts +++ b/src/agents/agent-command.ts @@ -23,6 +23,7 @@ import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; import { applyVerboseOverride } from "../sessions/level-overrides.js"; import { applyModelOverrideToSessionEntry } from "../sessions/model-overrides.js"; import { resolveSendPolicy } from "../sessions/send-policy.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeOptionalString } from "../shared/string-coerce.js"; import { sanitizeForLog } from "../terminal/ansi.js"; import { createTrajectoryRuntimeRecorder } from "../trajectory/runtime.js"; @@ -87,95 +88,106 @@ type SkillsFilterRuntime = typeof import("./skills/filter.js"); type SkillsRefreshStateRuntime = typeof import("./skills/refresh-state.js"); type SkillsRemoteRuntime = typeof import("../infra/skills-remote.js"); -let attemptExecutionRuntimePromise: Promise | undefined; -let acpManagerRuntimePromise: Promise | undefined; -let acpPolicyRuntimePromise: Promise | undefined; -let acpRuntimeErrorsRuntimePromise: Promise | undefined; -let acpSessionIdentifiersRuntimePromise: Promise | undefined; -let deliveryRuntimePromise: Promise | undefined; -let sessionStoreRuntimePromise: Promise | undefined; -let cliCompactionRuntimePromise: Promise | undefined; -let transcriptResolveRuntimePromise: Promise | undefined; -let cliDepsRuntimePromise: Promise | undefined; -let execDefaultsRuntimePromise: Promise | undefined; -let skillsRuntimePromise: Promise | undefined; -let skillsFilterRuntimePromise: Promise | undefined; -let skillsRefreshStateRuntimePromise: Promise | undefined; -let skillsRemoteRuntimePromise: Promise | undefined; +const attemptExecutionRuntimeLoader = createLazyImportLoader( + () => import("./command/attempt-execution.runtime.js"), +); +const acpManagerRuntimeLoader = createLazyImportLoader( + () => import("../acp/control-plane/manager.js"), +); +const acpPolicyRuntimeLoader = createLazyImportLoader( + () => import("../acp/policy.js"), +); +const acpRuntimeErrorsRuntimeLoader = createLazyImportLoader( + () => import("../acp/runtime/errors.js"), +); +const acpSessionIdentifiersRuntimeLoader = createLazyImportLoader( + () => import("../acp/runtime/session-identifiers.js"), +); +const deliveryRuntimeLoader = createLazyImportLoader( + () => import("./command/delivery.runtime.js"), +); +const sessionStoreRuntimeLoader = createLazyImportLoader( + () => import("./command/session-store.runtime.js"), +); +const cliCompactionRuntimeLoader = createLazyImportLoader( + () => import("./command/cli-compaction.js"), +); +const transcriptResolveRuntimeLoader = createLazyImportLoader( + () => import("../config/sessions/transcript-resolve.runtime.js"), +); +const cliDepsRuntimeLoader = createLazyImportLoader(() => import("../cli/deps.js")); +const execDefaultsRuntimeLoader = createLazyImportLoader( + () => import("./exec-defaults.js"), +); +const skillsRuntimeLoader = createLazyImportLoader(() => import("./skills.js")); +const skillsFilterRuntimeLoader = createLazyImportLoader( + () => import("./skills/filter.js"), +); +const skillsRefreshStateRuntimeLoader = createLazyImportLoader( + () => import("./skills/refresh-state.js"), +); +const skillsRemoteRuntimeLoader = createLazyImportLoader( + () => import("../infra/skills-remote.js"), +); function loadAttemptExecutionRuntime(): Promise { - attemptExecutionRuntimePromise ??= import("./command/attempt-execution.runtime.js"); - return attemptExecutionRuntimePromise; + return attemptExecutionRuntimeLoader.load(); } function loadAcpManagerRuntime(): Promise { - acpManagerRuntimePromise ??= import("../acp/control-plane/manager.js"); - return acpManagerRuntimePromise; + return acpManagerRuntimeLoader.load(); } function loadAcpPolicyRuntime(): Promise { - acpPolicyRuntimePromise ??= import("../acp/policy.js"); - return acpPolicyRuntimePromise; + return acpPolicyRuntimeLoader.load(); } function loadAcpRuntimeErrorsRuntime(): Promise { - acpRuntimeErrorsRuntimePromise ??= import("../acp/runtime/errors.js"); - return acpRuntimeErrorsRuntimePromise; + return acpRuntimeErrorsRuntimeLoader.load(); } function loadAcpSessionIdentifiersRuntime(): Promise { - acpSessionIdentifiersRuntimePromise ??= import("../acp/runtime/session-identifiers.js"); - return acpSessionIdentifiersRuntimePromise; + return acpSessionIdentifiersRuntimeLoader.load(); } function loadDeliveryRuntime(): Promise { - deliveryRuntimePromise ??= import("./command/delivery.runtime.js"); - return deliveryRuntimePromise; + return deliveryRuntimeLoader.load(); } function loadSessionStoreRuntime(): Promise { - sessionStoreRuntimePromise ??= import("./command/session-store.runtime.js"); - return sessionStoreRuntimePromise; + return sessionStoreRuntimeLoader.load(); } function loadCliCompactionRuntime(): Promise { - cliCompactionRuntimePromise ??= import("./command/cli-compaction.js"); - return cliCompactionRuntimePromise; + return cliCompactionRuntimeLoader.load(); } function loadTranscriptResolveRuntime(): Promise { - transcriptResolveRuntimePromise ??= import("../config/sessions/transcript-resolve.runtime.js"); - return transcriptResolveRuntimePromise; + return transcriptResolveRuntimeLoader.load(); } function loadCliDepsRuntime(): Promise { - cliDepsRuntimePromise ??= import("../cli/deps.js"); - return cliDepsRuntimePromise; + return cliDepsRuntimeLoader.load(); } function loadExecDefaultsRuntime(): Promise { - execDefaultsRuntimePromise ??= import("./exec-defaults.js"); - return execDefaultsRuntimePromise; + return execDefaultsRuntimeLoader.load(); } function loadSkillsRuntime(): Promise { - skillsRuntimePromise ??= import("./skills.js"); - return skillsRuntimePromise; + return skillsRuntimeLoader.load(); } function loadSkillsFilterRuntime(): Promise { - skillsFilterRuntimePromise ??= import("./skills/filter.js"); - return skillsFilterRuntimePromise; + return skillsFilterRuntimeLoader.load(); } function loadSkillsRefreshStateRuntime(): Promise { - skillsRefreshStateRuntimePromise ??= import("./skills/refresh-state.js"); - return skillsRefreshStateRuntimePromise; + return skillsRefreshStateRuntimeLoader.load(); } function loadSkillsRemoteRuntime(): Promise { - skillsRemoteRuntimePromise ??= import("../infra/skills-remote.js"); - return skillsRemoteRuntimePromise; + return skillsRemoteRuntimeLoader.load(); } async function resolveAgentCommandDeps(deps: CliDeps | undefined): Promise { diff --git a/src/agents/auth-profiles/session-override.ts b/src/agents/auth-profiles/session-override.ts index 4e0490e57cb..ef7e8e9eeb5 100644 --- a/src/agents/auth-profiles/session-override.ts +++ b/src/agents/auth-profiles/session-override.ts @@ -1,17 +1,17 @@ import type { SessionEntry } from "../../config/sessions/types.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { resolveAuthProfileOrder } from "../auth-profiles/order.js"; import { ensureAuthProfileStore, hasAnyAuthProfileStoreSource } from "../auth-profiles/store.js"; import { isProfileInCooldown } from "../auth-profiles/usage.js"; import { resolveProviderIdForAuth } from "../provider-auth-aliases.js"; -let sessionStoreRuntimePromise: - | Promise - | undefined; +const sessionStoreRuntimeLoader = createLazyImportLoader( + () => import("../../config/sessions/store.runtime.js"), +); function loadSessionStoreRuntime() { - sessionStoreRuntimePromise ??= import("../../config/sessions/store.runtime.js"); - return sessionStoreRuntimePromise; + return sessionStoreRuntimeLoader.load(); } function isProfileForProvider(params: { diff --git a/src/agents/bash-tools.exec.ts b/src/agents/bash-tools.exec.ts index 1a3dfd1c31d..134b82e1e06 100644 --- a/src/agents/bash-tools.exec.ts +++ b/src/agents/bash-tools.exec.ts @@ -18,6 +18,7 @@ import { } from "../infra/shell-env.js"; import { logInfo } from "../logger.js"; import { parseAgentSessionKey, resolveAgentIdFromSessionKey } from "../routing/session-key.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, @@ -127,11 +128,12 @@ function getNodeErrorCode(error: unknown): string | undefined { type FsSafeModule = typeof import("../infra/fs-safe.js"); -let fsSafeModulePromise: Promise | undefined; +const fsSafeModuleLoader = createLazyImportLoader( + () => import("../infra/fs-safe.js"), +); async function loadFsSafeModule(): Promise { - fsSafeModulePromise ??= import("../infra/fs-safe.js"); - return await fsSafeModulePromise; + return await fsSafeModuleLoader.load(); } function shouldSkipScriptPreflightPathError( diff --git a/src/agents/command/session-store.ts b/src/agents/command/session-store.ts index a91aba5f856..8d29c5d63e0 100644 --- a/src/agents/command/session-store.ts +++ b/src/agents/command/session-store.ts @@ -5,6 +5,7 @@ import { updateSessionStore, } from "../../config/sessions.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { clearCliSession, setCliSessionBinding, setCliSessionId } from "../cli-session.js"; import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js"; @@ -13,17 +14,15 @@ import { deriveSessionTotalTokens, hasNonzeroUsage } from "../usage.js"; type RunResult = Awaited>; -let usageFormatModulePromise: Promise | undefined; -let contextModulePromise: Promise | undefined; +const usageFormatModuleLoader = createLazyImportLoader(() => import("../../utils/usage-format.js")); +const contextModuleLoader = createLazyImportLoader(() => import("../context.js")); async function getUsageFormatModule() { - usageFormatModulePromise ??= import("../../utils/usage-format.js"); - return await usageFormatModulePromise; + return await usageFormatModuleLoader.load(); } async function getContextModule() { - contextModulePromise ??= import("../context.js"); - return await contextModulePromise; + return await contextModuleLoader.load(); } function resolveNonNegativeNumber(value: number | undefined): number | undefined { diff --git a/src/agents/context-runtime-state.ts b/src/agents/context-runtime-state.ts index a02ac5e72ee..b9cc74fb587 100644 --- a/src/agents/context-runtime-state.ts +++ b/src/agents/context-runtime-state.ts @@ -1,4 +1,5 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { createLazyImportLoader, type LazyPromiseLoader } from "../shared/lazy-promise.js"; import { MODEL_CONTEXT_TOKEN_CACHE } from "./context-cache.js"; const CONTEXT_WINDOW_RUNTIME_STATE_KEY = Symbol.for("openclaw.contextWindowRuntimeState"); @@ -8,7 +9,7 @@ type ContextWindowRuntimeState = { configuredConfig: OpenClawConfig | undefined; configLoadFailures: number; nextConfigLoadAttemptAtMs: number; - modelsConfigRuntimePromise: Promise | undefined; + modelsConfigRuntimeLoader: LazyPromiseLoader; }; export const CONTEXT_WINDOW_RUNTIME_STATE = (() => { @@ -21,7 +22,7 @@ export const CONTEXT_WINDOW_RUNTIME_STATE = (() => { configuredConfig: undefined, configLoadFailures: 0, nextConfigLoadAttemptAtMs: 0, - modelsConfigRuntimePromise: undefined, + modelsConfigRuntimeLoader: createLazyImportLoader(() => import("./models-config.runtime.js")), }; } return globalState[CONTEXT_WINDOW_RUNTIME_STATE_KEY]; @@ -32,6 +33,6 @@ export function resetContextWindowCacheForTest(): void { CONTEXT_WINDOW_RUNTIME_STATE.configuredConfig = undefined; CONTEXT_WINDOW_RUNTIME_STATE.configLoadFailures = 0; CONTEXT_WINDOW_RUNTIME_STATE.nextConfigLoadAttemptAtMs = 0; - CONTEXT_WINDOW_RUNTIME_STATE.modelsConfigRuntimePromise = undefined; + CONTEXT_WINDOW_RUNTIME_STATE.modelsConfigRuntimeLoader.clear(); MODEL_CONTEXT_TOKEN_CACHE.clear(); } diff --git a/src/agents/context.ts b/src/agents/context.ts index 84358d7b39b..f7ace678c81 100644 --- a/src/agents/context.ts +++ b/src/agents/context.ts @@ -101,8 +101,7 @@ export function applyConfiguredContextWindows(params: { } function loadModelsConfigRuntime() { - CONTEXT_WINDOW_RUNTIME_STATE.modelsConfigRuntimePromise ??= import("./models-config.runtime.js"); - return CONTEXT_WINDOW_RUNTIME_STATE.modelsConfigRuntimePromise; + return CONTEXT_WINDOW_RUNTIME_STATE.modelsConfigRuntimeLoader.load(); } function isLikelyOpenClawCliProcess(argv: string[] = process.argv): boolean { diff --git a/src/agents/harness/tool-result-middleware.ts b/src/agents/harness/tool-result-middleware.ts index a24cbed75f2..e511cc1593d 100644 --- a/src/agents/harness/tool-result-middleware.ts +++ b/src/agents/harness/tool-result-middleware.ts @@ -5,6 +5,7 @@ import type { AgentToolResultMiddlewareEvent, OpenClawAgentToolResult, } from "../../plugins/agent-tool-result-middleware-types.js"; +import { createLazyPromiseLoader } from "../../shared/lazy-promise.js"; import { truncateUtf16Safe } from "../../utils.js"; const log = createSubsystemLogger("agents/harness"); @@ -125,18 +126,18 @@ export function createAgentToolResultMiddlewareRunner( ) { const middlewareContext = { ...ctx, harness: ctx.harness ?? ctx.runtime }; let resolvedHandlers = handlers; - let resolvedHandlersPromise: Promise | undefined; + const resolvedHandlersLoader = createLazyPromiseLoader(async () => { + const { loadAgentToolResultMiddlewaresForRuntime } = + await import("../../plugins/agent-tool-result-middleware-loader.js"); + return loadAgentToolResultMiddlewaresForRuntime({ + runtime: ctx.runtime, + }); + }); const resolveHandlers = async (): Promise => { if (resolvedHandlers) { return resolvedHandlers; } - resolvedHandlersPromise ??= import("../../plugins/agent-tool-result-middleware-loader.js").then( - ({ loadAgentToolResultMiddlewaresForRuntime }) => - loadAgentToolResultMiddlewaresForRuntime({ - runtime: ctx.runtime, - }), - ); - resolvedHandlers = await resolvedHandlersPromise; + resolvedHandlers = await resolvedHandlersLoader.load(); return resolvedHandlers; }; return { diff --git a/src/agents/model-catalog.ts b/src/agents/model-catalog.ts index e199c5c1a56..827f34a37f8 100644 --- a/src/agents/model-catalog.ts +++ b/src/agents/model-catalog.ts @@ -7,6 +7,7 @@ import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-meta import { isManifestPluginAvailableForControlPlane } from "../plugins/manifest-contract-eligibility.js"; import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; import { augmentModelCatalogWithProviderPlugins } from "../plugins/provider-runtime.runtime.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, @@ -52,15 +53,16 @@ let modelCatalogPromise: Promise | null = null; let hasLoggedModelCatalogError = false; const defaultImportPiSdk = () => import("./pi-model-discovery-runtime.js"); let importPiSdk = defaultImportPiSdk; -let modelSuppressionPromise: Promise | undefined; +const modelSuppressionLoader = createLazyImportLoader( + () => import("./model-suppression.runtime.js"), +); function shouldLogModelCatalogTiming(): boolean { return process.env.OPENCLAW_DEBUG_INGRESS_TIMING === "1"; } function loadModelSuppression() { - modelSuppressionPromise ??= import("./model-suppression.runtime.js"); - return modelSuppressionPromise; + return modelSuppressionLoader.load(); } export function resetModelCatalogCache() { diff --git a/src/agents/model-fallback.ts b/src/agents/model-fallback.ts index aab84356a83..f4421ce46bd 100644 --- a/src/agents/model-fallback.ts +++ b/src/agents/model-fallback.ts @@ -5,6 +5,7 @@ import { import type { OpenClawConfig } from "../config/types.openclaw.js"; import { formatErrorMessage } from "../infra/errors.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeOptionalString } from "../shared/string-coerce.js"; import { sanitizeForLog } from "../terminal/ansi.js"; import { externalCliDiscoveryForProviders } from "./auth-profiles/external-cli-discovery.js"; @@ -181,11 +182,12 @@ type ModelFallbackRunResult = { type ModelFallbackAuthRuntime = typeof import("./model-fallback-auth.runtime.js"); -let modelFallbackAuthRuntimePromise: Promise | undefined; +const modelFallbackAuthRuntimeLoader = createLazyImportLoader( + () => import("./model-fallback-auth.runtime.js"), +); async function loadModelFallbackAuthRuntime() { - modelFallbackAuthRuntimePromise ??= import("./model-fallback-auth.runtime.js"); - return await modelFallbackAuthRuntimePromise; + return await modelFallbackAuthRuntimeLoader.load(); } function buildFallbackSuccess(params: { diff --git a/src/agents/pi-embedded-runner/compact.runtime.ts b/src/agents/pi-embedded-runner/compact.runtime.ts index f12b124bda9..eeb9137c796 100644 --- a/src/agents/pi-embedded-runner/compact.runtime.ts +++ b/src/agents/pi-embedded-runner/compact.runtime.ts @@ -1,10 +1,10 @@ +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import type { CompactEmbeddedPiSessionDirect } from "./compact.runtime.types.js"; -let compactRuntimePromise: Promise | null = null; +const compactRuntimeLoader = createLazyImportLoader(() => import("./compact.js")); function loadCompactRuntime() { - compactRuntimePromise ??= import("./compact.js"); - return compactRuntimePromise; + return compactRuntimeLoader.load(); } export async function compactEmbeddedPiSessionDirect( diff --git a/src/agents/pi-embedded-runner/moonshot-thinking-stream-wrappers.ts b/src/agents/pi-embedded-runner/moonshot-thinking-stream-wrappers.ts index 1c46a283089..77a3a09df7d 100644 --- a/src/agents/pi-embedded-runner/moonshot-thinking-stream-wrappers.ts +++ b/src/agents/pi-embedded-runner/moonshot-thinking-stream-wrappers.ts @@ -1,16 +1,16 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; import type { ThinkLevel } from "../../auto-reply/thinking.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js"; import { streamWithPayloadPatch } from "./stream-payload-utils.js"; type MoonshotThinkingType = "enabled" | "disabled"; type MoonshotThinkingKeep = "all"; const MOONSHOT_THINKING_KEEP_MODEL_ID = "kimi-k2.6"; -let piAiRuntimePromise: Promise | undefined; +const piAiRuntimeLoader = createLazyImportLoader(() => import("@mariozechner/pi-ai")); async function loadDefaultStreamFn(): Promise { - piAiRuntimePromise ??= import("@mariozechner/pi-ai"); - const runtime = await piAiRuntimePromise; + const runtime = await piAiRuntimeLoader.load(); return runtime.streamSimple; } diff --git a/src/agents/pi-embedded-subscribe.handlers.tools.ts b/src/agents/pi-embedded-subscribe.handlers.tools.ts index cf16a708815..5673e2f952d 100644 --- a/src/agents/pi-embedded-subscribe.handlers.tools.ts +++ b/src/agents/pi-embedded-subscribe.handlers.tools.ts @@ -18,6 +18,7 @@ import { } from "../infra/agent-events.js"; import type { ExecApprovalDecision } from "../infra/exec-approvals.js"; import type { PluginHookAfterToolCallEvent } from "../plugins/types.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeOptionalLowercaseString, readStringValue } from "../shared/string-coerce.js"; import type { ApplyPatchSummary } from "./apply-patch.js"; import type { ExecToolDetails } from "./bash-tools.exec-types.js"; @@ -50,29 +51,33 @@ type HookRunnerGlobalModule = typeof import("../plugins/hook-runner-global.js"); type MediaParseModule = typeof import("../media/parse.js"); type BeforeToolCallModule = typeof import("./pi-tools.before-tool-call.js"); -let execApprovalReplyModulePromise: Promise | undefined; -let hookRunnerGlobalModulePromise: Promise | undefined; -let mediaParseModulePromise: Promise | undefined; -let beforeToolCallModulePromise: Promise | undefined; +const execApprovalReplyModuleLoader = createLazyImportLoader( + () => import("../infra/exec-approval-reply.js"), +); +const hookRunnerGlobalModuleLoader = createLazyImportLoader( + () => import("../plugins/hook-runner-global.js"), +); +const mediaParseModuleLoader = createLazyImportLoader( + () => import("../media/parse.js"), +); +const beforeToolCallModuleLoader = createLazyImportLoader( + () => import("./pi-tools.before-tool-call.js"), +); function loadExecApprovalReply(): Promise { - execApprovalReplyModulePromise ??= import("../infra/exec-approval-reply.js"); - return execApprovalReplyModulePromise; + return execApprovalReplyModuleLoader.load(); } function loadHookRunnerGlobal(): Promise { - hookRunnerGlobalModulePromise ??= import("../plugins/hook-runner-global.js"); - return hookRunnerGlobalModulePromise; + return hookRunnerGlobalModuleLoader.load(); } function loadMediaParse(): Promise { - mediaParseModulePromise ??= import("../media/parse.js"); - return mediaParseModulePromise; + return mediaParseModuleLoader.load(); } function loadBeforeToolCall(): Promise { - beforeToolCallModulePromise ??= import("./pi-tools.before-tool-call.js"); - return beforeToolCallModulePromise; + return beforeToolCallModuleLoader.load(); } type ToolStartRecord = { diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index 9720de80cf8..6dc9452f71c 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -7,6 +7,7 @@ import type { DiagnosticTraceContext } from "../infra/diagnostic-trace-context.j import { resolveMergedSafeBinProfileFixtures } from "../infra/exec-safe-bin-runtime-policy.js"; import { logWarn } from "../logger.js"; import { getPluginToolMeta } from "../plugins/tools.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, @@ -83,11 +84,12 @@ const MEMORY_FLUSH_ALLOWED_TOOL_NAMES = new Set(["read", "write"]); type BashToolsModule = typeof import("./bash-tools.js"); -let bashToolsModulePromise: Promise | undefined; +const bashToolsModuleLoader = createLazyImportLoader( + () => import("./bash-tools.js"), +); function loadBashToolsModule(): Promise { - bashToolsModulePromise ??= import("./bash-tools.js"); - return bashToolsModulePromise; + return bashToolsModuleLoader.load(); } function createLazyExecTool(defaults?: ExecToolDefaults): AnyAgentTool { diff --git a/src/agents/skills-install-download.ts b/src/agents/skills-install-download.ts index 836ba9d524f..39e5d865278 100644 --- a/src/agents/skills-install-download.ts +++ b/src/agents/skills-install-download.ts @@ -10,6 +10,7 @@ import { writeFileFromPathWithinRoot } from "../infra/fs-safe.js"; import { assertCanonicalPathWithinBase } from "../infra/install-safe-path.js"; import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js"; import { isWithinDir } from "../infra/path-safety.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { ensureDir, resolveUserPath } from "../utils.js"; import { formatInstallFailureMessage } from "./skills-install-output.js"; @@ -17,11 +18,10 @@ import type { SkillInstallResult } from "./skills-install.types.js"; import type { SkillEntry, SkillInstallSpec } from "./skills.js"; import { resolveSkillToolsRootDir } from "./skills/tools-dir.js"; -let extractModulePromise: Promise | undefined; +const extractModuleLoader = createLazyImportLoader(() => import("./skills-install-extract.js")); async function loadExtractModule() { - extractModulePromise ??= import("./skills-install-extract.js"); - return await extractModulePromise; + return await extractModuleLoader.load(); } function isNodeReadableStream(value: unknown): value is NodeJS.ReadableStream { diff --git a/src/agents/subagent-announce.ts b/src/agents/subagent-announce.ts index cb741239513..5d739091895 100644 --- a/src/agents/subagent-announce.ts +++ b/src/agents/subagent-announce.ts @@ -7,6 +7,7 @@ import { } from "../auto-reply/tokens.js"; import { defaultRuntime } from "../runtime.js"; import { isCronSessionKey } from "../sessions/session-key-utils.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeOptionalString } from "../shared/string-coerce.js"; import { type DeliveryContext, normalizeDeliveryContext } from "../utils/delivery-context.js"; import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js"; @@ -61,13 +62,12 @@ const defaultSubagentAnnounceDeps: SubagentAnnounceDeps = { let subagentAnnounceDeps: SubagentAnnounceDeps = defaultSubagentAnnounceDeps; -let subagentRegistryRuntimePromise: Promise< - typeof import("./subagent-announce.registry.runtime.js") -> | null = null; +const subagentRegistryRuntimeLoader = createLazyImportLoader( + () => import("./subagent-announce.registry.runtime.js"), +); function loadSubagentRegistryRuntime() { - subagentRegistryRuntimePromise ??= import("./subagent-announce.registry.runtime.js"); - return subagentRegistryRuntimePromise; + return subagentRegistryRuntimeLoader.load(); } export { buildSubagentSystemPrompt } from "./subagent-system-prompt.js"; diff --git a/src/agents/subagent-control.ts b/src/agents/subagent-control.ts index 9559347482e..655a9d66385 100644 --- a/src/agents/subagent-control.ts +++ b/src/agents/subagent-control.ts @@ -14,6 +14,7 @@ import { callGateway } from "../gateway/call.js"; import { logVerbose } from "../globals.js"; import { formatErrorMessage } from "../infra/errors.js"; import { isSubagentSessionKey, parseAgentSessionKey } from "../routing/session-key.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js"; import { AGENT_LANE_SUBAGENT } from "./lanes.js"; import { @@ -64,12 +65,12 @@ let subagentControlDeps: { clearSessionQueues?: ClearSessionQueues; } = defaultSubagentControlDeps; -let subagentControlRuntimePromise: Promise | null = - null; +const subagentControlRuntimeLoader = createLazyImportLoader( + () => import("./subagent-control.runtime.js"), +); function loadSubagentControlRuntime() { - subagentControlRuntimePromise ??= import("./subagent-control.runtime.js"); - return subagentControlRuntimePromise; + return subagentControlRuntimeLoader.load(); } async function resolveSubagentControlRuntime(): Promise<{ diff --git a/src/agents/subagent-registry-lifecycle.ts b/src/agents/subagent-registry-lifecycle.ts index 6e6fd327c7d..30753bdcc5e 100644 --- a/src/agents/subagent-registry-lifecycle.ts +++ b/src/agents/subagent-registry-lifecycle.ts @@ -4,6 +4,7 @@ import type { callGateway as defaultCallGateway } from "../gateway/call.js"; import { formatErrorMessage, readErrorName } from "../infra/errors.js"; import { defaultRuntime } from "../runtime.js"; import { emitSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { completeTaskRunByRunId, failTaskRunByRunId, @@ -44,13 +45,14 @@ type BrowserCleanupModule = Pick< "cleanupBrowserSessionsForLifecycleEnd" >; -let browserCleanupPromise: Promise | null = null; +const browserCleanupLoader = createLazyImportLoader( + () => import("../browser-lifecycle-cleanup.js"), +); async function loadCleanupBrowserSessionsForLifecycleEnd(): Promise< BrowserCleanupModule["cleanupBrowserSessionsForLifecycleEnd"] > { - browserCleanupPromise ??= import("../browser-lifecycle-cleanup.js"); - return (await browserCleanupPromise).cleanupBrowserSessionsForLifecycleEnd; + return (await browserCleanupLoader.load()).cleanupBrowserSessionsForLifecycleEnd; } export function createSubagentRegistryLifecycleController(params: { diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index b190ecb3f73..e8dcec65420 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -13,6 +13,7 @@ import { callGateway } from "../gateway/call.js"; import { getAgentRunContext, onAgentEvent } from "../infra/agent-events.js"; import { registerPendingSpawnedChildrenQuery } from "../infra/outbound/pending-spawn-query.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; +import { createLazyImportLoader, createLazyPromiseLoader } from "../shared/lazy-promise.js"; import { importRuntimeModule } from "../shared/runtime-import.js"; import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; import type { DeliveryContext } from "../utils/delivery-context.types.js"; @@ -104,19 +105,21 @@ type SubagentRegistryDeps = { ) => Promise; }; -let subagentAnnouncePromise: Promise | null = null; -let browserCleanupPromise: Promise | null = null; +const subagentAnnounceLoader = createLazyImportLoader( + () => import("./subagent-announce.js"), +); +const browserCleanupLoader = createLazyImportLoader( + () => import("../browser-lifecycle-cleanup.js"), +); async function loadSubagentAnnounceModule(): Promise { - subagentAnnouncePromise ??= import("./subagent-announce.js"); - return await subagentAnnouncePromise; + return await subagentAnnounceLoader.load(); } async function loadCleanupBrowserSessionsForLifecycleEnd(): Promise< BrowserCleanupModule["cleanupBrowserSessionsForLifecycleEnd"] > { - browserCleanupPromise ??= import("../browser-lifecycle-cleanup.js"); - return (await browserCleanupPromise).cleanupBrowserSessionsForLifecycleEnd; + return (await browserCleanupLoader.load()).cleanupBrowserSessionsForLifecycleEnd; } const defaultSubagentRegistryDeps: SubagentRegistryDeps = { @@ -160,9 +163,15 @@ type RuntimePluginsModule = Pick< const SUBAGENT_REGISTRY_RUNTIME_SPEC = ["./subagent-registry.runtime", ".js"] as const; -let contextEngineInitPromise: Promise | null = null; -let contextEngineRegistryPromise: Promise | null = null; -let runtimePluginsPromise: Promise | null = null; +const contextEngineInitLoader = createLazyPromiseLoader(() => + importRuntimeModule(import.meta.url, SUBAGENT_REGISTRY_RUNTIME_SPEC), +); +const contextEngineRegistryLoader = createLazyPromiseLoader(() => + importRuntimeModule(import.meta.url, SUBAGENT_REGISTRY_RUNTIME_SPEC), +); +const runtimePluginsLoader = createLazyPromiseLoader(() => + importRuntimeModule(import.meta.url, SUBAGENT_REGISTRY_RUNTIME_SPEC), +); let sweeper: NodeJS.Timeout | null = null; const resumeRetryTimers = new Set>(); @@ -279,27 +288,15 @@ function resolveCompletionFromSessionEntry( } function loadContextEngineInitModule(): Promise { - contextEngineInitPromise ??= importRuntimeModule( - import.meta.url, - SUBAGENT_REGISTRY_RUNTIME_SPEC, - ); - return contextEngineInitPromise; + return contextEngineInitLoader.load(); } function loadContextEngineRegistryModule(): Promise { - contextEngineRegistryPromise ??= importRuntimeModule( - import.meta.url, - SUBAGENT_REGISTRY_RUNTIME_SPEC, - ); - return contextEngineRegistryPromise; + return contextEngineRegistryLoader.load(); } function loadRuntimePluginsModule(): Promise { - runtimePluginsPromise ??= importRuntimeModule( - import.meta.url, - SUBAGENT_REGISTRY_RUNTIME_SPEC, - ); - return runtimePluginsPromise; + return runtimePluginsLoader.load(); } async function ensureSubagentRegistryPluginRuntimeLoaded(params: { @@ -1035,11 +1032,11 @@ export function resetSubagentRegistryForTests(opts?: { persist?: boolean }) { endedHookInFlightRunIds.clear(); clearAllPendingLifecycleErrors(); clearAllPendingLifecycleTimeouts(); - contextEngineInitPromise = null; - contextEngineRegistryPromise = null; - runtimePluginsPromise = null; - subagentAnnouncePromise = null; - browserCleanupPromise = null; + contextEngineInitLoader.clear(); + contextEngineRegistryLoader.clear(); + runtimePluginsLoader.clear(); + subagentAnnounceLoader.clear(); + browserCleanupLoader.clear(); resetAnnounceQueuesForTests(); stopSweeper(); sweepInProgress = false; diff --git a/src/agents/tools/session-status-tool.ts b/src/agents/tools/session-status-tool.ts index 854bb461823..d335750182e 100644 --- a/src/agents/tools/session-status-tool.ts +++ b/src/agents/tools/session-status-tool.ts @@ -21,6 +21,7 @@ import { resolveAgentIdFromSessionKey, } from "../../routing/session-key.js"; import { applyModelOverrideToSessionEntry } from "../../sessions/model-overrides.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js"; import type { BuildStatusTextParams } from "../../status/status-text.types.js"; import { buildTaskStatusSnapshotForRelatedSessionKeyForOwner } from "../../tasks/task-owner-access.js"; @@ -62,12 +63,12 @@ type CommandsStatusRuntimeModule = { buildStatusText: (params: BuildStatusTextParams) => Promise; }; -let commandsStatusRuntimePromise: Promise | null = null; +const commandsStatusRuntimeLoader = createLazyImportLoader( + () => import("./session-status.runtime.js") as Promise, +); function loadCommandsStatusRuntime(): Promise { - commandsStatusRuntimePromise ??= - import("./session-status.runtime.js") as Promise; - return commandsStatusRuntimePromise; + return commandsStatusRuntimeLoader.load(); } function resolveSessionEntry(params: { diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index 95dcf1fc7cb..83bc34008b5 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -7,6 +7,7 @@ import { import { getRuntimeConfig } from "../../config/config.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { callGateway } from "../../gateway/call.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeDeliveryContext } from "../../utils/delivery-context.shared.js"; import type { GatewayMessageChannel } from "../../utils/message-channel.js"; import { optionalStringEnum } from "../schema/typebox.js"; @@ -47,11 +48,12 @@ const UNSUPPORTED_SESSIONS_SPAWN_PARAM_KEYS = [ type AcpSpawnModule = typeof import("../acp-spawn.js"); -let acpSpawnModulePromise: Promise | undefined; +const acpSpawnModuleLoader = createLazyImportLoader( + () => import("../acp-spawn.js"), +); async function loadAcpSpawnModule(): Promise { - acpSpawnModulePromise ??= import("../acp-spawn.js"); - return await acpSpawnModulePromise; + return await acpSpawnModuleLoader.load(); } function summarizeError(err: unknown): string { diff --git a/src/agents/tools/web-fetch.ts b/src/agents/tools/web-fetch.ts index a5f00788790..c60e931cb5d 100644 --- a/src/agents/tools/web-fetch.ts +++ b/src/agents/tools/web-fetch.ts @@ -4,6 +4,7 @@ import { SsrFBlockedError, type LookupFn, type SsrFPolicy } from "../../infra/ne import { logDebug } from "../../logger.js"; import type { RuntimeWebFetchMetadata } from "../../secrets/runtime-web-tools.types.js"; import { wrapExternalContent, wrapWebContent } from "../../security/external-content.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, @@ -81,19 +82,21 @@ type WebGuardedFetchModule = Pick< "fetchWithWebToolsNetworkGuard" >; -let webFetchRuntimePromise: Promise | null = null; -let webGuardedFetchPromise: Promise | null = null; +const webFetchRuntimeLoader = createLazyImportLoader( + () => import("../../web-fetch/runtime.js"), +); +const webGuardedFetchLoader = createLazyImportLoader( + () => import("./web-guarded-fetch.js"), +); async function loadWebFetchRuntime(): Promise { - webFetchRuntimePromise ??= import("../../web-fetch/runtime.js"); - return await webFetchRuntimePromise; + return await webFetchRuntimeLoader.load(); } async function loadWebGuardedFetch(): Promise< WebGuardedFetchModule["fetchWithWebToolsNetworkGuard"] > { - webGuardedFetchPromise ??= import("./web-guarded-fetch.js"); - return (await webGuardedFetchPromise).fetchWithWebToolsNetworkGuard; + return (await webGuardedFetchLoader.load()).fetchWithWebToolsNetworkGuard; } function resolveFetchConfig(cfg?: OpenClawConfig): WebFetchConfig { diff --git a/src/agents/tools/web-search-provider-common.ts b/src/agents/tools/web-search-provider-common.ts index 540ac935629..11e54e6005d 100644 --- a/src/agents/tools/web-search-provider-common.ts +++ b/src/agents/tools/web-search-provider-common.ts @@ -1,5 +1,6 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { normalizeResolvedSecretInputString } from "../../config/types.secrets.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; import { normalizeSecretInput } from "../../utils/normalize-secret-input.js"; import { @@ -19,20 +20,20 @@ type WebGuardedFetchModule = Pick< "withSelfHostedWebToolsEndpoint" | "withTrustedWebToolsEndpoint" >; -let webGuardedFetchPromise: Promise | null = null; +const webGuardedFetchLoader = createLazyImportLoader( + () => import("./web-guarded-fetch.js"), +); async function loadTrustedWebToolsEndpoint(): Promise< WebGuardedFetchModule["withTrustedWebToolsEndpoint"] > { - webGuardedFetchPromise ??= import("./web-guarded-fetch.js"); - return (await webGuardedFetchPromise).withTrustedWebToolsEndpoint; + return (await webGuardedFetchLoader.load()).withTrustedWebToolsEndpoint; } async function loadSelfHostedWebToolsEndpoint(): Promise< WebGuardedFetchModule["withSelfHostedWebToolsEndpoint"] > { - webGuardedFetchPromise ??= import("./web-guarded-fetch.js"); - return (await webGuardedFetchPromise).withSelfHostedWebToolsEndpoint; + return (await webGuardedFetchLoader.load()).withSelfHostedWebToolsEndpoint; } export type SearchConfigRecord = (NonNullable["web"] extends infer Web diff --git a/src/auto-reply/reply/agent-runner-memory.ts b/src/auto-reply/reply/agent-runner-memory.ts index 52950259327..af03a2014fe 100644 --- a/src/auto-reply/reply/agent-runner-memory.ts +++ b/src/auto-reply/reply/agent-runner-memory.ts @@ -26,6 +26,7 @@ import { logVerbose } from "../../globals.js"; import { registerAgentRunContext } from "../../infra/agent-events.js"; import { resolveMemoryFlushPlan } from "../../plugins/memory-state.js"; import { CommandLane } from "../../process/lanes.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; import type { TemplateContext } from "../templating.js"; import type { VerboseLevel } from "../thinking.js"; @@ -48,11 +49,12 @@ import { incrementCompactionCount } from "./session-updates.js"; type PiEmbeddedRuntime = typeof import("../../agents/pi-embedded.js"); -let piEmbeddedRuntimePromise: Promise | undefined; +const piEmbeddedRuntimeLoader = createLazyImportLoader( + () => import("../../agents/pi-embedded.js"), +); function loadPiEmbeddedRuntime(): Promise { - piEmbeddedRuntimePromise ??= import("../../agents/pi-embedded.js"); - return piEmbeddedRuntimePromise; + return piEmbeddedRuntimeLoader.load(); } async function compactEmbeddedPiSessionDefault( diff --git a/src/auto-reply/reply/agent-runner-payloads.ts b/src/auto-reply/reply/agent-runner-payloads.ts index 627e43f993a..eb929b77d6f 100644 --- a/src/auto-reply/reply/agent-runner-payloads.ts +++ b/src/auto-reply/reply/agent-runner-payloads.ts @@ -2,6 +2,7 @@ import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-pay import type { MessagingToolSend } from "../../agents/pi-embedded-messaging.types.js"; import type { ReplyToMode } from "../../config/types.js"; import { logVerbose } from "../../globals.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { stripLegacyBracketToolCallBlocks } from "../../shared/text/assistant-visible-text.js"; import { stripHeartbeatToken } from "../heartbeat.js"; import type { OriginatingChannelType } from "../templating.js"; @@ -17,13 +18,12 @@ import { import { normalizeReplyPayloadDirectives } from "./reply-delivery.js"; import { applyReplyThreading, isRenderablePayload } from "./reply-payloads-base.js"; -let replyPayloadsDedupeRuntimePromise: Promise< - typeof import("./reply-payloads-dedupe.runtime.js") -> | null = null; +const replyPayloadsDedupeRuntimeLoader = createLazyImportLoader( + () => import("./reply-payloads-dedupe.runtime.js"), +); function loadReplyPayloadsDedupeRuntime() { - replyPayloadsDedupeRuntimePromise ??= import("./reply-payloads-dedupe.runtime.js"); - return replyPayloadsDedupeRuntimePromise; + return replyPayloadsDedupeRuntimeLoader.load(); } async function normalizeReplyPayloadMedia(params: { diff --git a/src/auto-reply/reply/body.ts b/src/auto-reply/reply/body.ts index 147e4147875..e12332b2f8a 100644 --- a/src/auto-reply/reply/body.ts +++ b/src/auto-reply/reply/body.ts @@ -1,13 +1,13 @@ import type { SessionEntry } from "../../config/sessions/types.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { setAbortMemory } from "./abort-primitives.js"; -let sessionStoreRuntimePromise: Promise< - typeof import("../../config/sessions/store.runtime.js") -> | null = null; +const sessionStoreRuntimeLoader = createLazyImportLoader( + () => import("../../config/sessions/store.runtime.js"), +); function loadSessionStoreRuntime() { - sessionStoreRuntimePromise ??= import("../../config/sessions/store.runtime.js"); - return sessionStoreRuntimePromise; + return sessionStoreRuntimeLoader.load(); } export async function applySessionHints(params: { diff --git a/src/auto-reply/reply/commands-acp.ts b/src/auto-reply/reply/commands-acp.ts index d1b9368d036..9058c432cb9 100644 --- a/src/auto-reply/reply/commands-acp.ts +++ b/src/auto-reply/reply/commands-acp.ts @@ -1,4 +1,5 @@ import { logVerbose } from "../../globals.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { requireGatewayClientScopeForInternalChannel } from "./command-gates.js"; import { COMMAND, @@ -18,16 +19,17 @@ type AcpActionHandler = ( tokens: string[], ) => Promise; -let lifecycleHandlersPromise: Promise | undefined; -let runtimeOptionHandlersPromise: - | Promise - | undefined; -let diagnosticHandlersPromise: Promise | undefined; +const lifecycleHandlersLoader = createLazyImportLoader(() => import("./commands-acp/lifecycle.js")); +const runtimeOptionHandlersLoader = createLazyImportLoader( + () => import("./commands-acp/runtime-options.js"), +); +const diagnosticHandlersLoader = createLazyImportLoader( + () => import("./commands-acp/diagnostics.js"), +); async function loadAcpActionHandler(action: Exclude): Promise { if (action === "spawn" || action === "cancel" || action === "steer" || action === "close") { - lifecycleHandlersPromise ??= import("./commands-acp/lifecycle.js"); - const handlers = await lifecycleHandlersPromise; + const handlers = await lifecycleHandlersLoader.load(); return { spawn: handlers.handleAcpSpawnAction, cancel: handlers.handleAcpCancelAction, @@ -46,8 +48,7 @@ async function loadAcpActionHandler(action: Exclude): Promise action === "model" || action === "reset-options" ) { - runtimeOptionHandlersPromise ??= import("./commands-acp/runtime-options.js"); - const handlers = await runtimeOptionHandlersPromise; + const handlers = await runtimeOptionHandlersLoader.load(); return { status: handlers.handleAcpStatusAction, "set-mode": handlers.handleAcpSetModeAction, @@ -60,8 +61,7 @@ async function loadAcpActionHandler(action: Exclude): Promise }[action]; } - diagnosticHandlersPromise ??= import("./commands-acp/diagnostics.js"); - const handlers = await diagnosticHandlersPromise; + const handlers = await diagnosticHandlersLoader.load(); const diagnosticHandlers: Record<"doctor" | "install" | "sessions", AcpActionHandler> = { doctor: handlers.handleAcpDoctorAction, install: async (params, tokens) => handlers.handleAcpInstallAction(params, tokens), diff --git a/src/auto-reply/reply/commands-compact.ts b/src/auto-reply/reply/commands-compact.ts index 1a9024164a8..4196fbf1356 100644 --- a/src/auto-reply/reply/commands-compact.ts +++ b/src/auto-reply/reply/commands-compact.ts @@ -1,6 +1,7 @@ import { resolveAgentDir, resolveSessionAgentId } from "../../agents/agent-scope.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { logVerbose } from "../../globals.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, @@ -9,11 +10,10 @@ import { import type { CommandHandler } from "./commands-types.js"; import { stripMentions, stripStructuralPrefixes } from "./mentions.js"; -let compactRuntimePromise: Promise | null = null; +const compactRuntimeLoader = createLazyImportLoader(() => import("./commands-compact.runtime.js")); function loadCompactRuntime(): Promise { - compactRuntimePromise ??= import("./commands-compact.runtime.js"); - return compactRuntimePromise; + return compactRuntimeLoader.load(); } function extractCompactInstructions(params: { diff --git a/src/auto-reply/reply/commands-core.ts b/src/auto-reply/reply/commands-core.ts index 5508810308b..35bed1b2bb6 100644 --- a/src/auto-reply/reply/commands-core.ts +++ b/src/auto-reply/reply/commands-core.ts @@ -1,3 +1,4 @@ +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { shouldHandleTextCommands } from "../commands-registry.js"; import { maybeHandleResetCommand } from "./commands-reset.js"; import type { @@ -5,12 +6,12 @@ import type { CommandHandlerResult, HandleCommandsParams, } from "./commands-types.js"; -let commandHandlersRuntimePromise: Promise | null = - null; +const commandHandlersRuntimeLoader = createLazyImportLoader( + () => import("./commands-handlers.runtime.js"), +); function loadCommandHandlersRuntime() { - commandHandlersRuntimePromise ??= import("./commands-handlers.runtime.js"); - return commandHandlersRuntimePromise; + return commandHandlersRuntimeLoader.load(); } let HANDLERS: CommandHandler[] | null = null; diff --git a/src/auto-reply/reply/commands-reset-hooks.ts b/src/auto-reply/reply/commands-reset-hooks.ts index c26579e6008..e90555e39fc 100644 --- a/src/auto-reply/reply/commands-reset-hooks.ts +++ b/src/auto-reply/reply/commands-reset-hooks.ts @@ -4,13 +4,13 @@ import { logVerbose } from "../../globals.js"; import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js"; import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; import { resolveAgentIdFromSessionKey } from "../../routing/session-key.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import type { HandleCommandsParams } from "./commands-types.js"; -let routeReplyRuntimePromise: Promise | null = null; +const routeReplyRuntimeLoader = createLazyImportLoader(() => import("./route-reply.runtime.js")); function loadRouteReplyRuntime() { - routeReplyRuntimePromise ??= import("./route-reply.runtime.js"); - return routeReplyRuntimePromise; + return routeReplyRuntimeLoader.load(); } export type ResetCommandAction = "new" | "reset"; diff --git a/src/auto-reply/reply/commands-subagents.ts b/src/auto-reply/reply/commands-subagents.ts index 210a994c471..4ed65b21df4 100644 --- a/src/auto-reply/reply/commands-subagents.ts +++ b/src/auto-reply/reply/commands-subagents.ts @@ -1,4 +1,5 @@ import { logVerbose } from "../../globals.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { resolveHandledPrefix, resolveRequesterSessionKey, @@ -8,77 +9,80 @@ import { } from "./commands-subagents-dispatch.js"; import type { CommandHandler } from "./commands-types.js"; -let actionAgentsPromise: Promise | null = - null; -let actionFocusPromise: Promise | null = - null; -let actionHelpPromise: Promise | null = null; -let actionInfoPromise: Promise | null = null; -let actionKillPromise: Promise | null = null; -let actionListPromise: Promise | null = null; -let actionLogPromise: Promise | null = null; -let actionSendPromise: Promise | null = null; -let actionSpawnPromise: Promise | null = - null; -let actionUnfocusPromise: Promise | null = - null; -let controlRuntimePromise: Promise< - typeof import("./commands-subagents-control.runtime.js") -> | null = null; +const actionAgentsLoader = createLazyImportLoader( + () => import("./commands-subagents/action-agents.js"), +); +const actionFocusLoader = createLazyImportLoader( + () => import("./commands-subagents/action-focus.js"), +); +const actionHelpLoader = createLazyImportLoader( + () => import("./commands-subagents/action-help.js"), +); +const actionInfoLoader = createLazyImportLoader( + () => import("./commands-subagents/action-info.js"), +); +const actionKillLoader = createLazyImportLoader( + () => import("./commands-subagents/action-kill.js"), +); +const actionListLoader = createLazyImportLoader( + () => import("./commands-subagents/action-list.js"), +); +const actionLogLoader = createLazyImportLoader(() => import("./commands-subagents/action-log.js")); +const actionSendLoader = createLazyImportLoader( + () => import("./commands-subagents/action-send.js"), +); +const actionSpawnLoader = createLazyImportLoader( + () => import("./commands-subagents/action-spawn.js"), +); +const actionUnfocusLoader = createLazyImportLoader( + () => import("./commands-subagents/action-unfocus.js"), +); +const controlRuntimeLoader = createLazyImportLoader( + () => import("./commands-subagents-control.runtime.js"), +); function loadAgentsAction() { - actionAgentsPromise ??= import("./commands-subagents/action-agents.js"); - return actionAgentsPromise; + return actionAgentsLoader.load(); } function loadFocusAction() { - actionFocusPromise ??= import("./commands-subagents/action-focus.js"); - return actionFocusPromise; + return actionFocusLoader.load(); } function loadHelpAction() { - actionHelpPromise ??= import("./commands-subagents/action-help.js"); - return actionHelpPromise; + return actionHelpLoader.load(); } function loadInfoAction() { - actionInfoPromise ??= import("./commands-subagents/action-info.js"); - return actionInfoPromise; + return actionInfoLoader.load(); } function loadKillAction() { - actionKillPromise ??= import("./commands-subagents/action-kill.js"); - return actionKillPromise; + return actionKillLoader.load(); } function loadListAction() { - actionListPromise ??= import("./commands-subagents/action-list.js"); - return actionListPromise; + return actionListLoader.load(); } function loadLogAction() { - actionLogPromise ??= import("./commands-subagents/action-log.js"); - return actionLogPromise; + return actionLogLoader.load(); } function loadSendAction() { - actionSendPromise ??= import("./commands-subagents/action-send.js"); - return actionSendPromise; + return actionSendLoader.load(); } function loadSpawnAction() { - actionSpawnPromise ??= import("./commands-subagents/action-spawn.js"); - return actionSpawnPromise; + return actionSpawnLoader.load(); } function loadUnfocusAction() { - actionUnfocusPromise ??= import("./commands-subagents/action-unfocus.js"); - return actionUnfocusPromise; + return actionUnfocusLoader.load(); } function loadControlRuntime() { - controlRuntimePromise ??= import("./commands-subagents-control.runtime.js"); - return controlRuntimePromise; + return controlRuntimeLoader.load(); } export const handleSubagentsCommand: CommandHandler = async (params, allowTextCommands) => { diff --git a/src/auto-reply/reply/dispatch-acp-attachments.ts b/src/auto-reply/reply/dispatch-acp-attachments.ts index 8b07f9a3791..06258321587 100644 --- a/src/auto-reply/reply/dispatch-acp-attachments.ts +++ b/src/auto-reply/reply/dispatch-acp-attachments.ts @@ -1,16 +1,16 @@ import type { AcpTurnAttachment } from "../../acp/control-plane/manager.types.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { logVerbose } from "../../globals.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; import type { FinalizedMsgContext } from "../templating.js"; -let dispatchAcpMediaRuntimePromise: Promise< - typeof import("./dispatch-acp-media.runtime.js") -> | null = null; +const dispatchAcpMediaRuntimeLoader = createLazyImportLoader( + () => import("./dispatch-acp-media.runtime.js"), +); export function loadDispatchAcpMediaRuntime() { - dispatchAcpMediaRuntimePromise ??= import("./dispatch-acp-media.runtime.js"); - return dispatchAcpMediaRuntimePromise; + return dispatchAcpMediaRuntimeLoader.load(); } export type DispatchAcpAttachmentRuntime = Pick< diff --git a/src/auto-reply/reply/dispatch-acp-delivery.ts b/src/auto-reply/reply/dispatch-acp-delivery.ts index 1c7c8f3f2a4..fac3af6bd20 100644 --- a/src/auto-reply/reply/dispatch-acp-delivery.ts +++ b/src/auto-reply/reply/dispatch-acp-delivery.ts @@ -3,6 +3,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { TtsAutoMode } from "../../config/types.tts.js"; import { logVerbose } from "../../globals.js"; import { formatErrorMessage } from "../../infra/errors.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalLowercaseString, normalizeOptionalString, @@ -14,33 +15,31 @@ import type { FinalizedMsgContext } from "../templating.js"; import type { ReplyPayload } from "../types.js"; import type { ReplyDispatchKind, ReplyDispatcher } from "./reply-dispatcher.types.js"; -let routeReplyRuntimePromise: Promise | null = null; -let dispatchAcpTtsRuntimePromise: Promise | null = - null; -let channelPluginRuntimePromise: Promise | null = - null; -let messageActionRuntimePromise: Promise< - typeof import("../../infra/outbound/message-action-runner.js") -> | null = null; +const routeReplyRuntimeLoader = createLazyImportLoader(() => import("./route-reply.runtime.js")); +const dispatchAcpTtsRuntimeLoader = createLazyImportLoader( + () => import("./dispatch-acp-tts.runtime.js"), +); +const channelPluginRuntimeLoader = createLazyImportLoader( + () => import("../../channels/plugins/index.js"), +); +const messageActionRuntimeLoader = createLazyImportLoader( + () => import("../../infra/outbound/message-action-runner.js"), +); function loadRouteReplyRuntime() { - routeReplyRuntimePromise ??= import("./route-reply.runtime.js"); - return routeReplyRuntimePromise; + return routeReplyRuntimeLoader.load(); } function loadDispatchAcpTtsRuntime() { - dispatchAcpTtsRuntimePromise ??= import("./dispatch-acp-tts.runtime.js"); - return dispatchAcpTtsRuntimePromise; + return dispatchAcpTtsRuntimeLoader.load(); } function loadChannelPluginRuntime() { - channelPluginRuntimePromise ??= import("../../channels/plugins/index.js"); - return channelPluginRuntimePromise; + return channelPluginRuntimeLoader.load(); } function loadMessageActionRuntime() { - messageActionRuntimePromise ??= import("../../infra/outbound/message-action-runner.js"); - return messageActionRuntimePromise; + return messageActionRuntimeLoader.load(); } export type AcpDispatchDeliveryMeta = { diff --git a/src/auto-reply/reply/dispatch-acp.runtime.ts b/src/auto-reply/reply/dispatch-acp.runtime.ts index f87f7a58196..5b328c170f8 100644 --- a/src/auto-reply/reply/dispatch-acp.runtime.ts +++ b/src/auto-reply/reply/dispatch-acp.runtime.ts @@ -1,20 +1,20 @@ +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; + type ShouldBypassAcpDispatchForCommand = (typeof import("./dispatch-acp-command-bypass.js"))["shouldBypassAcpDispatchForCommand"]; type TryDispatchAcpReply = (typeof import("./dispatch-acp.js"))["tryDispatchAcpReply"]; -let dispatchAcpPromise: Promise | null = null; -let dispatchAcpCommandBypassPromise: Promise< - typeof import("./dispatch-acp-command-bypass.js") -> | null = null; +const dispatchAcpLoader = createLazyImportLoader(() => import("./dispatch-acp.js")); +const dispatchAcpCommandBypassLoader = createLazyImportLoader( + () => import("./dispatch-acp-command-bypass.js"), +); function loadDispatchAcp() { - dispatchAcpPromise ??= import("./dispatch-acp.js"); - return dispatchAcpPromise; + return dispatchAcpLoader.load(); } function loadDispatchAcpCommandBypass() { - dispatchAcpCommandBypassPromise ??= import("./dispatch-acp-command-bypass.js"); - return dispatchAcpCommandBypassPromise; + return dispatchAcpCommandBypassLoader.load(); } export async function shouldBypassAcpDispatchForCommand( diff --git a/src/auto-reply/reply/dispatch-acp.ts b/src/auto-reply/reply/dispatch-acp.ts index 20f31760bc7..66642503af9 100644 --- a/src/auto-reply/reply/dispatch-acp.ts +++ b/src/auto-reply/reply/dispatch-acp.ts @@ -17,6 +17,7 @@ import { generateSecureUuid } from "../../infra/secure-random.js"; import { prefixSystemMessage } from "../../infra/system-message.js"; import { markDiagnosticSessionProgress } from "../../logging/diagnostic.js"; import { resolveAgentIdFromSessionKey } from "../../routing/session-key.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, @@ -39,36 +40,33 @@ import { import { hasInboundMedia } from "./inbound-media.js"; import type { ReplyDispatchKind, ReplyDispatcher } from "./reply-dispatcher.types.js"; -let dispatchAcpManagerRuntimePromise: Promise< - typeof import("./dispatch-acp-manager.runtime.js") -> | null = null; -let dispatchAcpSessionRuntimePromise: Promise< - typeof import("./dispatch-acp-session.runtime.js") -> | null = null; -let dispatchAcpTtsRuntimePromise: Promise | null = - null; -let dispatchAcpTranscriptRuntimePromise: Promise< - typeof import("./dispatch-acp-transcript.runtime.js") -> | null = null; +const dispatchAcpManagerRuntimeLoader = createLazyImportLoader( + () => import("./dispatch-acp-manager.runtime.js"), +); +const dispatchAcpSessionRuntimeLoader = createLazyImportLoader( + () => import("./dispatch-acp-session.runtime.js"), +); +const dispatchAcpTtsRuntimeLoader = createLazyImportLoader( + () => import("./dispatch-acp-tts.runtime.js"), +); +const dispatchAcpTranscriptRuntimeLoader = createLazyImportLoader( + () => import("./dispatch-acp-transcript.runtime.js"), +); function loadDispatchAcpManagerRuntime() { - dispatchAcpManagerRuntimePromise ??= import("./dispatch-acp-manager.runtime.js"); - return dispatchAcpManagerRuntimePromise; + return dispatchAcpManagerRuntimeLoader.load(); } function loadDispatchAcpSessionRuntime() { - dispatchAcpSessionRuntimePromise ??= import("./dispatch-acp-session.runtime.js"); - return dispatchAcpSessionRuntimePromise; + return dispatchAcpSessionRuntimeLoader.load(); } function loadDispatchAcpTtsRuntime() { - dispatchAcpTtsRuntimePromise ??= import("./dispatch-acp-tts.runtime.js"); - return dispatchAcpTtsRuntimePromise; + return dispatchAcpTtsRuntimeLoader.load(); } function loadDispatchAcpTranscriptRuntime() { - dispatchAcpTranscriptRuntimePromise ??= import("./dispatch-acp-transcript.runtime.js"); - return dispatchAcpTranscriptRuntimePromise; + return dispatchAcpTranscriptRuntimeLoader.load(); } type DispatchProcessedRecorder = ( diff --git a/src/auto-reply/reply/dispatch-from-config.ts b/src/auto-reply/reply/dispatch-from-config.ts index 470b4653463..f4b6adc53f8 100644 --- a/src/auto-reply/reply/dispatch-from-config.ts +++ b/src/auto-reply/reply/dispatch-from-config.ts @@ -59,6 +59,7 @@ import { import { getGlobalHookRunner, getGlobalPluginRegistry } from "../../plugins/hook-runner-global.js"; import { isAcpSessionKey } from "../../routing/session-key.js"; import { resolveSendPolicy } from "../../sessions/send-policy.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, @@ -96,44 +97,39 @@ import { resolveReplyRoutingDecision } from "./routing-policy.js"; import { resolveSourceReplyVisibilityPolicy } from "./source-reply-delivery-mode.js"; import { resolveRunTypingPolicy } from "./typing-policy.js"; -let routeReplyRuntimePromise: Promise | null = null; -let getReplyFromConfigRuntimePromise: Promise< - typeof import("./get-reply-from-config.runtime.js") -> | null = null; -let abortRuntimePromise: Promise | null = null; -let ttsRuntimePromise: Promise | null = null; -let runtimePluginsPromise: Promise | null = null; -let replyMediaPathsRuntimePromise: Promise | null = - null; +const routeReplyRuntimeLoader = createLazyImportLoader(() => import("./route-reply.runtime.js")); +const getReplyFromConfigRuntimeLoader = createLazyImportLoader( + () => import("./get-reply-from-config.runtime.js"), +); +const abortRuntimeLoader = createLazyImportLoader(() => import("./abort.runtime.js")); +const ttsRuntimeLoader = createLazyImportLoader(() => import("../../tts/tts.runtime.js")); +const runtimePluginsLoader = createLazyImportLoader(() => import("./runtime-plugins.runtime.js")); +const replyMediaPathsRuntimeLoader = createLazyImportLoader( + () => import("./reply-media-paths.runtime.js"), +); function loadRouteReplyRuntime() { - routeReplyRuntimePromise ??= import("./route-reply.runtime.js"); - return routeReplyRuntimePromise; + return routeReplyRuntimeLoader.load(); } function loadGetReplyFromConfigRuntime() { - getReplyFromConfigRuntimePromise ??= import("./get-reply-from-config.runtime.js"); - return getReplyFromConfigRuntimePromise; + return getReplyFromConfigRuntimeLoader.load(); } function loadAbortRuntime() { - abortRuntimePromise ??= import("./abort.runtime.js"); - return abortRuntimePromise; + return abortRuntimeLoader.load(); } function loadTtsRuntime() { - ttsRuntimePromise ??= import("../../tts/tts.runtime.js"); - return ttsRuntimePromise; + return ttsRuntimeLoader.load(); } function loadRuntimePlugins() { - runtimePluginsPromise ??= import("./runtime-plugins.runtime.js"); - return runtimePluginsPromise; + return runtimePluginsLoader.load(); } function loadReplyMediaPathsRuntime() { - replyMediaPathsRuntimePromise ??= import("./reply-media-paths.runtime.js"); - return replyMediaPathsRuntimePromise; + return replyMediaPathsRuntimeLoader.load(); } async function maybeApplyTtsToReplyPayload( diff --git a/src/auto-reply/reply/get-reply-directives-apply.ts b/src/auto-reply/reply/get-reply-directives-apply.ts index 9e376389043..499d807f7b2 100644 --- a/src/auto-reply/reply/get-reply-directives-apply.ts +++ b/src/auto-reply/reply/get-reply-directives-apply.ts @@ -1,6 +1,7 @@ import type { SessionEntry, SessionScope } from "../../config/sessions/types.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { enqueueSystemEvent } from "../../infra/system-events.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import type { MsgContext } from "../templating.js"; import type { ElevatedLevel } from "../thinking.js"; import type { ReplyPayload } from "../types.js"; @@ -16,38 +17,36 @@ import type { TypingController } from "./typing.js"; type AgentDefaults = NonNullable["defaults"]; type AgentEntry = NonNullable["list"]>[number]; -let commandsStatusPromise: Promise | null = null; -let directiveLevelsPromise: Promise | null = null; -let directiveImplPromise: Promise | null = null; -let directiveFastLanePromise: Promise | null = - null; -let directivePersistPromise: Promise< - typeof import("./directive-handling.persist.runtime.js") -> | null = null; +const commandsStatusLoader = createLazyImportLoader(() => import("./commands-status.runtime.js")); +const directiveLevelsLoader = createLazyImportLoader( + () => import("./directive-handling.levels.js"), +); +const directiveImplLoader = createLazyImportLoader(() => import("./directive-handling.impl.js")); +const directiveFastLaneLoader = createLazyImportLoader( + () => import("./directive-handling.fast-lane.js"), +); +const directivePersistLoader = createLazyImportLoader( + () => import("./directive-handling.persist.runtime.js"), +); function loadCommandsStatus() { - commandsStatusPromise ??= import("./commands-status.runtime.js"); - return commandsStatusPromise; + return commandsStatusLoader.load(); } function loadDirectiveLevels() { - directiveLevelsPromise ??= import("./directive-handling.levels.js"); - return directiveLevelsPromise; + return directiveLevelsLoader.load(); } function loadDirectiveImpl() { - directiveImplPromise ??= import("./directive-handling.impl.js"); - return directiveImplPromise; + return directiveImplLoader.load(); } function loadDirectiveFastLane() { - directiveFastLanePromise ??= import("./directive-handling.fast-lane.js"); - return directiveFastLanePromise; + return directiveFastLaneLoader.load(); } function loadDirectivePersist() { - directivePersistPromise ??= import("./directive-handling.persist.runtime.js"); - return directivePersistPromise; + return directivePersistLoader.load(); } function hasOnlyModelDirective(directives: InlineDirectives): boolean { diff --git a/src/auto-reply/reply/get-reply-directives.ts b/src/auto-reply/reply/get-reply-directives.ts index 3078db39c86..593383feb58 100644 --- a/src/auto-reply/reply/get-reply-directives.ts +++ b/src/auto-reply/reply/get-reply-directives.ts @@ -7,6 +7,7 @@ import type { SkillCommandSpec } from "../../agents/skills.js"; import type { SessionEntry } from "../../config/sessions.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { normalizeAgentId } from "../../routing/session-key.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, @@ -40,18 +41,17 @@ import type { TypingController } from "./typing.js"; type AgentDefaults = NonNullable["defaults"]; -let commandsRegistryPromise: Promise | null = - null; -let skillCommandsPromise: Promise | null = null; +const commandsRegistryLoader = createLazyImportLoader( + () => import("../commands-registry.runtime.js"), +); +const skillCommandsLoader = createLazyImportLoader(() => import("../skill-commands.runtime.js")); function loadCommandsRegistry() { - commandsRegistryPromise ??= import("../commands-registry.runtime.js"); - return commandsRegistryPromise; + return commandsRegistryLoader.load(); } function loadSkillCommands() { - skillCommandsPromise ??= import("../skill-commands.runtime.js"); - return skillCommandsPromise; + return skillCommandsLoader.load(); } function canUseFastExplicitModelDirective(params: { diff --git a/src/auto-reply/reply/get-reply-inline-actions.ts b/src/auto-reply/reply/get-reply-inline-actions.ts index dce171e41f5..4864a749616 100644 --- a/src/auto-reply/reply/get-reply-inline-actions.ts +++ b/src/auto-reply/reply/get-reply-inline-actions.ts @@ -8,6 +8,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { logVerbose } from "../../globals.js"; import { formatErrorMessage } from "../../infra/errors.js"; import { generateSecureToken } from "../../infra/secure-random.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalLowercaseString, normalizeOptionalString, @@ -40,30 +41,34 @@ type OpenClawToolsRuntime = typeof import("../../agents/openclaw-tools.runtime.j 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; +const skillCommandsRuntimeLoader = createLazyImportLoader( + () => import("../skill-commands.runtime.js"), +); +const openClawToolsRuntimeLoader = createLazyImportLoader( + () => import("../../agents/openclaw-tools.runtime.js"), +); +const abortCutoffRuntimeLoader = createLazyImportLoader( + () => import("./abort-cutoff.runtime.js"), +); +const commandsRuntimeLoader = createLazyImportLoader( + () => import("./commands.runtime.js"), +); let builtinSlashCommands: Set | null = null; function loadSkillCommandsRuntime(): Promise { - skillCommandsRuntimePromise ??= import("../skill-commands.runtime.js"); - return skillCommandsRuntimePromise; + return skillCommandsRuntimeLoader.load(); } function loadOpenClawToolsRuntime(): Promise { - openClawToolsRuntimePromise ??= import("../../agents/openclaw-tools.runtime.js"); - return openClawToolsRuntimePromise; + return openClawToolsRuntimeLoader.load(); } function loadAbortCutoffRuntime(): Promise { - abortCutoffRuntimePromise ??= import("./abort-cutoff.runtime.js"); - return abortCutoffRuntimePromise; + return abortCutoffRuntimeLoader.load(); } function loadCommandsRuntime(): Promise { - commandsRuntimePromise ??= import("./commands.runtime.js"); - return commandsRuntimePromise; + return commandsRuntimeLoader.load(); } function getBuiltinSlashCommands(): Set { diff --git a/src/auto-reply/reply/get-reply-run.ts b/src/auto-reply/reply/get-reply-run.ts index 224a2dcbbaf..481d83aeba8 100644 --- a/src/auto-reply/reply/get-reply-run.ts +++ b/src/auto-reply/reply/get-reply-run.ts @@ -23,6 +23,7 @@ import { isSubagentSessionKey, normalizeMainKey, } from "../../routing/session-key.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import type { SilentReplyConversationType } from "../../shared/silent-reply-policy.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { isReasoningTagProvider } from "../../utils/provider-utils.js"; @@ -237,34 +238,32 @@ export function buildExecOverridePromptHint(params: { .join("\n"); } -let piEmbeddedRuntimePromise: Promise | null = - null; -let agentRunnerRuntimePromise: Promise | null = null; -let sessionUpdatesRuntimePromise: Promise | null = - null; -let sessionStoreRuntimePromise: Promise< - typeof import("../../config/sessions/store.runtime.js") -> | null = null; +const piEmbeddedRuntimeLoader = createLazyImportLoader( + () => import("../../agents/pi-embedded.runtime.js"), +); +const agentRunnerRuntimeLoader = createLazyImportLoader(() => import("./agent-runner.runtime.js")); +const sessionUpdatesRuntimeLoader = createLazyImportLoader( + () => import("./session-updates.runtime.js"), +); +const sessionStoreRuntimeLoader = createLazyImportLoader( + () => import("../../config/sessions/store.runtime.js"), +); const UNTRUSTED_SYSTEM_EVENT_LINE_RE = /^System \(untrusted\):/m; function loadPiEmbeddedRuntime() { - piEmbeddedRuntimePromise ??= import("../../agents/pi-embedded.runtime.js"); - return piEmbeddedRuntimePromise; + return piEmbeddedRuntimeLoader.load(); } function loadAgentRunnerRuntime() { - agentRunnerRuntimePromise ??= import("./agent-runner.runtime.js"); - return agentRunnerRuntimePromise; + return agentRunnerRuntimeLoader.load(); } function loadSessionUpdatesRuntime() { - sessionUpdatesRuntimePromise ??= import("./session-updates.runtime.js"); - return sessionUpdatesRuntimePromise; + return sessionUpdatesRuntimeLoader.load(); } function loadSessionStoreRuntime() { - sessionStoreRuntimePromise ??= import("../../config/sessions/store.runtime.js"); - return sessionStoreRuntimePromise; + return sessionStoreRuntimeLoader.load(); } function stripPromptThinkingDirectives(body: string): string { diff --git a/src/auto-reply/reply/get-reply.ts b/src/auto-reply/reply/get-reply.ts index b9fe70b45c8..1bbaf5e93ca 100644 --- a/src/auto-reply/reply/get-reply.ts +++ b/src/auto-reply/reply/get-reply.ts @@ -14,6 +14,7 @@ import { logVerbose } from "../../globals.js"; import { formatErrorMessage } from "../../infra/errors.js"; import { buildAgentHookContextChannelFields } from "../../plugins/hook-agent-context.js"; import { defaultRuntime } from "../../runtime.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { normalizeStringEntries } from "../../shared/string-normalization.js"; import type { GetReplyOptions } from "../get-reply-options.types.js"; @@ -45,57 +46,53 @@ import { createTypingController } from "./typing.js"; type ResetCommandAction = "new" | "reset"; -let sessionResetModelRuntimePromise: Promise< - typeof import("./session-reset-model.runtime.js") -> | null = null; -let stageSandboxMediaRuntimePromise: Promise< - typeof import("./stage-sandbox-media.runtime.js") -> | null = null; -let mediaUnderstandingApplyRuntimePromise: Promise< - typeof import("../../media-understanding/apply.runtime.js") -> | null = null; -let linkUnderstandingApplyRuntimePromise: Promise< - typeof import("../../link-understanding/apply.runtime.js") -> | null = null; -let commandsCoreRuntimePromise: Promise | null = null; +const sessionResetModelRuntimeLoader = createLazyImportLoader( + () => import("./session-reset-model.runtime.js"), +); +const stageSandboxMediaRuntimeLoader = createLazyImportLoader( + () => import("./stage-sandbox-media.runtime.js"), +); +const mediaUnderstandingApplyRuntimeLoader = createLazyImportLoader( + () => import("../../media-understanding/apply.runtime.js"), +); +const linkUnderstandingApplyRuntimeLoader = createLazyImportLoader( + () => import("../../link-understanding/apply.runtime.js"), +); +const commandsCoreRuntimeLoader = createLazyImportLoader( + () => import("./commands-core.runtime.js"), +); function loadSessionResetModelRuntime() { - sessionResetModelRuntimePromise ??= import("./session-reset-model.runtime.js"); - return sessionResetModelRuntimePromise; + return sessionResetModelRuntimeLoader.load(); } function loadStageSandboxMediaRuntime() { - stageSandboxMediaRuntimePromise ??= import("./stage-sandbox-media.runtime.js"); - return stageSandboxMediaRuntimePromise; + return stageSandboxMediaRuntimeLoader.load(); } function loadMediaUnderstandingApplyRuntime() { - mediaUnderstandingApplyRuntimePromise ??= import("../../media-understanding/apply.runtime.js"); - return mediaUnderstandingApplyRuntimePromise; + return mediaUnderstandingApplyRuntimeLoader.load(); } function loadLinkUnderstandingApplyRuntime() { - linkUnderstandingApplyRuntimePromise ??= import("../../link-understanding/apply.runtime.js"); - return linkUnderstandingApplyRuntimePromise; + return linkUnderstandingApplyRuntimeLoader.load(); } function loadCommandsCoreRuntime() { - commandsCoreRuntimePromise ??= import("./commands-core.runtime.js"); - return commandsCoreRuntimePromise; + return commandsCoreRuntimeLoader.load(); } -let hookRunnerGlobalPromise: Promise | null = - null; -let originRoutingPromise: Promise | null = null; +const hookRunnerGlobalLoader = createLazyImportLoader( + () => import("../../plugins/hook-runner-global.js"), +); +const originRoutingLoader = createLazyImportLoader(() => import("./origin-routing.js")); function loadHookRunnerGlobal() { - hookRunnerGlobalPromise ??= import("../../plugins/hook-runner-global.js"); - return hookRunnerGlobalPromise; + return hookRunnerGlobalLoader.load(); } function loadOriginRouting() { - originRoutingPromise ??= import("./origin-routing.js"); - return originRoutingPromise; + return originRoutingLoader.load(); } function mergeSkillFilters(channelFilter?: string[], agentFilter?: string[]): string[] | undefined { @@ -145,7 +142,7 @@ async function applyMediaUnderstandingIfNeeded(params: { await applyMediaUnderstanding(params); return true; } catch (err) { - mediaUnderstandingApplyRuntimePromise = null; + mediaUnderstandingApplyRuntimeLoader.clear(); logVerbose( `media understanding failed, proceeding with raw content: ${formatErrorMessage(err)}`, ); @@ -165,7 +162,7 @@ async function applyLinkUnderstandingIfNeeded(params: { await applyLinkUnderstanding(params); return true; } catch (err) { - linkUnderstandingApplyRuntimePromise = null; + linkUnderstandingApplyRuntimeLoader.clear(); logVerbose( `link understanding failed, proceeding with raw content: ${formatErrorMessage(err)}`, ); diff --git a/src/auto-reply/reply/groups.ts b/src/auto-reply/reply/groups.ts index a2b903bfca6..0ee646562f7 100644 --- a/src/auto-reply/reply/groups.ts +++ b/src/auto-reply/reply/groups.ts @@ -1,6 +1,7 @@ import { resolveChannelGroupRequireMention } from "../../config/group-policy.js"; import type { GroupKeyResolution, SessionEntry } from "../../config/sessions.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import type { SilentReplyPolicy } from "../../shared/silent-reply-policy.js"; import { normalizeOptionalLowercaseString, @@ -12,7 +13,7 @@ import { normalizeGroupActivation } from "../group-activation.js"; import type { TemplateContext } from "../templating.js"; import { extractExplicitGroupId } from "./group-id.js"; -let groupsRuntimePromise: Promise | null = null; +const groupsRuntimeLoader = createLazyImportLoader(() => import("./groups.runtime.js")); type DiscordGroupConfig = { requireMention?: boolean; @@ -26,8 +27,7 @@ type DiscordConfigWithGuilds = { }; function loadGroupsRuntime() { - groupsRuntimePromise ??= import("./groups.runtime.js"); - return groupsRuntimePromise; + return groupsRuntimeLoader.load(); } async function resolveRuntimeChannelId(raw?: string | null): Promise { diff --git a/src/auto-reply/reply/model-selection.ts b/src/auto-reply/reply/model-selection.ts index 5ed9097a301..33b61a1c892 100644 --- a/src/auto-reply/reply/model-selection.ts +++ b/src/auto-reply/reply/model-selection.ts @@ -16,6 +16,7 @@ import { import type { SessionEntry } from "../../config/sessions/types.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { applyModelOverrideToSessionEntry } from "../../sessions/model-overrides.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import type { ThinkLevel } from "./directives.js"; export { resolveModelDirectiveSelection, @@ -62,21 +63,19 @@ function shouldLogModelSelectionTiming(): boolean { return process.env.OPENCLAW_DEBUG_INGRESS_TIMING === "1"; } -let modelCatalogRuntimePromise: - | Promise - | undefined; -let sessionStoreRuntimePromise: - | Promise - | undefined; +const modelCatalogRuntimeLoader = createLazyImportLoader( + () => import("../../agents/model-catalog.runtime.js"), +); +const sessionStoreRuntimeLoader = createLazyImportLoader( + () => import("../../config/sessions/store.runtime.js"), +); function loadModelCatalogRuntime() { - modelCatalogRuntimePromise ??= import("../../agents/model-catalog.runtime.js"); - return modelCatalogRuntimePromise; + return modelCatalogRuntimeLoader.load(); } function loadSessionStoreRuntime() { - sessionStoreRuntimePromise ??= import("../../config/sessions/store.runtime.js"); - return sessionStoreRuntimePromise; + return sessionStoreRuntimeLoader.load(); } export async function createModelSelectionState(params: { diff --git a/src/auto-reply/reply/route-reply.ts b/src/auto-reply/reply/route-reply.ts index 92b3cef0e48..5cf6899d0b9 100644 --- a/src/auto-reply/reply/route-reply.ts +++ b/src/auto-reply/reply/route-reply.ts @@ -17,6 +17,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { formatErrorMessage } from "../../infra/errors.js"; import { buildOutboundSessionContext } from "../../infra/outbound/session-context.js"; import { hasReplyPayloadContent } from "../../interactive/payload.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import type { SilentReplyConversationType } from "../../shared/silent-reply-policy.js"; import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js"; import { INTERNAL_MESSAGE_CHANNEL, normalizeMessageChannel } from "../../utils/message-channel.js"; @@ -29,13 +30,12 @@ import { shouldSuppressReasoningPayload, } from "./reply-payloads.js"; -let deliverRuntimePromise: Promise< - typeof import("../../infra/outbound/deliver-runtime.js") -> | null = null; +const deliverRuntimeLoader = createLazyImportLoader( + () => import("../../infra/outbound/deliver-runtime.js"), +); function loadDeliverRuntime() { - deliverRuntimePromise ??= import("../../infra/outbound/deliver-runtime.js"); - return deliverRuntimePromise; + return deliverRuntimeLoader.load(); } export type RouteReplyParams = { diff --git a/src/auto-reply/reply/session-fork.ts b/src/auto-reply/reply/session-fork.ts index 968115ca7a4..47bd07ec3ac 100644 --- a/src/auto-reply/reply/session-fork.ts +++ b/src/auto-reply/reply/session-fork.ts @@ -1,4 +1,5 @@ import type { SessionEntry } from "../../config/sessions/types.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; /** * Default max parent token count beyond which thread/session parent forking is skipped. @@ -6,7 +7,7 @@ import type { SessionEntry } from "../../config/sessions/types.js"; * See #26905. */ const DEFAULT_PARENT_FORK_MAX_TOKENS = 100_000; -let sessionForkRuntimePromise: Promise | null = null; +const sessionForkRuntimeLoader = createLazyImportLoader(() => import("./session-fork.runtime.js")); export type ParentForkDecision = | { @@ -23,8 +24,7 @@ export type ParentForkDecision = }; function loadSessionForkRuntime(): Promise { - sessionForkRuntimePromise ??= import("./session-fork.runtime.js"); - return sessionForkRuntimePromise; + return sessionForkRuntimeLoader.load(); } function formatParentForkTooLargeMessage(params: { diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index 877f8d0b44e..cc2e31af08d 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -41,6 +41,7 @@ import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; import type { PluginHookSessionEndReason } from "../../plugins/hook-types.js"; import { isAcpSessionKey, normalizeMainKey } from "../../routing/session-key.js"; import { isInterSessionInputProvenance } from "../../sessions/input-provenance.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, @@ -65,13 +66,12 @@ import { buildSessionEndHookPayload, buildSessionStartHookPayload } from "./sess import { clearSessionResetRuntimeState } from "./session-reset-cleanup.js"; const log = createSubsystemLogger("session-init"); -let sessionArchiveRuntimePromise: Promise< - typeof import("../../gateway/session-archive.runtime.js") -> | null = null; +const sessionArchiveRuntimeLoader = createLazyImportLoader( + () => import("../../gateway/session-archive.runtime.js"), +); function loadSessionArchiveRuntime() { - sessionArchiveRuntimePromise ??= import("../../gateway/session-archive.runtime.js"); - return sessionArchiveRuntimePromise; + return sessionArchiveRuntimeLoader.load(); } function stripThreadIdFromDeliveryContext( diff --git a/src/cli/channels-cli.ts b/src/cli/channels-cli.ts index b3001574b98..d1fe07a49af 100644 --- a/src/cli/channels-cli.ts +++ b/src/cli/channels-cli.ts @@ -2,6 +2,7 @@ import type { Command } from "commander"; import { danger } from "../globals.js"; import { listBundledPackageChannelMetadata } from "../plugins/bundled-package-channel-metadata.js"; import { defaultRuntime } from "../runtime.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { formatDocsLink } from "../terminal/links.js"; import { theme } from "../terminal/theme.js"; import { runChannelLogin, runChannelLogout } from "./channel-auth.js"; @@ -15,11 +16,12 @@ type ChannelsCommandsModule = typeof import("../commands/channels.js"); const optionNamesRemove = ["channel", "account", "delete"] as const; -let channelsCommandsPromise: Promise | undefined; +const channelsCommandsLoader = createLazyImportLoader( + () => import("../commands/channels.js"), +); function loadChannelsCommands(): Promise { - channelsCommandsPromise ??= import("../commands/channels.js"); - return channelsCommandsPromise; + return channelsCommandsLoader.load(); } function runChannelsCommand(action: () => Promise) { diff --git a/src/cli/command-bootstrap.ts b/src/cli/command-bootstrap.ts index 3a6b537acbb..3625c136a1b 100644 --- a/src/cli/command-bootstrap.ts +++ b/src/cli/command-bootstrap.ts @@ -1,13 +1,13 @@ import type { RuntimeEnv } from "../runtime.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import type { CliPluginRegistryPolicy } from "./command-catalog.js"; import { resolveCliCommandPathPolicy } from "./command-path-policy.js"; import { ensureCliPluginRegistryLoaded } from "./plugin-registry-loader.js"; -let configGuardModulePromise: Promise | undefined; +const configGuardModuleLoader = createLazyImportLoader(() => import("./program/config-guard.js")); function loadConfigGuardModule() { - configGuardModulePromise ??= import("./program/config-guard.js"); - return configGuardModulePromise; + return configGuardModuleLoader.load(); } export async function ensureCliCommandBootstrap(params: { diff --git a/src/cli/daemon-cli/probe.ts b/src/cli/daemon-cli/probe.ts index 155f2c5639b..863bfe16a83 100644 --- a/src/cli/daemon-cli/probe.ts +++ b/src/cli/daemon-cli/probe.ts @@ -1,14 +1,14 @@ import type { OpenClawConfig } from "../../config/types.js"; import { formatErrorMessage } from "../../infra/errors.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { withProgress } from "../progress.js"; type GatewayStatusProbeKind = "connect" | "read"; -let probeGatewayModulePromise: Promise | undefined; +const probeGatewayModuleLoader = createLazyImportLoader(() => import("../../gateway/probe.js")); async function loadProbeGatewayModule(): Promise { - probeGatewayModulePromise ??= import("../../gateway/probe.js"); - return await probeGatewayModulePromise; + return await probeGatewayModuleLoader.load(); } function resolveProbeFailureMessage(result: { diff --git a/src/cli/daemon-cli/register-service-commands.ts b/src/cli/daemon-cli/register-service-commands.ts index 1992f77a309..33738f5a3d7 100644 --- a/src/cli/daemon-cli/register-service-commands.ts +++ b/src/cli/daemon-cli/register-service-commands.ts @@ -1,24 +1,22 @@ import type { Command } from "commander"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { inheritOptionFromParent } from "../command-options.js"; import type { DaemonInstallOptions, GatewayRpcOpts } from "./types.js"; -let daemonInstallModulePromise: Promise | undefined; -let daemonLifecycleModulePromise: Promise | undefined; -let daemonStatusModulePromise: Promise | undefined; +const daemonInstallModuleLoader = createLazyImportLoader(() => import("./install.runtime.js")); +const daemonLifecycleModuleLoader = createLazyImportLoader(() => import("./lifecycle.runtime.js")); +const daemonStatusModuleLoader = createLazyImportLoader(() => import("./status.runtime.js")); function loadDaemonInstallModule() { - daemonInstallModulePromise ??= import("./install.runtime.js"); - return daemonInstallModulePromise; + return daemonInstallModuleLoader.load(); } function loadDaemonLifecycleModule() { - daemonLifecycleModulePromise ??= import("./lifecycle.runtime.js"); - return daemonLifecycleModulePromise; + return daemonLifecycleModuleLoader.load(); } function loadDaemonStatusModule() { - daemonStatusModulePromise ??= import("./status.runtime.js"); - return daemonStatusModulePromise; + return daemonStatusModuleLoader.load(); } function resolveInstallOptions( diff --git a/src/cli/daemon-cli/status.gather.ts b/src/cli/daemon-cli/status.gather.ts index 3787a37efff..9968cdcd0ae 100644 --- a/src/cli/daemon-cli/status.gather.ts +++ b/src/cli/daemon-cli/status.gather.ts @@ -30,6 +30,7 @@ import { type PortUsageStatus, } from "../../infra/ports.js"; import { resolveConfiguredLogFilePath } from "../../logging/log-file-path.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeListenerAddress, parsePortFromArgs, pickProbeHostForBind } from "./shared.js"; import type { GatewayRpcOpts } from "./types.js"; @@ -81,43 +82,39 @@ type ResolvedGatewayStatus = { probeUrlOverride: string | null; }; -let gatewayProbeAuthModulePromise: - | Promise - | undefined; -let daemonInspectModulePromise: Promise | undefined; -let serviceAuditModulePromise: Promise | undefined; -let gatewayTlsModulePromise: Promise | undefined; -let daemonProbeModulePromise: Promise | undefined; -let restartHealthModulePromise: Promise | undefined; +const gatewayProbeAuthModuleLoader = createLazyImportLoader( + () => import("../../gateway/probe-auth.js"), +); +const daemonInspectModuleLoader = createLazyImportLoader(() => import("../../daemon/inspect.js")); +const serviceAuditModuleLoader = createLazyImportLoader( + () => import("../../daemon/service-audit.js"), +); +const gatewayTlsModuleLoader = createLazyImportLoader(() => import("../../infra/tls/gateway.js")); +const daemonProbeModuleLoader = createLazyImportLoader(() => import("./probe.js")); +const restartHealthModuleLoader = createLazyImportLoader(() => import("./restart-health.js")); function loadGatewayProbeAuthModule() { - gatewayProbeAuthModulePromise ??= import("../../gateway/probe-auth.js"); - return gatewayProbeAuthModulePromise; + return gatewayProbeAuthModuleLoader.load(); } function loadDaemonInspectModule() { - daemonInspectModulePromise ??= import("../../daemon/inspect.js"); - return daemonInspectModulePromise; + return daemonInspectModuleLoader.load(); } function loadServiceAuditModule() { - serviceAuditModulePromise ??= import("../../daemon/service-audit.js"); - return serviceAuditModulePromise; + return serviceAuditModuleLoader.load(); } function loadGatewayTlsModule() { - gatewayTlsModulePromise ??= import("../../infra/tls/gateway.js"); - return gatewayTlsModulePromise; + return gatewayTlsModuleLoader.load(); } function loadDaemonProbeModule() { - daemonProbeModulePromise ??= import("./probe.js"); - return daemonProbeModulePromise; + return daemonProbeModuleLoader.load(); } function loadRestartHealthModule() { - restartHealthModulePromise ??= import("./restart-health.js"); - return restartHealthModulePromise; + return restartHealthModuleLoader.load(); } function resolveSnapshotRuntimeConfig(snapshot: ConfigFileSnapshot | null): OpenClawConfig | null { diff --git a/src/cli/gateway-cli/register.ts b/src/cli/gateway-cli/register.ts index e273f0cc8d4..4d0b3dbc91c 100644 --- a/src/cli/gateway-cli/register.ts +++ b/src/cli/gateway-cli/register.ts @@ -13,6 +13,7 @@ import { } from "../../logging/diagnostic-stability.js"; import type { WriteDiagnosticSupportExportResult } from "../../logging/diagnostic-support-export.js"; import { defaultRuntime } from "../../runtime.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { formatDocsLink } from "../../terminal/links.js"; import { colorize, isRich, theme } from "../../terminal/theme.js"; import { runCommandWithRuntime } from "../cli-utils.js"; @@ -31,77 +32,69 @@ import { } from "./discover.js"; import { addGatewayRunCommand } from "./run.js"; -let configModulePromise: - | Promise - | undefined; -let gatewayStatusModulePromise: - | Promise - | undefined; -let gatewayHealthModulePromise: Promise | undefined; -let bonjourDiscoveryModulePromise: - | Promise - | undefined; -let wideAreaDnsModulePromise: Promise | undefined; -let healthStyleModulePromise: Promise | undefined; -let usageFormatModulePromise: Promise | undefined; -let stabilityBundleModulePromise: - | Promise - | undefined; -let supportExportModulePromise: - | Promise - | undefined; -let daemonStatusGatherModulePromise: - | Promise - | undefined; +const configModuleLoader = createLazyImportLoader( + () => import("../../config/read-best-effort-config.runtime.js"), +); +const gatewayStatusModuleLoader = createLazyImportLoader( + () => import("../../commands/gateway-status.js"), +); +const gatewayHealthModuleLoader = createLazyImportLoader(() => import("../../commands/health.js")); +const bonjourDiscoveryModuleLoader = createLazyImportLoader( + () => import("../../infra/bonjour-discovery.js"), +); +const wideAreaDnsModuleLoader = createLazyImportLoader(() => import("../../infra/widearea-dns.js")); +const healthStyleModuleLoader = createLazyImportLoader( + () => import("../../terminal/health-style.js"), +); +const usageFormatModuleLoader = createLazyImportLoader(() => import("../../utils/usage-format.js")); +const stabilityBundleModuleLoader = createLazyImportLoader( + () => import("../../logging/diagnostic-stability-bundle.js"), +); +const supportExportModuleLoader = createLazyImportLoader( + () => import("../../logging/diagnostic-support-export.js"), +); +const daemonStatusGatherModuleLoader = createLazyImportLoader( + () => import("../daemon-cli/status.gather.js"), +); function loadConfigModule() { - configModulePromise ??= import("../../config/read-best-effort-config.runtime.js"); - return configModulePromise; + return configModuleLoader.load(); } function loadGatewayStatusModule() { - gatewayStatusModulePromise ??= import("../../commands/gateway-status.js"); - return gatewayStatusModulePromise; + return gatewayStatusModuleLoader.load(); } function loadGatewayHealthModule() { - gatewayHealthModulePromise ??= import("../../commands/health.js"); - return gatewayHealthModulePromise; + return gatewayHealthModuleLoader.load(); } function loadBonjourDiscoveryModule() { - bonjourDiscoveryModulePromise ??= import("../../infra/bonjour-discovery.js"); - return bonjourDiscoveryModulePromise; + return bonjourDiscoveryModuleLoader.load(); } function loadWideAreaDnsModule() { - wideAreaDnsModulePromise ??= import("../../infra/widearea-dns.js"); - return wideAreaDnsModulePromise; + return wideAreaDnsModuleLoader.load(); } function loadHealthStyleModule() { - healthStyleModulePromise ??= import("../../terminal/health-style.js"); - return healthStyleModulePromise; + return healthStyleModuleLoader.load(); } function loadUsageFormatModule() { - usageFormatModulePromise ??= import("../../utils/usage-format.js"); - return usageFormatModulePromise; + return usageFormatModuleLoader.load(); } function loadStabilityBundleModule() { - stabilityBundleModulePromise ??= import("../../logging/diagnostic-stability-bundle.js"); - return stabilityBundleModulePromise; + return stabilityBundleModuleLoader.load(); } function loadSupportExportModule() { - supportExportModulePromise ??= import("../../logging/diagnostic-support-export.js"); - return supportExportModulePromise; + return supportExportModuleLoader.load(); } function loadDaemonStatusGatherModule() { - daemonStatusGatherModulePromise ??= import("../daemon-cli/status.gather.js"); - return daemonStatusGatherModulePromise; + return daemonStatusGatherModuleLoader.load(); } function runGatewayCommand(action: () => Promise, label?: string) { diff --git a/src/cli/gateway-cli/run-loop.ts b/src/cli/gateway-cli/run-loop.ts index dab2167c7f6..3aacff4eb2d 100644 --- a/src/cli/gateway-cli/run-loop.ts +++ b/src/cli/gateway-cli/run-loop.ts @@ -4,6 +4,7 @@ import { formatErrorMessage } from "../../infra/errors.js"; import { acquireGatewayLock } from "../../infra/gateway-lock.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; import type { RuntimeEnv } from "../../runtime.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; const gatewayLog = createSubsystemLogger("gateway"); const LAUNCHD_SUPERVISED_RESTART_EXIT_DELAY_MS = 1500; @@ -17,10 +18,11 @@ type RestartDrainTimeoutMs = number | undefined; type GatewayLifecycleRuntimeModule = typeof import("./lifecycle.runtime.js"); -let gatewayLifecycleRuntimeModule: Promise | undefined; +const gatewayLifecycleRuntimeLoader = createLazyImportLoader( + () => import("./lifecycle.runtime.js"), +); -const loadGatewayLifecycleRuntimeModule = () => - (gatewayLifecycleRuntimeModule ??= import("./lifecycle.runtime.js")); +const loadGatewayLifecycleRuntimeModule = () => gatewayLifecycleRuntimeLoader.load(); function createRestartIterationHook(onRestart: () => Promise | void): () => Promise { let isFirstIteration = true; diff --git a/src/cli/gateway-rpc.ts b/src/cli/gateway-rpc.ts index c2ba77b05b1..26b364924b3 100644 --- a/src/cli/gateway-rpc.ts +++ b/src/cli/gateway-rpc.ts @@ -2,16 +2,18 @@ import type { Command } from "commander"; import type { OperatorScope } from "../gateway/operator-scopes.js"; import type { GatewayClientMode, GatewayClientName } from "../gateway/protocol/client-info.js"; import type { DeviceIdentity } from "../infra/device-identity.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import type { GatewayRpcOpts } from "./gateway-rpc.types.js"; export type { GatewayRpcOpts } from "./gateway-rpc.types.js"; type GatewayRpcRuntimeModule = typeof import("./gateway-rpc.runtime.js"); -let gatewayRpcRuntimePromise: Promise | undefined; +const gatewayRpcRuntimeLoader = createLazyImportLoader( + () => import("./gateway-rpc.runtime.js"), +); async function loadGatewayRpcRuntime(): Promise { - gatewayRpcRuntimePromise ??= import("./gateway-rpc.runtime.js"); - return gatewayRpcRuntimePromise; + return gatewayRpcRuntimeLoader.load(); } export function addGatewayClientOptions(cmd: Command) { diff --git a/src/cli/logs-cli.ts b/src/cli/logs-cli.ts index 8d68b7bbfa8..5a967d84b8a 100644 --- a/src/cli/logs-cli.ts +++ b/src/cli/logs-cli.ts @@ -11,6 +11,7 @@ import { formatErrorMessage } from "../infra/errors.js"; import { readConfiguredLogTail } from "../logging/log-tail.js"; import { parseLogLine } from "../logging/parse-log-line.js"; import { formatTimestamp, isValidTimeZone } from "../logging/timestamps.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { formatDocsLink } from "../terminal/links.js"; import { clearActiveProgressLine } from "../terminal/progress-line.js"; @@ -21,11 +22,12 @@ import { addGatewayClientOptions, callGatewayFromCli } from "./gateway-rpc.js"; type LogsCliRuntimeModule = typeof import("./logs-cli.runtime.js"); -let logsCliRuntimePromise: Promise | undefined; +const logsCliRuntimeLoader = createLazyImportLoader( + () => import("./logs-cli.runtime.js"), +); async function loadLogsCliRuntime(): Promise { - logsCliRuntimePromise ??= import("./logs-cli.runtime.js"); - return logsCliRuntimePromise; + return logsCliRuntimeLoader.load(); } type LogsTailPayload = { diff --git a/src/cli/nodes-cli/rpc.ts b/src/cli/nodes-cli/rpc.ts index a8bbbe7d7c4..0c30b48900b 100644 --- a/src/cli/nodes-cli/rpc.ts +++ b/src/cli/nodes-cli/rpc.ts @@ -1,5 +1,6 @@ import { randomUUID } from "node:crypto"; import type { Command } from "commander"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { resolveNodeFromNodeList } from "../../shared/node-resolve.js"; import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; import { parseNodeList, parsePairingList } from "./format.js"; @@ -7,11 +8,12 @@ import type { NodeListNode, NodesRpcOpts } from "./types.js"; type NodesCliRpcRuntimeModule = typeof import("./rpc.runtime.js"); -let nodesCliRpcRuntimePromise: Promise | undefined; +const nodesCliRpcRuntimeLoader = createLazyImportLoader( + () => import("./rpc.runtime.js"), +); async function loadNodesCliRpcRuntime(): Promise { - nodesCliRpcRuntimePromise ??= import("./rpc.runtime.js"); - return nodesCliRpcRuntimePromise; + return nodesCliRpcRuntimeLoader.load(); } export const nodesCallOpts = (cmd: Command, defaults?: { timeoutMs?: number }) => diff --git a/src/cli/plugin-registry-loader.ts b/src/cli/plugin-registry-loader.ts index a2a8d2cd791..66e7325fb93 100644 --- a/src/cli/plugin-registry-loader.ts +++ b/src/cli/plugin-registry-loader.ts @@ -1,12 +1,12 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { loggingState } from "../logging/state.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; import type { CliPluginRegistryScope } from "./command-catalog.js"; -let pluginRegistryModulePromise: Promise | undefined; +const pluginRegistryModuleLoader = createLazyImportLoader(() => import("./plugin-registry.js")); function loadPluginRegistryModule() { - pluginRegistryModulePromise ??= import("./plugin-registry.js"); - return pluginRegistryModulePromise; + return pluginRegistryModuleLoader.load(); } export type CliPluginRegistryLoadPolicy = { diff --git a/src/cli/program/routed-command-definitions.ts b/src/cli/program/routed-command-definitions.ts index 0b23857f75a..3d49388e6fc 100644 --- a/src/cli/program/routed-command-definitions.ts +++ b/src/cli/program/routed-command-definitions.ts @@ -1,4 +1,5 @@ import { defaultRuntime } from "../../runtime.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { parseAgentsListRouteArgs, parseChannelsListRouteArgs, @@ -38,23 +39,24 @@ function defineRoutedCommand>( return definition; } -let configCliPromise: Promise | undefined; -let modelsListCommandPromise: Promise | undefined; -let modelsStatusCommandPromise: Promise | undefined; +const configCliLoader = createLazyImportLoader(() => import("../config-cli.js")); +const modelsListCommandLoader = createLazyImportLoader( + () => import("../../commands/models/list.list-command.js"), +); +const modelsStatusCommandLoader = createLazyImportLoader( + () => import("../../commands/models/list.status-command.js"), +); function loadConfigCli(): Promise { - configCliPromise ??= import("../config-cli.js"); - return configCliPromise; + return configCliLoader.load(); } function loadModelsListCommand(): Promise { - modelsListCommandPromise ??= import("../../commands/models/list.list-command.js"); - return modelsListCommandPromise; + return modelsListCommandLoader.load(); } function loadModelsStatusCommand(): Promise { - modelsStatusCommandPromise ??= import("../../commands/models/list.status-command.js"); - return modelsStatusCommandPromise; + return modelsStatusCommandLoader.load(); } export const routedCommandDefinitions = { diff --git a/src/cli/proxy-cli.ts b/src/cli/proxy-cli.ts index 1e1668e6c7f..a3fe580e0b4 100644 --- a/src/cli/proxy-cli.ts +++ b/src/cli/proxy-cli.ts @@ -1,13 +1,15 @@ import type { Command } from "commander"; import type { CaptureQueryPreset } from "../proxy-capture/types.js"; +import { createLazyImportLoader } from "../shared/lazy-promise.js"; type ProxyCliRuntime = typeof import("./proxy-cli.runtime.js"); -let proxyCliRuntimePromise: Promise | undefined; +const proxyCliRuntimeLoader = createLazyImportLoader( + () => import("./proxy-cli.runtime.js"), +); async function loadProxyCliRuntime(): Promise { - proxyCliRuntimePromise ??= import("./proxy-cli.runtime.js"); - return await proxyCliRuntimePromise; + return await proxyCliRuntimeLoader.load(); } function parseOptionalNumber(value: string | undefined): number | undefined { diff --git a/src/cron/isolated-agent/channel-output-policy.ts b/src/cron/isolated-agent/channel-output-policy.ts index 935cdbf4cce..f5e2bb4817b 100644 --- a/src/cron/isolated-agent/channel-output-policy.ts +++ b/src/cron/isolated-agent/channel-output-policy.ts @@ -1,12 +1,14 @@ +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js"; type ChannelPluginRuntime = typeof import("../../channels/plugins/index.js"); -let channelPluginRuntimePromise: Promise | undefined; +const channelPluginRuntimeLoader = createLazyImportLoader( + () => import("../../channels/plugins/index.js"), +); async function loadChannelPluginRuntime() { - channelPluginRuntimePromise ??= import("../../channels/plugins/index.js"); - return await channelPluginRuntimePromise; + return await channelPluginRuntimeLoader.load(); } export async function resolveCronChannelOutputPolicy(channel: string | undefined): Promise<{ diff --git a/src/cron/isolated-agent/delivery-dispatch.ts b/src/cron/isolated-agent/delivery-dispatch.ts index cc229eca2b3..e9cee927b3b 100644 --- a/src/cron/isolated-agent/delivery-dispatch.ts +++ b/src/cron/isolated-agent/delivery-dispatch.ts @@ -20,6 +20,7 @@ import type { OutboundDeliveryResult } from "../../infra/outbound/deliver.js"; import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js"; import { hasReplyPayloadContent } from "../../interactive/payload.js"; import { stringifyRouteThreadId } from "../../plugin-sdk/channel-route.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, @@ -173,57 +174,53 @@ type CompletedDirectCronDelivery = { results: OutboundDeliveryResult[]; }; -let gatewayCallRuntimePromise: Promise | undefined; -let deliveryOutboundRuntimePromise: - | Promise - | undefined; -let deliverySubagentRegistryRuntimePromise: - | Promise - | undefined; -let deliveryLoggerRuntimePromise: - | Promise - | undefined; -let subagentFollowupRuntimePromise: - | Promise - | undefined; -let ttsRuntimePromise: Promise | undefined; +const gatewayCallRuntimeLoader = createLazyImportLoader( + () => import("../../gateway/call.runtime.js"), +); +const deliveryOutboundRuntimeLoader = createLazyImportLoader( + () => import("./delivery-outbound.runtime.js"), +); +const deliverySubagentRegistryRuntimeLoader = createLazyImportLoader( + () => import("./delivery-subagent-registry.runtime.js"), +); +const deliveryLoggerRuntimeLoader = createLazyImportLoader( + () => import("./delivery-logger.runtime.js"), +); +const subagentFollowupRuntimeLoader = createLazyImportLoader( + () => import("./subagent-followup.runtime.js"), +); +const ttsRuntimeLoader = createLazyImportLoader(() => import("../../tts/tts.runtime.js")); const COMPLETED_DIRECT_CRON_DELIVERIES = new Map(); async function loadGatewayCallRuntime(): Promise { - gatewayCallRuntimePromise ??= import("../../gateway/call.runtime.js"); - return await gatewayCallRuntimePromise; + return await gatewayCallRuntimeLoader.load(); } async function loadDeliveryOutboundRuntime(): Promise< typeof import("./delivery-outbound.runtime.js") > { - deliveryOutboundRuntimePromise ??= import("./delivery-outbound.runtime.js"); - return await deliveryOutboundRuntimePromise; + return await deliveryOutboundRuntimeLoader.load(); } async function loadDeliverySubagentRegistryRuntime(): Promise< typeof import("./delivery-subagent-registry.runtime.js") > { - deliverySubagentRegistryRuntimePromise ??= import("./delivery-subagent-registry.runtime.js"); - return await deliverySubagentRegistryRuntimePromise; + return await deliverySubagentRegistryRuntimeLoader.load(); } async function loadDeliveryLoggerRuntime(): Promise { - deliveryLoggerRuntimePromise ??= import("./delivery-logger.runtime.js"); - return await deliveryLoggerRuntimePromise; + return await deliveryLoggerRuntimeLoader.load(); } async function loadSubagentFollowupRuntime(): Promise< typeof import("./subagent-followup.runtime.js") > { - subagentFollowupRuntimePromise ??= import("./subagent-followup.runtime.js"); - return await subagentFollowupRuntimePromise; + return await subagentFollowupRuntimeLoader.load(); } async function loadTtsRuntime(): Promise { - ttsRuntimePromise ??= import("../../tts/tts.runtime.js"); - return await ttsRuntimePromise; + return await ttsRuntimeLoader.load(); } async function logCronDeliveryWarn(message: string): Promise { diff --git a/src/cron/isolated-agent/delivery-target.ts b/src/cron/isolated-agent/delivery-target.ts index 7fd704cfb17..a302121ce8a 100644 --- a/src/cron/isolated-agent/delivery-target.ts +++ b/src/cron/isolated-agent/delivery-target.ts @@ -11,6 +11,7 @@ import { tryResolveLoadedOutboundTarget } from "../../infra/outbound/targets-loa import { resolveSessionDeliveryTarget } from "../../infra/outbound/targets-session.js"; import type { OutboundChannel } from "../../infra/outbound/targets.js"; import { normalizeAccountId } from "../../routing/session-key.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; export type DeliveryTargetResolution = | { @@ -31,13 +32,12 @@ export type DeliveryTargetResolution = error: Error; }; -let targetsRuntimePromise: - | Promise - | undefined; +const targetsRuntimeLoader = createLazyImportLoader( + () => import("../../infra/outbound/targets.runtime.js"), +); async function loadTargetsRuntime() { - targetsRuntimePromise ??= import("../../infra/outbound/targets.runtime.js"); - return await targetsRuntimePromise; + return await targetsRuntimeLoader.load(); } async function resolveOutboundTargetWithRuntime( @@ -95,21 +95,19 @@ function deliveryTargetsShareThreadRoute(params: { return Boolean(normalizedTo && normalizedLastTo && normalizedTo === normalizedLastTo); } -let channelSelectionRuntimePromise: - | Promise - | undefined; -let deliveryTargetRuntimePromise: - | Promise - | undefined; +const channelSelectionRuntimeLoader = createLazyImportLoader( + () => import("../../infra/outbound/channel-selection.runtime.js"), +); +const deliveryTargetRuntimeLoader = createLazyImportLoader( + () => import("./delivery-target.runtime.js"), +); async function loadChannelSelectionRuntime() { - channelSelectionRuntimePromise ??= import("../../infra/outbound/channel-selection.runtime.js"); - return await channelSelectionRuntimePromise; + return await channelSelectionRuntimeLoader.load(); } async function loadDeliveryTargetRuntime() { - deliveryTargetRuntimePromise ??= import("./delivery-target.runtime.js"); - return await deliveryTargetRuntimePromise; + return await deliveryTargetRuntimeLoader.load(); } export async function resolveDeliveryTarget( cfg: OpenClawConfig, diff --git a/src/cron/isolated-agent/run-execution.runtime.ts b/src/cron/isolated-agent/run-execution.runtime.ts index f7495d8aa71..961982bcb0f 100644 --- a/src/cron/isolated-agent/run-execution.runtime.ts +++ b/src/cron/isolated-agent/run-execution.runtime.ts @@ -8,14 +8,14 @@ export { normalizeVerboseLevel } from "../../auto-reply/thinking.shared.js"; export { resolveSessionTranscriptPath } from "../../config/sessions/paths.js"; export { registerAgentRunContext } from "../../infra/agent-events.js"; export { logWarn } from "../../logger.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; -let cronExecutionCliRuntimePromise: - | Promise - | undefined; +const cronExecutionCliRuntimeLoader = createLazyImportLoader( + () => import("./run-execution-cli.runtime.js"), +); async function loadCronExecutionCliRuntime() { - cronExecutionCliRuntimePromise ??= import("./run-execution-cli.runtime.js"); - return await cronExecutionCliRuntimePromise; + return await cronExecutionCliRuntimeLoader.load(); } export async function getCliSessionId( diff --git a/src/cron/isolated-agent/run-executor.ts b/src/cron/isolated-agent/run-executor.ts index 344aeae0ed8..e623996a84d 100644 --- a/src/cron/isolated-agent/run-executor.ts +++ b/src/cron/isolated-agent/run-executor.ts @@ -3,6 +3,7 @@ import { normalizeToolList } from "../../agents/tool-policy.js"; import type { ThinkLevel, VerboseLevel } from "../../auto-reply/thinking.js"; import type { AgentDefaultsConfig } from "../../config/types.agent-defaults.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import type { CronJob } from "../types.js"; import { resolveCronChannelOutputPolicy, @@ -36,17 +37,19 @@ type CronPromptRunResult = Awaited>; type CronEmbeddedRuntime = typeof import("./run-embedded.runtime.js"); type CronSubagentRegistryRuntime = typeof import("./run-subagent-registry.runtime.js"); -let cronEmbeddedRuntimePromise: Promise | undefined; -let cronSubagentRegistryRuntimePromise: Promise | undefined; +const cronEmbeddedRuntimeLoader = createLazyImportLoader( + () => import("./run-embedded.runtime.js"), +); +const cronSubagentRegistryRuntimeLoader = createLazyImportLoader( + () => import("./run-subagent-registry.runtime.js"), +); async function loadCronEmbeddedRuntime() { - cronEmbeddedRuntimePromise ??= import("./run-embedded.runtime.js"); - return await cronEmbeddedRuntimePromise; + return await cronEmbeddedRuntimeLoader.load(); } async function loadCronSubagentRegistryRuntime() { - cronSubagentRegistryRuntimePromise ??= import("./run-subagent-registry.runtime.js"); - return await cronSubagentRegistryRuntimePromise; + return await cronSubagentRegistryRuntimeLoader.load(); } function resolveCronOwnerOnlyToolAllowlist(toolsAllow: string[] | undefined): string[] | undefined { diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 504949553df..e6f5c2bd898 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -7,6 +7,7 @@ import type { CliDeps } from "../../cli/outbound-send-deps.js"; import type { AgentDefaultsConfig } from "../../config/types.agent-defaults.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { stringifyRouteThreadId } from "../../plugin-sdk/channel-route.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { resolveCronDeliveryPlan, type CronDeliveryPlan } from "../delivery-plan.js"; import type { @@ -62,63 +63,55 @@ import { resolveCronAgentSessionKey } from "./session-key.js"; import { resolveCronSession } from "./session.js"; import { resolveCronSkillsSnapshot } from "./skills-snapshot.js"; -let sessionStoreRuntimePromise: - | Promise - | undefined; -let cronExecutorRuntimePromise: Promise | undefined; -let cronExternalContentRuntimePromise: - | Promise - | undefined; -let cronAuthProfileRuntimePromise: - | Promise - | undefined; -let cronContextRuntimePromise: Promise | undefined; -let cronModelCatalogRuntimePromise: - | Promise - | undefined; -let cronDeliveryRuntimePromise: Promise | undefined; -let cronModelPreflightRuntimePromise: - | Promise - | undefined; +const sessionStoreRuntimeLoader = createLazyImportLoader( + () => import("../../config/sessions/store.runtime.js"), +); +const cronExecutorRuntimeLoader = createLazyImportLoader(() => import("./run-executor.runtime.js")); +const cronExternalContentRuntimeLoader = createLazyImportLoader( + () => import("./run-external-content.runtime.js"), +); +const cronAuthProfileRuntimeLoader = createLazyImportLoader( + () => import("./run-auth-profile.runtime.js"), +); +const cronContextRuntimeLoader = createLazyImportLoader(() => import("./run-context.runtime.js")); +const cronModelCatalogRuntimeLoader = createLazyImportLoader( + () => import("./run-model-catalog.runtime.js"), +); +const cronDeliveryRuntimeLoader = createLazyImportLoader(() => import("./run-delivery.runtime.js")); +const cronModelPreflightRuntimeLoader = createLazyImportLoader( + () => import("./model-preflight.runtime.js"), +); async function loadSessionStoreRuntime() { - sessionStoreRuntimePromise ??= import("../../config/sessions/store.runtime.js"); - return await sessionStoreRuntimePromise; + return await sessionStoreRuntimeLoader.load(); } async function loadCronExecutorRuntime() { - cronExecutorRuntimePromise ??= import("./run-executor.runtime.js"); - return await cronExecutorRuntimePromise; + return await cronExecutorRuntimeLoader.load(); } async function loadCronExternalContentRuntime() { - cronExternalContentRuntimePromise ??= import("./run-external-content.runtime.js"); - return await cronExternalContentRuntimePromise; + return await cronExternalContentRuntimeLoader.load(); } async function loadCronAuthProfileRuntime() { - cronAuthProfileRuntimePromise ??= import("./run-auth-profile.runtime.js"); - return await cronAuthProfileRuntimePromise; + return await cronAuthProfileRuntimeLoader.load(); } async function loadCronContextRuntime() { - cronContextRuntimePromise ??= import("./run-context.runtime.js"); - return await cronContextRuntimePromise; + return await cronContextRuntimeLoader.load(); } async function loadCronModelCatalogRuntime() { - cronModelCatalogRuntimePromise ??= import("./run-model-catalog.runtime.js"); - return await cronModelCatalogRuntimePromise; + return await cronModelCatalogRuntimeLoader.load(); } async function loadCronDeliveryRuntime() { - cronDeliveryRuntimePromise ??= import("./run-delivery.runtime.js"); - return await cronDeliveryRuntimePromise; + return await cronDeliveryRuntimeLoader.load(); } async function loadCronModelPreflightRuntime() { - cronModelPreflightRuntimePromise ??= import("./model-preflight.runtime.js"); - return await cronModelPreflightRuntimePromise; + return await cronModelPreflightRuntimeLoader.load(); } function hasConfiguredAuthProfiles(cfg: OpenClawConfig): boolean { diff --git a/src/cron/isolated-agent/skills-snapshot.ts b/src/cron/isolated-agent/skills-snapshot.ts index 97204f73324..7ce63a25b56 100644 --- a/src/cron/isolated-agent/skills-snapshot.ts +++ b/src/cron/isolated-agent/skills-snapshot.ts @@ -1,14 +1,14 @@ import type { SkillSnapshot } from "../../agents/skills.js"; import { matchesSkillFilter } from "../../agents/skills/filter.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; +import { createLazyImportLoader } from "../../shared/lazy-promise.js"; -let skillsSnapshotRuntimePromise: - | Promise - | undefined; +const skillsSnapshotRuntimeLoader = createLazyImportLoader( + () => import("./skills-snapshot.runtime.js"), +); async function loadSkillsSnapshotRuntime() { - skillsSnapshotRuntimePromise ??= import("./skills-snapshot.runtime.js"); - return await skillsSnapshotRuntimePromise; + return await skillsSnapshotRuntimeLoader.load(); } export async function resolveCronSkillsSnapshot(params: { diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 63625f6eccd..d8c1070ef5e 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -1026,7 +1026,7 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) { loadModules: options.loadModules, runtimeSubagentMode, pluginSdkResolution: options.pluginSdkResolution, - coreGatewayMethodNames, + ...(coreGatewayMethodNames !== undefined && { coreGatewayMethodNames }), activate: options.activate, }); return {