mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-21 19:15:47 +00:00
fix(memory): preserve session corpus labels (#71898)
Summary: - The PR updates memory-core `memory_search` result shaping to surface `corpus` from each hit's `source`, adds ... session corpus-label coverage, adds a changelog entry, and includes a small tempdir test assertion cleanup. - Reproducibility: yes. Current main has a high-confidence source-level reproduction: session hits keep `sourc ... the final mapper hard-codes `corpus: "memory"`; the PR body also supplies live Gateway before/after output. Automerge notes: - PR branch already contained follow-up commit before automerge: test(memory): clarify corpus label regression - PR branch already contained follow-up commit before automerge: fix(memory): type session corpus results - PR branch already contained follow-up commit before automerge: fix(memory): preserve session corpus labels - PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-7189… Validation: - ClawSweeper review passed for head02d0db0861. - Required merge gates passed before the squash merge. Prepared head SHA:02d0db0861Review: https://github.com/openclaw/openclaw/pull/71898#issuecomment-4340800992 Co-authored-by: Ruben Cuevas <hi@rubencu.com> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
This commit is contained in:
parent
15cf49222f
commit
00bb0dde4d
4 changed files with 85 additions and 7 deletions
|
|
@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai
|
|||
|
||||
### Fixes
|
||||
|
||||
- Memory: keep `memory_search` result `corpus` labels aligned with the hit source, so session transcript hits surface as `sessions` and memory-file hits stay `memory`. Fixes #72885. (#71898, #72886) Thanks @rubencu.
|
||||
- Google/Gemini: normalize retired nested Gemini 3 Pro Preview ids while converting manifest catalog rows into emitted provider config, so `google/gemini-3.1-pro-preview` is used for testing instead of `google/gemini-3-pro-preview`.
|
||||
- Native apps: advertise the Gateway protocol compatibility range so chat and node sessions can connect to v3 gateways after additive v4 client updates.
|
||||
- Gateway/agents: keep stale `sessions_send` ACP manager and `web_fetch` runtime chunks importable after package updates, preventing live gateways from breaking before restart. Fixes #78804. Thanks @Gomesy72.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
getMemorySearchManagerMockConfigs,
|
||||
getMemorySearchManagerMockParams,
|
||||
|
|
@ -13,6 +13,26 @@ import {
|
|||
expectUnavailableMemorySearchDetails,
|
||||
} from "./tools.test-helpers.js";
|
||||
|
||||
const sessionStore = vi.hoisted(() => ({
|
||||
"agent:main:main": {
|
||||
sessionId: "thread-1",
|
||||
updatedAt: 1,
|
||||
sessionFile: "/tmp/sessions/thread-1.jsonl",
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/session-transcript-hit", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("openclaw/plugin-sdk/session-transcript-hit")>();
|
||||
return {
|
||||
...actual,
|
||||
loadCombinedSessionStoreForGateway: vi.fn(() => ({
|
||||
storePath: "(test)",
|
||||
store: sessionStore,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
describe("memory_search unavailable payloads", () => {
|
||||
beforeEach(() => {
|
||||
resetMemoryToolMockState({ searchImpl: async () => [] });
|
||||
|
|
@ -107,6 +127,12 @@ describe("memory_search unavailable payloads", () => {
|
|||
(result.details as { debug?: { searchMs?: number } }).debug?.searchMs,
|
||||
).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("memory_search corpus labels", () => {
|
||||
beforeEach(() => {
|
||||
resetMemoryToolMockState({ searchImpl: async () => [] });
|
||||
});
|
||||
|
||||
it("uses explicit plugin context agent over synthetic active-memory session keys", async () => {
|
||||
const tool = createMemorySearchToolOrThrow({
|
||||
|
|
@ -170,4 +196,57 @@ describe("memory_search unavailable payloads", () => {
|
|||
|
||||
expect(getMemorySearchManagerMockConfigs()).toEqual([patchedConfig]);
|
||||
});
|
||||
|
||||
it("preserves source corpus labels for memory and session transcript hits", async () => {
|
||||
setMemorySearchImpl(async () => [
|
||||
{
|
||||
path: "MEMORY.md",
|
||||
startLine: 3,
|
||||
endLine: 4,
|
||||
score: 0.95,
|
||||
snippet: "Durable memory note",
|
||||
source: "memory" as const,
|
||||
},
|
||||
{
|
||||
path: "sessions/thread-1.jsonl",
|
||||
startLine: 1,
|
||||
endLine: 2,
|
||||
score: 0.9,
|
||||
snippet: "Thread transcript note",
|
||||
source: "sessions" as const,
|
||||
},
|
||||
]);
|
||||
|
||||
const tool = createMemorySearchToolOrThrow({
|
||||
config: {
|
||||
agents: { list: [{ id: "main", default: true }] },
|
||||
memory: { citations: "off" },
|
||||
tools: { sessions: { visibility: "all" } },
|
||||
},
|
||||
agentSessionKey: "agent:main:main",
|
||||
});
|
||||
const result = await tool.execute("mixed", { query: "thread note" });
|
||||
const details = result.details as { results: Array<{ corpus: string; path: string }> };
|
||||
|
||||
expect(details.results).toEqual([
|
||||
{
|
||||
corpus: "memory",
|
||||
path: "MEMORY.md",
|
||||
startLine: 3,
|
||||
endLine: 4,
|
||||
score: 0.95,
|
||||
snippet: "Durable memory note",
|
||||
source: "memory",
|
||||
},
|
||||
{
|
||||
corpus: "sessions",
|
||||
path: "sessions/thread-1.jsonl",
|
||||
startLine: 1,
|
||||
endLine: 2,
|
||||
score: 0.9,
|
||||
snippet: "Thread transcript note",
|
||||
source: "sessions",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import {
|
|||
} from "./tools.shared.js";
|
||||
|
||||
type MemorySearchToolResult =
|
||||
| (Record<string, unknown> & { corpus: "memory"; score: number; path: string })
|
||||
| (MemorySearchResult & { corpus: MemorySource })
|
||||
| MemoryCorpusSearchResult;
|
||||
|
||||
function sortMemorySearchToolResults<T extends { score: number; path: string }>(results: T[]): T[] {
|
||||
|
|
@ -267,9 +267,7 @@ export function createMemorySearchTool(options: {
|
|||
});
|
||||
const searchStartedAt = Date.now();
|
||||
let rawResults: MemorySearchResult[] = [];
|
||||
let surfacedMemoryResults: Array<
|
||||
Record<string, unknown> & { corpus: "memory"; score: number; path: string }
|
||||
> = [];
|
||||
let surfacedMemoryResults: Array<MemorySearchResult & { corpus: MemorySource }> = [];
|
||||
let provider: string | undefined;
|
||||
let model: string | undefined;
|
||||
let fallback: unknown;
|
||||
|
|
@ -326,7 +324,7 @@ export function createMemorySearchTool(options: {
|
|||
: decorated;
|
||||
surfacedMemoryResults = memoryResults.map((result) => ({
|
||||
...result,
|
||||
corpus: "memory" as const,
|
||||
corpus: result.source,
|
||||
}));
|
||||
const sleepTimezone = resolveMemoryDeepDreamingConfig({
|
||||
pluginConfig: resolveMemoryCorePluginConfig(cfg),
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ describe("image-ops temp dir", () => {
|
|||
expect(prefix?.endsWith("-")).toBe(true);
|
||||
const uuid = prefix?.slice(uuidPrefix.length, -1) ?? "";
|
||||
expect(uuid).toHaveLength(36);
|
||||
expect(Array.from(uuid).every((char) => /[0-9a-f-]/u.test(char))).toBe(true);
|
||||
expect(/^[0-9a-f-]+$/u.test(uuid)).toBe(true);
|
||||
expect([8, 13, 18, 23].map((index) => uuid[index])).toEqual(["-", "-", "-", "-"]);
|
||||
expect(path.dirname(prefix ?? "")).toBe(secureRoot);
|
||||
expect(createdTempDir.startsWith(prefix ?? "")).toBe(true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue