diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index 6b8e1ae9aeed..1601352d6bdd 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -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 | diff --git a/extensions/memory-core/src/short-term-promotion-memory-write.ts b/extensions/memory-core/src/short-term-promotion-memory-write.ts index e966a4310767..ef012d02afaa 100644 --- a/extensions/memory-core/src/short-term-promotion-memory-write.ts +++ b/extensions/memory-core/src/short-term-promotion-memory-write.ts @@ -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) { diff --git a/src/plugin-sdk/file-access-runtime.ts b/src/plugin-sdk/file-access-runtime.ts index 2d038d8a18b3..5f296640c4c2 100644 --- a/src/plugin-sdk/file-access-runtime.ts +++ b/src/plugin-sdk/file-access-runtime.ts @@ -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,