mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
perf: fast-path gateway foreground startup
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
defineImportedProgramCommandGroupSpecs,
|
||||
type CommandGroupDescriptorSpec,
|
||||
} from "./command-group-descriptors.js";
|
||||
import { removeCommandByName } from "./command-tree.js";
|
||||
import { loadPrivateQaCliModule } from "./private-qa-cli.js";
|
||||
import {
|
||||
registerCommandGroupByName,
|
||||
@@ -26,6 +27,28 @@ export { getSubCliCommandsWithSubcommands };
|
||||
|
||||
type SubCliRegistrar = (program: Command) => Promise<void> | void;
|
||||
|
||||
function shouldRegisterGatewayRunOnly(name: string, argv: string[]): boolean {
|
||||
if (name !== "gateway") {
|
||||
return false;
|
||||
}
|
||||
const invocation = resolveCliArgvInvocation(argv);
|
||||
if (invocation.hasHelpOrVersion || invocation.commandPath[0] !== "gateway") {
|
||||
return false;
|
||||
}
|
||||
return invocation.commandPath.length === 1 || invocation.commandPath[1] === "run";
|
||||
}
|
||||
|
||||
async function registerGatewayRunOnly(program: Command): Promise<void> {
|
||||
const { addGatewayRunCommand } = await import("../gateway-cli/run.js");
|
||||
removeCommandByName(program, "gateway");
|
||||
const gateway = addGatewayRunCommand(
|
||||
program.command("gateway").description("Run, inspect, and query the WebSocket Gateway"),
|
||||
);
|
||||
addGatewayRunCommand(
|
||||
gateway.command("run").description("Run the WebSocket Gateway (foreground)"),
|
||||
);
|
||||
}
|
||||
|
||||
async function registerSubCliWithPluginCommands(
|
||||
program: Command,
|
||||
registerSubCli: () => Promise<void>,
|
||||
@@ -241,7 +264,15 @@ export function getSubCliEntries(): ReadonlyArray<SubCliDescriptor> {
|
||||
return getSubCliEntryDescriptors();
|
||||
}
|
||||
|
||||
export async function registerSubCliByName(program: Command, name: string): Promise<boolean> {
|
||||
export async function registerSubCliByName(
|
||||
program: Command,
|
||||
name: string,
|
||||
argv: string[] = process.argv,
|
||||
): Promise<boolean> {
|
||||
if (shouldRegisterGatewayRunOnly(name, argv)) {
|
||||
await registerGatewayRunOnly(program);
|
||||
return true;
|
||||
}
|
||||
return registerCommandGroupByName(program, resolveSubCliCommandGroups(), name);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,25 @@ const { registerPluginsCli, registerPluginCliCommandsFromValidatedConfig } = vi.
|
||||
}),
|
||||
registerPluginCliCommandsFromValidatedConfig: vi.fn(async () => null),
|
||||
}));
|
||||
const { addGatewayRunCommand, gatewayRunAction, registerGatewayCli } = vi.hoisted(() => {
|
||||
const runAction = vi.fn();
|
||||
return {
|
||||
addGatewayRunCommand: vi.fn((command: Command) =>
|
||||
command.option("--force", "force", false).action(runAction),
|
||||
),
|
||||
gatewayRunAction: runAction,
|
||||
registerGatewayCli: vi.fn((program: Command) => {
|
||||
program
|
||||
.command("gateway")
|
||||
.command("call")
|
||||
.action(() => undefined);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../acp-cli.js", () => ({ registerAcpCli }));
|
||||
vi.mock("../gateway-cli.js", () => ({ registerGatewayCli }));
|
||||
vi.mock("../gateway-cli/run.js", () => ({ addGatewayRunCommand }));
|
||||
vi.mock("../nodes-cli.js", () => ({ registerNodesCli }));
|
||||
vi.mock("../capability-cli.js", () => ({ registerCapabilityCli }));
|
||||
vi.mock("../plugins-cli.js", () => ({ registerPluginsCli }));
|
||||
@@ -93,6 +110,9 @@ describe("registerSubCliCommands", () => {
|
||||
inferAction.mockClear();
|
||||
registerPluginsCli.mockClear();
|
||||
registerPluginCliCommandsFromValidatedConfig.mockClear();
|
||||
addGatewayRunCommand.mockClear();
|
||||
gatewayRunAction.mockClear();
|
||||
registerGatewayCli.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -174,6 +194,30 @@ describe("registerSubCliCommands", () => {
|
||||
expect(acpAction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("registers only the gateway run surface for gateway startup", async () => {
|
||||
const argv = ["node", "openclaw", "gateway", "--force"];
|
||||
process.argv = argv;
|
||||
const program = new Command().name("openclaw");
|
||||
|
||||
await registerSubCliByName(program, "gateway", argv);
|
||||
|
||||
expect(addGatewayRunCommand).toHaveBeenCalledTimes(2);
|
||||
expect(registerGatewayCli).not.toHaveBeenCalled();
|
||||
await program.parseAsync(["gateway", "--force"], { from: "user" });
|
||||
expect(gatewayRunAction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("keeps the full gateway CLI for non-run gateway subcommands", async () => {
|
||||
const argv = ["node", "openclaw", "gateway", "call", "health"];
|
||||
process.argv = argv;
|
||||
const program = new Command().name("openclaw");
|
||||
|
||||
await registerSubCliByName(program, "gateway", argv);
|
||||
|
||||
expect(addGatewayRunCommand).not.toHaveBeenCalled();
|
||||
expect(registerGatewayCli).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["plugins update", ["plugins", "update", "lossless-claw"]],
|
||||
["plugins update --all", ["plugins", "update", "--all"]],
|
||||
|
||||
@@ -46,8 +46,12 @@ export function getSubCliEntries(): ReadonlyArray<SubCliDescriptor> {
|
||||
return getSubCliEntryDescriptors();
|
||||
}
|
||||
|
||||
export async function registerSubCliByName(program: Command, name: string): Promise<boolean> {
|
||||
if (await registerSubCliByNameCore(program, name)) {
|
||||
export async function registerSubCliByName(
|
||||
program: Command,
|
||||
name: string,
|
||||
argv: string[] = process.argv,
|
||||
): Promise<boolean> {
|
||||
if (await registerSubCliByNameCore(program, name, argv)) {
|
||||
return true;
|
||||
}
|
||||
return registerCommandGroupByName(program, resolveSubCliCommandGroups(), name);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
shouldUseBrowserHelpFastPath,
|
||||
shouldUseRootHelpFastPath,
|
||||
} from "./run-main-policy.js";
|
||||
import { isGatewayRunFastPathArgv } from "./run-main.js";
|
||||
|
||||
const memoryWikiCommandAliasRegistry: PluginManifestCommandAliasRegistry = {
|
||||
plugins: [
|
||||
@@ -28,6 +29,17 @@ const memoryCoreCommandAliasRegistry: PluginManifestCommandAliasRegistry = {
|
||||
],
|
||||
};
|
||||
|
||||
describe("isGatewayRunFastPathArgv", () => {
|
||||
it("matches only plain gateway foreground starts without root options or help", () => {
|
||||
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway"])).toBe(true);
|
||||
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "--force"])).toBe(true);
|
||||
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "run"])).toBe(true);
|
||||
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "call", "health"])).toBe(false);
|
||||
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "--help"])).toBe(false);
|
||||
expect(isGatewayRunFastPathArgv(["node", "openclaw", "--no-color", "gateway"])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rewriteUpdateFlagArgv", () => {
|
||||
it("leaves argv unchanged when --update is absent", () => {
|
||||
const argv = ["node", "entry.js", "status"];
|
||||
|
||||
@@ -72,6 +72,52 @@ function createGatewayCliMainStartupTrace(argv: string[]) {
|
||||
};
|
||||
}
|
||||
|
||||
export function isGatewayRunFastPathArgv(argv: string[]): boolean {
|
||||
if (argv[2] !== "gateway") {
|
||||
return false;
|
||||
}
|
||||
const invocation = resolveCliArgvInvocation(argv);
|
||||
if (invocation.hasHelpOrVersion || invocation.commandPath[0] !== "gateway") {
|
||||
return false;
|
||||
}
|
||||
return invocation.commandPath.length === 1 || invocation.commandPath[1] === "run";
|
||||
}
|
||||
|
||||
async function tryRunGatewayRunFastPath(
|
||||
argv: string[],
|
||||
startupTrace: ReturnType<typeof createGatewayCliMainStartupTrace>,
|
||||
): Promise<boolean> {
|
||||
if (!isGatewayRunFastPathArgv(argv)) {
|
||||
return false;
|
||||
}
|
||||
const [{ Command }, { addGatewayRunCommand }] = await startupTrace.measure(
|
||||
"gateway-run-imports",
|
||||
() => Promise.all([import("commander"), import("./gateway-cli/run.js")]),
|
||||
);
|
||||
const program = new Command();
|
||||
program.name("openclaw");
|
||||
program.enablePositionalOptions();
|
||||
program.exitOverride((err) => {
|
||||
process.exitCode = typeof err.exitCode === "number" ? err.exitCode : 1;
|
||||
throw err;
|
||||
});
|
||||
const gateway = addGatewayRunCommand(
|
||||
program.command("gateway").description("Run, inspect, and query the WebSocket Gateway"),
|
||||
);
|
||||
addGatewayRunCommand(
|
||||
gateway.command("run").description("Run the WebSocket Gateway (foreground)"),
|
||||
);
|
||||
try {
|
||||
await startupTrace.measure("gateway-run-parse", () => program.parseAsync(argv));
|
||||
} catch (error) {
|
||||
if (!isCommanderParseExit(error)) {
|
||||
throw error;
|
||||
}
|
||||
process.exitCode = error.exitCode;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function closeCliMemoryManagers(): Promise<void> {
|
||||
const { hasMemoryRuntime } = await import("../plugins/memory-state.js");
|
||||
if (!hasMemoryRuntime()) {
|
||||
@@ -255,6 +301,10 @@ export async function runCli(argv: string[] = process.argv) {
|
||||
await startupTrace.measure("proxy-dispatcher", () => ensureCliEnvProxyDispatcher());
|
||||
maybeWarnAboutDebugProxyCoverage();
|
||||
|
||||
if (await tryRunGatewayRunFastPath(normalizedArgv, startupTrace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { tryRouteCli } = await startupTrace.measure("route-import", () => import("./route.js"));
|
||||
if (await startupTrace.measure("route", () => tryRouteCli(normalizedArgv))) {
|
||||
return;
|
||||
@@ -339,7 +389,7 @@ export async function runCli(argv: string[] = process.argv) {
|
||||
await registerCoreCliByName(program, ctx, primary, parseArgv);
|
||||
}
|
||||
const { registerSubCliByName } = await import("./program/register.subclis.js");
|
||||
await registerSubCliByName(program, primary);
|
||||
await registerSubCliByName(program, primary, parseArgv);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user