@openclaw/matrix 2026.10.6 → 2026.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +48 -0
  2. package/node_modules/@openclaw/fs-safe/README.md +14 -4
  3. package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +28 -3
  4. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  5. package/node_modules/@openclaw/fs-safe/dist/archive-read.js +11 -5
  6. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +4 -2
  7. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -2
  8. package/node_modules/@openclaw/fs-safe/dist/clone-metadata.js +1 -1
  9. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +2 -0
  10. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +4 -3
  11. package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
  12. package/node_modules/@openclaw/fs-safe/dist/copy-link.js +169 -0
  13. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.d.ts +9 -0
  14. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.js +39 -0
  15. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +1 -1
  16. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +3 -6
  17. package/node_modules/@openclaw/fs-safe/dist/copy.js +3 -3
  18. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.d.ts +12 -0
  19. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.js +29 -0
  20. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +3 -0
  21. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +27 -2
  22. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +1 -1
  23. package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
  24. package/node_modules/@openclaw/fs-safe/dist/file-hash.js +4 -3
  25. package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +1 -0
  26. package/node_modules/@openclaw/fs-safe/dist/file-lock.js +1 -0
  27. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.d.ts +9 -0
  28. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.js +42 -0
  29. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
  30. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.d.ts +1 -0
  31. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.js +5 -0
  32. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +16 -3
  33. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +57 -18
  34. package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +1 -1
  35. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +1 -1
  36. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +2 -0
  37. package/node_modules/@openclaw/fs-safe/dist/native-operations.js +1 -1
  38. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +1 -1
  39. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +2 -0
  40. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +1 -1
  41. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +19 -1
  42. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +4 -1
  43. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +18 -0
  44. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +1 -1
  45. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +3 -0
  46. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
  47. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +3 -0
  48. package/node_modules/@openclaw/fs-safe/dist/native.d.ts +5 -3
  49. package/node_modules/@openclaw/fs-safe/dist/native.js +25 -6
  50. package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +9 -1
  51. package/node_modules/@openclaw/fs-safe/dist/overwrite-file-handle.js +4 -1
  52. package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +1 -0
  53. package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
  54. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +36 -47
  55. package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +2 -0
  56. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +3 -0
  57. package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
  58. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +5 -3
  59. package/node_modules/@openclaw/fs-safe/dist/pipe.d.ts +9 -0
  60. package/node_modules/@openclaw/fs-safe/dist/pipe.js +26 -0
  61. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +2 -5
  62. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
  63. package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +1 -1
  64. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +12 -11
  65. package/node_modules/@openclaw/fs-safe/dist/root-copy.d.ts +18 -0
  66. package/node_modules/@openclaw/fs-safe/dist/root-copy.js +128 -0
  67. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +12 -4
  68. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +6 -92
  69. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +1 -1
  70. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
  71. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +8 -1
  72. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
  73. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +1 -1
  74. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-target.js +8 -1
  75. package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +1 -0
  76. package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +1 -0
  77. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +2 -2
  78. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +11 -5
  79. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +9 -1
  80. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.d.ts +16 -0
  81. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.js +51 -0
  82. package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
  83. package/node_modules/@openclaw/fs-safe/docs/contributing.md +35 -5
  84. package/node_modules/@openclaw/fs-safe/docs/copy.md +22 -0
  85. package/node_modules/@openclaw/fs-safe/docs/durability.md +4 -2
  86. package/node_modules/@openclaw/fs-safe/docs/errors.md +8 -0
  87. package/node_modules/@openclaw/fs-safe/docs/guest.md +2 -0
  88. package/node_modules/@openclaw/fs-safe/docs/install.md +13 -5
  89. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +5 -5
  90. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +15 -5
  91. package/node_modules/@openclaw/fs-safe/docs/native.md +51 -2
  92. package/node_modules/@openclaw/fs-safe/docs/pipe.md +81 -0
  93. package/node_modules/@openclaw/fs-safe/docs/platform-capabilities.md +63 -0
  94. package/node_modules/@openclaw/fs-safe/docs/quickstart.md +23 -3
  95. package/node_modules/@openclaw/fs-safe/docs/reading.md +7 -1
  96. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
  97. package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
  98. package/node_modules/@openclaw/fs-safe/docs/store.md +11 -0
  99. package/node_modules/@openclaw/fs-safe/docs/types.md +45 -80
  100. package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
  101. package/node_modules/@openclaw/fs-safe/package.json +18 -10
  102. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  103. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  104. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  105. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  106. package/node_modules/@openclaw/fs-safe-freebsd-arm64/fs-safe-native.node +0 -0
  107. package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +29 -0
  108. package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
  109. package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +29 -0
  110. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  111. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  112. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  113. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  114. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  115. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  116. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  117. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  118. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/fs-safe-native.node +0 -0
  119. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +29 -0
  120. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  121. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  122. package/package.json +5 -5
@@ -1,6 +1,8 @@
1
1
  # Quickstart
2
2
 
3
- Five minutes. By the end you will have a working `root()` and know how to read, write, atomically replace, and unpack an archive — without your code being able to escape the workspace.
3
+ Build a `root()` and use it to read, write, atomically replace, and unpack an
4
+ archive under a trusted workspace. Confinement strength and concurrent-mutation
5
+ limits depend on the operation and platform; see the [security model](security-model.md).
4
6
 
