diff --git a/src/agents/tools/sessions-search-tool.test.ts b/src/agents/tools/sessions-search-tool.test.ts index ec3c0512fa49..917ecc208c19 100644 --- a/src/agents/tools/sessions-search-tool.test.ts +++ b/src/agents/tools/sessions-search-tool.test.ts @@ -7,6 +7,7 @@ import { replaceSessionEntrySync } from "../../config/sessions/session-accessor. import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { callGateway as gatewayCall } from "../../gateway/call.js"; import { createSessionVisibilityChecker } from "../../plugin-sdk/session-visibility.js"; +import { normalizeToolParameters } from "../agent-tools.schema.js"; import { describeSessionLinkRule } from "../tool-description-presets.js"; import { compactToolOutputHint } from "../tool-schema-hints.js"; import { createSessionsSearchTool } from "./sessions-search-tool.js"; @@ -202,6 +203,25 @@ describe("sessions_search tool", () => { ); }); + it("rejects an empty query in the schema while accepting keywords", () => { + const tool = createTool({}); + expect(Value.Check(tool.parameters, { query: "" })).toBe(false); + expect(Value.Check(tool.parameters, { query: "loan" })).toBe(true); + expect(Value.Check(tool.parameters, { query: " loan " })).toBe(true); + }); + + it.each(["openai", "google"])( + "rejects blank execution with retry guidance after %s schema normalization", + async (modelProvider) => { + const tool = normalizeToolParameters(createTool({}), { modelProvider }); + for (const query of ["", " "]) { + await expect(tool.execute!("blank-query", { query })).rejects.toThrow( + /query must not be empty; retry with non-empty keywords/, + ); + } + }, + ); + it("filters invisible hits before applying the limit", async () => { const requests: CallGatewayRequest[] = []; const tool = createTool({ diff --git a/src/agents/tools/sessions-search-tool.ts b/src/agents/tools/sessions-search-tool.ts index c015697a559a..ffa65afc1086 100644 --- a/src/agents/tools/sessions-search-tool.ts +++ b/src/agents/tools/sessions-search-tool.ts @@ -50,7 +50,11 @@ const SESSIONS_SEARCH_INDEXING_WARNING = const SessionsSearchToolSchema = Type.Object({ user: requesterProfileSchema(), - query: Type.String({ maxLength: SESSIONS_SEARCH_MAX_QUERY_CHARS }), + query: Type.String({ + minLength: 1, + maxLength: SESSIONS_SEARCH_MAX_QUERY_CHARS, + description: "Required non-empty keywords to match in past user and assistant text.", + }), sessionKey: Type.Optional(Type.String()), limit: optionalPositiveIntegerSchema({ maximum: SESSIONS_SEARCH_MAX_LIMIT, @@ -310,7 +314,9 @@ export function createSessionsSearchTool(opts?: { const params = args as Record; const query = readToolStringParam(params, "query") ?? ""; if (!query) { - throw new ToolInputError("query must not be empty"); + throw new ToolInputError( + "query must not be empty; retry with non-empty keywords to match in past session text", + ); } if (query.length > SESSIONS_SEARCH_MAX_QUERY_CHARS) { throw new ToolInputError( diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json index 6f8c9edce197..cf693c40dd47 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json @@ -1332,7 +1332,9 @@ "type": "integer" }, "query": { + "description": "Required non-empty keywords to match in past user and assistant text.", "maxLength": 4096, + "minLength": 1, "type": "string" }, "sessionKey": { diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md.diff b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md.diff index b927e3a0bd64..57f083c8aa24 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md.diff +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md.diff @@ -1,5 +1,5 @@ ---- telegram-direct-codex-message-tool.md sha256=d7dfb9a21011bee9b1cd8b769cae0d29d9ebbbf1e8d96a3a84873bfa8711242e -+++ discord-group-codex-message-tool.md sha256=71b5d8e9471f38e06145340b0c186246822fad3484100924cd3e607a0d7e14bc +--- telegram-direct-codex-message-tool.md sha256=5c0befbd287efb1bba2f0a88b440cdfadaf5da175afb9b219dff96928564e79e ++++ discord-group-codex-message-tool.md sha256=b5294f195059c7243fab65032ce6fb353189d5a0ea32bb08bdb30f6f2a9a96b1 @@ -1,1 +1,1 @@ -# Telegram Direct Codex Message Tool Turn +# Discord Group Codex Message Tool Turn @@ -32,10 +32,10 @@ - "chars": 1348, + "chars": 1347, @@ -261,2 +261,2 @@ -- "chars": 72263, -- "roughTokens": 18066 -+ "chars": 72861, -+ "roughTokens": 18216 +- "chars": 72395, +- "roughTokens": 18099 ++ "chars": 72993, ++ "roughTokens": 18249 @@ -265,2 +265,2 @@ - "chars": 2992, - "roughTokens": 748 @@ -47,10 +47,10 @@ + "chars": 28695, + "roughTokens": 7174 @@ -277,2 +277,2 @@ -- "chars": 99643, -- "roughTokens": 24911 -+ "chars": 101558, -+ "roughTokens": 25390 +- "chars": 99775, +- "roughTokens": 24944 ++ "chars": 101690, ++ "roughTokens": 25423 @@ -281,2 +281,2 @@ - "chars": 879, - "roughTokens": 220 diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md index ffa73b7d6c9b..a443854e82d2 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md @@ -258,8 +258,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 0 }, "dynamicToolsJson": { - "chars": 72263, - "roughTokens": 18066 + "chars": 72395, + "roughTokens": 18099 }, "openClawDeveloperInstructions": { "chars": 2992, @@ -274,8 +274,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 6845 }, "totalWithDynamicToolsJson": { - "chars": 99643, - "roughTokens": 24911 + "chars": 99775, + "roughTokens": 24944 }, "userInputText": { "chars": 879, diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md.diff b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md.diff index eaad0e32d804..1d615c2d2c21 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md.diff +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md.diff @@ -1,5 +1,5 @@ ---- telegram-direct-codex-message-tool.md sha256=d7dfb9a21011bee9b1cd8b769cae0d29d9ebbbf1e8d96a3a84873bfa8711242e -+++ telegram-heartbeat-codex-tool.md sha256=046f19e66869e2dfb9bacc08a63a493637cc2993cda89fda643e4edd8854269d +--- telegram-direct-codex-message-tool.md sha256=5c0befbd287efb1bba2f0a88b440cdfadaf5da175afb9b219dff96928564e79e ++++ telegram-heartbeat-codex-tool.md sha256=4483ad37d42f6fb1a62d148f1ccc7528c79b045fe34379fa6cf83954f151e2ef @@ -1,1 +1,1 @@ -# Telegram Direct Codex Message Tool Turn +# Telegram Direct Codex Heartbeat Tool Turn @@ -37,20 +37,20 @@ + "chars": 1218, + "roughTokens": 305 @@ -261,2 +258,2 @@ -- "chars": 72263, -- "roughTokens": 18066 -+ "chars": 73756, -+ "roughTokens": 18439 +- "chars": 72395, +- "roughTokens": 18099 ++ "chars": 73888, ++ "roughTokens": 18472 @@ -273,2 +270,2 @@ - "chars": 27378, - "roughTokens": 6845 + "chars": 27715, + "roughTokens": 6929 @@ -277,2 +274,2 @@ -- "chars": 99643, -- "roughTokens": 24911 -+ "chars": 101473, -+ "roughTokens": 25369 +- "chars": 99775, +- "roughTokens": 24944 ++ "chars": 101605, ++ "roughTokens": 25402 @@ -281,2 +278,2 @@ - "chars": 879, - "roughTokens": 220