perf: fast-path gateway foreground startup

This commit is contained in:
Peter Steinberger
2026-04-28 02:04:57 +01:00
parent 6b7886e024
commit 955f0a692a
5 changed files with 145 additions and 4 deletions

View File

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

View File

@@ -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"]],

View File

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