From 3913aa999dc0f06ba6905bdbefcafbf2e5bdc64c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 09:12:03 +0100 Subject: [PATCH] test: lighten fast lane imports --- src/cli/run-main-policy.ts | 155 ++++++++++++++++++++++ src/cli/run-main.test.ts | 28 +--- src/cli/run-main.ts | 145 +++----------------- src/media-understanding/runner.entries.ts | 35 ++++- src/media-understanding/runner.ts | 54 ++++++-- 5 files changed, 259 insertions(+), 158 deletions(-) create mode 100644 src/cli/run-main-policy.ts diff --git a/src/cli/run-main-policy.ts b/src/cli/run-main-policy.ts new file mode 100644 index 00000000000..513f5e94eb7 --- /dev/null +++ b/src/cli/run-main-policy.ts @@ -0,0 +1,155 @@ +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { + resolveManifestCommandAliasOwnerInRegistry, + type PluginManifestCommandAliasRecord, + type PluginManifestCommandAliasRegistry, +} from "../plugins/manifest-command-aliases.js"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalLowercaseString, +} from "../shared/string-coerce.js"; +import { resolveCliArgvInvocation } from "./argv-invocation.js"; +import { resolveCliCommandPathPolicy } from "./command-path-policy.js"; + +export function rewriteUpdateFlagArgv(argv: string[]): string[] { + const index = argv.indexOf("--update"); + if (index === -1) { + return argv; + } + + const next = [...argv]; + next.splice(index, 1, "update"); + return next; +} + +export function shouldEnsureCliPath(argv: string[]): boolean { + const invocation = resolveCliArgvInvocation(argv); + if (invocation.hasHelpOrVersion || shouldStartCrestodianForBareRoot(argv)) { + return false; + } + return resolveCliCommandPathPolicy(invocation.commandPath).ensureCliPath; +} + +export function shouldUseRootHelpFastPath( + argv: string[], + env: NodeJS.ProcessEnv = process.env, +): boolean { + const invocation = resolveCliArgvInvocation(argv); + return ( + env.OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH !== "1" && + (invocation.isRootHelpInvocation || + (invocation.commandPath.length === 1 && + invocation.commandPath[0] === "help" && + invocation.hasHelpOrVersion)) + ); +} + +export function shouldUseBrowserHelpFastPath( + argv: string[], + env: NodeJS.ProcessEnv = process.env, +): boolean { + if (env.OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH === "1") { + return false; + } + const invocation = resolveCliArgvInvocation(argv); + return ( + invocation.commandPath.length === 1 && + invocation.commandPath[0] === "browser" && + invocation.hasHelpOrVersion + ); +} + +export function shouldStartCrestodianForBareRoot(argv: string[]): boolean { + const invocation = resolveCliArgvInvocation(argv); + return invocation.commandPath.length === 0 && !invocation.hasHelpOrVersion; +} + +export function shouldStartCrestodianForModernOnboard(argv: string[]): boolean { + const invocation = resolveCliArgvInvocation(argv); + return ( + invocation.commandPath[0] === "onboard" && + argv.includes("--modern") && + !invocation.hasHelpOrVersion + ); +} + +export function resolveMissingPluginCommandMessage( + pluginId: string, + config?: OpenClawConfig, + options?: { + registry?: PluginManifestCommandAliasRegistry; + resolveCommandAliasOwner?: (params: { + command: string | undefined; + config?: OpenClawConfig; + registry?: PluginManifestCommandAliasRegistry; + }) => PluginManifestCommandAliasRecord | undefined; + }, +): string | null { + const normalizedPluginId = normalizeLowercaseStringOrEmpty(pluginId); + if (!normalizedPluginId) { + return null; + } + const allow = + Array.isArray(config?.plugins?.allow) && config.plugins.allow.length > 0 + ? config.plugins.allow + .filter((entry): entry is string => typeof entry === "string") + .map((entry) => normalizeOptionalLowercaseString(entry)) + .filter(Boolean) + : []; + const commandAlias = options?.registry + ? resolveManifestCommandAliasOwnerInRegistry({ + command: normalizedPluginId, + registry: options.registry, + }) + : options?.resolveCommandAliasOwner?.({ + command: normalizedPluginId, + config, + ...(options?.registry ? { registry: options.registry } : {}), + }); + const parentPluginId = commandAlias?.pluginId; + if (parentPluginId) { + if (allow.length > 0 && !allow.includes(parentPluginId)) { + return ( + `"${normalizedPluginId}" is not a plugin; it is a command provided by the ` + + `"${parentPluginId}" plugin. Add "${parentPluginId}" to \`plugins.allow\` ` + + `instead of "${normalizedPluginId}".` + ); + } + if (config?.plugins?.entries?.[parentPluginId]?.enabled === false) { + return ( + `The \`openclaw ${normalizedPluginId}\` command is unavailable because ` + + `\`plugins.entries.${parentPluginId}.enabled=false\`. Re-enable that entry if you want ` + + "the bundled plugin command surface." + ); + } + if (commandAlias.kind === "runtime-slash") { + const cliHint = commandAlias.cliCommand + ? `Use \`openclaw ${commandAlias.cliCommand}\` for related CLI operations, or ` + : "Use "; + return ( + `"${normalizedPluginId}" is a runtime slash command (/${normalizedPluginId}), not a CLI command. ` + + `It is provided by the "${parentPluginId}" plugin. ` + + `${cliHint}\`/${normalizedPluginId}\` in a chat session.` + ); + } + } + + if (allow.length > 0 && !allow.includes(normalizedPluginId)) { + if (parentPluginId && allow.includes(parentPluginId)) { + return null; + } + return ( + `The \`openclaw ${normalizedPluginId}\` command is unavailable because ` + + `\`plugins.allow\` excludes "${normalizedPluginId}". Add "${normalizedPluginId}" to ` + + `\`plugins.allow\` if you want that bundled plugin CLI surface.` + ); + } + if (config?.plugins?.entries?.[normalizedPluginId]?.enabled === false) { + return ( + `The \`openclaw ${normalizedPluginId}\` command is unavailable because ` + + `\`plugins.entries.${normalizedPluginId}.enabled=false\`. Re-enable that entry if you want ` + + "the bundled plugin CLI surface." + ); + } + return null; +} diff --git a/src/cli/run-main.test.ts b/src/cli/run-main.test.ts index 49cd8d20657..aa6ccee4ae1 100644 --- a/src/cli/run-main.test.ts +++ b/src/cli/run-main.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import type { PluginManifestRegistry } from "../plugins/manifest-registry.js"; +import type { PluginManifestCommandAliasRegistry } from "../plugins/manifest-command-aliases.js"; import { rewriteUpdateFlagArgv, resolveMissingPluginCommandMessage, @@ -8,44 +8,24 @@ import { shouldStartCrestodianForModernOnboard, shouldUseBrowserHelpFastPath, shouldUseRootHelpFastPath, -} from "./run-main.js"; +} from "./run-main-policy.js"; -const memoryWikiCommandAliasRegistry: PluginManifestRegistry = { +const memoryWikiCommandAliasRegistry: PluginManifestCommandAliasRegistry = { plugins: [ { id: "memory-wiki", - channels: [], - providers: [], - cliBackends: [], - skills: [], - hooks: [], - origin: "bundled", - rootDir: "/tmp/memory-wiki", - source: "bundled", - manifestPath: "/tmp/memory-wiki/openclaw.plugin.json", commandAliases: [{ name: "wiki" }], }, ], - diagnostics: [], }; -const memoryCoreCommandAliasRegistry: PluginManifestRegistry = { +const memoryCoreCommandAliasRegistry: PluginManifestCommandAliasRegistry = { plugins: [ { id: "memory-core", - channels: [], - providers: [], - cliBackends: [], - skills: [], - hooks: [], - origin: "bundled", - rootDir: "/tmp/memory-core", - source: "bundled", - manifestPath: "/tmp/memory-core/openclaw.plugin.json", commandAliases: [{ name: "dreaming", kind: "runtime-slash", cliCommand: "memory" }], }, ], - diagnostics: [], }; describe("rewriteUpdateFlagArgv", () => { diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index 563d463d3f5..2c27476fc26 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -20,23 +20,36 @@ import { finalizeDebugProxyCapture, initializeDebugProxyCapture, } from "../proxy-capture/runtime.js"; -import { - normalizeLowercaseStringOrEmpty, - normalizeOptionalLowercaseString, - normalizeOptionalString, -} from "../shared/string-coerce.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import { resolveCliArgvInvocation } from "./argv-invocation.js"; import { shouldRegisterPrimaryCommandOnly, shouldSkipPluginCommandRegistration, } from "./command-registration-policy.js"; -import { shouldEnsureCliPathForCommandPath } from "./command-startup-policy.js"; import { maybeRunCliInContainer, parseCliContainerArgs } from "./container-target.js"; import { applyCliProfileEnv, parseCliProfileArgs } from "./profile.js"; import { createCliProgress } from "./progress.js"; import { tryRouteCli } from "./route.js"; +import { + resolveMissingPluginCommandMessage as resolveMissingPluginCommandMessageFromPolicy, + rewriteUpdateFlagArgv, + shouldEnsureCliPath, + shouldStartCrestodianForBareRoot, + shouldStartCrestodianForModernOnboard, + shouldUseBrowserHelpFastPath, + shouldUseRootHelpFastPath, +} from "./run-main-policy.js"; import { normalizeWindowsArgv } from "./windows-argv.js"; +export { + rewriteUpdateFlagArgv, + shouldEnsureCliPath, + shouldStartCrestodianForBareRoot, + shouldStartCrestodianForModernOnboard, + shouldUseBrowserHelpFastPath, + shouldUseRootHelpFastPath, +} from "./run-main-policy.js"; + async function closeCliMemoryManagers(): Promise { if (!hasMemoryRuntime()) { return; @@ -49,129 +62,15 @@ async function closeCliMemoryManagers(): Promise { } } -export function rewriteUpdateFlagArgv(argv: string[]): string[] { - const index = argv.indexOf("--update"); - if (index === -1) { - return argv; - } - - const next = [...argv]; - next.splice(index, 1, "update"); - return next; -} - -export function shouldEnsureCliPath(argv: string[]): boolean { - const invocation = resolveCliArgvInvocation(argv); - if (invocation.hasHelpOrVersion || shouldStartCrestodianForBareRoot(argv)) { - return false; - } - return shouldEnsureCliPathForCommandPath(invocation.commandPath); -} - -export function shouldUseRootHelpFastPath(argv: string[]): boolean { - const invocation = resolveCliArgvInvocation(argv); - return ( - process.env.OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH !== "1" && - (invocation.isRootHelpInvocation || - (invocation.commandPath.length === 1 && - invocation.commandPath[0] === "help" && - invocation.hasHelpOrVersion)) - ); -} - -export function shouldUseBrowserHelpFastPath(argv: string[]): boolean { - if (process.env.OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH === "1") { - return false; - } - const invocation = resolveCliArgvInvocation(argv); - return ( - invocation.commandPath.length === 1 && - invocation.commandPath[0] === "browser" && - invocation.hasHelpOrVersion - ); -} - -export function shouldStartCrestodianForBareRoot(argv: string[]): boolean { - const invocation = resolveCliArgvInvocation(argv); - return invocation.commandPath.length === 0 && !invocation.hasHelpOrVersion; -} - -export function shouldStartCrestodianForModernOnboard(argv: string[]): boolean { - const invocation = resolveCliArgvInvocation(argv); - return ( - invocation.commandPath[0] === "onboard" && - argv.includes("--modern") && - !invocation.hasHelpOrVersion - ); -} - export function resolveMissingPluginCommandMessage( pluginId: string, config?: OpenClawConfig, options?: { registry?: PluginManifestCommandAliasRegistry }, ): string | null { - const normalizedPluginId = normalizeLowercaseStringOrEmpty(pluginId); - if (!normalizedPluginId) { - return null; - } - const allow = - Array.isArray(config?.plugins?.allow) && config.plugins.allow.length > 0 - ? config.plugins.allow - .filter((entry): entry is string => typeof entry === "string") - .map((entry) => normalizeOptionalLowercaseString(entry)) - .filter(Boolean) - : []; - const commandAlias = resolveManifestCommandAliasOwner({ - command: normalizedPluginId, - config, - registry: options?.registry, + return resolveMissingPluginCommandMessageFromPolicy(pluginId, config, { + ...(options?.registry ? { registry: options.registry } : {}), + resolveCommandAliasOwner: resolveManifestCommandAliasOwner, }); - const parentPluginId = commandAlias?.pluginId; - if (parentPluginId) { - if (allow.length > 0 && !allow.includes(parentPluginId)) { - return ( - `"${normalizedPluginId}" is not a plugin; it is a command provided by the ` + - `"${parentPluginId}" plugin. Add "${parentPluginId}" to \`plugins.allow\` ` + - `instead of "${normalizedPluginId}".` - ); - } - if (config?.plugins?.entries?.[parentPluginId]?.enabled === false) { - return ( - `The \`openclaw ${normalizedPluginId}\` command is unavailable because ` + - `\`plugins.entries.${parentPluginId}.enabled=false\`. Re-enable that entry if you want ` + - "the bundled plugin command surface." - ); - } - if (commandAlias.kind === "runtime-slash") { - const cliHint = commandAlias.cliCommand - ? `Use \`openclaw ${commandAlias.cliCommand}\` for related CLI operations, or ` - : "Use "; - return ( - `"${normalizedPluginId}" is a runtime slash command (/${normalizedPluginId}), not a CLI command. ` + - `It is provided by the "${parentPluginId}" plugin. ` + - `${cliHint}\`/${normalizedPluginId}\` in a chat session.` - ); - } - } - - if (allow.length > 0 && !allow.includes(normalizedPluginId)) { - if (parentPluginId && allow.includes(parentPluginId)) { - return null; - } - return ( - `The \`openclaw ${normalizedPluginId}\` command is unavailable because ` + - `\`plugins.allow\` excludes "${normalizedPluginId}". Add "${normalizedPluginId}" to ` + - `\`plugins.allow\` if you want that bundled plugin CLI surface.` - ); - } - if (config?.plugins?.entries?.[normalizedPluginId]?.enabled === false) { - return ( - `The \`openclaw ${normalizedPluginId}\` command is unavailable because ` + - `\`plugins.entries.${normalizedPluginId}.enabled=false\`. Re-enable that entry if you want ` + - "the bundled plugin CLI surface." - ); - } - return null; } function shouldLoadCliDotEnv(env: NodeJS.ProcessEnv = process.env): boolean { diff --git a/src/media-understanding/runner.entries.ts b/src/media-understanding/runner.entries.ts index 4b3ce7e7118..56d300a4f2e 100644 --- a/src/media-understanding/runner.entries.ts +++ b/src/media-understanding/runner.entries.ts @@ -4,7 +4,6 @@ import { collectProviderApiKeysForExecution, executeWithApiKeyRotation, } from "../agents/api-key-rotation.js"; -import { requireApiKey, resolveApiKeyForProvider } from "../agents/model-auth.js"; import { mergeModelProviderRequestOverrides, sanitizeConfiguredModelProviderRequest, @@ -46,6 +45,26 @@ import type { import { estimateBase64Size, resolveVideoMaxBase64Bytes } from "./video.js"; export type ProviderRegistry = Map; +type ResolveApiKeyForProvider = typeof import("../agents/model-auth.js").resolveApiKeyForProvider; +type RequireApiKey = typeof import("../agents/model-auth.js").requireApiKey; + +let cachedModelAuth: { + resolveApiKeyForProvider: ResolveApiKeyForProvider; + requireApiKey: RequireApiKey; +} | null = null; + +async function loadModelAuth() { + cachedModelAuth ??= await import("../agents/model-auth.js"); + return cachedModelAuth; +} + +function resolveLiteralProviderApiKey(params: { + cfg: OpenClawConfig; + providerId: string; +}): string | null { + const value = params.cfg.models?.providers?.[params.providerId]?.apiKey; + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; +} function sanitizeProviderHeaders( headers: Record | undefined, @@ -394,6 +413,20 @@ async function resolveProviderExecutionAuth(params: { entry: MediaUnderstandingModelConfig; agentDir?: string; }) { + const literalApiKey = resolveLiteralProviderApiKey({ + cfg: params.cfg, + providerId: params.providerId, + }); + if (literalApiKey) { + return { + apiKeys: collectProviderApiKeysForExecution({ + provider: params.providerId, + primaryApiKey: literalApiKey, + }), + providerConfig: params.cfg.models?.providers?.[params.providerId], + }; + } + const { requireApiKey, resolveApiKeyForProvider } = await loadModelAuth(); const auth = await resolveApiKeyForProvider({ provider: params.providerId, cfg: params.cfg, diff --git a/src/media-understanding/runner.ts b/src/media-understanding/runner.ts index 12b3c6d99f3..aa6f99b7759 100644 --- a/src/media-understanding/runner.ts +++ b/src/media-understanding/runner.ts @@ -2,12 +2,6 @@ import { constants as fsConstants } from "node:fs"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { hasAvailableAuthForProvider } from "../agents/model-auth.js"; -import { - findModelInCatalog, - loadModelCatalog, - modelSupportsVision, -} from "../agents/model-catalog.js"; import { findNormalizedProviderValue } from "../agents/provider-id.js"; import type { MsgContext } from "../auto-reply/templating.js"; import { @@ -58,12 +52,45 @@ export { createMediaAttachmentCache, normalizeMediaAttachments } from "./runner. export type { ActiveMediaModel } from "./active-model.types.js"; type ProviderRegistry = Map; +type HasAvailableAuthForProvider = + typeof import("../agents/model-auth.js").hasAvailableAuthForProvider; +type ModelCatalogApi = typeof import("../agents/model-catalog.js"); +type ModelCatalog = Awaited>; export type RunCapabilityResult = { outputs: MediaUnderstandingOutput[]; decision: MediaUnderstandingDecision; }; +let cachedHasAvailableAuthForProvider: HasAvailableAuthForProvider | null = null; +let cachedModelCatalogApi: ModelCatalogApi | null = null; + +async function loadModelCatalogApi(): Promise { + cachedModelCatalogApi ??= await import("../agents/model-catalog.js"); + return cachedModelCatalogApi; +} + +function resolveLiteralProviderApiKey( + cfg: OpenClawConfig | undefined, + providerId: string, +): string | null { + const value = cfg?.models?.providers?.[providerId]?.apiKey; + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; +} + +async function hasProviderAuthAvailable(params: { + provider: string; + cfg?: OpenClawConfig; + agentDir?: string; +}): Promise { + if (resolveLiteralProviderApiKey(params.cfg, params.provider)) { + return true; + } + cachedHasAvailableAuthForProvider ??= (await import("../agents/model-auth.js")) + .hasAvailableAuthForProvider; + return await cachedHasAvailableAuthForProvider(params); +} + function resolveConfiguredKeyProviderOrder(params: { cfg: OpenClawConfig; providerRegistry: ProviderRegistry; @@ -113,11 +140,13 @@ function resolveConfiguredImageModel(params: { function resolveCatalogImageModelId(params: { providerId: string; - catalog: Awaited>; + catalog: ModelCatalog; + modelSupportsVision: ModelCatalogApi["modelSupportsVision"]; }): string | undefined { const matches = params.catalog.filter( (entry) => - normalizeMediaProviderId(entry.provider) === params.providerId && modelSupportsVision(entry), + normalizeMediaProviderId(entry.provider) === params.providerId && + params.modelSupportsVision(entry), ); if (matches.length === 0) { return undefined; @@ -135,6 +164,7 @@ async function explicitImageModelVisionStatus(params: { if (configured?.id?.trim() === params.model && configured.input?.includes("image")) { return "supported"; } + const { findModelInCatalog, loadModelCatalog, modelSupportsVision } = await loadModelCatalogApi(); const catalog = await loadModelCatalog({ config: params.cfg }); const entry = findModelInCatalog(catalog, params.providerId, params.model); if (!entry) { @@ -171,10 +201,12 @@ async function resolveAutoImageModelId(params: { if (defaultModel) { return defaultModel; } + const { loadModelCatalog, modelSupportsVision } = await loadModelCatalogApi(); const catalog = await loadModelCatalog({ config: params.cfg }); return resolveCatalogImageModelId({ providerId: params.providerId, catalog, + modelSupportsVision, }); } @@ -457,7 +489,7 @@ async function resolveKeyEntry(params: { return null; } if ( - !(await hasAvailableAuthForProvider({ + !(await hasProviderAuthAvailable({ provider: providerId, cfg, agentDir, @@ -649,7 +681,7 @@ async function resolveActiveModelEntry(params: { if (params.capability === "video" && !provider.describeVideo) { return null; } - const hasAuth = await hasAvailableAuthForProvider({ + const hasAuth = await hasProviderAuthAvailable({ provider: providerId, cfg: params.cfg, agentDir: params.agentDir, @@ -824,6 +856,8 @@ export async function runCapability(params: { activeProvider && !hasExplicitImageUnderstandingConfig({ cfg, config }) ) { + const { findModelInCatalog, loadModelCatalog, modelSupportsVision } = + await loadModelCatalogApi(); const catalog = await loadModelCatalog({ config: cfg }); const entry = findModelInCatalog(catalog, activeProvider, params.activeModel?.model ?? ""); if (modelSupportsVision(entry)) {