refactor: unify lazy module loaders

This commit is contained in:
Peter Steinberger
2026-05-02 10:15:16 +01:00
parent 4407c317f3
commit db06fcd990
39 changed files with 408 additions and 264 deletions

View File

@@ -1,6 +1,7 @@
import type { Mock } from "vitest";
import { vi } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { createTestRuntime } from "./test-runtime-config-helpers.js";
type ReplaceConfigFileResult = Awaited<
@@ -44,11 +45,12 @@ vi.mock("./agents.command-shared.js", () => ({
export const runtime = createTestRuntime();
let agentsBindCommandModulePromise: Promise<typeof import("./agents.commands.bind.js")> | undefined;
const agentsBindCommandModuleLoader = createLazyImportLoader(
() => import("./agents.commands.bind.js"),
);
export async function loadFreshAgentsBindCommandModuleForTest() {
agentsBindCommandModulePromise ??= import("./agents.commands.bind.js");
return await agentsBindCommandModulePromise;
return await agentsBindCommandModuleLoader.load();
}
export function resetAgentsBindTestHarness(): void {

View File

@@ -6,7 +6,7 @@ import { normalizeChannelId as normalizeBundledChannelId } from "../channels/reg
import { isRouteBinding, listRouteBindings } from "../config/bindings.js";
import type { AgentRouteBinding } from "../config/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { listManifestChannelContributionIds } from "../plugins/manifest-channel-contributions.js";
import { listManifestChannelContributionIds } from "../plugins/manifest-contribution-ids.js";
import { DEFAULT_ACCOUNT_ID, normalizeAgentId } from "../routing/session-key.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeStringEntries } from "../shared/string-normalization.js";

View File

@@ -6,6 +6,7 @@ import type { AgentRouteBinding } from "../config/types.js";
import { normalizeAgentId } from "../routing/session-key.js";
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
import { defaultRuntime } from "../runtime.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { describeBinding } from "./agents.binding-format.js";
import { requireValidConfig, requireValidConfigFileSnapshot } from "./agents.command-shared.js";
@@ -29,11 +30,12 @@ type AgentsUnbindOptions = {
json?: boolean;
};
let agentBindingsModulePromise: Promise<AgentBindingsModule> | undefined;
const agentBindingsModuleLoader = createLazyImportLoader<AgentBindingsModule>(
() => import("./agents.bindings.js"),
);
function loadAgentBindingsModule(): Promise<AgentBindingsModule> {
agentBindingsModulePromise ??= import("./agents.bindings.js");
return agentBindingsModulePromise;
return agentBindingsModuleLoader.load();
}
function resolveAgentId(

View File

@@ -5,15 +5,17 @@ import {
type BackupCreateResult,
} from "../infra/backup-create.js";
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
export type { BackupCreateOptions, BackupCreateResult } from "../infra/backup-create.js";
type BackupVerifyRuntime = typeof import("./backup-verify.js");
let backupVerifyRuntimePromise: Promise<BackupVerifyRuntime> | undefined;
const backupVerifyRuntimeLoader = createLazyImportLoader<BackupVerifyRuntime>(
() => import("./backup-verify.js"),
);
function loadBackupVerifyRuntime(): Promise<BackupVerifyRuntime> {
backupVerifyRuntimePromise ??= import("./backup-verify.js");
return backupVerifyRuntimePromise;
return backupVerifyRuntimeLoader.load();
}
export async function backupCreateCommand(

View File

@@ -8,7 +8,7 @@ import type { ChannelMeta } from "../../channels/plugins/types.public.js";
import { isStaticallyChannelConfigured } from "../../config/channel-configured-shared.js";
import { applyPluginAutoEnable } from "../../config/plugin-auto-enable.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { listManifestChannelContributionIds } from "../../plugins/manifest-channel-contributions.js";
import { listManifestChannelContributionIds } from "../../plugins/manifest-contribution-ids.js";
import type { ChannelChoice } from "../onboard-types.js";
import {
listSetupDiscoveryChannelPluginCatalogEntries,

View File

@@ -11,6 +11,7 @@ import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-regi
import type { OpenClawConfig } from "../../config/config.js";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
import { createLazyImportLoader } from "../../shared/lazy-promise.js";
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
import { createClackPrompter } from "../../wizard/clack-prompter.js";
import { applyAgentBindings, describeBinding } from "../agents.bindings.js";
@@ -22,17 +23,19 @@ import { requireValidConfigFileSnapshot, shouldUseWizard } from "./shared.js";
type ChannelSetupPluginInstallModule = typeof import("../channel-setup/plugin-install.js");
type OnboardChannelsModule = typeof import("../onboard-channels.js");
let channelSetupPluginInstallPromise: Promise<ChannelSetupPluginInstallModule> | undefined;
let onboardChannelsPromise: Promise<OnboardChannelsModule> | undefined;
const channelSetupPluginInstallLoader = createLazyImportLoader<ChannelSetupPluginInstallModule>(
() => import("../channel-setup/plugin-install.js"),
);
const onboardChannelsLoader = createLazyImportLoader<OnboardChannelsModule>(
() => import("../onboard-channels.js"),
);
function loadChannelSetupPluginInstall(): Promise<ChannelSetupPluginInstallModule> {
channelSetupPluginInstallPromise ??= import("../channel-setup/plugin-install.js");
return channelSetupPluginInstallPromise;
return channelSetupPluginInstallLoader.load();
}
function loadOnboardChannels(): Promise<OnboardChannelsModule> {
onboardChannelsPromise ??= import("../onboard-channels.js");
return onboardChannelsPromise;
return onboardChannelsLoader.load();
}
export type ChannelsAddOptions = {

View File

@@ -3,7 +3,7 @@ import { normalizeChannelId as normalizeBundledChannelId } from "../../channels/
import { getResolvedLoggerSettings } from "../../logging.js";
import { resolveLogFile } from "../../logging/log-tail.js";
import { parseLogLine } from "../../logging/parse-log-line.js";
import { listManifestChannelContributionIds } from "../../plugins/manifest-channel-contributions.js";
import { listManifestChannelContributionIds } from "../../plugins/manifest-contribution-ids.js";
import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import { theme } from "../../terminal/theme.js";

View File

@@ -12,6 +12,7 @@ import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js";
import { resolvePluginContributionOwners } from "../plugins/plugin-registry.js";
import type { RuntimeEnv } from "../runtime.js";
import { defaultRuntime } from "../runtime.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { note } from "../terminal/note.js";
import { isPlainObject, resolveUserPath } from "../utils.js";
@@ -56,11 +57,12 @@ type SetupPluginConfigModule = typeof import("../wizard/setup.plugin-config.js")
const GATEWAY_HINT_PROBE_TIMEOUT_MS = 300;
let setupPluginConfigModulePromise: Promise<SetupPluginConfigModule> | undefined;
const setupPluginConfigModuleLoader = createLazyImportLoader<SetupPluginConfigModule>(
() => import("../wizard/setup.plugin-config.js"),
);
function loadSetupPluginConfigModule(): Promise<SetupPluginConfigModule> {
setupPluginConfigModulePromise ??= import("../wizard/setup.plugin-config.js");
return setupPluginConfigModulePromise;
return setupPluginConfigModuleLoader.load();
}
function mergeWizardConfigOntoLatest(current: unknown, base: unknown, next: unknown): unknown {

View File

@@ -1,5 +1,5 @@
import { mergeMissing } from "../../../config/legacy.shared.js";
import { loadPluginMetadataSnapshot } from "../../../plugins/plugin-metadata-snapshot.js";
import { loadManifestMetadataSnapshot } from "../../../plugins/manifest-contract-eligibility.js";
import {
cloneRecord,
ensureRecord,
@@ -17,7 +17,7 @@ const LEGACY_GLOBAL_WEB_SEARCH_PROVIDER_ID = "brave";
function getBundledLegacyWebSearchOwners(): ReadonlyMap<string, string> {
const owners = new Map<string, string>();
for (const plugin of loadPluginMetadataSnapshot({ config: {}, env: process.env }).plugins) {
for (const plugin of loadManifestMetadataSnapshot({ config: {}, env: process.env }).plugins) {
if (plugin.origin !== "bundled") {
continue;
}

View File

@@ -6,7 +6,7 @@ import { installPluginFromNpmSpec } from "../../../plugins/install.js";
import { loadInstalledPluginIndexInstallRecords } from "../../../plugins/installed-plugin-index-records.js";
import { writePersistedInstalledPluginIndexInstallRecords } from "../../../plugins/installed-plugin-index-records.js";
import { buildNpmResolutionInstallFields } from "../../../plugins/installs.js";
import { loadPluginMetadataSnapshot } from "../../../plugins/plugin-metadata-snapshot.js";
import { loadManifestMetadataSnapshot } from "../../../plugins/manifest-contract-eligibility.js";
import { resolveProviderInstallCatalogEntries } from "../../../plugins/provider-install-catalog.js";
import { updateNpmInstalledPlugins } from "../../../plugins/update.js";
import { asObjectRecord } from "./object.js";
@@ -154,7 +154,7 @@ export async function repairMissingConfiguredPluginInstalls(params: {
}): Promise<{ changes: string[]; warnings: string[] }> {
const env = params.env ?? process.env;
const knownIds = new Set(
loadPluginMetadataSnapshot({
loadManifestMetadataSnapshot({
config: params.cfg,
env,
}).plugins.map((plugin) => plugin.id),

View File

@@ -1,8 +1,8 @@
import { normalizeToolName } from "../../../agents/tool-policy-shared.js";
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import { normalizePluginId } from "../../../plugins/config-state.js";
import { loadManifestMetadataSnapshot } from "../../../plugins/manifest-contract-eligibility.js";
import type { PluginManifestRegistry } from "../../../plugins/manifest-registry.js";
import { loadPluginMetadataSnapshot } from "../../../plugins/plugin-metadata-snapshot.js";
type ToolAllowlistSource = {
label: string;
@@ -147,7 +147,7 @@ export function collectPluginToolAllowlistWarnings(params: {
const registry =
params.manifestRegistry ??
loadPluginMetadataSnapshot({
loadManifestMetadataSnapshot({
config: params.cfg,
env: params.env ?? process.env,
}).manifestRegistry;

View File

@@ -3,14 +3,16 @@ import { isToolAllowedByPolicies } from "../../../agents/tool-policy-match.js";
import { mergeAlsoAllowPolicy, resolveToolProfilePolicy } from "../../../agents/tool-policy.js";
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import type { AgentToolsConfig, ToolsConfig } from "../../../config/types.tools.js";
import { createLazyImportLoader } from "../../../shared/lazy-promise.js";
type ChannelDoctorModule = typeof import("./channel-doctor.js");
let channelDoctorModulePromise: Promise<ChannelDoctorModule> | undefined;
const channelDoctorModuleLoader = createLazyImportLoader<ChannelDoctorModule>(
() => import("./channel-doctor.js"),
);
function loadChannelDoctorModule(): Promise<ChannelDoctorModule> {
channelDoctorModulePromise ??= import("./channel-doctor.js");
return channelDoctorModulePromise;
return channelDoctorModuleLoader.load();
}
function hasRecord(value: unknown): value is Record<string, unknown> {

View File

@@ -3,7 +3,7 @@ import { CHANNEL_IDS } from "../../../channels/ids.js";
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import { normalizePluginId } from "../../../plugins/config-state.js";
import { loadInstalledPluginIndexInstallRecordsSync } from "../../../plugins/installed-plugin-index-records.js";
import { loadPluginMetadataSnapshot } from "../../../plugins/plugin-metadata-snapshot.js";
import { loadManifestMetadataSnapshot } from "../../../plugins/manifest-contract-eligibility.js";
import { sanitizeForLog } from "../../../terminal/ansi.js";
import { asObjectRecord } from "./object.js";
@@ -29,7 +29,7 @@ function collectPluginRegistryState(
env?: NodeJS.ProcessEnv,
): StalePluginRegistryState {
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
const registry = loadPluginMetadataSnapshot({
const registry = loadManifestMetadataSnapshot({
config: cfg,
workspaceDir: workspaceDir ?? undefined,
env: env ?? process.env,

View File

@@ -2,6 +2,7 @@ import { withProgress } from "../cli/progress.js";
import { readBestEffortConfig, resolveGatewayPort } from "../config/config.js";
import { resolveWideAreaDiscoveryDomain } from "../infra/widearea-dns.js";
import type { RuntimeEnv } from "../runtime.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { isRich } from "../terminal/theme.js";
import { inferSshTargetFromRemoteUrl, resolveSshTarget } from "./gateway-status/discovery.js";
import {
@@ -18,23 +19,20 @@ import {
} from "./gateway-status/output.js";
import { runGatewayStatusProbePass } from "./gateway-status/probe-run.js";
let sshConfigModulePromise: Promise<typeof import("../infra/ssh-config.js")> | undefined;
let sshTunnelModulePromise: Promise<typeof import("../infra/ssh-tunnel.js")> | undefined;
let gatewayTlsModulePromise: Promise<typeof import("../infra/tls/gateway.js")> | undefined;
const sshConfigModuleLoader = createLazyImportLoader(() => import("../infra/ssh-config.js"));
const sshTunnelModuleLoader = createLazyImportLoader(() => import("../infra/ssh-tunnel.js"));
const gatewayTlsModuleLoader = createLazyImportLoader(() => import("../infra/tls/gateway.js"));
function loadSshConfigModule() {
sshConfigModulePromise ??= import("../infra/ssh-config.js");
return sshConfigModulePromise;
return sshConfigModuleLoader.load();
}
function loadSshTunnelModule() {
sshTunnelModulePromise ??= import("../infra/ssh-tunnel.js");
return sshTunnelModulePromise;
return sshTunnelModuleLoader.load();
}
function loadGatewayTlsModule() {
gatewayTlsModulePromise ??= import("../infra/tls/gateway.js");
return gatewayTlsModulePromise;
return gatewayTlsModuleLoader.load();
}
export async function gatewayStatusCommand(

View File

@@ -23,6 +23,7 @@ import { getActivePluginRegistry } from "../plugins/runtime.js";
import { buildChannelAccountBindings, resolvePreferredAccountId } from "../routing/bindings.js";
import { normalizeAgentId } from "../routing/session-key.js";
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { asNullableRecord } from "../shared/record-coerce.js";
import { styleHealthChannelLine } from "../terminal/health-style.js";
import { isRich } from "../terminal/theme.js";
@@ -48,11 +49,12 @@ const DEFAULT_TIMEOUT_MS = 10_000;
type ConfigModule = typeof import("../config/config.js");
let configModulePromise: Promise<ConfigModule> | undefined;
const configModuleLoader = createLazyImportLoader<ConfigModule>(
() => import("../config/config.js"),
);
function loadConfigModule(): Promise<ConfigModule> {
configModulePromise ??= import("../config/config.js");
return configModulePromise;
return configModuleLoader.load();
}
const debugHealth = (...args: unknown[]) => {

View File

@@ -2,6 +2,7 @@ import type { Api, Model } from "@mariozechner/pi-ai";
import type { ModelRegistry } from "@mariozechner/pi-coding-agent";
import { parseModelRef } from "../../agents/model-selection.js";
import type { RuntimeEnv } from "../../runtime.js";
import { createLazyImportLoader } from "../../shared/lazy-promise.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import { createModelListAuthIndex } from "./list.auth-index.js";
import { resolveConfiguredEntries } from "./list.configured.js";
@@ -17,23 +18,26 @@ type RegistryLoadModule = typeof import("./list.registry-load.js");
type RowSourcesModule = typeof import("./list.row-sources.js");
type SourcePlanModule = typeof import("./list.source-plan.js");
let registryLoadModulePromise: Promise<RegistryLoadModule> | undefined;
let rowSourcesModulePromise: Promise<RowSourcesModule> | undefined;
let sourcePlanModulePromise: Promise<SourcePlanModule> | undefined;
const registryLoadModuleLoader = createLazyImportLoader<RegistryLoadModule>(
() => import("./list.registry-load.js"),
);
const rowSourcesModuleLoader = createLazyImportLoader<RowSourcesModule>(
() => import("./list.row-sources.js"),
);
const sourcePlanModuleLoader = createLazyImportLoader<SourcePlanModule>(
() => import("./list.source-plan.js"),
);
function loadRegistryLoadModule(): Promise<RegistryLoadModule> {
registryLoadModulePromise ??= import("./list.registry-load.js");
return registryLoadModulePromise;
return registryLoadModuleLoader.load();
}
function loadRowSourcesModule(): Promise<RowSourcesModule> {
rowSourcesModulePromise ??= import("./list.row-sources.js");
return rowSourcesModulePromise;
return rowSourcesModuleLoader.load();
}
function loadSourcePlanModule(): Promise<SourcePlanModule> {
sourcePlanModulePromise ??= import("./list.source-plan.js");
return sourcePlanModulePromise;
return sourcePlanModuleLoader.load();
}
export async function modelsListCommand(

View File

@@ -4,8 +4,8 @@ import {
planManifestModelCatalogRows,
} from "../../model-catalog/index.js";
import type { NormalizedModelCatalogRow } from "../../model-catalog/index.js";
import { loadManifestMetadataSnapshot } from "../../plugins/manifest-contract-eligibility.js";
import type { PluginManifestRegistry } from "../../plugins/manifest-registry.js";
import { loadPluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js";
import {
getPluginRecord,
isPluginEnabled,
@@ -98,7 +98,7 @@ function loadManifestCatalogRowsForList(params: {
? normalizeModelCatalogProviderId(params.providerFilter)
: undefined;
const mode = params.mode ?? "static-authoritative";
const snapshot = loadPluginMetadataSnapshot({
const snapshot = loadManifestMetadataSnapshot({
config: params.cfg,
env: params.env ?? process.env,
});

View File

@@ -28,16 +28,18 @@ import {
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { coerceSecretRef, normalizeSecretInputString } from "../../config/types.secrets.js";
import { type SecretRefResolveCache, resolveSecretRefString } from "../../secrets/resolve.js";
import { createLazyImportLoader } from "../../shared/lazy-promise.js";
import { redactSecrets } from "../status-all/format.js";
import { DEFAULT_PROVIDER, formatMs } from "./shared.js";
const PROBE_PROMPT = "Reply with OK. Do not use tools.";
let embeddedRunnerModulePromise: Promise<typeof import("../../agents/pi-embedded.js")> | undefined;
const embeddedRunnerModuleLoader = createLazyImportLoader(
() => import("../../agents/pi-embedded.js"),
);
function loadEmbeddedRunnerModule() {
embeddedRunnerModulePromise ??= import("../../agents/pi-embedded.js");
return embeddedRunnerModulePromise;
return embeddedRunnerModuleLoader.load();
}
export type AuthProbeStatus =

View File

@@ -9,6 +9,7 @@ import { normalizeProviderId } from "../../agents/provider-id.js";
import type { ModelDefinitionConfig, ModelProviderConfig } from "../../config/types.models.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { NormalizedModelCatalogRow } from "../../model-catalog/index.js";
import { createLazyImportLoader } from "../../shared/lazy-promise.js";
import type { ModelListAuthIndex } from "./list.auth-index.js";
import type { ListRowModel } from "./list.model-row.js";
import { toModelRow } from "./list.model-row.js";
@@ -36,23 +37,26 @@ export type RowBuilderContext = {
skipRuntimeModelSuppression?: boolean;
};
let modelCatalogModulePromise: Promise<ModelCatalogModule> | undefined;
let modelResolverModulePromise: Promise<ModelResolverModule> | undefined;
let providerCatalogModulePromise: Promise<ProviderCatalogModule> | undefined;
const modelCatalogModuleLoader = createLazyImportLoader<ModelCatalogModule>(
() => import("../../agents/model-catalog.js"),
);
const modelResolverModuleLoader = createLazyImportLoader<ModelResolverModule>(
() => import("../../agents/pi-embedded-runner/model.js"),
);
const providerCatalogModuleLoader = createLazyImportLoader<ProviderCatalogModule>(
() => import("./list.provider-catalog.js"),
);
function loadModelCatalogModule(): Promise<ModelCatalogModule> {
modelCatalogModulePromise ??= import("../../agents/model-catalog.js");
return modelCatalogModulePromise;
return modelCatalogModuleLoader.load();
}
function loadModelResolverModule(): Promise<ModelResolverModule> {
modelResolverModulePromise ??= import("../../agents/pi-embedded-runner/model.js");
return modelResolverModulePromise;
return modelResolverModuleLoader.load();
}
function loadProviderCatalogModule(): Promise<ProviderCatalogModule> {
providerCatalogModulePromise ??= import("./list.provider-catalog.js");
return providerCatalogModulePromise;
return providerCatalogModuleLoader.load();
}
function matchesRowFilter(filter: RowFilter, model: { provider: string; baseUrl?: string }) {

View File

@@ -42,6 +42,7 @@ import type { ProviderSyntheticAuthResult } from "../../plugins/provider-externa
import { resolveProviderSyntheticAuthWithPlugin } from "../../plugins/provider-runtime.js";
import { resolveRuntimeSyntheticAuthProviderRefs } from "../../plugins/synthetic-auth.runtime.js";
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
import { createLazyImportLoader } from "../../shared/lazy-promise.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { colorize, theme } from "../../terminal/theme.js";
import { shortenHomePath } from "../../utils.js";
@@ -61,10 +62,18 @@ type ProgressRuntime = typeof import("../../cli/progress.js");
type TerminalTableRuntime = typeof import("../../terminal/table.js");
type ListProbeRuntime = typeof import("./list.probe.js");
let providerUsageRuntimePromise: Promise<ProviderUsageRuntime> | undefined;
let progressRuntimePromise: Promise<ProgressRuntime> | undefined;
let terminalTableRuntimePromise: Promise<TerminalTableRuntime> | undefined;
let listProbeRuntimePromise: Promise<ListProbeRuntime> | undefined;
const providerUsageRuntimeLoader = createLazyImportLoader<ProviderUsageRuntime>(
() => import("../../infra/provider-usage.js"),
);
const progressRuntimeLoader = createLazyImportLoader<ProgressRuntime>(
() => import("../../cli/progress.js"),
);
const terminalTableRuntimeLoader = createLazyImportLoader<TerminalTableRuntime>(
() => import("../../terminal/table.js"),
);
const listProbeRuntimeLoader = createLazyImportLoader<ListProbeRuntime>(
() => import("./list.probe.js"),
);
const DISPLAY_MODEL_PARSE_OPTIONS = { allowPluginNormalization: false } as const;
@@ -77,23 +86,19 @@ type StatusSyntheticAuth = {
};
function loadProviderUsageRuntime(): Promise<ProviderUsageRuntime> {
providerUsageRuntimePromise ??= import("../../infra/provider-usage.js");
return providerUsageRuntimePromise;
return providerUsageRuntimeLoader.load();
}
function loadProgressRuntime(): Promise<ProgressRuntime> {
progressRuntimePromise ??= import("../../cli/progress.js");
return progressRuntimePromise;
return progressRuntimeLoader.load();
}
function loadTerminalTableRuntime(): Promise<TerminalTableRuntime> {
terminalTableRuntimePromise ??= import("../../terminal/table.js");
return terminalTableRuntimePromise;
return terminalTableRuntimeLoader.load();
}
function loadListProbeRuntime(): Promise<ListProbeRuntime> {
listProbeRuntimePromise ??= import("./list.probe.js");
return listProbeRuntimePromise;
return listProbeRuntimeLoader.load();
}
function resolveProviderConfigForStatus(

View File

@@ -5,6 +5,7 @@ import { loadSessionStore, resolveSessionTotalTokens } from "../config/sessions.
import { info } from "../globals.js";
import { parseAgentSessionKey } from "../routing/session-key.js";
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { isRich, theme } from "../terminal/theme.js";
import { resolveSessionStoreTargetsOrExit } from "./session-store-targets.js";
import {
@@ -33,7 +34,7 @@ type SessionRow = SessionDisplayRow & {
const AGENT_PAD = 10;
const KIND_PAD = 6;
const TOKENS_PAD = 20;
let contextLookupRuntimePromise: Promise<typeof import("../agents/context.js")> | null = null;
const contextLookupRuntimeLoader = createLazyImportLoader(() => import("../agents/context.js"));
const formatKTokens = (value: number) => `${(value / 1000).toFixed(value >= 10_000 ? 0 : 1)}k`;
@@ -72,8 +73,7 @@ const formatTokensCell = (
};
async function lookupContextTokensForDisplay(model: string): Promise<number | undefined> {
contextLookupRuntimePromise ??= import("../agents/context.js");
const { lookupContextTokens } = await contextLookupRuntimePromise;
const { lookupContextTokens } = await contextLookupRuntimeLoader.load();
return lookupContextTokens(model, { allowAsyncLoad: false });
}

View File

@@ -5,6 +5,7 @@ import type { OptionalBootstrapFileName } from "../config/types.agent-defaults.j
import type { OpenClawConfig } from "../config/types.js";
import type { RuntimeEnv } from "../runtime.js";
import { defaultRuntime } from "../runtime.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { shortenHomePath } from "../utils.js";
import { safeParseWithSchema } from "../utils/zod-parse.js";
@@ -41,23 +42,26 @@ type AgentWorkspaceModule = typeof import("../agents/workspace.js");
type ConfigIOModule = typeof import("../config/config.js");
type ConfigLoggingModule = typeof import("../config/logging.js");
let agentWorkspaceModulePromise: Promise<AgentWorkspaceModule> | undefined;
let configIOModulePromise: Promise<ConfigIOModule> | undefined;
let configLoggingModulePromise: Promise<ConfigLoggingModule> | undefined;
const agentWorkspaceModuleLoader = createLazyImportLoader<AgentWorkspaceModule>(
() => import("../agents/workspace.js"),
);
const configIOModuleLoader = createLazyImportLoader<ConfigIOModule>(
() => import("../config/config.js"),
);
const configLoggingModuleLoader = createLazyImportLoader<ConfigLoggingModule>(
() => import("../config/logging.js"),
);
function loadAgentWorkspaceModule(): Promise<AgentWorkspaceModule> {
agentWorkspaceModulePromise ??= import("../agents/workspace.js");
return agentWorkspaceModulePromise;
return agentWorkspaceModuleLoader.load();
}
function loadConfigIOModule(): Promise<ConfigIOModule> {
configIOModulePromise ??= import("../config/config.js");
return configIOModulePromise;
return configIOModuleLoader.load();
}
function loadConfigLoggingModule(): Promise<ConfigLoggingModule> {
configLoggingModulePromise ??= import("../config/logging.js");
return configLoggingModulePromise;
return configLoggingModuleLoader.load();
}
async function createDefaultConfigIO(): Promise<ConfigIO> {

View File

@@ -1,26 +1,26 @@
import { resolveReadOnlyChannelPluginsForConfig } from "../channels/plugins/read-only.js";
import type { OpenClawConfig } from "../config/types.js";
import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import type { HealthSummary } from "./health.js";
import { getDaemonStatusSummary, getNodeDaemonStatusSummary } from "./status.daemon.js";
let providerUsagePromise: Promise<typeof import("../infra/provider-usage.js")> | undefined;
let securityAuditModulePromise: Promise<typeof import("../security/audit.runtime.js")> | undefined;
let gatewayCallModulePromise: Promise<typeof import("../gateway/call.js")> | undefined;
const providerUsageLoader = createLazyImportLoader(() => import("../infra/provider-usage.js"));
const securityAuditModuleLoader = createLazyImportLoader(
() => import("../security/audit.runtime.js"),
);
const gatewayCallModuleLoader = createLazyImportLoader(() => import("../gateway/call.js"));
function loadProviderUsage() {
providerUsagePromise ??= import("../infra/provider-usage.js");
return providerUsagePromise;
return providerUsageLoader.load();
}
function loadSecurityAuditModule() {
securityAuditModulePromise ??= import("../security/audit.runtime.js");
return securityAuditModulePromise;
return securityAuditModuleLoader.load();
}
function loadGatewayCallModule() {
gatewayCallModulePromise ??= import("../gateway/call.js");
return gatewayCallModulePromise;
return gatewayCallModuleLoader.load();
}
export async function resolveStatusSecurityAudit(params: {

View File

@@ -6,6 +6,7 @@ import {
type ConnectPairingRequiredReason,
} from "../gateway/protocol/connect-error-details.js";
import { type RuntimeEnv } from "../runtime.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { sanitizeTerminalText } from "../terminal/safe-text.js";
import { runStatusJsonCommand } from "./status-json-command.ts";
import { buildStatusOverviewSurfaceFromScan } from "./status-overview-surface.ts";
@@ -20,47 +21,41 @@ import { buildStatusCommandReportData } from "./status.command-report-data.ts";
import { buildStatusCommandReportLines } from "./status.command-report.ts";
import { logGatewayConnectionDetails } from "./status.gateway-connection.ts";
let statusScanModulePromise: Promise<typeof import("./status.scan.js")> | undefined;
let statusScanFastJsonModulePromise:
| Promise<typeof import("./status.scan.fast-json.js")>
| undefined;
let statusAllModulePromise: Promise<typeof import("./status-all.js")> | undefined;
let statusCommandTextRuntimePromise:
| Promise<typeof import("./status.command.text-runtime.js")>
| undefined;
let statusGatewayConnectionRuntimePromise:
| Promise<typeof import("./status.gateway-connection.runtime.js")>
| undefined;
let statusNodeModeModulePromise: Promise<typeof import("./status.node-mode.js")> | undefined;
const statusScanModuleLoader = createLazyImportLoader(() => import("./status.scan.js"));
const statusScanFastJsonModuleLoader = createLazyImportLoader(
() => import("./status.scan.fast-json.js"),
);
const statusAllModuleLoader = createLazyImportLoader(() => import("./status-all.js"));
const statusCommandTextRuntimeLoader = createLazyImportLoader(
() => import("./status.command.text-runtime.js"),
);
const statusGatewayConnectionRuntimeLoader = createLazyImportLoader(
() => import("./status.gateway-connection.runtime.js"),
);
const statusNodeModeModuleLoader = createLazyImportLoader(() => import("./status.node-mode.js"));
function loadStatusScanModule() {
statusScanModulePromise ??= import("./status.scan.js");
return statusScanModulePromise;
return statusScanModuleLoader.load();
}
function loadStatusScanFastJsonModule() {
statusScanFastJsonModulePromise ??= import("./status.scan.fast-json.js");
return statusScanFastJsonModulePromise;
return statusScanFastJsonModuleLoader.load();
}
function loadStatusAllModule() {
statusAllModulePromise ??= import("./status-all.js");
return statusAllModulePromise;
return statusAllModuleLoader.load();
}
function loadStatusCommandTextRuntime() {
statusCommandTextRuntimePromise ??= import("./status.command.text-runtime.js");
return statusCommandTextRuntimePromise;
return statusCommandTextRuntimeLoader.load();
}
function loadStatusGatewayConnectionRuntime() {
statusGatewayConnectionRuntimePromise ??= import("./status.gateway-connection.runtime.js");
return statusGatewayConnectionRuntimePromise;
return statusGatewayConnectionRuntimeLoader.load();
}
function loadStatusNodeModeModule() {
statusNodeModeModulePromise ??= import("./status.node-mode.js");
return statusNodeModeModulePromise;
return statusNodeModeModuleLoader.load();
}
export function resolvePairingRecoveryContext(params: {

View File

@@ -3,6 +3,7 @@ import path from "node:path";
import { resolveMemorySearchConfig } from "../agents/memory-search.js";
import { resolveStateDir } from "../config/paths.js";
import type { OpenClawConfig } from "../config/types.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import type { getAgentLocalStatuses as getAgentLocalStatusesFn } from "./status.agent-local.js";
import {
resolveSharedMemoryStatusSnapshot,
@@ -10,13 +11,12 @@ import {
type MemoryStatusSnapshot,
} from "./status.scan.shared.js";
let statusScanDepsRuntimeModulePromise:
| Promise<typeof import("./status.scan.deps.runtime.js")>
| undefined;
const statusScanDepsRuntimeModuleLoader = createLazyImportLoader(
() => import("./status.scan.deps.runtime.js"),
);
function loadStatusScanDepsRuntimeModule() {
statusScanDepsRuntimeModulePromise ??= import("./status.scan.deps.runtime.js");
return statusScanDepsRuntimeModulePromise;
return statusScanDepsRuntimeModuleLoader.load();
}
export function resolveDefaultMemoryStorePath(agentId: string): string {

View File

@@ -4,6 +4,7 @@ import { resolveOsSummary } from "../infra/os-summary.js";
import type { UpdateCheckResult } from "../infra/update-check.js";
import { hasConfiguredChannelsForReadOnlyScope } from "../plugins/channel-plugin-ids.js";
import type { RuntimeEnv } from "../runtime.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import type { buildChannelsTable as buildChannelsTableFn } from "./status-all/channels.js";
import type { getAgentLocalStatuses as getAgentLocalStatusesFn } from "./status.agent-local.js";
import {
@@ -13,65 +14,60 @@ import {
import { loadStatusScanCommandConfig } from "./status.scan.config-shared.js";
import type { GatewayProbeSnapshot } from "./status.scan.shared.js";
let statusScanDepsRuntimeModulePromise:
| Promise<typeof import("./status.scan.deps.runtime.js")>
| undefined;
let statusAgentLocalModulePromise: Promise<typeof import("./status.agent-local.js")> | undefined;
let statusUpdateModulePromise: Promise<typeof import("./status.update.js")> | undefined;
let statusScanRuntimeModulePromise: Promise<typeof import("./status.scan.runtime.js")> | undefined;
let gatewayCallModulePromise: Promise<typeof import("../gateway/call.js")> | undefined;
let statusSummaryModulePromise: Promise<typeof import("./status.summary.js")> | undefined;
let configModulePromise: Promise<typeof import("../config/config.js")> | undefined;
let commandConfigResolutionModulePromise:
| Promise<typeof import("../cli/command-config-resolution.js")>
| undefined;
let commandSecretTargetsModulePromise:
| Promise<typeof import("../cli/command-secret-targets.js")>
| undefined;
const statusScanDepsRuntimeModuleLoader = createLazyImportLoader(
() => import("./status.scan.deps.runtime.js"),
);
const statusAgentLocalModuleLoader = createLazyImportLoader(
() => import("./status.agent-local.js"),
);
const statusUpdateModuleLoader = createLazyImportLoader(() => import("./status.update.js"));
const statusScanRuntimeModuleLoader = createLazyImportLoader(
() => import("./status.scan.runtime.js"),
);
const gatewayCallModuleLoader = createLazyImportLoader(() => import("../gateway/call.js"));
const statusSummaryModuleLoader = createLazyImportLoader(() => import("./status.summary.js"));
const configModuleLoader = createLazyImportLoader(() => import("../config/config.js"));
const commandConfigResolutionModuleLoader = createLazyImportLoader(
() => import("../cli/command-config-resolution.js"),
);
const commandSecretTargetsModuleLoader = createLazyImportLoader(
() => import("../cli/command-secret-targets.js"),
);
function loadStatusScanDepsRuntimeModule() {
statusScanDepsRuntimeModulePromise ??= import("./status.scan.deps.runtime.js");
return statusScanDepsRuntimeModulePromise;
return statusScanDepsRuntimeModuleLoader.load();
}
function loadStatusAgentLocalModule() {
statusAgentLocalModulePromise ??= import("./status.agent-local.js");
return statusAgentLocalModulePromise;
return statusAgentLocalModuleLoader.load();
}
function loadStatusUpdateModule() {
statusUpdateModulePromise ??= import("./status.update.js");
return statusUpdateModulePromise;
return statusUpdateModuleLoader.load();
}
function loadStatusScanRuntimeModule() {
statusScanRuntimeModulePromise ??= import("./status.scan.runtime.js");
return statusScanRuntimeModulePromise;
return statusScanRuntimeModuleLoader.load();
}
function loadGatewayCallModule() {
gatewayCallModulePromise ??= import("../gateway/call.js");
return gatewayCallModulePromise;
return gatewayCallModuleLoader.load();
}
function loadStatusSummaryModule() {
statusSummaryModulePromise ??= import("./status.summary.js");
return statusSummaryModulePromise;
return statusSummaryModuleLoader.load();
}
function loadConfigModule() {
configModulePromise ??= import("../config/config.js");
return configModulePromise;
return configModuleLoader.load();
}
function loadCommandConfigResolutionModule() {
commandConfigResolutionModulePromise ??= import("../cli/command-config-resolution.js");
return commandConfigResolutionModulePromise;
return commandConfigResolutionModuleLoader.load();
}
function loadCommandSecretTargetsModule() {
commandSecretTargetsModulePromise ??= import("../cli/command-secret-targets.js");
return commandSecretTargetsModulePromise;
return commandSecretTargetsModuleLoader.load();
}
async function resolveStatusChannelsStatus(params: {

View File

@@ -7,6 +7,7 @@ import type { GatewayProbeResult, probeGateway as probeGatewayFn } from "../gate
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../gateway/protocol/client-info.js";
import type { MemoryProviderStatus } from "../memory-host-sdk/engine-storage.js";
import { defaultSlotIdForKey } from "../plugins/slots.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { isLoopbackIpAddress } from "../shared/net/ip.js";
import {
normalizeOptionalLowercaseString,
@@ -16,23 +17,20 @@ import { pickGatewaySelfPresence } from "./gateway-presence.js";
import { isProbeReachable } from "./gateway-status/helpers.js";
export { pickGatewaySelfPresence } from "./gateway-presence.js";
let gatewayProbeModulePromise: Promise<typeof import("./status.gateway-probe.js")> | undefined;
let probeGatewayModulePromise: Promise<typeof import("../gateway/probe.js")> | undefined;
let gatewayCallModulePromise: Promise<typeof import("../gateway/call.js")> | undefined;
const gatewayProbeModuleLoader = createLazyImportLoader(() => import("./status.gateway-probe.js"));
const probeGatewayModuleLoader = createLazyImportLoader(() => import("../gateway/probe.js"));
const gatewayCallModuleLoader = createLazyImportLoader(() => import("../gateway/call.js"));
function loadGatewayProbeModule() {
gatewayProbeModulePromise ??= import("./status.gateway-probe.js");
return gatewayProbeModulePromise;
return gatewayProbeModuleLoader.load();
}
function loadProbeGatewayModule() {
probeGatewayModulePromise ??= import("../gateway/probe.js");
return probeGatewayModulePromise;
return probeGatewayModuleLoader.load();
}
function loadGatewayCallModule() {
gatewayCallModulePromise ??= import("../gateway/call.js");
return gatewayCallModulePromise;
return gatewayCallModuleLoader.load();
}
export type MemoryStatusSnapshot = MemoryProviderStatus & {

View File

@@ -11,24 +11,25 @@ import { resolveHeartbeatSummaryForAgent } from "../infra/heartbeat-summary.js";
import { peekSystemEvents } from "../infra/system-events.js";
import { hasConfiguredChannelsForReadOnlyScope } from "../plugins/channel-plugin-ids.js";
import { parseAgentSessionKey } from "../routing/session-key.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { createLazyRuntimeSurface } from "../shared/lazy-runtime.js";
import { resolveRuntimeServiceVersion } from "../version.js";
import type { HeartbeatStatus, SessionStatus, StatusSummary } from "./status.types.js";
let channelSummaryModulePromise: Promise<typeof import("../infra/channel-summary.js")> | undefined;
let linkChannelModulePromise: Promise<typeof import("./status.link-channel.js")> | undefined;
let taskRegistryMaintenanceModulePromise:
| Promise<typeof import("../tasks/task-registry.maintenance.js")>
| undefined;
const channelSummaryModuleLoader = createLazyImportLoader(
() => import("../infra/channel-summary.js"),
);
const linkChannelModuleLoader = createLazyImportLoader(() => import("./status.link-channel.js"));
const taskRegistryMaintenanceModuleLoader = createLazyImportLoader(
() => import("../tasks/task-registry.maintenance.js"),
);
function loadChannelSummaryModule() {
channelSummaryModulePromise ??= import("../infra/channel-summary.js");
return channelSummaryModulePromise;
return channelSummaryModuleLoader.load();
}
function loadLinkChannelModule() {
linkChannelModulePromise ??= import("./status.link-channel.js");
return linkChannelModulePromise;
return linkChannelModuleLoader.load();
}
const loadStatusSummaryRuntimeModule = createLazyRuntimeSurface(
@@ -37,8 +38,7 @@ const loadStatusSummaryRuntimeModule = createLazyRuntimeSurface(
);
function loadTaskRegistryMaintenanceModule() {
taskRegistryMaintenanceModulePromise ??= import("../tasks/task-registry.maintenance.js");
return taskRegistryMaintenanceModulePromise;
return taskRegistryMaintenanceModuleLoader.load();
}
const buildFlags = (entry?: SessionEntry): string[] => {

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
import { runExec } from "../process/exec.js";
import { createLazyPromiseLoader } from "../shared/lazy-promise.js";
export type ImageMetadata = {
width: number;
@@ -54,8 +55,6 @@ function prefersSips(): boolean {
);
}
let mediaAttachmentImageOpsPromise: Promise<MediaAttachmentImageOps> | null = null;
function isMediaAttachmentImageOps(value: unknown): value is MediaAttachmentImageOps {
if (!value || typeof value !== "object") {
return false;
@@ -71,30 +70,24 @@ function isMediaAttachmentImageOps(value: unknown): value is MediaAttachmentImag
);
}
async function loadMediaAttachmentImageOps(): Promise<MediaAttachmentImageOps> {
if (!mediaAttachmentImageOpsPromise) {
mediaAttachmentImageOpsPromise = Promise.resolve()
.then(async () => {
const { loadBundledPluginPublicArtifactModuleSync } =
await import("../plugins/public-surface-loader.js");
const mod = loadBundledPluginPublicArtifactModuleSync<MediaAttachmentImageOpsModule>({
dirName: MEDIA_UNDERSTANDING_CORE_PLUGIN_ID,
artifactBasename: MEDIA_UNDERSTANDING_CORE_IMAGE_OPS_ARTIFACT,
});
const ops = mod.createMediaAttachmentImageOps?.({
maxInputPixels: MAX_IMAGE_INPUT_PIXELS,
});
if (!isMediaAttachmentImageOps(ops)) {
throw new Error("Media understanding core did not expose image ops");
}
return ops;
})
.catch((err) => {
mediaAttachmentImageOpsPromise = null;
throw err;
});
const mediaAttachmentImageOpsLoader = createLazyPromiseLoader(async () => {
const { loadBundledPluginPublicArtifactModuleSync } =
await import("../plugins/public-surface-loader.js");
const mod = loadBundledPluginPublicArtifactModuleSync<MediaAttachmentImageOpsModule>({
dirName: MEDIA_UNDERSTANDING_CORE_PLUGIN_ID,
artifactBasename: MEDIA_UNDERSTANDING_CORE_IMAGE_OPS_ARTIFACT,
});
const ops = mod.createMediaAttachmentImageOps?.({
maxInputPixels: MAX_IMAGE_INPUT_PIXELS,
});
if (!isMediaAttachmentImageOps(ops)) {
throw new Error("Media understanding core did not expose image ops");
}
return await mediaAttachmentImageOpsPromise;
return ops;
});
async function loadMediaAttachmentImageOps(): Promise<MediaAttachmentImageOps> {
return await mediaAttachmentImageOpsLoader.load();
}
function isPositiveImageDimension(value: number): boolean {

View File

@@ -1,4 +1,5 @@
import path from "node:path";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { type MediaKind, mediaKindFromMime } from "./constants.js";
/** @internal */
@@ -66,7 +67,7 @@ const AUDIO_FILE_EXTENSIONS = new Set([
".wav",
]);
let fileTypeModulePromise: Promise<typeof import("file-type")> | undefined;
const fileTypeModuleLoader = createLazyImportLoader(() => import("file-type"));
export function normalizeMimeType(mime?: string | null): string | undefined {
if (!mime) {
@@ -89,8 +90,7 @@ async function sniffMime(buffer?: Buffer): Promise<string | undefined> {
return undefined;
}
try {
fileTypeModulePromise ??= import("file-type");
const { fileTypeFromBuffer } = await fileTypeModulePromise;
const { fileTypeFromBuffer } = await fileTypeModuleLoader.load();
const type = await fileTypeFromBuffer(sliceMimeSniffBuffer(buffer));
if (type?.mime) {
return type.mime;

View File

@@ -1,14 +1,14 @@
import type QRCode from "qrcode";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
type QrCodeRuntime = typeof QRCode;
let qrCodeRuntimePromise: Promise<QrCodeRuntime> | null = null;
const qrCodeRuntimeLoader = createLazyImportLoader<QrCodeRuntime>(() =>
import("qrcode").then((mod) => mod.default ?? mod),
);
export async function loadQrCodeRuntime(): Promise<QrCodeRuntime> {
if (!qrCodeRuntimePromise) {
qrCodeRuntimePromise = import("qrcode").then((mod) => mod.default ?? mod);
}
return await qrCodeRuntimePromise;
return await qrCodeRuntimeLoader.load();
}
export function normalizeQrText(text: string): string {

View File

@@ -17,6 +17,7 @@ import {
createSafeNpmInstallEnv,
} from "../infra/safe-package-install.js";
import { runCommandWithTimeout } from "../process/exec.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { resolveUserPath } from "../utils.js";
import {
@@ -38,11 +39,10 @@ import { linkOpenClawPeerDependencies } from "./plugin-peer-link.js";
export { resolvePluginInstallDir } from "./install-paths.js";
let pluginInstallRuntimePromise: Promise<typeof import("./install.runtime.js")> | undefined;
const pluginInstallRuntimeLoader = createLazyImportLoader(() => import("./install.runtime.js"));
async function loadPluginInstallRuntime() {
pluginInstallRuntimePromise ??= import("./install.runtime.js");
return pluginInstallRuntimePromise;
return await pluginInstallRuntimeLoader.load();
}
type PluginInstallLogger = {

View File

@@ -1,26 +0,0 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { listPluginContributionIds, loadPluginRegistrySnapshot } from "./plugin-registry.js";
export function listManifestChannelContributionIds(
params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
includeDisabled?: boolean;
} = {},
): readonly string[] {
const env = params.env ?? process.env;
const index = loadPluginRegistrySnapshot({
config: params.config,
workspaceDir: params.workspaceDir,
env,
});
return listPluginContributionIds({
index,
contribution: "channels",
config: params.config,
workspaceDir: params.workspaceDir,
env,
includeDisabled: params.includeDisabled,
});
}

View File

@@ -0,0 +1,54 @@
import {
listPluginContributionIds,
loadPluginRegistrySnapshot,
type LoadPluginRegistryParams,
type PluginRegistryContributionKey,
type PluginRegistrySnapshot,
} from "./plugin-registry.js";
export type ListManifestContributionIdsParams = LoadPluginRegistryParams & {
contribution: PluginRegistryContributionKey;
index?: PluginRegistrySnapshot;
includeDisabled?: boolean;
};
export function listManifestContributionIds(
params: ListManifestContributionIdsParams,
): readonly string[] {
const env = params.env ?? process.env;
const index =
params.index ??
loadPluginRegistrySnapshot({
config: params.config,
workspaceDir: params.workspaceDir,
env,
candidates: params.candidates,
preferPersisted: params.preferPersisted,
});
return listPluginContributionIds({
index,
contribution: params.contribution,
config: params.config,
workspaceDir: params.workspaceDir,
env,
includeDisabled: params.includeDisabled,
});
}
export function listManifestChannelContributionIds(
params: Omit<ListManifestContributionIdsParams, "contribution"> = {},
): readonly string[] {
return listManifestContributionIds({
...params,
contribution: "channels",
});
}
export function listManifestProviderContributionIds(
params: Omit<ListManifestContributionIdsParams, "contribution"> = {},
): readonly string[] {
return listManifestContributionIds({
...params,
contribution: "providers",
});
}

View File

@@ -9,17 +9,17 @@ import {
isValidFileSecretRefId,
resolveDefaultSecretProviderAlias,
} from "../secrets/ref-contract.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import {
normalizeOptionalString,
normalizeStringifiedOptionalString,
} from "../shared/string-coerce.js";
import type { WizardPrompter } from "../wizard/prompts.js";
let secretResolvePromise: Promise<typeof import("../secrets/resolve.js")> | undefined;
const secretResolveLoader = createLazyImportLoader(() => import("../secrets/resolve.js"));
function loadSecretResolve() {
secretResolvePromise ??= import("../secrets/resolve.js");
return secretResolvePromise;
return secretResolveLoader.load();
}
const ENV_SOURCE_LABEL_RE = /(?:^|:\s)([A-Z][A-Z0-9_]*)$/;

View File

@@ -1,22 +1,20 @@
import { normalizeProviderId } from "../agents/model-selection.js";
import type { ModelProviderConfig } from "../config/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { listManifestProviderContributionIds } from "./manifest-contribution-ids.js";
import type { PluginMetadataRegistryView } from "./plugin-metadata-snapshot.types.js";
import {
listPluginContributionIds,
loadPluginRegistrySnapshot,
type LoadPluginRegistryParams,
type PluginRegistrySnapshot,
} from "./plugin-registry.js";
import { type LoadPluginRegistryParams, type PluginRegistrySnapshot } from "./plugin-registry.js";
import type { ProviderDiscoveryOrder, ProviderPlugin } from "./types.js";
const DISCOVERY_ORDER: readonly ProviderDiscoveryOrder[] = ["simple", "profile", "paired", "late"];
const DANGEROUS_PROVIDER_KEYS = new Set(["__proto__", "prototype", "constructor"]);
let providerRuntimePromise: Promise<typeof import("./provider-discovery.runtime.js")> | undefined;
const providerRuntimeLoader = createLazyImportLoader(
() => import("./provider-discovery.runtime.js"),
);
function loadProviderRuntime() {
providerRuntimePromise ??= import("./provider-discovery.runtime.js");
return providerRuntimePromise;
return providerRuntimeLoader.load();
}
function resolveProviderCatalogHook(provider: ProviderPlugin) {
@@ -62,13 +60,11 @@ export function resolveInstalledPluginProviderContributionIds(
params.candidates && params.preferPersisted === undefined
? { ...params, preferPersisted: false }
: params;
const index = params.index ?? loadPluginRegistrySnapshot(registryParams);
return sortedValues(
listPluginContributionIds({
index,
contribution: "providers",
listManifestProviderContributionIds({
...registryParams,
index: params.index,
includeDisabled: params.includeDisabled,
config: params.config,
}),
);
}

View File

@@ -1,3 +1,5 @@
import { createLazyImportLoader } from "../shared/lazy-promise.js";
type ProviderRuntimeModule = typeof import("./provider-runtime.js");
type AugmentModelCatalogWithProviderPlugins =
@@ -12,13 +14,14 @@ type PrepareProviderRuntimeAuth = ProviderRuntimeModule["prepareProviderRuntimeA
type RefreshProviderOAuthCredentialWithPlugin =
ProviderRuntimeModule["refreshProviderOAuthCredentialWithPlugin"];
let providerRuntimePromise: Promise<ProviderRuntimeModule> | undefined;
const providerRuntimeLoader = createLazyImportLoader<ProviderRuntimeModule>(
() => import("./provider-runtime.js"),
);
async function loadProviderRuntime(): Promise<ProviderRuntimeModule> {
// Keep the heavy provider runtime behind an actual async boundary so callers
// can import this wrapper eagerly without collapsing the lazy chunk.
providerRuntimePromise ??= import("./provider-runtime.js");
return providerRuntimePromise;
return await providerRuntimeLoader.load();
}
export async function augmentModelCatalogWithProviderPlugins(

View File

@@ -0,0 +1,59 @@
import { describe, expect, it, vi } from "vitest";
import { createLazyImportLoader, createLazyPromiseLoader } from "./lazy-promise.js";
describe("createLazyPromiseLoader", () => {
it("dedupes concurrent loads and reuses the resolved value", async () => {
let calls = 0;
const loader = createLazyPromiseLoader(async () => `loaded-${++calls}`);
await expect(Promise.all([loader.load(), loader.load()])).resolves.toEqual([
"loaded-1",
"loaded-1",
]);
await expect(loader.load()).resolves.toBe("loaded-1");
expect(calls).toBe(1);
});
it("evicts rejected loads by default so retries can recover", async () => {
let calls = 0;
const loader = createLazyPromiseLoader(async () => {
calls += 1;
if (calls === 1) {
throw new Error("transient");
}
return "recovered";
});
await expect(loader.load()).rejects.toThrow("transient");
await expect(loader.load()).resolves.toBe("recovered");
expect(calls).toBe(2);
});
it("can keep rejected loads when requested", async () => {
const load = vi.fn(async () => {
throw new Error("sticky");
});
const loader = createLazyPromiseLoader(load, { cacheRejections: true });
await expect(loader.load()).rejects.toThrow("sticky");
await expect(loader.load()).rejects.toThrow("sticky");
expect(load).toHaveBeenCalledOnce();
});
it("clears cached values", async () => {
let calls = 0;
const loader = createLazyPromiseLoader(() => `loaded-${++calls}`);
await expect(loader.load()).resolves.toBe("loaded-1");
loader.clear();
await expect(loader.load()).resolves.toBe("loaded-2");
});
});
describe("createLazyImportLoader", () => {
it("wraps import-shaped loaders", async () => {
const loader = createLazyImportLoader(async () => ({ value: "module" }));
await expect(loader.load()).resolves.toEqual({ value: "module" });
});
});

View File

@@ -0,0 +1,44 @@
export type LazyPromiseLoader<T> = {
load(): Promise<T>;
clear(): void;
};
export type LazyPromiseLoaderOptions = {
cacheRejections?: boolean;
};
export function createLazyPromiseLoader<T>(
load: () => T | Promise<T>,
options: LazyPromiseLoaderOptions = {},
): LazyPromiseLoader<T> {
let promise: Promise<T> | undefined;
const createPromise = (): Promise<T> => {
const loaded = Promise.resolve().then(load);
if (options.cacheRejections !== true) {
void loaded.catch(() => {
if (promise === loaded) {
promise = undefined;
}
});
}
return loaded;
};
return {
async load(): Promise<T> {
promise ??= createPromise();
return await promise;
},
clear(): void {
promise = undefined;
},
};
}
export function createLazyImportLoader<T>(
load: () => Promise<T>,
options?: LazyPromiseLoaderOptions,
): LazyPromiseLoader<T> {
return createLazyPromiseLoader(load, options);
}