test: lighten fast lane imports

This commit is contained in:
Peter Steinberger
2026-04-27 09:12:03 +01:00
parent b09345e3f6
commit 3913aa999d
5 changed files with 259 additions and 158 deletions

155
src/cli/run-main-policy.ts Normal file
View File

@@ -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;
}

View File

@@ -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", () => {

View File

@@ -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<void> {
if (!hasMemoryRuntime()) {
return;
@@ -49,129 +62,15 @@ async function closeCliMemoryManagers(): Promise<void> {
}
}
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 {

View File

@@ -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<string, MediaUnderstandingProvider>;
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<string, unknown> | 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,

View File

@@ -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<string, MediaUnderstandingProvider>;
type HasAvailableAuthForProvider =
typeof import("../agents/model-auth.js").hasAvailableAuthForProvider;
type ModelCatalogApi = typeof import("../agents/model-catalog.js");
type ModelCatalog = Awaited<ReturnType<ModelCatalogApi["loadModelCatalog"]>>;
export type RunCapabilityResult = {
outputs: MediaUnderstandingOutput[];
decision: MediaUnderstandingDecision;
};
let cachedHasAvailableAuthForProvider: HasAvailableAuthForProvider | null = null;
let cachedModelCatalogApi: ModelCatalogApi | null = null;
async function loadModelCatalogApi(): Promise<ModelCatalogApi> {
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<boolean> {
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<ReturnType<typeof loadModelCatalog>>;
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)) {