import { jsonResult, readNumberParam, readStringParam, type AnyAgentTool, type OpenClawConfig, } from "openclaw/plugin-sdk/memory-core-host-runtime-core"; import { clampResultsByInjectedChars, decorateCitations, resolveMemoryCitationsMode, shouldIncludeCitations, } from "./tools.citations.js"; import { buildMemorySearchUnavailableResult, createMemoryTool, getMemoryManagerContext, getMemoryManagerContextWithPurpose, loadMemoryToolRuntime, MemoryGetSchema, MemorySearchSchema, } from "./tools.shared.js"; export function createMemorySearchTool(options: { config?: OpenClawConfig; agentSessionKey?: string; }): AnyAgentTool | null { return createMemoryTool({ options, label: "Memory Search", name: "memory_search", description: "Mandatory recall step: semantically search MEMORY.md + memory/*.md (and optional session transcripts) before answering questions about prior work, decisions, dates, people, preferences, or todos; returns top snippets with path + lines. If response has disabled=true, memory retrieval is unavailable and should be surfaced to the user.", parameters: MemorySearchSchema, execute: ({ cfg, agentId }) => async (_toolCallId, params) => { const query = readStringParam(params, "query", { required: true }); const maxResults = readNumberParam(params, "maxResults"); const minScore = readNumberParam(params, "minScore"); const { resolveMemoryBackendConfig } = await loadMemoryToolRuntime(); const memory = await getMemoryManagerContext({ cfg, agentId }); if ("error" in memory) { return jsonResult(buildMemorySearchUnavailableResult(memory.error)); } try { const citationsMode = resolveMemoryCitationsMode(cfg); const includeCitations = shouldIncludeCitations({ mode: citationsMode, sessionKey: options.agentSessionKey, }); const rawResults = await memory.manager.search(query, { maxResults, minScore, sessionKey: options.agentSessionKey, }); const status = memory.manager.status(); const decorated = decorateCitations(rawResults, includeCitations); const resolved = resolveMemoryBackendConfig({ cfg, agentId }); const results = status.backend === "qmd" ? clampResultsByInjectedChars(decorated, resolved.qmd?.limits.maxInjectedChars) : decorated; const searchMode = (status.custom as { searchMode?: string } | undefined)?.searchMode; return jsonResult({ results, provider: status.provider, model: status.model, fallback: status.fallback, citations: citationsMode, mode: searchMode, }); } catch (err) { const message = err instanceof Error ? err.message : String(err); return jsonResult(buildMemorySearchUnavailableResult(message)); } }, }); } export function createMemoryGetTool(options: { config?: OpenClawConfig; agentSessionKey?: string; }): AnyAgentTool | null { return createMemoryTool({ options, label: "Memory Get", name: "memory_get", description: "Safe snippet read from MEMORY.md or memory/*.md with optional from/lines; use after memory_search to pull only the needed lines and keep context small.", parameters: MemoryGetSchema, execute: ({ cfg, agentId }) => async (_toolCallId, params) => { const relPath = readStringParam(params, "path", { required: true }); const from = readNumberParam(params, "from", { integer: true }); const lines = readNumberParam(params, "lines", { integer: true }); const { readAgentMemoryFile, resolveMemoryBackendConfig } = await loadMemoryToolRuntime(); const resolved = resolveMemoryBackendConfig({ cfg, agentId }); if (resolved.backend === "builtin") { try { const result = await readAgentMemoryFile({ cfg, agentId, relPath, from: from ?? undefined, lines: lines ?? undefined, }); return jsonResult(result); } catch (err) { const message = err instanceof Error ? err.message : String(err); return jsonResult({ path: relPath, text: "", disabled: true, error: message }); } } const memory = await getMemoryManagerContextWithPurpose({ cfg, agentId, purpose: "status", }); if ("error" in memory) { return jsonResult({ path: relPath, text: "", disabled: true, error: memory.error }); } try { const result = await memory.manager.readFile({ relPath, from: from ?? undefined, lines: lines ?? undefined, }); return jsonResult(result); } catch (err) { const message = err instanceof Error ? err.message : String(err); return jsonResult({ path: relPath, text: "", disabled: true, error: message }); } }, }); }