fix(active-memory): skip payload-less memory_search toolResults in tr… (openclaw#68773)

Verified:
- pnpm install --frozen-lockfile
- pnpm test extensions/active-memory/index.test.ts
- pnpm exec oxfmt --check --threads=1 extensions/active-memory/index.ts extensions/active-memory/index.test.ts CHANGELOG.md
- git diff --check origin/main..HEAD
- gh pr checks 68773 --repo openclaw/openclaw --required

Co-authored-by: SimbaKingjoe <126222269+SimbaKingjoe@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
SimbaKingjoe
2026-04-28 21:20:13 +08:00
committed by GitHub
parent 189c91eae6
commit bdb75bd8c7
3 changed files with 50 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
- fix(agents): canonicalize provider aliases in byProvider tool policy lookup [AI]. (#72917) Thanks @pgondhi987.
- fix(security): block npm_execpath injection from workspace .env [AI-assisted]. (#73262) Thanks @pgondhi987.
- Tools/web_fetch: decode response bodies from raw bytes using declared HTTP, XML, or HTML meta charsets before extraction, so Shift_JIS and other legacy-charset pages no longer return mojibake. Fixes #72916. Thanks @amknight.
- Active Memory: skip payload-less `memory_search` transcript tool results when building debug telemetry, so newer empty entries no longer hide the latest useful debug payload. (#68773) Thanks @SimbaKingjoe.
- Channels/Discord: bound message read/search REST calls, route those actions through Gateway execution, and fall back to `CommandTargetSessionKey` for inbound hook session keys so Discord reads do not hang and hooks still fire when `SessionKey` is empty. Fixes #73431. (#73521) Thanks @amknight.
- Plugins/media: auto-enable provider plugins referenced by `agents.defaults.imageGenerationModel`, `videoGenerationModel`, and `musicGenerationModel` primary/fallback refs, so configured Google and MiniMax media providers do not stay disabled behind a restrictive plugin allowlist. Thanks @vincentkoc.
- Memory-core/dreaming: retry managed dreaming cron registration after startup when the cron service is not reachable yet, so the scheduled Memory Dreaming Promotion sweep recovers without waiting for heartbeat traffic. Fixes #72841. Thanks @amknight.

View File

@@ -1101,6 +1101,54 @@ describe("active-memory plugin", () => {
]);
});
it("skips newest memory_search toolResult entries that carry no debug payload", async () => {
const sessionKey = "agent:main:transcript-debug";
hoisted.sessionStore[sessionKey] = { sessionId: "s-main", updatedAt: 0 };
runEmbeddedPiAgent.mockImplementationOnce(async (params: { sessionFile: string }) => {
const lines = [
JSON.stringify({
message: {
role: "toolResult",
toolName: "memory_search",
details: { debug: { backend: "qmd", hits: 3 } },
},
}),
JSON.stringify({
message: {
role: "toolResult",
toolName: "memory_search",
details: {},
},
}),
];
await fs.writeFile(params.sessionFile, `${lines.join("\n")}\n`, "utf8");
return { payloads: [{ text: "wings are fine." }] };
});
await hooks.before_prompt_build(
{ prompt: "debug transcript bug", messages: [] },
{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
);
const updater = hoisted.updateSessionStore.mock.calls.at(-1)?.[1] as
| ((store: Record<string, Record<string, unknown>>) => void)
| undefined;
const store = {
[sessionKey]: { sessionId: "s-main", updatedAt: 0 },
} as Record<string, Record<string, unknown>>;
updater?.(store);
const entries = store[sessionKey]?.pluginDebugEntries as
| { pluginId: string; lines: string[] }[]
| undefined;
const debugLine = entries?.[0]?.lines.find((line) =>
line.startsWith("🔎 Active Memory Debug:"),
);
expect(debugLine).toBeDefined();
expect(debugLine).toContain("backend=qmd");
expect(debugLine).toContain("hits=3");
});
it("replaces stale structured active-memory lines on a later empty run", async () => {
const sessionKey = "agent:main:stale-active-memory-lines";
hoisted.sessionStore[sessionKey] = {

View File

@@ -1234,7 +1234,7 @@ async function readActiveMemorySearchDebug(
continue;
}
const details = asRecord(message.details);
const debug = asRecord(details?.debug) ?? {};
const debug = asRecord(details?.debug);
const warning = normalizeOptionalString(details?.warning);
const action = normalizeOptionalString(details?.action);
const error = normalizeOptionalString(details?.error);