5
7
  If you have used Go's `os.Root` / `OpenInRoot` or Rust's [`cap-std`](https://github.com/bytecodealliance/cap-std), this is the same shape: a capability-style handle that carries the boundary across every operation. The first thing to internalize is that you stop reasoning about *paths* and start reasoning about *the handle*.
6
8
 
@@ -31,7 +33,9 @@ await fs.write("notes/today.txt", "hello\n");
31
33
  const text = await fs.readText("notes/today.txt");
32
34
  ```
33
35
 
34
- Writes use a sibling temp file plus `rename`, so a partial write never appears at the destination. Reads open with `O_NOFOLLOW` where available and verify the opened fd matches the path identity before returning the buffer.
36
+ Replacement writes use a sibling temp file plus `rename`, so a partial write
37
+ never appears at the destination. Reads open with `O_NOFOLLOW` where available
38
+ and verify the opened fd matches the path identity before returning the buffer.
35
39
 
36
40
  `create()` is the don't-clobber variant of `write()` and throws `already-exists` when the target is already there:
37
41
 
@@ -39,6 +43,13 @@ Writes use a sibling temp file plus `rename`, so a partial write never appears a
39
43
  await fs.create("notes/README.md", "seed\n"); // throws if it already exists
40
44
  ```
41
45
 
46
+ Default create-only writes can expose incomplete content on the JavaScript
47
+ fallback. Pass `{ atomic: true }` to stage complete content before publication.
48
+ When hardlinks are refused, staged publication can still expose an empty
49
+ destination placeholder before replacing it with the completed file. See
50
+ [atomic creation](writing.md#atomic-buffered-creation) and
51
+ [publication without hardlinks](writing.md#publication-without-hardlinks).
52
+
42
53
  ## 3. JSON, with parsing
43
54
 
44
55
  ```ts
@@ -60,7 +71,16 @@ await fs.move("notes/today.txt", "notes/archive/today.txt", { overwrite: true })
60
71
  await fs.remove("notes/archive/today.txt");
61
72
  ```
62
73
 
63
- `move()` defaults to no clobber. That mode requires the native helper so a concurrent target cannot be replaced between an absence check and the rename; without it, the call fails with `helper-unavailable`. Pass `{ overwrite: true }` when replacing the target is intentional. `remove()` removes files and empty directories by default. To remove a non-empty directory, pass `{ recursive: true }`; use `maxEntries`, `maxDepth`, and `signal` to bound the work. See [`root()`](root.md) for removal ordering, limits, and partial-removal semantics.
74
+ `move()` defaults to no clobber and requires native support. Linux `auto` can
75
+ fall back when no-replace rename is unsupported; its directory fallback can
76
+ replace a concurrently created empty directory. Use native `require` when
77
+ that must fail closed. See the [move contract](writing.md#fs-move-from-to-options).
78
+ Pass `{ overwrite: true }` when replacement is intended.
79
+
80
+ `remove()` removes files and empty directories by default. For a non-empty
81
+ directory, pass `{ recursive: true }`; use `maxEntries`, `maxDepth`, and `signal`
82
+ to bound the work. See [`root()`](root.md) for removal ordering, limits, and
83
+ partial-removal semantics.
64
84
 
65
85
  ## 5. Inspect
66
86
 
@@ -81,6 +81,12 @@ For tighter control over malformed-or-missing JSON, use the standalone helpers i
81
81
 
82
82
  Returns a `FileHandle` plus `containment: "best-effort"`, the verified `realPath`, and `stat`. Use this for streaming or partial reads, and **always close the handle**:
83
83
 
84
+ `open()` accepts `RootOpenOptions`: the read symlink and hardlink policies,
85
+ without `maxBytes`. It does not consume bytes or apply the Root's read-size
86
+ budget to later handle or stream operations. Bound that I/O in the caller, or
87
+ use `read()` / `readBytes()` when the library should enforce the whole-file cap.
88
+ The result also supports `await using` for automatic handle cleanup.
89
+
84
90
  ```ts
85
91
  const opened = await fs.open("large.log");
86
92
  try {
@@ -103,7 +109,7 @@ type RootReadOptions = {
103
109
  };
104
110
  ```
105
111
 
106
- `maxBytes` is enforced eagerly: the library reads up to `maxBytes + 1` and throws `too-large` if there is more, so a hostile target cannot silently exhaust memory. Values must be non-negative safe integers or positive `Infinity`; zero is an active cap, while `Infinity` disables it. Explicitly forwarding `undefined` preserves the Root default.
112
+ For the byte-consuming read methods, `maxBytes` is enforced eagerly: the library reads up to `maxBytes + 1` and throws `too-large` if there is more, so a hostile target cannot silently exhaust memory. Values must be non-negative safe integers or positive `Infinity`; zero is an active cap, while `Infinity` disables it. Explicitly forwarding `undefined` preserves the Root default.
107
113
 
108
114
  Safe reads always add the platform's nonblocking open flag where available so a raced FIFO cannot pin a worker indefinitely; regular-file descriptor reads retain normal Node behavior.
109
115
 
@@ -27,11 +27,11 @@ import {
27
27
 
28
28
  ## Constants
29
29
 
30
- ```ts
31
- DEFAULT_SECRET_FILE_MAX_BYTES = 16 * 1024; // 16 KiB
32
- PRIVATE_SECRET_DIR_MODE = 0o700;
33
- PRIVATE_SECRET_FILE_MODE = 0o600;
34
- ```
30
+ | Exported constant | Value |
31
+ |---|---|
32
+ | `DEFAULT_SECRET_FILE_MAX_BYTES` | `16 * 1024` (16 KiB) |
33
+ | `PRIVATE_SECRET_DIR_MODE` | `0o700` |
34
+ | `PRIVATE_SECRET_FILE_MODE` | `0o600` |
35
35
 
36
36
  The 16 KiB cap is intentionally aggressive — credentials should be small. If you need bigger, pass `maxBytes` explicitly.
37
37
 
@@ -1,5 +1,7 @@
1
1
  # File lock
2
2
 
3
+ For [Linux kernel write leases](platform-capabilities.md#linux-write-leases), `FileWriteLease.isHeld() === false` means “do not act on the file,” including while the kernel is still processing a lease break. The caller’s `SIGIO` handler still owns break notification.
4
+
3
5
  `acquireFileLock()` and `withFileLock()` provide a cross-process file lock with retry and process-exit cleanup. The lock is implemented as a sidecar file (e.g. `state.json` ↔ `state.json.lock`) — only one acquirer can create the sidecar with `O_CREAT | O_EXCL` at a time.
4
6
 
5
7
  On Windows, both the target and an explicitly supplied `lockPath` reject NTFS
@@ -72,21 +74,29 @@ The raw sidecar bytes are not a canonical JSON representation: tools that trim o
72
74
  ## API
73
75
 
74
76
  ```ts
75
- function acquireFileLock<TPayload>(
77
+ import type {
78
+ FileLockAcquireOptions,
79
+ FileLockHandle,
80
+ FileLockManager,
81
+ FileLockSyncAcquireOptions,
82
+ FileLockSyncHandle,
83
+ } from "@openclaw/fs-safe/file-lock";
84
+
85
+ declare function acquireFileLock<TPayload extends Record<string, unknown>>(
76
86
  targetPath: string,
77
87
  options: FileLockAcquireOptions<TPayload>,
78
88
  ): Promise<FileLockHandle>;
79
89
 
80
- function withFileLock<T, TPayload>(
90
+ declare function withFileLock<T, TPayload extends Record<string, unknown>>(
81
91
  targetPath: string,
82
92
  options: FileLockAcquireOptions<TPayload>,
83
93
  fn: () => Promise<T>,
84
94
  ): Promise<T>;
85
95
 
86
- function createFileLockManager(key: string): FileLockManager;
96
+ declare function createFileLockManager(key: string): FileLockManager;
87
97
 
88
- function acquireFileLockSync<TPayload>(targetPath: string, options: FileLockSyncAcquireOptions<TPayload>): FileLockSyncHandle;
89
- function withFileLockSync<T, TPayload>(targetPath: string, options: FileLockSyncAcquireOptions<TPayload>, fn: () => T): T;
98
+ declare function acquireFileLockSync<TPayload extends Record<string, unknown>>(targetPath: string, options: FileLockSyncAcquireOptions<TPayload>): FileLockSyncHandle;
99
+ declare function withFileLockSync<T, TPayload extends Record<string, unknown>>(targetPath: string, options: FileLockSyncAcquireOptions<TPayload>, fn: () => T): T;
90
100
  ```
91
101
 
92
102
  `managerKey` is an optional identifier used to keep state isolated across multiple lock domains in the same process. Use distinct keys for distinct domains (`"snapshot"`, `"compact"`, `"build"`). If omitted, fs-safe derives one from the target path.
@@ -141,7 +151,7 @@ type FileLockAcquireOptions<TPayload extends Record<string, unknown>> = {
141
151
  shouldReclaim?: (params: {
142
152
  lockPath: string;
143
153
  normalizedTargetPath: string;
144
- payload: Record<string, unknown> | null;
154
+ payload: unknown;
145
155
  staleMs: number;
146
156
  nowMs: number;
147
157
  heldByThisProcess: boolean;
@@ -150,7 +160,7 @@ type FileLockAcquireOptions<TPayload extends Record<string, unknown>> = {
150
160
  lockPath: string;
151
161
  normalizedTargetPath: string;
152
162
  raw: string;
153
- payload: Record<string, unknown> | null;
163
+ payload: unknown;
154
164
  }) => boolean | Promise<boolean>;
155
165
  metadata?: Record<string, unknown>; // attached to heldEntries() output for diagnostics
156
166
  parsePayload?: (raw: string) => unknown;
@@ -542,24 +552,32 @@ holder still alive":
542
552
 
543
553
  ```ts
544
554
  import { kill } from "node:process";
555
+ import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
545
556
 
546
557
  const handle = await acquireFileLock(targetPath, {
547
558
  staleMs: 60_000,
548
559
  payload: () => ({ pid: process.pid }),
549
- shouldReclaim: ({ payload, nowMs, staleMs }) => {
550
- if (!payload) return true;
551
- const pid = Number(payload.pid);
552
- if (!Number.isFinite(pid)) return true;
560
+ shouldReclaim: ({ payload }) => {
561
+ if (!payload || typeof payload !== "object" || !("pid" in payload)) return true;
562
+ const pid = payload.pid;
563
+ if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0) return true;
553
564
  try {
554
565
  kill(pid, 0);
555
566
  return false; // process still alive — keep waiting
556
- } catch {
557
- return true; // process gone — fail closed for recovery
567
+ } catch (error) {
568
+ return error !== null && typeof error === "object" &&
569
+ "code" in error && error.code === "ESRCH"; // only a missing process is stale
558
570
  }
559
571
  },
560
572
  });
561
573
  ```
562
574
 
575
+ The observed payload is `unknown`, even when this caller writes a typed payload.
576
+ Validate it before reading fields. Permission failures such as `EPERM` do not
577
+ prove that the holder exited. PID checks assume a shared local PID namespace;
578
+ PID reuse can keep a stale lock waiting. This callback classifies staleness,
579
+ not ownership or permission to remove another holder's lock.
580
+
563
581
  `heldByThisProcess` is true when this manager already holds the lock. A `true` result marks the observed sidecar as stale; `staleRecovery` then decides whether acquisition fails closed or attempts caller-approved removal.
564
582
 
565
583
  ## Stale recovery: guarded `remove-if-unchanged`
@@ -84,6 +84,17 @@ Batch loading skips invalid entry names, malformed, oversized, or unreadable ent
84
84
 
85
85
  Failed destinations are create-only. Quarantine publishes the claimed file by hardlink, so the queue and failed directories must share a filesystem with hardlink support. If `failed/<id>.json` already exists, quarantine rejects while preserving both that earlier evidence and the current claimed entry instead of overwriting either file. The `read` callback continues to receive the logical `.json` path even though bytes are read and migrations are written through the claimed path.
86
86
 
87
+ Claim, quarantine, and retirement recovery require hardlinks. If a link syscall
88
+ is refused with `EACCES`, `EPERM`, `EXDEV`, `ENOTSUP`, `EOPNOTSUPP`, or `ENOSYS`,
89
+ the operation rejects with `FsSafeError("helper-unavailable")` and retains the
90
+ original error in `cause`. This includes Android/Termux environments that deny
91
+ hardlinks. The refusal leaves the pending entry, processing claim, or retirement
92
+ record intact for retry after the capability or permission problem is resolved;
93
+ it does not acknowledge delivery. Batch loading propagates this capability
94
+ failure too. The queue does not fall back to copying or overwriting rename:
95
+ those operations cannot preserve its shared-inode recovery and create-only
96
+ collision evidence. Enqueue alone does not prove that an entry can be claimed.
97
+
87
98
  Migrations stay bound to the exact processing file opened for that load. The
88
99
  read descriptor remains pinned while the callback runs outside the transfer
89
100
  lock; after the callback returns, migration reacquires the lock and rechecks the
@@ -79,103 +79,68 @@ Used by helpers that accept either an encoding (returning a string) or `null` (r
79
79
 
80
80
  ## `OpenResult` / `ReadResult`
81
81
 
82
- Returned by `Root.open()` and `Root.read()`:
82
+ Import the result types rather than copying their shapes:
83
83
 
84
84
  ```ts
85
- type OpenResult = {
86
- handle: import("node:fs/promises").FileHandle;
87
- containment: "kernel-atomic" | "best-effort";
88
- realPath: string;
89
- stat: import("node:fs").Stats;
90
- };
91
-
92
- type ReadResult = {
93
- buffer: Buffer;
94
- containment: "kernel-atomic" | "best-effort";
95
- realPath: string;
96
- stat: import("node:fs").Stats;
97
- };
85
+ import type { OpenResult, ReadResult, WritableOpenResult } from "@openclaw/fs-safe/root";
98
86
  ```
99
87
 
100
88
  `realPath` is the canonical real path the read or open landed on, after symlink resolution; `stat` is the verified `fstat` result. Public root results currently report `containment: "best-effort"`; the union also describes direct native `openBeneath()` results, which report `"kernel-atomic"` on Linux. See the [security model](security-model.md#containment-guarantees-by-platform).
101
89
 
90
+ `ReadResult` carries the read `buffer`. `OpenResult` and `WritableOpenResult`
91
+ carry an owned Node `FileHandle` as `handle` and implement
92
+ `[Symbol.asyncDispose]()`; use `await using` or explicitly close the handle.
93
+ See [reading](reading.md#fs-open-rel-options) and
94
+ [writable handles](writing.md#openwritable-for-streaming) for ownership and streaming.
95
+
102
96
  ## `RootDefaults` / `RootOptions`
103
97
 
104
98
  ```ts
105
- type RenameIdentityPolicy = "strict" | "verify-content-with-lock";
106
-
107
- type RootDefaults = {
108
- assertBeforeMutation?: () => void;
109
- denyMutations?: DenyMutationPolicy;
110
- durable?: boolean; // default true for write/create/writeJson/createJson/append
111
- hardlinks?: "reject" | "allow";
112
- maxBytes?: number;
113
- mkdir?: boolean; // default true for mutation methods
114
- mode?: number;
115
- renameIdentity?: RenameIdentityPolicy;
116
- symlinks?: "reject" | "follow-within-root" | "follow-parents-within-root";
117
- mutationSymlinks?: MutationSymlinkPolicy;
118
- };
119
-
120
- type DenyMutationPolicy = {
121
- paths?: readonly string[];
122
- prefixes?: readonly string[];
123
- };
124
-
125
- type RootOptions = {
126
- rootDir: string;
127
- defaults?: RootDefaults;
128
- };
99
+ import type {
100
+ DenyMutationPolicy,
101
+ RenameIdentityPolicy,
102
+ RootDefaults,
103
+ RootOptions,
104
+ } from "@openclaw/fs-safe/root";
129
105
  ```
130
106
 
131
- `RootDefaults` is what `root(rootDir, defaults)` accepts. See [`root()`](root.md) for the per-method options that override these. `denyMutations` and `assertBeforeMutation` are exceptions: deny entries are merged, and the root authority assertion runs before the per-call assertion.
107
+ `RootDefaults` is what `root(rootDir, defaults)` accepts. `RootOptions` is the
108
+ `{ rootDir, defaults? }` record. See the [Root signature and defaults](root.md#signature)
109
+ for fields and behavior. `denyMutations` and `assertBeforeMutation` compose with
110
+ per-call restrictions: deny entries are merged, and the root authority assertion
111
+ runs before the per-call assertion.
132
112
 
133
113
  ## `RootReadOptions` / `RootWriteOptions` / `RootCopyOptions`
134
114
 
135
115
  ```ts
136
- import type { CopyCloneMode, RootCopyPublicationReceipt } from "@openclaw/fs-safe";
137
-
138
- type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "symlinks">;
139
- type RootWriteOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "durable" | "mkdir" | "mode" | "renameIdentity" | "mutationSymlinks"> & {
140
- encoding?: BufferEncoding;
141
- overwrite?: boolean;
142
- };
143
- type RootCopyOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "durable" | "maxBytes" | "mkdir" | "mode" | "mutationSymlinks"> & {
144
- sourceHardlinks?: "reject" | "allow";
145
- overwrite?: boolean;
146
- clone?: CopyCloneMode;
147
- signal?: AbortSignal;
148
- preserveSourceMode?: boolean;
149
- onDestinationPublished?: (receipt: RootCopyPublicationReceipt) => void;
150
- };
151
- type RootOpenWritableOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mkdir" | "mode" | "mutationSymlinks"> & {
152
- writeMode?: "replace" | "append" | "update";
153
- };
154
- type RootWriteJsonOptions = RootWriteOptions & {
155
- replacer?: Parameters<typeof JSON.stringify>[1];
156
- space?: Parameters<typeof JSON.stringify>[2];
157
- trailingNewline?: boolean;
158
- };
159
- type RootAppendOptions = RootWriteOptions & {
160
- prependNewlineIfNeeded?: boolean;
161
- };
162
- type RootMoveOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"> & {
163
- overwrite?: boolean;
164
- };
165
- type RootRemoveOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"> & {
166
- recursive?: boolean;
167
- force?: boolean;
168
- order?: "filesystem" | "sorted";
169
- maxEntries?: number;
170
- maxDepth?: number;
171
- signal?: AbortSignal;
172
- };
173
- type RootMkdirOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"> & {
174
- private?: boolean;
175
- };
116
+ import type {
117
+ RootAppendOptions,
118
+ RootCopyOptions,
119
+ RootCreateJsonOptions,
120
+ RootCreateOptions,
121
+ RootCreateStreamOptions,
122
+ RootMkdirOptions,
123
+ RootMoveOptions,
124
+ RootOpenOptions,
125
+ RootOpenWritableOptions,
126
+ RootReadOptions,
127
+ RootRemoveOptions,
128
+ RootWriteJsonOptions,
129
+ RootWriteOptions,
130
+ } from "@openclaw/fs-safe/root";
176
131
  ```
177
132
 
178
- Per-method option shapes. Each picks the `RootDefaults` keys that apply, plus method-specific extras.
133
+ Each method accepts only its applicable defaults and method-specific options:
134
+
135
+ | Types | Contract |
136
+ |---|---|
137
+ | `RootReadOptions` | Read symlink/hardlink policy and `maxBytes`; see [read options](reading.md#read-options). |
138
+ | `RootOpenOptions` | Read link policies, without `maxBytes`. The returned handle's I/O belongs to the caller. |
139
+ | `RootWriteOptions`, `RootWriteJsonOptions`, `RootAppendOptions` | [Write options](writing.md#write-options), JSON formatting, and append newline handling. |
140
+ | `RootCreateOptions`, `RootCreateJsonOptions`, `RootCreateStreamOptions` | [Exclusive creation](writing.md#atomic-buffered-creation), private permissions, and `durable: "file"`; streamed creation also accepts byte limits and cancellation. |
141
+ | `RootCopyOptions` | [Guarded copying](writing.md#write-verbs), source policy, `CopyCloneMode`, and `RootCopyPublicationReceipt`. |
142
+ | `RootOpenWritableOptions` | [Writable handles](writing.md#openwritable-for-streaming) with `writeMode`, without buffered-write durability or byte limits. |
143
+ | `RootMoveOptions`, `RootRemoveOptions`, `RootMkdirOptions` | [Mutation methods](root.md#writes) with their collision, removal-budget, and directory-privacy options. |
179
144
 
180
145
  ## `SymlinkPolicy` / `MutationSymlinkPolicy` / `HardlinkPolicy`
181
146
 
@@ -223,12 +223,12 @@ await fs.createJson("config/settings.json", { enabled: true }, { atomic: true })
223
223
  await fs.create("config/flushed.json", initial, { atomic: true, durable: "file" });
224
224
  ```
225
225
 
226
- `atomic: true` keeps the destination absent until all bytes have been written.
226
+ `atomic: true` finishes all bytes in a sibling stage before publication.
227
227
  The native backend uses its no-replace rename; the JavaScript fallback hardlinks
228
228
  the completed stage and unlinks its temporary name in the same JavaScript turn.
229
- The fallback requires hardlink support and fails without publishing partial bytes
230
- when that mechanism is unavailable. Other processes can briefly observe both
231
- names. Existing and raced entries are preserved, including dangling symlinks;
229
+ When the OS refuses hardlinks, it uses the [placeholder-rename fallback](#publication-without-hardlinks).
230
+ Other processes can briefly observe both linked names, or an empty placeholder
231
+ before the atomic replacement. Entries present at the exclusive claim are preserved, including dangling symlinks;
232
232
  ordinary confinement, type, hardlink, and symlink-policy rejections still apply.
233
233
  `assertBeforeMutation` retains its live checks through content writes and publication.
234
234
 
@@ -254,7 +254,7 @@ complete publication, and an indeterminate link or native rename must preserve n
254
254
  recovery. A cleanup or close failure also retains the original operation failure.
255
255
  The JavaScript fallback checks the destination again immediately before attempting
256
256
  publication. A collision observed there leaves publication unattempted and cleans
257
- the owned stage. A later collision or other error from the link call remains
257
+ the owned stage. A later collision or unclassified error from the link call remains
258
258
  indeterminate, including `EEXIST`; the error code alone does not prove that the
259
259
  filesystem left both names unchanged.
260
260
  No later verification, mode, or synchronization failure authorizes deleting an
@@ -293,9 +293,10 @@ unlimited. Zero permits an empty input only. Invalid limits reject before I/O.
293
293
  Unlike buffered creation's JavaScript fallback, streamed creation stages all
294
294
  chunks before publishing the final name. Native mode uses no-replace rename;
295
295
  the JavaScript fallback hardlinks the completed stage and removes its temporary
296
- name in the same JavaScript turn. That fallback requires a filesystem supporting
297
- hardlinks; other processes may briefly observe both names. An existing or
298
- concurrently created destination is preserved. Existing-target preflight does
296
+ name in the same JavaScript turn. If hardlinks are refused, it uses the
297
+ [placeholder-rename fallback](#publication-without-hardlinks); other processes may
298
+ briefly observe an empty placeholder. A destination present at the exclusive
299
+ claim is preserved. Existing-target preflight does
299
300
  not consume the input. The final mode and durability policy use the same guarded
300
301
  writer as other Root operations.
301
302
 
@@ -408,8 +409,103 @@ With `overwrite: false`, an existing destination produces `already-exists` and
408
409
  is never altered. Copying prepares a private sibling file before publishing its
409
410
  completed contents. Native mode uses no-replace rename. The guarded JavaScript
410
411
  fallback links the completed stage and removes its temporary name in the same
411
- JavaScript turn; the filesystem must support hardlinks. Other processes can
412
- briefly observe both names. The source is never hardlinked to the destination.
412
+ JavaScript turn. If the OS refuses hardlinks, it uses the placeholder-rename
413
+ fallback below. Other processes can briefly observe both linked names or an
414
+ empty placeholder. The source is never hardlinked to the destination.
415
+
416
+ ### Copy metadata and source links
417
+
418
+ `preserveMetadata: true` restores metadata to the completed staging entry before
419
+ publication. It defaults to `false`; it does not select permissions, ownership,
420
+ ACLs, or extended attributes. Use the existing `preserveSourceMode` option as
421
+ well when source permissions should be selected. An explicit `mode` or Root
422
+ mode default continues to take precedence over `preserveSourceMode`.
423
+
424
+ | State | `preserveMetadata: true` | Permission selection |
425
+ | --- | --- | --- |
426
+ | POSIX access and modification times | Preserved | Not affected by `mode` |
427
+ | POSIX permission and executable bits | Not selected by this option | Existing `mode` / `preserveSourceMode` rules |
428
+ | Windows creation, access and write times | Preserved by native copying | Not affected by `mode` |
429
+ | Windows hidden, system, archive, temporary, offline, not-content-indexed | Preserved by native copying | Not affected by `mode` |
430
+ | Windows readonly | Not selected by this option | Existing `mode` / `preserveSourceMode` rules |
431
+ | Ownership, ACLs, POSIX extended attributes | Not copied | Existing destination policy |
432
+ | Windows sparse/integrity flags | Not set through metadata copying | Owned by the clone/FSCTL mechanism |
433
+
434
+ This applies to clones, native byte copies, and JavaScript byte copies. On
435
+ POSIX, native timestamp restoration retains nanoseconds; JavaScript restoration
436
+ uses Node's timestamp precision, as the portable tree copier does. On
437
+ Windows without the native metadata helper, JavaScript can restore only access
438
+ and write times; creation time and the non-permission attributes are not
439
+ preserved. Native `require` mode rejects a missing helper before publication.
440
+ The Windows attribute mask and POSIX timestamp conversion share the
441
+ [tree-copy implementation](copy.md#api); native tree copying has additional
442
+ metadata guarantees that this narrower option does not request.
443
+
444
+ `sourceSymlinks` defaults to `"reject"`, preserving the existing rejection of
445
+ symlinked absolute string sources with code `symlink` and message
446
+ `symlink not allowed`. There is no follow mode. Set
447
+ `sourceSymlinks: "copy-link"` to copy a source link itself, including a dangling
448
+ file or directory link. Its literal target remains data: copying never resolves
449
+ it. Windows uses the link's own directory attribute and copies the reparse
450
+ payload through the same native mechanism as `copyTree`.
451
+
452
+ ```ts
453
+ await destination.copyIn("template-link", "/srv/templates/template-link", {
454
+ sourceSymlinks: "copy-link",
455
+ preserveMetadata: true,
456
+ });
457
+ ```
458
+
459
+ Link destinations are always exclusive. Omit `overwrite` or set it to `false`;
460
+ explicit `overwrite: true` with a link source fails with `invalid-path` before
461
+ mutation. Regular files retain the usual overwrite behavior. Destination Root
462
+ confinement, denied paths, parent-link policy, and mutation-authority checks
463
+ apply to copied links too.
464
+ macOS link permissions use the selected mode on the link itself; Linux link
465
+ permissions remain fixed by the kernel. The link target's permissions are never
466
+ changed.
467
+
468
+ `"copy-link"` requires an absolute-path string source. A `{ root, relativePath }`
469
+ source fails with `invalid-path` before either source method is called or a
470
+ destination is mutated: its `open` and `stat` capabilities cannot expose literal
471
+ link text or Windows link type without following the link. With the default
472
+ `"reject"`, capability sources continue to use their own Root read policies.
473
+
474
+ macOS and Windows link copying requires native support and otherwise reports
475
+ `helper-unavailable` before destination mutation. macOS `link(2)` follows its
476
+ source, so it cannot publish dangling symlink inodes exclusively. Linux can
477
+ publish the staged symlink inode with guarded hardlink/unlink when native
478
+ support is off or automatic no-replace rename is unavailable. This has the
479
+ same documented pathname identity windows as the JavaScript write fallback;
480
+ no operation follows the link target. Successful native publication uses
481
+ no-replace rename. No link publication uses the regular-file placeholder
482
+ fallback.
483
+
484
+ ### Publication without hardlinks
485
+
486
+ For staged JavaScript no-replace copies and writes, `EACCES`, `EPERM`, `EXDEV`,
487
+ `ENOTSUP`, `EOPNOTSUPP`, and `ENOSYS` from the link operation permit a
488
+ **placeholder-rename** fallback, including Android/Termux hardlink restrictions.
489
+ It exclusively creates a mode-`0600`, no-follow destination, closes it after
490
+ capturing its exact identity, rechecks the parent and stage, and requires the
491
+ same empty, singly linked regular placeholder before renaming the completed
492
+ stage over it. A real creation-permission failure still rejects. `EEXIST` from
493
+ the link operation does not permit fallback or establish non-publication.
494
+
495
+ An empty placeholder is observable before the atomic replacement, and a crash
496
+ can leave that placeholder and the completed stage. Detected placeholder swaps
497
+ reject without removing foreign entries; failed pre-rename operations remove
498
+ only a verified owned empty placeholder. Failed cleanup reports `helper-failed`
499
+ with `details.publication: "not-published"` and `details.cleanup: "failed"`.
500
+ After rename, verification requires the stage name to be absent and the target
501
+ to retain the staged identity; the complete published file is preserved on failure.
502
+
503
+ These pathname checks are best-effort, not compare-and-swap: a same-directory
504
+ writer can replace the placeholder after the final check, and rename can replace
505
+ that intervening file or symlink (without following it). Protect the parent from
506
+ untrusted mutation when strict concurrent no-clobber is required. This fallback
507
+ preserves existing receipt shapes; `RootCopyPublicationReceipt` reports identity
508
+ and path, with no method or fallback field.
413
509
 
414
510
  `clone` chooses the file-data transfer strategy through `CopyCloneMode`, shared
415
511
  with [`copyTree`](copy.md#api). File copies default to `"never"`; tree copies
@@ -421,11 +517,19 @@ default to `"auto"`:
421
517
  | `auto` | Try native file cloning, then copy offload or ordinary byte copying when cloning is unavailable. |
422
518
  | `always` | Require native cloning; fail when the binding or filesystem cannot provide it. |
423
519
 
424
- Native file cloning supports APFS and supported Linux filesystems. On Linux, a
520
+ Native file cloning supports APFS, supported Linux filesystems, and Windows ReFS
521
+ (including Dev Drive). On Linux, a
425
522
  `FICLONE` request denied with `EPERM`, as container seccomp profiles commonly
426
523
  do, counts as unavailable cloning: `auto` copies bytes and `always` fails with
427
- `unsupported-platform`. Windows currently uses byte copying for `never` and
428
- `auto`; `always` fails. Clone choice
524
+ `unsupported-platform`. On Windows, `always` requires source and destination on
525
+ the same ReFS volume; other volumes report `unsupported-platform`. `auto` falls
526
+ back to bytes only for unavailable cloning capabilities, and `never` copies bytes.
527
+ ReFS named streams are rejected rather than silently discarded by cloning.
528
+ For successful native ReFS clones through either `copyIn` or `copyTree`, all full
529
+ clusters are block-cloned; a final partial cluster may be copied. Exact tail bytes and logical file size are
530
+ preserved. See the [shared ReFS contract](copy.md) and Microsoft's
531
+ [cluster-alignment and EOF requirements](https://learn.microsoft.com/en-us/windows/win32/fileio/block-cloning#restrictions-and-remarks).
532
+ Clone choice
429
533
  does not change modes, durability, root confinement, or source and publication
430
534
  identity checks. The shared strategy does not replace Root's guarded regular-file
431
535
  contract with `copyTree`'s caller-owned immutable-tree and metadata contract.
@@ -560,6 +664,8 @@ await fs.remove("scratch/finished-job", {
560
664
  Budgets must be non-negative safe integers or explicit `Infinity`, and require
561
665
  `recursive: true`. Omitted budgets retain their finite defaults. An entry or
562
666
  depth limit throws `too-large` before processing the over-budget entry.
667
+ Once the parent is admitted, `maxEntries: 0` rejects before inspecting even a
668
+ missing requested entry; `force: true` does not bypass that budget.
563
669
 
564
670
  The default filesystem order streams each directory once, using memory and open
565
671
  handles proportional to depth rather than directory width. Sorted order also
@@ -643,7 +749,8 @@ for the precise parent-pinning guarantee and remaining same-call limitations.
643
749
 
644
750
  ### `fs.mkdir(rel)`
645
751
 
646
- `mkdir -p`. Creates missing parents.
752
+ `mkdir -p`. Creates missing parents. An existing regular file at the requested
753
+ directory name rejects with `not-file` in every native mode and remains unchanged.
647
754
 
648
755
  `require` mode creates each missing component relative to a retained directory,
649
756
  opens the child without following a final symlink, and checks its identity before
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
5
5
  "keywords": [
6
6
  "filesystem",
@@ -83,6 +83,10 @@
83
83
  "types": "./dist/secret.d.ts",
84
84
  "default": "./dist/secret.js"
85
85
  },
86
+ "./pipe": {
87
+ "types": "./dist/pipe.d.ts",
88
+ "default": "./dist/pipe.js"
89
+ },
86
90
  "./permissions": {
87
91
  "types": "./dist/permissions-public.d.ts",
88
92
  "default": "./dist/permissions-public.js"
@@ -156,6 +160,7 @@
156
160
  "docs:check": "node scripts/check-doc-examples.mjs",
157
161
  "docs:site": "node scripts/build-docs-site.mjs",
158
162
  "native:build": "pnpm --filter @openclaw/fs-safe-native-build build",
163
+ "native:build:freebsd": "node scripts/build-freebsd.mjs",
159
164
  "native:test": "cargo test --workspace",
160
165
  "pack:check": "pnpm build && node scripts/check-pack.mjs",
161
166
  "public-api:update": "pnpm build && node scripts/check-pack.mjs --update-public-api",
@@ -173,19 +178,22 @@
173
178
  "archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
174
179
  },
175
180
  "optionalDependencies": {
176
- "@openclaw/fs-safe-darwin-arm64": "0.25.0",
177
- "@openclaw/fs-safe-darwin-x64": "0.25.0",
178
- "@openclaw/fs-safe-linux-arm64-gnu": "0.25.0",
179
- "@openclaw/fs-safe-linux-arm64-musl": "0.25.0",
180
- "@openclaw/fs-safe-linux-x64-gnu": "0.25.0",
181
- "@openclaw/fs-safe-linux-x64-musl": "0.25.0",
182
- "@openclaw/fs-safe-win32-x64-msvc": "0.25.0",
181
+ "@openclaw/fs-safe-darwin-arm64": "0.26.0",
182
+ "@openclaw/fs-safe-darwin-x64": "0.26.0",
183
+ "@openclaw/fs-safe-freebsd-arm64": "0.26.0",
184
+ "@openclaw/fs-safe-freebsd-x64": "0.26.0",
185
+ "@openclaw/fs-safe-linux-arm64-gnu": "0.26.0",
186
+ "@openclaw/fs-safe-linux-arm64-musl": "0.26.0",
187
+ "@openclaw/fs-safe-linux-x64-gnu": "0.26.0",
188
+ "@openclaw/fs-safe-linux-x64-musl": "0.26.0",
189
+ "@openclaw/fs-safe-win32-arm64-msvc": "0.26.0",
190
+ "@openclaw/fs-safe-win32-x64-msvc": "0.26.0",
183
191
  "jszip": "^3.10.2"
184
192
  },
185
193
  "devDependencies": {
186
194
  "@emnapi/runtime": "2.0.0-alpha.6",
187
195
  "@napi-rs/cli": "3.10.8",
188
- "@types/node": "^26.6.4",
196
+ "@types/node": "^26.6.5",
189
197
  "@vitest/coverage-v8": "5.0.3",
190
198
  "fast-check": "^4.10.2",
191
199
  "istanbul-lib-coverage": "3.2.2",
@@ -194,7 +202,7 @@
194
202
  "sigstore": "5.0.0",
195
203
  "tar": "7.5.22",
196
204
  "typescript": "^7.0.2",
197
- "vite": "8.3.3",
205
+ "vite": "8.3.4",
198
206
  "vitest": "^5.0.3"
199
207
  },
200
208
  "engines": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe-darwin-arm64",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "macOS arm64 native binding for @openclaw/fs-safe.",
5
5
  "license": "MIT",
6
6
  "author": "OpenClaw Team <dev@openclaw.ai>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe-darwin-x64",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "macOS x64 native binding for @openclaw/fs-safe.",
5
5
  "license": "MIT",
6
6
  "author": "OpenClaw Team <dev@openclaw.ai>",