fix: address review feedback (#5871)

This commit is contained in:
Catalin Lupuleti
2026-02-26 22:21:23 +00:00
committed by Vincent Koc
parent c5e6209310
commit a8f0557c9d
4 changed files with 140 additions and 18 deletions

View File

@@ -8,6 +8,7 @@ import {
getVerboseFlag,
hasHelpOrVersion,
hasFlag,
isRootHelpRequest,
isRootVersionRequest,
shouldMigrateState,
shouldMigrateStateFromPath,
@@ -332,4 +333,55 @@ describe("argv helpers", () => {
])("isRootVersionRequest: $name", ({ argv, expected }) => {
expect(isRootVersionRequest(argv)).toBe(expected);
});
// isRootHelpRequest: guards the entry.ts fast-path help exit.
it.each([
{
name: "--help flag at root",
argv: ["node", "openclaw", "--help"],
expected: true,
},
{
name: "-h flag at root",
argv: ["node", "openclaw", "-h"],
expected: true,
},
{
name: "--help after a root boolean flag",
argv: ["node", "openclaw", "--dev", "--help"],
expected: true,
},
{
name: "--help after a root value flag",
argv: ["node", "openclaw", "--profile", "dev", "--help"],
expected: true,
},
{
name: "--help after -- terminator must NOT trigger (forwarded arg)",
argv: ["node", "openclaw", "nodes", "run", "--", "git", "--help"],
expected: false,
},
{
name: "-h after -- terminator must NOT trigger",
argv: ["node", "openclaw", "--", "-h"],
expected: false,
},
{
name: "--help with subcommand must NOT trigger (subcommand-scoped)",
argv: ["node", "openclaw", "status", "--help"],
expected: false,
},
{
name: "-h with subcommand must NOT trigger",
argv: ["node", "openclaw", "models", "-h"],
expected: false,
},
{
name: "normal command without help flag",
argv: ["node", "openclaw", "status"],
expected: false,
},
])("isRootHelpRequest: $name", ({ argv, expected }) => {
expect(isRootHelpRequest(argv)).toBe(expected);
});
});

View File

