diff --git a/extensions/memory-wiki/src/gateway.test.ts b/extensions/memory-wiki/src/gateway.test.ts index b3eb61e7bb9..332d8211fd3 100644 --- a/extensions/memory-wiki/src/gateway.test.ts +++ b/extensions/memory-wiki/src/gateway.test.ts @@ -468,6 +468,37 @@ describe("memory-wiki gateway methods", () => { }); }); + it.each([ + ["wiki.importRuns", { limit: 0 }, "limit must be a positive integer"], + [ + "wiki.search", + { query: "Teams Azure", maxResults: 1.5 }, + "maxResults must be a positive integer", + ], + ["wiki.get", { lookup: "Teams Azure", fromLine: 1.5 }, "fromLine must be a positive integer"], + ["wiki.get", { lookup: "Teams Azure", lineCount: 0 }, "lineCount must be a positive integer"], + ])("rejects invalid positive integer gateway param for %s", async (method, params, message) => { + const { config } = await createVault({ prefix: "memory-wiki-gateway-" }); + const { api, registerGatewayMethod } = createPluginApi(); + + registerMemoryWikiGatewayMethods({ api, config }); + const handler = findGatewayHandler(registerGatewayMethod, method); + if (!handler) { + throw new Error(`${method} handler missing`); + } + const respond = vi.fn(); + + await handler({ + params, + respond, + }); + + expect(readRespondError(respond)).toEqual({ + code: "internal_error", + message, + }); + }); + it("forwards wiki.search mode and corpus options over the gateway", async () => { const { config } = await createVault({ prefix: "memory-wiki-gateway-" }); const { api, registerGatewayMethod } = createPluginApi(); diff --git a/extensions/memory-wiki/src/gateway.ts b/extensions/memory-wiki/src/gateway.ts index 6f7a026fb95..1f28877c873 100644 --- a/extensions/memory-wiki/src/gateway.ts +++ b/extensions/memory-wiki/src/gateway.ts @@ -1,5 +1,6 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import { resolveDefaultAgentId } from "openclaw/plugin-sdk/memory-host-core"; +import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers"; import type { OpenClawConfig, OpenClawPluginApi } from "../api.js"; import { applyMemoryWikiMutation, normalizeMemoryWikiMutationInput } from "./apply.js"; import { compileMemoryWikiVault } from "./compile.js"; @@ -55,20 +56,6 @@ function readStringParam( return undefined; } -function readNumberParam(params: Record, key: string): number | undefined { - const value = params[key]; - if (typeof value === "number" && Number.isFinite(value)) { - return value; - } - if (typeof value === "string" && value.trim()) { - const parsed = Number(value); - if (Number.isFinite(parsed)) { - return parsed; - } - } - return undefined; -} - function readEnumParam( params: Record, key: string, @@ -135,7 +122,7 @@ export function registerMemoryWikiGatewayMethods(params: { "wiki.importRuns", async ({ params: requestParams, respond }) => { try { - const limit = readNumberParam(requestParams, "limit"); + const limit = readPositiveIntegerParam(requestParams, "limit"); respond(true, await listMemoryWikiImportRuns(config, limit !== undefined ? { limit } : {})); } catch (error) { respondError(respond, error); @@ -287,7 +274,7 @@ export function registerMemoryWikiGatewayMethods(params: { try { await syncImportedSourcesIfNeeded(config, appConfig); const query = readStringParam(requestParams, "query", { required: true }); - const maxResults = readNumberParam(requestParams, "maxResults"); + const maxResults = readPositiveIntegerParam(requestParams, "maxResults"); const searchBackend = readEnumParam(requestParams, "backend", WIKI_SEARCH_BACKENDS); const searchCorpus = readEnumParam(requestParams, "corpus", WIKI_SEARCH_CORPORA); const mode = readEnumParam(requestParams, "mode", WIKI_SEARCH_MODES); @@ -337,8 +324,8 @@ export function registerMemoryWikiGatewayMethods(params: { try { await syncImportedSourcesIfNeeded(config, appConfig); const lookup = readStringParam(requestParams, "lookup", { required: true }); - const fromLine = readNumberParam(requestParams, "fromLine"); - const lineCount = readNumberParam(requestParams, "lineCount"); + const fromLine = readPositiveIntegerParam(requestParams, "fromLine"); + const lineCount = readPositiveIntegerParam(requestParams, "lineCount"); const searchBackend = readEnumParam(requestParams, "backend", WIKI_SEARCH_BACKENDS); const searchCorpus = readEnumParam(requestParams, "corpus", WIKI_SEARCH_CORPORA); const agentId = resolveGatewayAgentId(requestParams, appConfig); diff --git a/extensions/memory-wiki/src/tool.ts b/extensions/memory-wiki/src/tool.ts index dd7c071ccc6..41cc71c0810 100644 --- a/extensions/memory-wiki/src/tool.ts +++ b/extensions/memory-wiki/src/tool.ts @@ -36,7 +36,7 @@ const WikiSearchModeSchema = Type.Union(WIKI_SEARCH_MODES.map((value) => Type.Li const WikiSearchSchema = Type.Object( { query: Type.String({ minLength: 1 }), - maxResults: Type.Optional(Type.Number({ minimum: 1 })), + maxResults: Type.Optional(Type.Integer({ minimum: 1 })), backend: Type.Optional(WikiSearchBackendSchema), corpus: Type.Optional(WikiSearchCorpusSchema), mode: Type.Optional(WikiSearchModeSchema), @@ -46,8 +46,8 @@ const WikiSearchSchema = Type.Object( const WikiGetSchema = Type.Object( { lookup: Type.String({ minLength: 1 }), - fromLine: Type.Optional(Type.Number({ minimum: 1 })), - lineCount: Type.Optional(Type.Number({ minimum: 1 })), + fromLine: Type.Optional(Type.Integer({ minimum: 1 })), + lineCount: Type.Optional(Type.Integer({ minimum: 1 })), backend: Type.Optional(WikiSearchBackendSchema), corpus: Type.Optional(WikiSearchCorpusSchema), },