mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
fix(cli): skip startup work for positional help
This commit is contained in:
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- CLI/startup: read generated startup metadata from the bundled `dist` layout before falling back to live help rendering, so root/browser help and channel-option bootstrap stay on the fast path. Thanks @vincentkoc.
|
||||
- CLI/help: treat positional `help` invocations like `openclaw channels help` as help paths for startup gating, avoiding model/auth warmup while preserving positional arguments such as `openclaw docs help`. Thanks @gumadeiras.
|
||||
- Matrix/E2EE: stabilize recovery and broken-device QA flows while avoiding Matrix device-cleanup sync races that could leave shutdown-time crypto work running. Thanks @gumadeiras.
|
||||
- Cron: classify isolated runs as errors from structured embedded-run execution-denial metadata, with final-output marker fallback for `SYSTEM_RUN_DENIED`, `INVALID_REQUEST`, and approval-binding refusals, so blocked commands no longer appear green in cron history. Fixes #67172; carries forward #67186. Thanks @oc-gh-dr, @hclsys, and @1yihui.
|
||||
- Onboarding/GitHub Copilot: add manifest-owned `--github-copilot-token` support for non-interactive setup, including env fallback, tokenRef storage in ref mode, saved-profile reuse, and current Copilot default-model wiring. Refs #50002 and supersedes #50003. Thanks @scottgl9.
|
||||
|
||||
@@ -202,6 +202,10 @@ describe("lookupContextTokens", () => {
|
||||
|
||||
expect(shouldEagerWarmContextWindowCache(["node", "openclaw", "chat"])).toBe(true);
|
||||
expect(shouldEagerWarmContextWindowCache(["node", "openclaw", "chat", "--help"])).toBe(false);
|
||||
expect(
|
||||
shouldEagerWarmContextWindowCache(["node", "openclaw", "matrix", "encryption", "help"]),
|
||||
).toBe(false);
|
||||
expect(shouldEagerWarmContextWindowCache(["node", "openclaw", "help", "matrix"])).toBe(false);
|
||||
expect(
|
||||
shouldEagerWarmContextWindowCache(["node", "openclaw", "browser", "status", "--help"]),
|
||||
).toBe(false);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// the agent reports a model id. This includes custom models.json entries.
|
||||
|
||||
import path from "node:path";
|
||||
import { isHelpOrVersionInvocation } from "../cli/argv.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { computeBackoff, type BackoffPolicy } from "../infra/backoff.js";
|
||||
@@ -130,18 +131,6 @@ function getCommandPathFromArgv(argv: string[]): string[] {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function hasHelpOrVersionFlag(argv: string[]): boolean {
|
||||
for (const arg of argv.slice(2)) {
|
||||
if (arg === FLAG_TERMINATOR) {
|
||||
return false;
|
||||
}
|
||||
if (arg === "-h" || arg === "--help" || arg === "-V" || arg === "--version") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const SKIP_EAGER_WARMUP_PRIMARY_COMMANDS = new Set([
|
||||
"agent",
|
||||
"backup",
|
||||
@@ -175,7 +164,7 @@ export function shouldEagerWarmContextWindowCache(argv: string[] = process.argv)
|
||||
if (!isLikelyOpenClawCliProcess(argv)) {
|
||||
return false;
|
||||
}
|
||||
if (hasHelpOrVersionFlag(argv)) {
|
||||
if (isHelpOrVersionInvocation(argv)) {
|
||||
return false;
|
||||
}
|
||||
const [primary] = getCommandPathFromArgv(argv);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
getCommandPathWithRootOptions,
|
||||
getPrimaryCommand,
|
||||
hasHelpOrVersion,
|
||||
isHelpOrVersionInvocation,
|
||||
isRootHelpInvocation,
|
||||
} from "./argv.js";
|
||||
|
||||
@@ -18,7 +18,7 @@ export function resolveCliArgvInvocation(argv: string[]): CliArgvInvocation {
|
||||
argv,
|
||||
commandPath: getCommandPathWithRootOptions(argv, 2),
|
||||
primary: getPrimaryCommand(argv),
|
||||
hasHelpOrVersion: hasHelpOrVersion(argv),
|
||||
hasHelpOrVersion: isHelpOrVersionInvocation(argv),
|
||||
isRootHelpInvocation: isRootHelpInvocation(argv),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
getVerboseFlag,
|
||||
hasHelpOrVersion,
|
||||
hasFlag,
|
||||
isHelpOrVersionInvocation,
|
||||
isRootHelpInvocation,
|
||||
isRootVersionInvocation,
|
||||
shouldMigrateState,
|
||||
@@ -67,6 +68,71 @@ describe("argv helpers", () => {
|
||||
expect(hasHelpOrVersion(argv)).toBe(expected);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "root help command",
|
||||
argv: ["node", "openclaw", "help"],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "root help command with target",
|
||||
argv: ["node", "openclaw", "help", "matrix"],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "nested help command",
|
||||
argv: ["node", "openclaw", "matrix", "encryption", "help"],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "known subcommand root help command",
|
||||
argv: ["node", "openclaw", "config", "help"],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "known leaf command positional help",
|
||||
argv: ["node", "openclaw", "docs", "help"],
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "known subcommand leaf positional help",
|
||||
argv: ["node", "openclaw", "config", "set", "some.path", "help"],
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "unknown plugin command help",
|
||||
argv: ["node", "openclaw", "external-plugin", "tools", "help"],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "help flag",
|
||||
argv: ["node", "openclaw", "matrix", "encryption", "--help"],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "help as option value",
|
||||
argv: ["node", "openclaw", "agent", "--message", "help"],
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "help after terminator",
|
||||
argv: ["node", "openclaw", "nodes", "invoke", "--", "help"],
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "help flag after terminator",
|
||||
argv: ["node", "openclaw", "nodes", "invoke", "--", "--help"],
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "version flag after terminator",
|
||||
argv: ["node", "openclaw", "nodes", "invoke", "--", "--version"],
|
||||
expected: false,
|
||||
},
|
||||
])("detects help/version invocations: $name", ({ argv, expected }) => {
|
||||
expect(isHelpOrVersionInvocation(argv)).toBe(expected);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "root --version",
|
||||
|
||||
@@ -4,10 +4,21 @@ import {
|
||||
FLAG_TERMINATOR,
|
||||
isValueToken,
|
||||
} from "../infra/cli-root-options.js";
|
||||
import { CORE_CLI_COMMAND_DESCRIPTORS } from "./program/core-command-descriptors.js";
|
||||
import { SUB_CLI_DESCRIPTORS } from "./program/subcli-descriptors.js";
|
||||
|
||||
const HELP_FLAGS = new Set(["-h", "--help"]);
|
||||
const VERSION_FLAGS = new Set(["-V", "--version"]);
|
||||
const ROOT_VERSION_ALIAS_FLAG = "-v";
|
||||
const ROOT_COMMAND_DESCRIPTORS = [...CORE_CLI_COMMAND_DESCRIPTORS, ...SUB_CLI_DESCRIPTORS];
|
||||
const KNOWN_ROOT_COMMANDS: ReadonlySet<string> = new Set(
|
||||
ROOT_COMMAND_DESCRIPTORS.map((descriptor) => descriptor.name),
|
||||
);
|
||||
const ROOT_COMMANDS_WITH_SUBCOMMANDS: ReadonlySet<string> = new Set(
|
||||
ROOT_COMMAND_DESCRIPTORS.filter((descriptor) => descriptor.hasSubcommands).map(
|
||||
(descriptor) => descriptor.name,
|
||||
),
|
||||
);
|
||||
|
||||
export function hasHelpOrVersion(argv: string[]): boolean {
|
||||
return (
|
||||
@@ -15,6 +26,55 @@ export function hasHelpOrVersion(argv: string[]): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function isHelpOrVersionInvocation(argv: string[]): boolean {
|
||||
if (hasRootVersionAlias(argv)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const args = argv.slice(2);
|
||||
let sawCommandOption = false;
|
||||
const positionals: string[] = [];
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
const arg = args[i];
|
||||
if (!arg || arg === FLAG_TERMINATOR) {
|
||||
break;
|
||||
}
|
||||
const rootConsumed = consumeRootOptionToken(args, i);
|
||||
if (rootConsumed > 0) {
|
||||
i += rootConsumed - 1;
|
||||
continue;
|
||||
}
|
||||
if (HELP_FLAGS.has(arg) || VERSION_FLAGS.has(arg)) {
|
||||
return true;
|
||||
}
|
||||
if (arg.startsWith("-")) {
|
||||
sawCommandOption = true;
|
||||
continue;
|
||||
}
|
||||
positionals.push(arg);
|
||||
if (arg !== "help") {
|
||||
continue;
|
||||
}
|
||||
if (sawCommandOption) {
|
||||
return false;
|
||||
}
|
||||
if (positionals.length === 1) {
|
||||
return true;
|
||||
}
|
||||
const [primary] = positionals;
|
||||
// Positional `help` may be a command argument for known leaf commands.
|
||||
// Unknown roots are treated as plugin command namespaces.
|
||||
if (!primary || !KNOWN_ROOT_COMMANDS.has(primary)) {
|
||||
return true;
|
||||
}
|
||||
if (positionals.length === 2 && ROOT_COMMANDS_WITH_SUBCOMMANDS.has(primary)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function parsePositiveInt(value: string): number | undefined {
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
if (Number.isNaN(parsed) || parsed <= 0) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Command } from "commander";
|
||||
import { setVerbose } from "../../globals.js";
|
||||
import type { LogLevel } from "../../logging/levels.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { getVerboseFlag, hasHelpOrVersion } from "../argv.js";
|
||||
import { getVerboseFlag, isHelpOrVersionInvocation } from "../argv.js";
|
||||
import { resolveCliName } from "../cli-name.js";
|
||||
import {
|
||||
applyCliExecutionStartupPresentation,
|
||||
@@ -65,7 +65,7 @@ export function registerPreActionHooks(program: Command, programVersion: string)
|
||||
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
||||
setProcessTitleForCommand(actionCommand);
|
||||
const argv = process.argv;
|
||||
if (hasHelpOrVersion(argv)) {
|
||||
if (isHelpOrVersionInvocation(argv)) {
|
||||
return;
|
||||
}
|
||||
const jsonOutputMode = isCommandJsonOutputMode(actionCommand, argv);
|
||||
|
||||
Reference in New Issue
Block a user