From faefae79eb9d8b1ea52b35ad51229350ee51fe0a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 16 May 2026 02:56:41 +0100 Subject: [PATCH] fix: honor active memory transcript privacy --- extensions/active-memory/index.test.ts | 24 ++++++++++++++++++++--- extensions/active-memory/index.ts | 27 ++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/extensions/active-memory/index.test.ts b/extensions/active-memory/index.test.ts index 2d498f6b67c..86c00858bcb 100644 --- a/extensions/active-memory/index.test.ts +++ b/extensions/active-memory/index.test.ts @@ -1,7 +1,10 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { appendSqliteSessionTranscriptEvent } from "openclaw/plugin-sdk/agent-harness-runtime"; +import { + appendSqliteSessionTranscriptEvent, + hasSqliteSessionTranscriptEvents, +} from "openclaw/plugin-sdk/agent-harness-runtime"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-runtime"; import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; @@ -4052,11 +4055,21 @@ describe("active-memory plugin", () => { ); }); - it("keeps subagent transcripts in sqlite by default", async () => { + it("deletes subagent transcripts by default after reading sqlite recall output", async () => { const mkdtempSpy = vi.spyOn(fs, "mkdtemp"); const rmSpy = vi.spyOn(fs, "rm"); + let transcriptScope: TranscriptScope | undefined; + runEmbeddedPiAgent.mockImplementationOnce( + async (params: { agentId?: string; sessionId: string }) => { + transcriptScope = transcriptScopeFromRunParams(params); + await writeSqliteTranscriptEvents(transcriptScope, [ + { type: "message", message: { role: "assistant", content: "private recall summary" } }, + ]); + return { payloads: [{ text: "private recall summary" }] }; + }, + ); - await hooks.before_prompt_build( + const result = await hooks.before_prompt_build( { prompt: "what wings should i order? sqlite transcript scope", messages: [] }, { agentId: "main", @@ -4066,11 +4079,16 @@ describe("active-memory plugin", () => { }, ); + expect(result).toMatchObject({ + prependContext: expect.stringContaining("private recall summary"), + }); const runParams = runEmbeddedPiAgent.mock.calls.at(-1)?.[0]; expect(runParams).toMatchObject({ agentId: "main", sessionId: expect.stringMatching(/^active-memory-[a-z0-9]+-[a-f0-9]{8}$/), }); + expect(transcriptScope).toBeDefined(); + expect(hasSqliteSessionTranscriptEvents(transcriptScope as TranscriptScope)).toBe(false); expect(mkdtempSpy).not.toHaveBeenCalled(); expect(rmSpy).not.toHaveBeenCalled(); }); diff --git a/extensions/active-memory/index.ts b/extensions/active-memory/index.ts index 241d8b55eba..d4d2bbd0381 100644 --- a/extensions/active-memory/index.ts +++ b/extensions/active-memory/index.ts @@ -1,5 +1,8 @@ import crypto from "node:crypto"; -import { loadSqliteSessionTranscriptBoundedEvents } from "openclaw/plugin-sdk/agent-harness-runtime"; +import { + deleteSqliteSessionTranscript, + loadSqliteSessionTranscriptBoundedEvents, +} from "openclaw/plugin-sdk/agent-harness-runtime"; import { DEFAULT_PROVIDER, parseModelRef, @@ -1558,6 +1561,17 @@ async function streamBoundedTranscriptEvents(params: { } } +function deleteTransientRecallTranscript(transcriptScope: TranscriptScope | undefined): void { + if (!transcriptScope) { + return; + } + try { + deleteSqliteSessionTranscript(transcriptScope); + } catch { + // Best-effort cleanup; recall results should not fail because transcript cleanup did. + } +} + function extractActiveMemorySearchDebugFromSessionRecord( value: unknown, ): ActiveMemorySearchDebug | undefined { @@ -2568,8 +2582,9 @@ async function maybeResolveActiveRecall(params: { }); let terminalMemorySearchWatch: TerminalMemorySearchWatch | undefined; + let subagentPromise: Promise | undefined; try { - const subagentPromise = runRecallSubagent({ + subagentPromise = runRecallSubagent({ ...params, modelRef: resolvedModelRef, abortSignal: controller.signal, @@ -2753,6 +2768,14 @@ async function maybeResolveActiveRecall(params: { } finally { terminalMemorySearchWatch?.stop(); clearTimeout(timeoutId); + if (!params.config.persistTranscripts) { + deleteTransientRecallTranscript(transcriptScope); + subagentPromise + ?.finally(() => { + deleteTransientRecallTranscript(transcriptScope); + }) + .catch(() => undefined); + } } }