fix(cli): support --query in memory search command (#25904)

This commit is contained in:
Peter Steinberger
2026-02-25 01:41:49 +00:00
parent 389ccda0f6
commit 559b5eab71
3 changed files with 56 additions and 2 deletions

View File

@@ -78,6 +78,7 @@ Docs: https://docs.openclaw.ai
- macOS/Menu bar: stop reusing the injector delegate for the "Usage cost (30 days)" submenu to prevent recursive submenu injection loops when opening cost history. (#25341) Thanks @yingchunbai.
- Control UI/Chat images: route image-click opens through a shared safe-open helper (allowing only safe URL schemes) and open new tabs with opener isolation to block tabnabbing. (#18685, #25444, #25847) Thanks @Mariana-Codebase and @shakkernerd.
- CLI/Doctor: correct stale recovery hints to use valid commands (`openclaw gateway status --deep` and `openclaw configure --section model`). (#24485) Thanks @chilu18.
- CLI/Memory search: accept `--query <text>` for `openclaw memory search` (while keeping positional query support), and emit a clear error when neither form is provided. (#25904, #25857) Thanks @niceysam and @stakeswky.
- Security/Sandbox: canonicalize bind-mount source paths via existing-ancestor realpath so symlink-parent + non-existent-leaf paths cannot bypass allowed-source-roots or blocked-path checks. Thanks @tdjackey.
- Doctor/Plugins: auto-enable now resolves third-party channel plugins by manifest plugin id (not channel id), preventing invalid `plugins.entries.<channelId>` writes when ids differ. (#25275) Thanks @zerone0x.

View File

@@ -382,6 +382,49 @@ describe("memory cli", () => {
expect(close).toHaveBeenCalled();
});
it("accepts --query for memory search", async () => {
const close = vi.fn(async () => {});
const search = vi.fn(async () => []);
mockManager({ search, close });
const log = spyRuntimeLogs();
await runMemoryCli(["search", "--query", "deployment notes"]);
expect(search).toHaveBeenCalledWith("deployment notes", {
maxResults: undefined,
minScore: undefined,
});
expect(log).toHaveBeenCalledWith("No matches.");
expect(close).toHaveBeenCalled();
expect(process.exitCode).toBeUndefined();
});
it("prefers --query when positional and flag are both provided", async () => {
const close = vi.fn(async () => {});
const search = vi.fn(async () => []);
mockManager({ search, close });
spyRuntimeLogs();
await runMemoryCli(["search", "positional", "--query", "flagged"]);
expect(search).toHaveBeenCalledWith("flagged", {
maxResults: undefined,
minScore: undefined,
});
expect(close).toHaveBeenCalled();
});
it("fails when neither positional query nor --query is provided", async () => {
const error = spyRuntimeErrors();
await runMemoryCli(["search"]);
expect(error).toHaveBeenCalledWith(
"Missing search query. Provide a positional query or use --query <text>.",
);
expect(getMemorySearchManager).not.toHaveBeenCalled();
expect(process.exitCode).toBe(1);
});
it("prints search results as json when requested", async () => {
const close = vi.fn(async () => {});
const search = vi.fn(async () => [

View File

@@ -702,19 +702,29 @@ export function registerMemoryCli(program: Command) {
memory
.command("search")
.description("Search memory files")
.argument("<query>", "Search query")
.argument("[query]", "Search query")
.option("--query <text>", "Search query (alternative to positional argument)")
.option("--agent <id>", "Agent id (default: default agent)")
.option("--max-results <n>", "Max results", (value: string) => Number(value))
.option("--min-score <n>", "Minimum score", (value: string) => Number(value))
.option("--json", "Print JSON")
.action(
async (
query: string,
queryArg: string | undefined,
opts: MemoryCommandOptions & {
query?: string;
maxResults?: number;
minScore?: number;
},
) => {
const query = opts.query ?? queryArg;
if (!query) {
defaultRuntime.error(
"Missing search query. Provide a positional query or use --query <text>.",
);
process.exitCode = 1;
return;
}
const cfg = loadConfig();
const agentId = resolveAgent(cfg, opts.agent);
await withMemoryManagerForAgent({