fix(cli): guarded documented marketplace verbs from launch leak
`omp marketplace add xyz` and the other documented `omp plugin <action>` verbs (marketplace, discover, upgrade, uninstall, enable, disable) were never registered top-level commands, so `resolveCliArgv` forwarded the whole argv to `launch` as an LLM prompt. The #2935 guard only caught bare single-word verbs, missing the multi-word documented grammar. Extended `reservedTopLevelWordMessage` to hint at the real `omp plugin <action>` command for these verbs when used bare, with a marketplace sub-action, or with a `name@marketplace` plugin id, while genuine prose prompts beginning with the same words still route to `launch`. Fixes #4845
This commit is contained in:
@@ -46,31 +46,62 @@ export const commands: CommandEntry[] = [
|
||||
{ name: "search", load: () => import("./commands/web-search").then(m => m.default), aliases: ["q"] },
|
||||
];
|
||||
|
||||
// Documented-looking plugin-management verbs that are NOT registered top-level
|
||||
// commands. Without a guard `resolveCliArgv` rewrites e.g. `omp list` to
|
||||
// `omp launch list`, silently forwarding the bare verb to the model as a prompt
|
||||
// instead of managing plugins (#2935; same class as the `install` leak fixed in
|
||||
// #1496/#1498). A bare (single-arg) use gets a hint pointing at the real
|
||||
// `omp plugin <action>` command; multi-word invocations still fall through to
|
||||
// `launch`, so genuine prompts that merely begin with one of these words work.
|
||||
const RESERVED_TOP_LEVEL_WORDS = new Map<string, string>([
|
||||
[
|
||||
"extensions",
|
||||
// Documented-looking plugin/marketplace verbs that are NOT registered top-level
|
||||
// commands. Without a guard `resolveCliArgv` rewrites e.g. `omp marketplace add
|
||||
// xyz` to `omp launch marketplace add xyz`, silently forwarding the argv to the
|
||||
// model as a prompt instead of managing plugins (#4845; same class as the
|
||||
// `list`/`remove` leak fixed in #2935 and the `install` leak in #1496/#1498).
|
||||
// The real commands live under `omp plugin <action>`; each entry maps a verb to
|
||||
// a hint pointing there. See {@link reservedTopLevelWordMessage} for when a hint
|
||||
// fires vs. when the argv still falls through to `launch`.
|
||||
const RESERVED_TOP_LEVEL_WORDS: Record<string, string> = {
|
||||
extensions:
|
||||
'`omp extensions` is not a management command. Use `omp plugin list` / `omp plugin install`, or run `omp launch extensions` if you meant to send "extensions" as a prompt.',
|
||||
],
|
||||
[
|
||||
"list",
|
||||
'`omp list` is not a top-level command. Use `omp plugin list` to list installed plugins, or run `omp launch list` if you meant to send "list" as a prompt.',
|
||||
],
|
||||
[
|
||||
"remove",
|
||||
list: '`omp list` is not a top-level command. Use `omp plugin list` to list installed plugins, or run `omp launch list` if you meant to send "list" as a prompt.',
|
||||
remove:
|
||||
'`omp remove` is not a top-level command. Use `omp plugin uninstall <name>` to remove a plugin, or run `omp launch remove` if you meant to send "remove" as a prompt.',
|
||||
],
|
||||
]);
|
||||
uninstall:
|
||||
'`omp uninstall` is not a top-level command. Use `omp plugin uninstall <name@marketplace>` to remove a plugin, or run `omp launch uninstall` if you meant to send "uninstall" as a prompt.',
|
||||
marketplace:
|
||||
'`omp marketplace` is not a top-level command. Use `omp plugin marketplace <add|remove|update|list>` to manage marketplaces, or run `omp launch marketplace` if you meant to send "marketplace" as a prompt.',
|
||||
discover:
|
||||
'`omp discover` is not a top-level command. Use `omp plugin discover [marketplace]` to browse available plugins, or run `omp launch discover` if you meant to send "discover" as a prompt.',
|
||||
upgrade:
|
||||
'`omp upgrade` is not a top-level command. Use `omp plugin upgrade [name@marketplace]` to upgrade plugins, or run `omp launch upgrade` if you meant to send "upgrade" as a prompt.',
|
||||
enable:
|
||||
'`omp enable` is not a top-level command. Use `omp plugin enable <name@marketplace>` to enable a plugin, or run `omp launch enable` if you meant to send "enable" as a prompt.',
|
||||
disable:
|
||||
'`omp disable` is not a top-level command. Use `omp plugin disable <name@marketplace>` to disable a plugin, or run `omp launch disable` if you meant to send "disable" as a prompt.',
|
||||
};
|
||||
|
||||
export function reservedTopLevelWordMessage(first: string | undefined, argc = 1): string | undefined {
|
||||
if (argc !== 1 || !first || first.startsWith("-") || first.startsWith("@")) return undefined;
|
||||
return RESERVED_TOP_LEVEL_WORDS.get(first);
|
||||
// Sub-actions that make `omp marketplace <sub>` unambiguously a management
|
||||
// command even when multi-word (the reporter's `omp marketplace add xyz`,
|
||||
// #4845). Mirrors the switch in `handleMarketplace` (cli/plugin-cli.ts).
|
||||
const MARKETPLACE_SUBCOMMANDS: Record<string, true> = { add: true, remove: true, rm: true, update: true, list: true };
|
||||
|
||||
/**
|
||||
* Hint for a reserved plugin/marketplace verb used as a top-level command, or
|
||||
* `undefined` when the argv should fall through to `launch`.
|
||||
*
|
||||
* A bare verb (`omp marketplace`) always hints. A multi-word invocation only
|
||||
* hints when the arguments follow the documented plugin grammar — a marketplace
|
||||
* sub-action (`omp marketplace add …`) or a `name@marketplace` plugin id
|
||||
* (`omp uninstall foo@bar`) — so genuine prompts that merely begin with one of
|
||||
* these words (`omp list all my files`, `omp upgrade the deps`) still launch.
|
||||
*
|
||||
* Flags (`-…`) and `@file` arguments in the verb slot are never management
|
||||
* commands; those fall through to the default `launch` command.
|
||||
*/
|
||||
export function reservedTopLevelWordMessage(argv: readonly string[]): string | undefined {
|
||||
const first = argv[0];
|
||||
if (!first || first.startsWith("-") || first.startsWith("@")) return undefined;
|
||||
const hint = RESERVED_TOP_LEVEL_WORDS[first];
|
||||
if (!hint) return undefined;
|
||||
const second = argv[1];
|
||||
if (second === undefined) return hint;
|
||||
if (first === "marketplace" && MARKETPLACE_SUBCOMMANDS[second]) return hint;
|
||||
if (second.includes("@")) return hint;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +143,7 @@ function leadingSubcommandIndex(argv: string[]): number {
|
||||
*/
|
||||
export function resolveCliArgv(argv: string[]): ResolvedCliArgv {
|
||||
const first = argv[0];
|
||||
const reservedMessage = reservedTopLevelWordMessage(first, argv.length);
|
||||
const reservedMessage = reservedTopLevelWordMessage(argv);
|
||||
if (reservedMessage) return { error: reservedMessage };
|
||||
if (first === "--help" || first === "-h" || first === "--version" || first === "-v" || first === "help") {
|
||||
return { argv };
|
||||
|
||||
Reference in New Issue
Block a user