diff --git a/extensions/memory-core/index.ts b/extensions/memory-core/index.ts index 56c4b550987..20d2b786ae5 100644 --- a/extensions/memory-core/index.ts +++ b/extensions/memory-core/index.ts @@ -74,8 +74,8 @@ const MemoryGetSchema = { type: "object", properties: { path: { type: "string" }, - from: { type: "number" }, - lines: { type: "number" }, + from: { type: "integer", minimum: 1 }, + lines: { type: "integer", minimum: 1 }, corpus: { type: "string", enum: ["memory", "wiki", "all"] }, }, required: ["path"], diff --git a/extensions/memory-core/src/tools.citations.test.ts b/extensions/memory-core/src/tools.citations.test.ts index f98e14a6ba6..991d1507f01 100644 --- a/extensions/memory-core/src/tools.citations.test.ts +++ b/extensions/memory-core/src/tools.citations.test.ts @@ -207,6 +207,21 @@ describe("memory tools", () => { expect(getMemorySearchManagerMockCalls()).toBe(0); }); + it("rejects fractional memory_get ranges before reading files", async () => { + setMemoryBackend("builtin"); + const tool = createMemoryGetToolOrThrow(); + + await expect( + tool.execute("call_fractional_range", { + path: "memory/2026-02-19.md", + from: 1.5, + lines: 2, + }), + ).rejects.toThrow("from must be a positive integer"); + expect(getReadAgentMemoryFileMockCalls()).toBe(0); + expect(getMemorySearchManagerMockCalls()).toBe(0); + }); + it("returns truncation metadata and a continuation notice for partial memory_get results", async () => { setMemoryBackend("builtin"); setMemoryReadFileImpl(async (params: MemoryReadParams) => ({ diff --git a/extensions/memory-core/src/tools.shared.ts b/extensions/memory-core/src/tools.shared.ts index 1c6b58ec159..79a0832b9b5 100644 --- a/extensions/memory-core/src/tools.shared.ts +++ b/extensions/memory-core/src/tools.shared.ts @@ -37,8 +37,8 @@ export const MemorySearchSchema = Type.Object({ export const MemoryGetSchema = Type.Object({ path: Type.String(), - from: Type.Optional(Type.Number()), - lines: Type.Optional(Type.Number()), + from: Type.Optional(Type.Integer()), + lines: Type.Optional(Type.Integer()), corpus: Type.Optional(stringEnum(["memory", "wiki", "all"])), }); diff --git a/extensions/memory-core/src/tools.ts b/extensions/memory-core/src/tools.ts index 8be405dbf5c..fbae68dd9f2 100644 --- a/extensions/memory-core/src/tools.ts +++ b/extensions/memory-core/src/tools.ts @@ -4,6 +4,7 @@ import { asToolParamsRecord, jsonResult, readNumberParam, + readPositiveIntegerParam, readStringParam, type MemoryCorpusSearchResult, type OpenClawConfig, @@ -443,8 +444,8 @@ export function createMemoryGetTool(options: { async (_toolCallId, params) => { const rawParams = asToolParamsRecord(params); const relPath = readStringParam(rawParams, "path", { required: true }); - const from = readNumberParam(rawParams, "from", { integer: true }); - const lines = readNumberParam(rawParams, "lines", { integer: true }); + const from = readPositiveIntegerParam(rawParams, "from"); + const lines = readPositiveIntegerParam(rawParams, "lines"); const requestedCorpus = readStringParam(rawParams, "corpus") as | "memory" | "wiki" diff --git a/src/plugin-sdk/memory-core-host-runtime-core.ts b/src/plugin-sdk/memory-core-host-runtime-core.ts index 5e37b8368e5..4ee73cea197 100644 --- a/src/plugin-sdk/memory-core-host-runtime-core.ts +++ b/src/plugin-sdk/memory-core-host-runtime-core.ts @@ -8,6 +8,7 @@ export { asToolParamsRecord, jsonResult, readNumberParam, + readPositiveIntegerParam, readStringParam, type AnyAgentTool, } from "../agents/tools/common.js";