mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 20:30:45 +00:00
* Tighten context limits and bound memory excerpts * Align startup context defaults in config docs * Align qmd memory_get bounds with shared limits * Preserve qmd partial memory reads * Fix shared memory read type import * Add changelog entry for context bounds
111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import {
|
|
resolveAgentContextLimits,
|
|
resolveAgentWorkspaceDir,
|
|
} from "../../../../src/agents/agent-scope.js";
|
|
import { resolveMemorySearchConfig } from "../../../../src/agents/memory-search.js";
|
|
import type { OpenClawConfig } from "../../../../src/config/config.js";
|
|
import {
|
|
buildMemoryReadResult,
|
|
DEFAULT_MEMORY_READ_LINES,
|
|
type MemoryReadResult,
|
|
} from "../../../../src/memory-host-sdk/host/read-file-shared.js";
|
|
import { isFileMissingError, statRegularFile } from "./fs-utils.js";
|
|
import { isMemoryPath, normalizeExtraMemoryPaths } from "./internal.js";
|
|
|
|
export async function readMemoryFile(params: {
|
|
workspaceDir: string;
|
|
extraPaths?: string[];
|
|
relPath: string;
|
|
from?: number;
|
|
lines?: number;
|
|
defaultLines?: number;
|
|
maxChars?: number;
|
|
}): Promise<MemoryReadResult> {
|
|
const rawPath = params.relPath.trim();
|
|
if (!rawPath) {
|
|
throw new Error("path required");
|
|
}
|
|
const absPath = path.isAbsolute(rawPath)
|
|
? path.resolve(rawPath)
|
|
: path.resolve(params.workspaceDir, rawPath);
|
|
const relPath = path.relative(params.workspaceDir, absPath).replace(/\\/g, "/");
|
|
const inWorkspace = relPath.length > 0 && !relPath.startsWith("..") && !path.isAbsolute(relPath);
|
|
const allowedWorkspace = inWorkspace && isMemoryPath(relPath);
|
|
let allowedAdditional = false;
|
|
if (!allowedWorkspace && (params.extraPaths?.length ?? 0) > 0) {
|
|
const additionalPaths = normalizeExtraMemoryPaths(params.workspaceDir, params.extraPaths);
|
|
for (const additionalPath of additionalPaths) {
|
|
try {
|
|
const stat = await fs.lstat(additionalPath);
|
|
if (stat.isSymbolicLink()) {
|
|
continue;
|
|
}
|
|
if (stat.isDirectory()) {
|
|
if (absPath === additionalPath || absPath.startsWith(`${additionalPath}${path.sep}`)) {
|
|
allowedAdditional = true;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (stat.isFile() && absPath === additionalPath && absPath.endsWith(".md")) {
|
|
allowedAdditional = true;
|
|
break;
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
if (!allowedWorkspace && !allowedAdditional) {
|
|
throw new Error("path required");
|
|
}
|
|
if (!absPath.endsWith(".md")) {
|
|
throw new Error("path required");
|
|
}
|
|
const statResult = await statRegularFile(absPath);
|
|
if (statResult.missing) {
|
|
return { text: "", path: relPath };
|
|
}
|
|
let content: string;
|
|
try {
|
|
content = await fs.readFile(absPath, "utf-8");
|
|
} catch (err) {
|
|
if (isFileMissingError(err)) {
|
|
return { text: "", path: relPath };
|
|
}
|
|
throw err;
|
|
}
|
|
return buildMemoryReadResult({
|
|
content,
|
|
relPath,
|
|
from: params.from,
|
|
lines: params.lines,
|
|
defaultLines: params.defaultLines ?? DEFAULT_MEMORY_READ_LINES,
|
|
maxChars: params.maxChars,
|
|
suggestReadFallback: allowedWorkspace,
|
|
});
|
|
}
|
|
|
|
export async function readAgentMemoryFile(params: {
|
|
cfg: OpenClawConfig;
|
|
agentId: string;
|
|
relPath: string;
|
|
from?: number;
|
|
lines?: number;
|
|
}): Promise<MemoryReadResult> {
|
|
const settings = resolveMemorySearchConfig(params.cfg, params.agentId);
|
|
if (!settings) {
|
|
throw new Error("memory search disabled");
|
|
}
|
|
const contextLimits = resolveAgentContextLimits(params.cfg, params.agentId);
|
|
return await readMemoryFile({
|
|
workspaceDir: resolveAgentWorkspaceDir(params.cfg, params.agentId),
|
|
extraPaths: settings.extraPaths,
|
|
relPath: params.relPath,
|
|
from: params.from,
|
|
lines: params.lines,
|
|
defaultLines: contextLimits?.memoryGetDefaultLines,
|
|
maxChars: contextLimits?.memoryGetMaxChars,
|
|
});
|
|
}
|