@@ -7,6 +7,13 @@ const ROOT_BOOLEAN_FLAGS = new Set(["--dev", "--no-color"]);
const ROOT_VALUE_FLAGS = new Set(["--profile", "--log-level"]);
const FLAG_TERMINATOR = "--";
/**
* Returns true if argv contains any help or version flag.
* NOTE: uses `argv.some()` without `--` terminator awareness, so forwarded args
* like `nodes run -- git --help` will produce a false positive. Use
* `isRootHelpRequest`/`isRootVersionRequest` for fast-path guards where
* correctness around forwarded args matters.
*/
export function hasHelpOrVersion(argv: string[]): boolean {
return (
argv.some((arg) => HELP_FLAGS.has(arg) || VERSION_FLAGS.has(arg)) || hasRootVersionAlias(argv)
@@ -24,6 +31,48 @@ export function isRootVersionRequest(argv: string[]): boolean {
return hasFlag(argv, "--version") || hasFlag(argv, "-V") || hasRootVersionAlias(argv);
}
/**
* Returns true only when the process is a root-level help request:
* - `-h` or `--help` appears before any `--` terminator, and
* - no subcommand (non-flag token) precedes the help flag.
* `openclaw status --help` returns false (subcommand-scoped); `openclaw --help` returns true.
* Used by the entry.ts fast path to display help without loading route.ts or run-main.js.
*/
export function isRootHelpRequest(argv: string[]): boolean {
const args = argv.slice(2);
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (!arg) {
continue;
}
if (arg === FLAG_TERMINATOR) {
break;
}
if (arg === "-h" || arg === "--help") {
return true;
}
if (ROOT_BOOLEAN_FLAGS.has(arg)) {
continue;
}
if (arg.startsWith("--profile=")) {
continue;
}
if (ROOT_VALUE_FLAGS.has(arg)) {
const next = args[i + 1];
if (isValueToken(next)) {
i += 1;
}
continue;
}
if (arg.startsWith("-")) {
continue;
}
// Non-flag token is a subcommand — this is a subcommand-scoped help request.
return false;
}
return false;
}
function isValueToken(arg: string | undefined): boolean {
if (!arg) {
return false;

View File

@@ -23,14 +23,16 @@ export function resolveCliChannelOptions(): string[] {
const catalog = listChannelPluginCatalogEntries().map((entry) => entry.id);
const base = dedupe([...CHAT_CHANNEL_ORDER, ...catalog]);
if (isTruthyEnvValue(process.env.OPENCLAW_EAGER_CHANNEL_OPTIONS)) {
// CHANGED SEMANTIC: ensurePluginRegistryLoaded() is intentionally NOT called
// here to avoid pulling in plugins/loader.ts → jiti at startup (slow on
// low-powered devices). As a result, OPENCLAW_EAGER_CHANNEL_OPTIONS is
// effectively a no-op for its original purpose of force-loading all plugins
// into the option list before Commander parses args. Plugin IDs are only
// included here if the registry was already populated by some other means
// (e.g. the preaction hook for a real command). If this env var behaviour is
// needed, restore the ensurePluginRegistryLoaded() call explicitly.
// Emit a deprecation warning: ensurePluginRegistryLoaded() was removed to avoid
// pulling plugins/loader.ts → jiti at startup (slow on low-powered devices).
// OPENCLAW_EAGER_CHANNEL_OPTIONS no longer force-loads plugin channels; plugin IDs
// are only included if the registry was already populated by another code path.
process.emitWarning(
"OPENCLAW_EAGER_CHANNEL_OPTIONS no longer force-loads plugin channels at startup. " +
"Plugin IDs are only included if the registry was pre-loaded by another means. " +
"Remove this env var to silence this warning.",
{ code: "OPENCLAW_EAGER_CHANNEL_OPTIONS_DEPRECATED" },
);
const pluginIds = listChannelPlugins().map((plugin) => plugin.id);
return dedupe([...base, ...pluginIds]);
}

View File

@@ -2,7 +2,7 @@
import { spawn } from "node:child_process";
import process from "node:process";
import { fileURLToPath } from "node:url";
import { isRootVersionRequest } from "./cli/argv.js";
import { isRootHelpRequest, isRootVersionRequest } from "./cli/argv.js";
import { applyCliProfileEnv, parseCliProfileArgs } from "./cli/profile.js";
import { shouldSkipRespawnForArgv } from "./cli/respawn-policy.js";
import { normalizeWindowsArgv } from "./cli/windows-argv.js";
@@ -143,14 +143,33 @@ if (
process.exit(0);
}
import("./cli/run-main.js")
.then(({ runCli }) => runCli(process.argv))
.catch((error) => {
console.error(
"[openclaw] Failed to start CLI:",
error instanceof Error ? (error.stack ?? error.message) : error,
);
process.exitCode = 1;
});
// Fast path: display root help without loading run-main.js or route.ts.
// isRootHelpRequest stops at -- and requires no subcommand before -h/--help,
// so `openclaw status --help` still falls through to the full CLI path.
// Importing program.js directly avoids the route.ts static import in run-main.ts.
if (isRootHelpRequest(argv)) {
import("./cli/program.js")
.then(({ buildProgram }) => {
buildProgram().outputHelp();
process.exit(0);
})
.catch((error) => {
console.error(
"[openclaw] Failed to display help:",
error instanceof Error ? (error.stack ?? error.message) : error,
);
process.exit(1);
});
} else {
import("./cli/run-main.js")
.then(({ runCli }) => runCli(process.argv))
.catch((error) => {
console.error(
"[openclaw] Failed to start CLI:",
error instanceof Error ? (error.stack ?? error.message) : error,
);
process.exitCode = 1;
});
}
}
}