fix(active-memory): fast-fail stalled recall paths

This commit is contained in:
clawsweeper
2026-05-02 22:04:04 +00:00
parent bcec726f9b
commit 714459a954
3 changed files with 48 additions and 7 deletions

View File

@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
- CLI/plugins: stop treating the non-plugin `auth` command root as a bundled plugin id, so restrictive `plugins.allow` configs no longer tell users to add stale `auth` plugin entries.
- Doctor/plugins: update configured plugin installs whose stale manifests still declare channels without `channelConfigs`, so beta upgrades repair old Discord-style package payloads during `doctor --fix`.
- Active Memory: keep non-empty `memory_search` results from being fast-failed as empty when debug telemetry reports zero hits.
- Plugins/externalization: repair missing configured plugin installs from npm by default, reserve ClawHub downloads for explicit `clawhubSpec` metadata, and cover agent-runtime/env-selected plugin repair. Thanks @vincentkoc.
- Upgrade/config: validate configured web-search providers and statically suppressed model/provider pairs against the active plugin set at config load, so stale plugin state fails loud before runtime fallback.
- Status/update: resolve beta update-channel checks from the installed version when config still says `stable`, and let `status --deep` reuse live gateway channel credential state instead of warning on command-path-only token misses.

View File

@@ -2342,6 +2342,49 @@ describe("active-memory plugin", () => {
]);
});
it("does not fast-fail memory_search results solely because debug hits is zero", async () => {
__testing.setMinimumTimeoutMsForTests(1);
__testing.setSetupGraceTimeoutMsForTests(0);
api.pluginConfig = {
agents: ["main"],
timeoutMs: 500,
logging: true,
};
plugin.register(api as unknown as OpenClawPluginApi);
const sessionKey = "agent:main:terminal-zero-hit-with-results";
hoisted.sessionStore[sessionKey] = {
sessionId: "s-terminal-zero-hit-with-results",
updatedAt: 0,
};
runEmbeddedPiAgent.mockImplementationOnce(async (params: { sessionFile: string }) => {
await writeTranscriptJsonl(params.sessionFile, [
{
message: {
role: "toolResult",
toolName: "memory_search",
details: {
results: [{ path: "memory/food.md", text: "User usually orders ramen." }],
debug: { backend: "qmd", hits: 0, searchMs: 8 },
},
},
},
]);
await new Promise((resolve) => setTimeout(resolve, 50));
return { payloads: [{ text: "User usually orders ramen." }] };
});
const result = await hooks.before_prompt_build(
{ prompt: "what food do i usually order? zero hit with results", messages: [] },
{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
);
expect(result?.prependContext).toContain("User usually orders ramen.");
expect(getActiveMemoryLines(sessionKey)).toEqual([
expect.stringContaining("🧩 Active Memory: status=ok"),
expect.stringContaining("🔎 Active Memory Debug: backend=qmd searchMs=8 hits=0"),
]);
});
it("fast-fails unavailable memory_search results without injecting provider errors", async () => {
const CONFIGURED_TIMEOUT_MS = 1_000;
__testing.setMinimumTimeoutMsForTests(1);

View File

@@ -1595,13 +1595,10 @@ function extractTerminalMemorySearchResultFromSessionRecord(
const disabled = details?.disabled === true;
const unavailable =
disabled || Boolean(debug?.warning) || Boolean(debug?.error) || Boolean(details?.error);
const hits =
typeof debug?.hits === "number" && Number.isFinite(debug.hits)
? debug.hits
: results
? results.length
: undefined;
if (unavailable || hits === 0) {
const debugHits =
typeof debug?.hits === "number" && Number.isFinite(debug.hits) ? debug.hits : undefined;
const zeroHitSearch = results !== undefined ? results.length === 0 : debugHits === 0;
if (unavailable || zeroHitSearch) {
return { status: "empty", searchDebug: debug };
}
return undefined;