From c4b3fabe6b43e5d12ccfd9e7689a43658dea5fbf Mon Sep 17 00:00:00 2001 From: Jah-yee Date: Thu, 19 Mar 2026 05:21:35 +0800 Subject: [PATCH] fix: add withTimeout protection to auto-recall in openclaw-plugin Fixes issue #748 - The auto-recall logic in examples/openclaw-plugin/index.ts was missing timeout protection, causing the agent to hang indefinitely if OpenViking HTTP calls hang during transient connection issues. This fix: - Imports withTimeout from process-manager.js - Adds AUTO_RECALL_TIMEOUT_MS constant (5000ms) - Wraps the entire auto-recall block in withTimeout - Updates catch block to mention 'timed out' for clarity This is the same fix that was applied to openclaw-memory-plugin in PR #688. --- examples/openclaw-plugin/index.ts | 131 ++++++++++++++++-------------- 1 file changed, 70 insertions(+), 61 deletions(-) diff --git a/examples/openclaw-plugin/index.ts b/examples/openclaw-plugin/index.ts index 2d773a59..1015c815 100644 --- a/examples/openclaw-plugin/index.ts +++ b/examples/openclaw-plugin/index.ts @@ -24,6 +24,7 @@ import { quickRecallPrecheck, resolvePythonCommand, prepareLocalPort, + withTimeout, } from "./process-manager.js"; import { createMemoryOpenVikingContextEngine } from "./context-engine.js"; @@ -430,6 +431,8 @@ const contextEnginePlugin = { const prependContextParts: string[] = []; + const AUTO_RECALL_TIMEOUT_MS = 5_000; + if (cfg.autoRecall && queryText.length >= 5) { const precheck = await quickRecallPrecheck(cfg.mode, cfg.baseUrl, cfg.port, localProcess); if (!precheck.ok) { @@ -438,69 +441,75 @@ const contextEnginePlugin = { ); } else { try { - const candidateLimit = Math.max(cfg.recallLimit * 4, 20); - const [userSettled, agentSettled] = await Promise.allSettled([ - client.find(queryText, { - targetUri: "viking://user/memories", - limit: candidateLimit, - scoreThreshold: 0, - }), - client.find(queryText, { - targetUri: "viking://agent/memories", - limit: candidateLimit, - scoreThreshold: 0, - }), - ]); - - const userResult = userSettled.status === "fulfilled" ? userSettled.value : { memories: [] }; - const agentResult = agentSettled.status === "fulfilled" ? agentSettled.value : { memories: [] }; - if (userSettled.status === "rejected") { - api.logger.warn(`openviking: user memories search failed: ${String(userSettled.reason)}`); - } - if (agentSettled.status === "rejected") { - api.logger.warn(`openviking: agent memories search failed: ${String(agentSettled.reason)}`); - } - - const allMemories = [...(userResult.memories ?? []), ...(agentResult.memories ?? [])]; - const uniqueMemories = allMemories.filter((memory, index, self) => - index === self.findIndex((m) => m.uri === memory.uri) - ); - const leafOnly = uniqueMemories.filter((m) => m.level === 2); - const processed = postProcessMemories(leafOnly, { - limit: candidateLimit, - scoreThreshold: cfg.recallScoreThreshold, - }); - const memories = pickMemoriesForInjection(processed, cfg.recallLimit, queryText); - - if (memories.length > 0) { - const memoryLines = await Promise.all( - memories.map(async (item: FindResultItem) => { - if (item.level === 2) { - try { - const content = await client.read(item.uri); - if (content && typeof content === "string" && content.trim()) { - return `- [${item.category ?? "memory"}] ${content.trim()}`; + await withTimeout( + (async () => { + const candidateLimit = Math.max(cfg.recallLimit * 4, 20); + const [userSettled, agentSettled] = await Promise.allSettled([ + client.find(queryText, { + targetUri: "viking://user/memories", + limit: candidateLimit, + scoreThreshold: 0, + }), + client.find(queryText, { + targetUri: "viking://agent/memories", + limit: candidateLimit, + scoreThreshold: 0, + }), + ]); + + const userResult = userSettled.status === "fulfilled" ? userSettled.value : { memories: [] }; + const agentResult = agentSettled.status === "fulfilled" ? agentSettled.value : { memories: [] }; + if (userSettled.status === "rejected") { + api.logger.warn(`openviking: user memories search failed: ${String(userSettled.reason)}`); + } + if (agentSettled.status === "rejected") { + api.logger.warn(`openviking: agent memories search failed: ${String(agentSettled.reason)}`); + } + + const allMemories = [...(userResult.memories ?? []), ...(agentResult.memories ?? [])]; + const uniqueMemories = allMemories.filter((memory, index, self) => + index === self.findIndex((m) => m.uri === memory.uri) + ); + const leafOnly = uniqueMemories.filter((m) => m.level === 2); + const processed = postProcessMemories(leafOnly, { + limit: candidateLimit, + scoreThreshold: cfg.recallScoreThreshold, + }); + const memories = pickMemoriesForInjection(processed, cfg.recallLimit, queryText); + + if (memories.length > 0) { + const memoryLines = await Promise.all( + memories.map(async (item: FindResultItem) => { + if (item.level === 2) { + try { + const content = await client.read(item.uri); + if (content && typeof content === "string" && content.trim()) { + return `- [${item.category ?? "memory"}] ${content.trim()}`; + } + } catch { + // fallback to abstract + } } - } catch { - // fallback to abstract - } - } - return `- [${item.category ?? "memory"}] ${item.abstract ?? item.uri}`; - }), - ); - const memoryContext = memoryLines.join("\n"); - api.logger.info(`openviking: injecting ${memories.length} memories into context`); - api.logger.info( - `openviking: inject-detail ${toJsonLog({ count: memories.length, memories: summarizeInjectionMemories(memories) })}`, - ); - prependContextParts.push( - "\nThe following OpenViking memories may be relevant:\n" + - `${memoryContext}\n` + - "", - ); - } + return `- [${item.category ?? "memory"}] ${item.abstract ?? item.uri}`; + }), + ); + const memoryContext = memoryLines.join("\n"); + api.logger.info(`openviking: injecting ${memories.length} memories into context`); + api.logger.info( + `openviking: inject-detail ${toJsonLog({ count: memories.length, memories: summarizeInjectionMemories(memories) })}`, + ); + prependContextParts.push( + "\nThe following OpenViking memories may be relevant:\n" + + `${memoryContext}\n` + + "", + ); + } + })(), + AUTO_RECALL_TIMEOUT_MS, + `openviking: auto-recall timed out after ${AUTO_RECALL_TIMEOUT_MS}ms`, + ); } catch (err) { - api.logger.warn(`openviking: auto-recall failed: ${String(err)}`); + api.logger.warn(`openviking: auto-recall failed or timed out: ${String(err)}`); } } }