refactor(memory): reuse complete writes for recovery (#154422)

Use the released fs-safe writer while keeping restoration, truncation, synchronization and failure reporting in the Memory owner. Related: #154370.

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Peter Steinberger 2026-09-21 01:12:46 -07:00 • committed by GitHub
parent 68e4d37f71
commit 5460db0d6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 7 additions and 17 deletions

View file

@ -416,7 +416,7 @@ Use `isLoopbackHost(host)` when a plugin must accept only the local machine. It
| `plugin-sdk/concurrency-runtime` | Private-local after July 2026; Bounded async task concurrency (`runTasksWithConcurrency`) and cancellable permit admission (`createPermitPool`) with caller-owned release |
| `plugin-sdk/dedupe-runtime` | In-memory and persistent-backed dedupe cache helpers |
| `plugin-sdk/delivery-queue-runtime` | Private-local after July 2026; Outbound pending-delivery drain helper |
| `plugin-sdk/file-access-runtime` | Private-local after July 2026; Safe local-file, path-containment, temp-root, media-source path, directory-durability, and borrowed-handle readers: `readFileHandleBounded` reads through EOF under a byte cap; `readFileWindowFully` fills a positional buffer through short reads; `sha256File` streams a bounded SHA-256 digest from a path or borrowed handle. Borrowed handles stay open. `resolvePathPrefixSync` observes a canonical existing prefix and raw missing suffix, following physical symlink traversal without authorizing later access. |
| `plugin-sdk/file-access-runtime` | Private-local after July 2026; Safe local-file, path-containment, temp-root, media-source path, directory-durability, and borrowed-handle I/O: `readFileHandleBounded` reads through EOF under a byte cap; `readFileWindowFully` fills a positional buffer through short reads; `writeFileWindowFully` completes short writes without truncation, synchronization, or rollback; `sha256File` streams a bounded SHA-256 digest from a path or borrowed handle. Borrowed handles stay open. `resolvePathPrefixSync` observes a canonical existing prefix and raw missing suffix, following physical symlink traversal without authorizing later access. |
| `plugin-sdk/heartbeat-runtime` | Private-local after July 2026; Heartbeat wake, event, and visibility helpers |
| `plugin-sdk/expect-runtime` | Private-local after July 2026; Required-value assertion helper for provable runtime invariants |
| `plugin-sdk/number-runtime` | Private-local after July 2026; Numeric coercion helper |

View file

@ -2,7 +2,10 @@ import { createHash } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { extractErrorCode } from "openclaw/plugin-sdk/error-runtime";
import { resolvePathPrefixSync } from "openclaw/plugin-sdk/file-access-runtime";
import {
resolvePathPrefixSync,
writeFileWindowFully,
} from "openclaw/plugin-sdk/file-access-runtime";
import type { MemoryWorkspaceFiles } from "openclaw/plugin-sdk/memory-core-host-engine-storage";
import { replaceFileAtomic } from "openclaw/plugin-sdk/security-runtime";
import { getMemoryWorkspaceMaintenance, readWorkspaceText } from "./memory-workspace-files.js";
@ -114,21 +117,7 @@ async function writeExistingMemoryInPlace(params: {
} catch (error) {
const original = Buffer.from(params.expectedContent, "utf-8");
try {
let restored = 0;
while (restored < original.length) {
const { bytesWritten } = await handle.write(
original,
restored,
original.length - restored,
restored,
);
if (bytesWritten <= 0) {
throw new Error(`${path.basename(params.filePath)} restore write made no progress`, {
cause: error,
});
}
restored += bytesWritten;
}
await writeFileWindowFully(handle, original, 0);
await handle.truncate(original.length);
await handle.sync();
} catch (restoreError) {

View file

@ -28,6 +28,7 @@ export {
resolvePathPrefixSync,
} from "../infra/fs-safe-advanced.js";
export { readFileWindowFully } from "../infra/file-read.js";
export { writeFileWindowFully } from "../infra/file-descriptor.js";
export { openRootFile } from "../infra/boundary-file-read.js";
export {
ensureDurableDirectory,