mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:00:43 +00:00
refactor: unify lazy module loaders
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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[]) => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 & {
|
||||
|
||||
@@ -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[] => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
54
src/plugins/manifest-contribution-ids.ts
Normal file
54
src/plugins/manifest-contribution-ids.ts
Normal 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",
|
||||
});
|
||||
}
|
||||
@@ -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_]*)$/;
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
59
src/shared/lazy-promise.test.ts
Normal file
59
src/shared/lazy-promise.test.ts
Normal 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" });
|
||||
});
|
||||
});
|
||||
44
src/shared/lazy-promise.ts
Normal file
44
src/shared/lazy-promise.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user