fix(memory): keep archive transcript visibility safe

Keep reset/deleted session archives searchable while preserving visibility filtering, and keep internal cron-run archives opaque when live ownership metadata is gone.\n\nRefs #56131.\nThanks @buyitsydney.
This commit is contained in:
buyitsydney
2026-05-03 16:17:45 +08:00
committed by GitHub
parent d583662fd9
commit 2ffdb5d248
7 changed files with 361 additions and 25 deletions

View File

@@ -11,6 +11,7 @@ const crossAgentStore = {
sessionFile: "/tmp/sessions/w1.jsonl",
},
};
let combinedSessionStore: typeof crossAgentStore | Record<string, never> = crossAgentStore;
vi.mock("openclaw/plugin-sdk/session-transcript-hit", async (importOriginal) => {
const actual =
@@ -19,7 +20,7 @@ vi.mock("openclaw/plugin-sdk/session-transcript-hit", async (importOriginal) =>
...actual,
loadCombinedSessionStoreForGateway: vi.fn(() => ({
storePath: "(test)",
store: crossAgentStore,
store: combinedSessionStore,
})),
};
});
@@ -27,6 +28,7 @@ vi.mock("openclaw/plugin-sdk/session-transcript-hit", async (importOriginal) =>
describe("filterMemorySearchHitsBySessionVisibility", () => {
afterEach(() => {
vi.mocked(sessionTranscriptHit.loadCombinedSessionStoreForGateway).mockClear();
combinedSessionStore = crossAgentStore;
});
it("drops sessions-sourced hits when requester key is missing (fail closed)", async () => {
@@ -148,4 +150,57 @@ describe("filterMemorySearchHitsBySessionVisibility", () => {
});
expect(filtered).toEqual([]);
});
it("keeps same-agent deleted archive hits using owner metadata when the live store entry is gone", async () => {
combinedSessionStore = {};
const hit: MemorySearchResult = {
path: "sessions/main/deleted-stem.jsonl.deleted.2026-02-16T22-27-33.000Z",
source: "sessions",
score: 1,
snippet: "x",
startLine: 1,
endLine: 2,
};
const cfg = asOpenClawConfig({
tools: {
sessions: { visibility: "agent" },
},
});
const filtered = await filterMemorySearchHitsBySessionVisibility({
cfg,
requesterSessionKey: "agent:main:main",
sandboxed: false,
hits: [hit],
});
expect(filtered).toEqual([hit]);
});
it("still denies cross-agent deleted archive hits resolved from owner metadata when a2a is disabled", async () => {
combinedSessionStore = {};
const hit: MemorySearchResult = {
path: "sessions/peer/deleted-stem.jsonl.deleted.2026-02-16T22-27-33.000Z",
source: "sessions",
score: 1,
snippet: "x",
startLine: 1,
endLine: 2,
};
const cfg = asOpenClawConfig({
tools: {
sessions: { visibility: "all" },
agentToAgent: { enabled: false },
},
});
const filtered = await filterMemorySearchHitsBySessionVisibility({
cfg,
requesterSessionKey: "agent:main:main",
sandboxed: false,
hits: [hit],
});
expect(filtered).toEqual([]);
});
});

View File

@@ -1,7 +1,7 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core-host-runtime-core";
import type { MemorySearchResult } from "openclaw/plugin-sdk/memory-core-host-runtime-files";
import {
extractTranscriptStemFromSessionsMemoryHit,
extractTranscriptIdentityFromSessionsMemoryHit,
loadCombinedSessionStoreForGateway,
resolveTranscriptStemToSessionKeys,
} from "openclaw/plugin-sdk/session-transcript-hit";
@@ -42,13 +42,16 @@ export async function filterMemorySearchHitsBySessionVisibility(params: {
if (!params.requesterSessionKey || !guard) {
continue;
}
const stem = extractTranscriptStemFromSessionsMemoryHit(hit.path);
if (!stem) {
const identity = extractTranscriptIdentityFromSessionsMemoryHit(hit.path);
if (!identity) {
continue;
}
const keys = resolveTranscriptStemToSessionKeys({
store: combinedSessionStore,
stem,
stem: identity.stem,
...(identity.archived && identity.ownerAgentId
? { archivedOwnerAgentId: identity.ownerAgentId }
: {}),
});
if (keys.length === 0) {
continue;