@openclaw/feishu 2026.9.8 → 2026.10.1-beta.1
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.
- package/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
- package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
- package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
- package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
- package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
- package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
- package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
- package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
- package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
- package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
- package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
- package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
- package/dist/api.js +234 -297
- package/dist/channel-plugin-api.js +1 -1
- package/dist/doctor-contract-api.js +2 -2
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/setup-entry.js +1 -1
- package/dist/subagent-hooks-api.js +1 -1
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
- package/node_modules/@openclaw/fs-safe/README.md +70 -379
- package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
- package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
- package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
- package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
- package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
- package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
- package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
- package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
- package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
- package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
- package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
- package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
- package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
- package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
- package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
- package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
- package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
- package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
- package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
- package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
- package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
- package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
- package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
- package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
- package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
- package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
- package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
- package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
- package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
- package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
- package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
- package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
- package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
- package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
- package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
- package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
- package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
- package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
- package/node_modules/@openclaw/fs-safe/package.json +10 -10
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/package.json +5 -5
- package/skills/feishu-wiki/SKILL.md +1 -1
- package/dist/.setup/accounts-wRqItHug.mjs +0 -206
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
- package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
- package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
- package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +0 -110
|
@@ -57,66 +57,36 @@ This is a **library-level guardrail**, not OS-level isolation. It does not repla
|
|
|
57
57
|
pnpm add @openclaw/fs-safe
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Node 22 or newer.
|
|
60
|
+
Requires Node.js 22 or newer. Bun 1.4.2 is also supported with the
|
|
61
|
+
[Bun runtime requirements](docs/install.md#bun-runtime), including the matching
|
|
62
|
+
Rust addon on macOS and Linux. See
|
|
63
|
+
[Installation](docs/install.md) for supported platforms and optional dependencies.
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
The package installs one prebuilt native binding for the current supported target. It
|
|
65
|
-
supplies fd-relative and atomic no-replace primitives that Node does not expose
|
|
66
|
-
directly. Configure the lazy loader before first use when you need a strict
|
|
67
|
-
environment policy:
|
|
65
|
+
Configure native policy before first use:
|
|
68
66
|
|
|
69
67
|
```ts
|
|
70
68
|
import { configureFsSafeNative } from "@openclaw/fs-safe";
|
|
71
69
|
|
|
72
70
|
configureFsSafeNative({ mode: "auto" }); // default: native when available
|
|
73
71
|
configureFsSafeNative({ mode: "off" }); // disable the addon; use supported fallbacks
|
|
74
|
-
configureFsSafeNative({ mode: "require" }); // fail closed if the
|
|
72
|
+
configureFsSafeNative({ mode: "require" }); // fail closed if the operation's native capability is unavailable
|
|
75
73
|
```
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
Equivalent env var: `FS_SAFE_NATIVE_MODE=auto|off|require`. The seven bindings
|
|
86
|
-
ship as exact-version optional packages filtered by OS, CPU, and Linux libc, so
|
|
87
|
-
a normal install receives only its matching binary. Linux GNU x64/arm64 bindings
|
|
88
|
-
support [glibc 2.28 or newer](docs/install.md#supported-native-platforms), including
|
|
89
|
-
RHEL 8-family systems. There are no postinstall
|
|
90
|
-
steps, runtime downloads, or consumer Rust builds. On a platform without a
|
|
91
|
-
published binding, or when optional dependencies are omitted, `auto` silently retains lexical and canonical root
|
|
92
|
-
checks, no-follow opens, guarded temp+rename writes, and post-write identity
|
|
93
|
-
verification. See the [native
|
|
94
|
-
helper policy](docs/native-helper.md) for the exact boundary and deployment
|
|
95
|
-
tradeoff, and [native architecture](docs/native.md) for the platform mechanisms
|
|
96
|
-
and policy ownership model.
|
|
97
|
-
|
|
98
|
-
Open results report the mechanism's containment class as `"kernel-atomic"` or
|
|
99
|
-
`"best-effort"`. Linux native `openBeneath()` is kernel-atomic when `openat2`
|
|
100
|
-
is available. Older kernels and syscall-filtered containers use a guarded
|
|
101
|
-
descriptor-relative walk reporting best-effort; nested no-clobber moves keep
|
|
102
|
-
atomic `renameat2(RENAME_NOREPLACE)`. macOS, Windows, and guarded JavaScript
|
|
103
|
-
results are best-effort. See [Linux compatibility](docs/native.md#linux-without-openat2). See the [security model](docs/security-model.md#containment-guarantees-by-platform) before using that fact in higher-level policy.
|
|
75
|
+
`FS_SAFE_NATIVE_MODE=auto|off|require` selects the same policy. Native-only
|
|
76
|
+
operations fail with `helper-unavailable` when their capability is unavailable.
|
|
77
|
+
Guarded JavaScript mutations are best-effort: a hostile peer can redirect a
|
|
78
|
+
pathname mutation before its post-check detects the escape. `require` selects
|
|
79
|
+
hardened native paths where documented, but does not make every operation
|
|
80
|
+
kernel-atomic. Read the [native helper policy](docs/native-helper.md) and
|
|
81
|
+
[operation/platform matrix](docs/security-model.md#native-root-mutation-capabilities)
|
|
82
|
+
when concurrent mutation is in scope.
|
|
104
83
|
|
|
105
84
|
## Migrating from the Python helper
|
|
106
85
|
|
|
107
|
-
Version 0.5
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
`pythonPath`, `FS_SAFE_PYTHON`, and interpreter provisioning because the native
|
|
112
|
-
loader does not spawn Python.
|
|
113
|
-
|
|
114
|
-
Version 0.5 retains the old function and documented `FS_SAFE_PYTHON*`
|
|
115
|
-
and OpenClaw Python environment names emit one `FS_SAFE_PYTHON_DEPRECATED`
|
|
116
|
-
warning and map the old mode to its native equivalent. They are migration
|
|
117
|
-
bridges for shipped 0.4 consumers, not an alternate helper contract. Update
|
|
118
|
-
startup configuration as part of the 0.5 upgrade rather than relying on the
|
|
119
|
-
warning path. Follow the [0.5 migration checklist](docs/migrating-to-0.5.md).
|
|
86
|
+
Version 0.5 replaced the Python worker with prebuilt native bindings. Follow the
|
|
87
|
+
[0.5 migration checklist](docs/migrating-to-0.5.md) to replace the removed Python
|
|
88
|
+
configuration with native mode selection; current archive changes are covered
|
|
89
|
+
in the [0.6 migration guide](docs/migrating-to-0.6.md).
|
|
120
90
|
|
|
121
91
|
## Quick start
|
|
122
92
|
|
|
@@ -138,68 +108,18 @@ await fs.move("notes/today.txt", "notes/archive/today.txt", { overwrite: true })
|
|
|
138
108
|
await fs.remove("notes/archive/today.txt");
|
|
139
109
|
```
|
|
140
110
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
before every removal, and supports cancellation. See [removal options and partial
|
|
144
|
-
failure semantics](docs/writing.md).
|
|
145
|
-
|
|
146
|
-
`root()` takes the trusted directory; relative paths in subsequent calls are resolved against it. Defaults you pass to `root()` apply to every call below; per-call options override them.
|
|
147
|
-
|
|
148
|
-
`copyIn()` also accepts `{ root: sourceRoot, relativePath }`, `overwrite: false`,
|
|
149
|
-
and `clone: "auto"` for guarded, exclusive file copies with optional native
|
|
150
|
-
copy-on-write acceleration. Byte limits, cancellation, and publication receipts
|
|
151
|
-
are described in the [Root copy contract](docs/root.md#writes).
|
|
152
|
-
|
|
153
|
-
When you need metadata or a `FileHandle`:
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
const { buffer, realPath, stat } = await fs.read("notes/today.txt");
|
|
157
|
-
const opened = await fs.open("notes/today.txt");
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
`create()` is the don't-clobber variant of `write()` and throws `already-exists` when the target already exists:
|
|
111
|
+
`root()` requires an existing trusted directory. Its defaults apply to each
|
|
112
|
+
operation; per-call options handle exceptions. See the [Root reference](docs/root.md).
|
|
161
113
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
114
|
+
`write()` replaces contents by default. Use `create()` or `overwrite: false`
|
|
115
|
+
when an existing destination should be an error. `move()` defaults to no clobber
|
|
116
|
+
and requires native support for the atomic collision decision; it fails with
|
|
117
|
+
`helper-unavailable` when unavailable. Pass `overwrite: true` when replacement
|
|
118
|
+
is intended.
|
|
165
119
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
before writing payload bytes. Native `off` or a missing ACL capability rejects
|
|
170
|
-
with `helper-unavailable`; nonprivate creation is unchanged. See the
|
|
171
|
-
[creation contract](docs/creation.md#permission-options) for parent ACL handling
|
|
172
|
-
and platform support.
|
|
173
|
-
|
|
174
|
-
Pass `{ atomic: true }` to buffered `create()` or `createJson()` to keep the
|
|
175
|
-
destination absent until complete content is ready, including with native support
|
|
176
|
-
disabled. The JavaScript fallback requires hardlinks and never downgrades to a
|
|
177
|
-
partial visible file. Omitted or `false` retains the existing buffered behavior.
|
|
178
|
-
Atomic visibility is separate from the existing `durable` synchronization policy.
|
|
179
|
-
Use `durable: "file"` on `create()` or `createJson()` when file-flush errors,
|
|
180
|
-
including `EPERM`, must propagate. It combines with `atomic: true` without
|
|
181
|
-
requiring strict parent-directory synchronization.
|
|
182
|
-
|
|
183
|
-
`create()` also accepts an `AsyncIterable<Uint8Array>` for large or incrementally
|
|
184
|
-
produced files. Streamed creates keep the destination absent until all chunks
|
|
185
|
-
are written, support `maxBytes` and `signal`, and recheck mutation authority
|
|
186
|
-
before writes and publication. See [streamed creation](docs/writing.md#streamed-creation)
|
|
187
|
-
for producer ownership and cancellation semantics.
|
|
188
|
-
|
|
189
|
-
`write()` replaces file contents by default; pass `{ overwrite: false }` or use `create()` when an existing file should be an error. `move()` defaults to no clobber because it can otherwise delete an unrelated target while also consuming the source. No-clobber moves require the native helper so the collision decision and rename are one descriptor-relative operation; they fail with `helper-unavailable` rather than falling back to a replacing rename. Pass `{ overwrite: true }` when replacing the target is intended.
|
|
190
|
-
|
|
191
|
-
Mutating methods accept `assertBeforeMutation: () => void` for live lease or
|
|
192
|
-
cancellation checks immediately before filesystem dispatch. Root defaults and
|
|
193
|
-
per-call checks compose; cleanup and already-dispatched work still settle.
|
|
194
|
-
See [live mutation authority](docs/root.md#live-mutation-authority) for the exact
|
|
195
|
-
scope, including raw writable handles and lock bookkeeping.
|
|
196
|
-
For workspaces with directory aliases, use `symlinks: "follow-parents-within-root"`
|
|
197
|
-
on reads and `mutationSymlinks: "follow-parents-within-root"` on mutations or root
|
|
198
|
-
defaults. Contained parent symlinks are resolved by the library, while a final
|
|
199
|
-
symlink is rejected. Read policy and mutation policy are separate; omitting
|
|
200
|
-
`mutationSymlinks` preserves the existing mutation behavior. See [root policies](docs/root.md#defaults-vs-per-call-options).
|
|
201
|
-
|
|
202
|
-
Use `ensureRoot()` when a computed relative directory target resolves to the root itself (`""` or `"."`) and you want the operation to be accepted. `root()` still requires the trusted root directory to already exist.
|
|
120
|
+
See [Writing](docs/writing.md) for copy sources, mutation authority, symlink
|
|
121
|
+
policy, writable handles, and bounded removal, and [Creation](docs/creation.md)
|
|
122
|
+
for private permissions, atomic creation, and durability options.
|
|
203
123
|
|
|
204
124
|
## Reading
|
|
205
125
|
|
|
@@ -227,35 +147,10 @@ Root reads default to `DEFAULT_ROOT_MAX_BYTES` (16 MiB). Pass a larger `maxBytes
|
|
|
227
147
|
for expected large reads, or `Number.POSITIVE_INFINITY` when the caller has a
|
|
228
148
|
separate size budget.
|
|
229
149
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
```ts
|
|
235
|
-
await using opened = await fs.openWritable("logs/current.log", { writeMode: "append" });
|
|
236
|
-
{
|
|
237
|
-
await opened.handle.appendFile("line\n");
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
`nonBlockingRead` remains as a compatibility hint in `RootDefaults`. Safe read/open operations already use nonblocking descriptor opens where the platform supports them so a raced FIFO cannot pin a worker; filesystem safety policy remains explicit through `hardlinks`, `symlinks`, and `denyMutations`.
|
|
242
|
-
|
|
243
|
-
On POSIX, `openWritable()` also uses nonblocking admission for existing targets
|
|
244
|
-
so a no-reader FIFO cannot stall validation. Ordinary regular-file write
|
|
245
|
-
semantics are unchanged; see [writing](docs/writing.md#openwritable-for-streaming).
|
|
246
|
-
|
|
247
|
-
```ts
|
|
248
|
-
const locked = await root("/srv/workspace", {
|
|
249
|
-
denyMutations: {
|
|
250
|
-
paths: ["/srv/workspace/.env"],
|
|
251
|
-
prefixes: ["/srv/workspace/.ssh"],
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
await locked.write(".env", "token"); // FsSafeError code "denied-path"
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
`stat()`, `exists()`, `list()`, and `entries()` check the exact selected objects while collecting their advisory results, but they cannot pin a later operation to the same filesystem object. Use `read()`, `open()`, `write()`, `create()`, `copyIn()`, `move()`, or `remove()` for operation-local identity checks, and inspect `containment` when the platform distinction matters.
|
|
150
|
+
See [Reading](docs/reading.md) for absolute-path loaders, aliases, and read
|
|
151
|
+
budgets, and [Writing](docs/writing.md#openwritable-for-streaming) for writable
|
|
152
|
+
handles. Inspection results from `stat()`, `exists()`, `list()`, and `entries()`
|
|
153
|
+
are advisory; use the operation methods for identity checks at the time of I/O.
|
|
259
154
|
|
|
260
155
|
## Subpaths
|
|
261
156
|
|
|
@@ -264,30 +159,7 @@ and error exports. Prefer focused subpaths when a consumer needs a narrower
|
|
|
264
159
|
contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are grouped under
|
|
265
160
|
`@openclaw/fs-safe/advanced` instead of being separate public leaf contracts.
|
|
266
161
|
|
|
267
|
-
|
|
268
|
-
|---|---|
|
|
269
|
-
| `@openclaw/fs-safe/root` | `root()`, `Root`, `RootDefaults`, and root-bounded walking with pruning/error markers |
|
|
270
|
-
| `@openclaw/fs-safe/config` | process-global native helper and lock defaults |
|
|
271
|
-
| `@openclaw/fs-safe/path` | canonical path checks: `isPathInside`, `safeRealpathSync`, `isNotFoundPathError`, `isSymlinkOpenError` |
|
|
272
|
-
| `@openclaw/fs-safe/json` | `tryReadJson`, `readJson`, `readJsonIfExists`, `writeJson`, sync variants |
|
|
273
|
-
| `@openclaw/fs-safe/output` | `writeExternalFileWithinRoot` for external libraries that need a temp output path |
|
|
274
|
-
| `@openclaw/fs-safe/store` | `fileStore`, `fileStoreSync`, and `jsonStore` |
|
|
275
|
-
| `@openclaw/fs-safe/secret` | sync/async strict and try-style secret reads, atomic replace, and create-only secret writes |
|
|
276
|
-
| `@openclaw/fs-safe/atomic` | `replaceFileAtomic`, `replaceFileAtomicSync`, `replaceDirectoryAtomic`, `movePathWithCopyFallback` |
|
|
277
|
-
| `@openclaw/fs-safe/durability` | pinned directory identities, strict directory sync, durable nested-directory creation, exclusive publication, streaming and synchronous SHA-256, provenance receipts, and sync-failure policy |
|
|
278
|
-
| `@openclaw/fs-safe/temp` | `tempWorkspace`, `tempWorkspaceSync`, `withTempWorkspace`, `resolveSecureTempRoot` |
|
|
279
|
-
| `@openclaw/fs-safe/secure-file` | fd-pinned absolute file reads with owner, mode, ACL, trusted-dir, size, and timeout checks |
|
|
280
|
-
| `@openclaw/fs-safe/file-lock` | async/sync sidecar locks, root-bounded sidecars, ownership verification, and stale policy |
|
|
281
|
-
| `@openclaw/fs-safe/permissions` | POSIX mode and Windows ACL inspection, raw owner/ACE facts, private-directory creation, and remediation helpers |
|
|
282
|
-
| [`@openclaw/fs-safe/watch`](docs/watch.md) | Guarded observation with native event hints, bounded scans, and joined close |
|
|
283
|
-
| `@openclaw/fs-safe/walk` | budget-bounded directory walking with symlink policy, filters, and truncation accounting; not root-bounded |
|
|
284
|
-
| `@openclaw/fs-safe/copy` | directory copying with `clone: "auto"`, `"always"`, or `"never"`; native APFS, Btrfs, ReFS, XFS, and ZFS cloning, portable byte copying, and clone metadata; see [directory copying](docs/copy.md) |
|
|
285
|
-
| `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits, gzip/zstd/bzip2 support, and bounded entry reads |
|
|
286
|
-
| `@openclaw/fs-safe/advanced` | lower-level composition helpers such as path scopes, root-file open, bounded descriptor reads, [borrowed-handle and descriptor copying](docs/copy.md#borrowed-filehandle-transfers), [complete byte-window writes](docs/advanced.md#borrowed-handle-writes), [exact directory identity](docs/directory-identity.md), [case probing](docs/path-case.md), [suffix-alias probing](docs/path-suffix-aliases.md), [in-place writes](docs/in-place-write.md), [versioned install-ID encoding](docs/install-path.md#safepathsegmenthashedv2), filename sanitizing, temp-file targets, sibling-temp writes, local-root readers, regular-file helpers, `pathExists`, and `withTimeout`; less stable than focused public subpaths |
|
|
287
|
-
| `@openclaw/fs-safe/errors` | `FsSafeError`, closed codes/categories, causes, and operation-specific details receipts |
|
|
288
|
-
| `@openclaw/fs-safe/types` | shared types: `DirEntry`, `PathStat`, … |
|
|
289
|
-
| `@openclaw/fs-safe/test-hooks` | hooks the test suite uses to inject races; registration requires `NODE_ENV=test` or `VITEST=true` |
|
|
290
|
-
| `@openclaw/fs-safe/guest` | Python source and exit constants for caller-launched filesystem operations in Linux/macOS guests without Node; see the [guest protocol and trust boundary](docs/guest.md) |
|
|
162
|
+
See the [complete subpath catalogue](docs/install.md#subpath-exports) for every entry point and its contents.
|
|
291
163
|
|
|
292
164
|
## Failure semantics in the name
|
|
293
165
|
|
|
@@ -307,53 +179,14 @@ JSON5-backed plugin manifests.
|
|
|
307
179
|
|
|
308
180
|
## Directory durability
|
|
309
181
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const receipt = await ensureDurableDirectory({
|
|
314
|
-
directoryPath: "/srv/backups/sqlite",
|
|
315
|
-
mode: 0o700,
|
|
316
|
-
});
|
|
317
|
-
const pinned = await pinDirectory(receipt);
|
|
318
|
-
try {
|
|
319
|
-
await publishSnapshot();
|
|
320
|
-
const outcome = await pinned.sync();
|
|
321
|
-
// `unsupported` is explicit on platforms without directory flushing.
|
|
322
|
-
console.log(outcome.status);
|
|
323
|
-
} finally {
|
|
324
|
-
await pinned.close();
|
|
325
|
-
}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
The durability subpath pins a directory descriptor to its pathname identity,
|
|
329
|
-
detects symlink/FIFO/replacement races, and synchronizes every new parent edge
|
|
330
|
-
when creating a nested directory. Strict sync propagates real I/O failures and
|
|
331
|
-
reports known Windows directory-flush limitations explicitly. Separate
|
|
332
|
-
best-effort helpers preserve operations that do not promise crash durability.
|
|
333
|
-
|
|
334
|
-
`publishFileExclusive()` adds no-clobber hardlink/copy/rename strategies and a
|
|
335
|
-
typed post-creation receipt. Its `onSyncFailure` policy defaults to
|
|
336
|
-
`"rollback"`; backup writers can choose `"preserve"` to keep a complete target
|
|
337
|
-
when parent-directory sync fails, then inspect `details.directorySync` and
|
|
338
|
-
retry or record the weaker durability state.
|
|
339
|
-
|
|
340
|
-
See [Directory durability](docs/durability.md) for the receipt, pin lifecycle,
|
|
341
|
-
publication policy, creation callback, and platform contract.
|
|
182
|
+
Use the [Directory durability](docs/durability.md) reference for directory
|
|
183
|
+
receipts, pinned synchronization, and exclusive publication policies, including
|
|
184
|
+
whether a completed target is preserved after a parent-directory sync failure.
|
|
342
185
|
|
|
343
186
|
## Atomic writes
|
|
344
187
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
original directory on Linux/macOS and requires native support for this operation.
|
|
348
|
-
It offers atomic replace/no-replace publication, not expected-inode replacement
|
|
349
|
-
or a crash-durability promise; application checks and coordination remain yours.
|
|
350
|
-
|
|
351
|
-
For an already staged POSIX symlink, [`retainSymlinkInDirectory()`](docs/staged-symlink.md)
|
|
352
|
-
admits caller-captured identity and retains that exact inode through no-replace
|
|
353
|
-
publication or explicit recovery. Same-target foreign replacements are not adopted.
|
|
354
|
-
Staging, cooperative locking and crash recovery remain application responsibilities.
|
|
355
|
-
|
|
356
|
-
`replaceFileAtomic()` writes a sibling temp file, applies its exact mode through the still-open descriptor, optionally fsyncs it, and renames it over the destination. It never follows the published destination path to set file permissions. Mode preservation inherits only rwx bits from an existing non-symlink regular file; special bits, ownership, ACLs, and extended attributes are not copied. Pinned-destination hardlink rejection, rename retry / copy fallback on `EPERM`, bounded original-content restoration after a torn fallback, parent-directory fsync, and a `beforeRename` hook for backup or observer flows are all opt-in. `movePathWithCopyFallback()` stages cross-device moves before commit and removes only the copied source entries, so concurrent source additions or replacements are preserved. Its optional synchronous `assertBeforeMutation` hook rechecks caller authority before renames and each source removal; `onDestinationPublished` reports an exact bigint destination identity before later checks or cleanup can fail. See [mutation authority and publication receipts](docs/atomic.md#mutation-authority-and-publication-receipts).
|
|
188
|
+
`replaceFileAtomic()` writes a sibling temp and renames it over the destination.
|
|
189
|
+
File and parent-directory synchronization are opt-in:
|
|
357
190
|
|
|
358
191
|
```ts
|
|
359
192
|
import { replaceFileAtomic } from "@openclaw/fs-safe/atomic";
|
|
@@ -367,13 +200,10 @@ await replaceFileAtomic({
|
|
|
367
200
|
});
|
|
368
201
|
```
|
|
369
202
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
The observer receives exact bigint identities from retained descriptors, including
|
|
375
|
-
when later completion fails. These facts do not authorize rollback; the caller
|
|
376
|
-
still owns current authority and content checks. See [atomic write authority](docs/atomic.md#atomic-write-authority-and-destination-state).
|
|
203
|
+
See [Atomic writes](docs/atomic.md) for synchronous adapters, fallback recovery,
|
|
204
|
+
mutation authority, and publication receipts. Retained lifecycles have separate
|
|
205
|
+
contracts: [staged files](docs/staged-file.md), [entry publication](docs/entry-publication.md),
|
|
206
|
+
and [staged symlinks](docs/staged-symlink.md).
|
|
377
207
|
|
|
378
208
|
## External outputs
|
|
379
209
|
|
|
@@ -393,22 +223,8 @@ await writeExternalFileWithinRoot({
|
|
|
393
223
|
});
|
|
394
224
|
```
|
|
395
225
|
|
|
396
|
-
The callback receives a staged path
|
|
397
|
-
|
|
398
|
-
cross-device-tolerant finalization. `"sibling"` stages in the target directory,
|
|
399
|
-
fsyncs the completed file, and atomically renames it over the target. Choose it
|
|
400
|
-
when the destination directory is itself the writable boundary and atomic
|
|
401
|
-
replacement matters.
|
|
402
|
-
|
|
403
|
-
For sibling producers that can leave partial output before throwing, opt in to
|
|
404
|
-
`producerIsolation: "private-directory"`. The callback writes inside an owned
|
|
405
|
-
private workspace on the target filesystem, allowing cleanup after producer
|
|
406
|
-
failure while preserving sibling publication behavior. See [external outputs](docs/output.md)
|
|
407
|
-
for the identity checks and cleanup limits.
|
|
408
|
-
|
|
409
|
-
Use it when the final filename is known before the external writer runs. If the
|
|
410
|
-
filename depends on sniffing the produced bytes, write to a private temp
|
|
411
|
-
workspace first, then finalize through the normal root APIs after validation.
|
|
226
|
+
The callback receives a staged path. Choose workspace or sibling staging and
|
|
227
|
+
producer isolation using the [staging-mode guide](docs/output.md#choosing-a-staging-mode).
|
|
412
228
|
|
|
413
229
|
## Stores
|
|
414
230
|
|
|
@@ -425,73 +241,11 @@ const store = files.json("settings.json", { lock: true });
|
|
|
425
241
|
await store.updateOr({ enabled: false }, (current) => ({ ...current, enabled: true }));
|
|
426
242
|
```
|
|
427
243
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
because they do not carry a bound root and often need multiple authority, path,
|
|
434
|
-
and policy knobs.
|
|
435
|
-
|
|
436
|
-
Sidecar locks fail closed on stale holders by default. Opt-in `remove-if-unchanged`
|
|
437
|
-
recovery requires caller approval and serializes snapshot verification and unlink
|
|
438
|
-
with an exclusive reclaim guard so a replacement lock cannot be deleted; see the
|
|
439
|
-
[file lock docs](docs/sidecar-lock.md).
|
|
440
|
-
|
|
441
|
-
Use `fileStore()` for cache/blob/media-style directories where callers
|
|
442
|
-
need safe relative paths, size limits, atomic replacement, stream writes, and
|
|
443
|
-
TTL cleanup behind one root. Pass `private: true` for credentials, auth
|
|
444
|
-
profiles, tokens, and per-agent private state; private mode keeps the same
|
|
445
|
-
store shape while routing writes through the secret-file atomic path.
|
|
446
|
-
|
|
447
|
-
```ts
|
|
448
|
-
import { fileStore } from "@openclaw/fs-safe/store";
|
|
449
|
-
|
|
450
|
-
const media = fileStore({
|
|
451
|
-
rootDir: "/safe/workspace/media",
|
|
452
|
-
maxBytes: 5 * 1024 * 1024,
|
|
453
|
-
mode: 0o600,
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
await media.write("inbound/photo.jpg", bytes);
|
|
457
|
-
await media.writeJson("state/photo.json", { id: "photo" });
|
|
458
|
-
const cached = await media.readJsonIfExists("state/photo.json");
|
|
459
|
-
const opened = await media.open("inbound/photo.jpg");
|
|
460
|
-
await media.pruneExpired({ ttlMs: 10 * 60 * 1000, recursive: true });
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
The `store` subpath also includes durable JSON queue helpers for the common
|
|
464
|
-
"one JSON file per work item" pattern: atomic entry writes, pending-entry loads,
|
|
465
|
-
acknowledgement via `.delivered` markers, failed-entry moves, and stale temp
|
|
466
|
-
cleanup. On Windows, every independently supplied queue path rejects NTFS
|
|
467
|
-
alternate-stream and directory-index namespace spellings before reads, locks,
|
|
468
|
-
or mutations. Retry, dedupe, and transport semantics stay with the caller.
|
|
469
|
-
|
|
470
|
-
`tempWorkspace()` exposes `write()`, `writeText()`, `writeJson()`, `copyIn()`, and `read()` for
|
|
471
|
-
single-file scratch workflows without hand-rolled path joins, plus a `store: FileStore` view of
|
|
472
|
-
the workspace dir for the richer cases (`writeStream`, `readJsonIfExists`, `store.json<T>(rel)`).
|
|
473
|
-
Compatible creation and cleanup remain available without native support.
|
|
474
|
-
Set `cleanupSafety: "require-bounded"` to require collision-safe quarantine and
|
|
475
|
-
descriptor-bounded recursive cleanup before creating a child. See the
|
|
476
|
-
[temp workspace contract](docs/temp.md).
|
|
477
|
-
On POSIX, bounded cleanup requires owner read and search in the final `dirMode`
|
|
478
|
-
(`0o500`); restrictive modes select compatible fallback or reject `require-bounded`
|
|
479
|
-
before child creation.
|
|
480
|
-
Linux bounded cleanup requires the exact `openat2`/`RESOLVE_NO_XDEV` capability
|
|
481
|
-
at runtime; compatible mode falls back when unavailable, while `require-bounded`
|
|
482
|
-
rejects before child creation.
|
|
483
|
-
|
|
484
|
-
`tempFile()` is the smaller one-file temp helper. It is intentionally an
|
|
485
|
-
advanced primitive: use `tempWorkspace()` for the stable temp surface and reach
|
|
486
|
-
for `tempFile()` only when you need a raw file target.
|
|
487
|
-
|
|
488
|
-
```ts
|
|
489
|
-
import { tempFile } from "@openclaw/fs-safe/advanced";
|
|
490
|
-
|
|
491
|
-
await using target = await tempFile({ prefix: "download", fileName: "payload.bin" });
|
|
492
|
-
await fs.promises.writeFile(target.path, bytes);
|
|
493
|
-
const checksumPath = target.file("payload.sha256");
|
|
494
|
-
```
|
|
244
|
+
See [JSON stores](docs/json-store.md) for single-path stores and update semantics,
|
|
245
|
+
[File stores](docs/file-store.md) for blobs, streams, and private state, and
|
|
246
|
+
[File locks](docs/sidecar-lock.md) for coordination and stale-lock recovery.
|
|
247
|
+
The store subpath also provides [durable JSON queues](docs/store.md#durable-json-queues).
|
|
248
|
+
Use [temp workspaces](docs/temp.md) for scoped scratch files and cleanup policies.
|
|
495
249
|
|
|
496
250
|
## Exact file comparison
|
|
497
251
|
|
|
@@ -502,18 +256,10 @@ descriptors' positions and ownership.
|
|
|
502
256
|
|
|
503
257
|
## Secure absolute file reads
|
|
504
258
|
|
|
505
|
-
Use `readSecureFile()`
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
pinned handle. On Windows, both the bytes and the owner/DACL facts come from that
|
|
510
|
-
handle. In native `auto` or `off` mode, a packaged, readable PowerShell script can
|
|
511
|
-
inspect that borrowed handle when the native capability is unavailable, subject
|
|
512
|
-
to the [Windows security fallback prerequisites](docs/install.md#windows-security-fallback).
|
|
513
|
-
It emits one fallback warning per process for secure reads and adds PowerShell
|
|
514
|
-
startup and compilation overhead per call. Native `require` remains strict, and
|
|
515
|
-
native operation failures are terminal. Neither route reopens the pathname to
|
|
516
|
-
inspect its ACL.
|
|
259
|
+
Use [`readSecureFile()`](docs/secure-file.md) for an absolute credential path.
|
|
260
|
+
It validates permissions, ownership, identity, and size through the opened handle.
|
|
261
|
+
See [Windows fallback prerequisites](docs/install.md#windows-security-fallback)
|
|
262
|
+
when native support is unavailable.
|
|
517
263
|
|
|
518
264
|
```ts
|
|
519
265
|
import { readSecureFile } from "@openclaw/fs-safe/secure-file";
|
|
@@ -531,10 +277,8 @@ flows where a warning is preferable to refusing the file.
|
|
|
531
277
|
|
|
532
278
|
## Directory walking
|
|
533
279
|
|
|
534
|
-
[`Root.entries()`](docs/entries.md)
|
|
535
|
-
|
|
536
|
-
cancellation, entry limits that throw on overflow, and bounded sorted-name
|
|
537
|
-
collection. Use it when the caller owns traversal or symlink validation:
|
|
280
|
+
[`Root.entries()`](docs/entries.md) lists immediate children without following
|
|
281
|
+
child symlinks:
|
|
538
282
|
|
|
539
283
|
```ts
|
|
540
284
|
for await (const entry of fs.entries("plugins", { maxEntries: 1_000 })) {
|
|
@@ -542,40 +286,11 @@ for await (const entry of fs.entries("plugins", { maxEntries: 1_000 })) {
|
|
|
542
286
|
}
|
|
543
287
|
```
|
|
544
288
|
|
|
545
|
-
|
|
546
|
-
`
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
import { walkDirectory } from "@openclaw/fs-safe/walk";
|
|
551
|
-
|
|
552
|
-
const scan = await walkDirectory("/safe/workspace", {
|
|
553
|
-
maxDepth: 4,
|
|
554
|
-
maxEntries: 10_000,
|
|
555
|
-
symlinks: "skip",
|
|
556
|
-
include: (entry) => entry.kind === "file",
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
for (const file of scan.entries) {
|
|
560
|
-
console.log(file.relativePath);
|
|
561
|
-
}
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
Check `scan.truncated` before treating the result as complete, and `scan.failedDirs` to tell an incomplete scan (a directory that could not be read) from an empty one before pruning state from the listing.
|
|
565
|
-
|
|
566
|
-
`walkDirectory()` accepts asynchronous `include` and `descend` callbacks through `AsyncWalkDirectoryOptions`, so a marker lookup can prune a directory before its children are read. Decisions remain serial and retain the options object as their `this` receiver; `walkDirectorySync()` and its options remain synchronous. See [Directory walking](docs/walk.md) for callback timing, JavaScript result compatibility, and error handling.
|
|
567
|
-
|
|
568
|
-
For caller-controlled paths, `Root.walk()` is the root-bounded async iterator.
|
|
569
|
-
It supports entry/depth budgets, including links without following their targets,
|
|
570
|
-
in-root symlink following, cancellation, and a
|
|
571
|
-
truncation marker (or typed error) when a budget is reached. Its `entryFilter`
|
|
572
|
-
accepts `"include"`, `"skip"`, or `"skip-subtree"`, directly or through a Promise.
|
|
573
|
-
After an awaited decision resolves, the walk rechecks cancellation and the
|
|
574
|
-
current listing directory and Root identities before using it. Pending callbacks
|
|
575
|
-
settle before cancellation or iterator disposal completes. Callback failures
|
|
576
|
-
reject the walk. `onDirectoryError: "skip-and-report"` yields typed `"directory-error"` markers
|
|
577
|
-
for directory read or identity-check failures while preserving entries from
|
|
578
|
-
readable subtrees.
|
|
289
|
+
Use `Root.walk()` for root-bounded recursive traversal of caller-controlled
|
|
290
|
+
paths. Standalone `walkDirectory()` and `walkDirectorySync()` provide best-effort
|
|
291
|
+
inventories; inspect `truncated` and `failedDirs` before treating a scan as complete.
|
|
292
|
+
See [Directory walking](docs/walk.md) for budgets, ordering, filtering, and
|
|
293
|
+
cancellation contracts.
|
|
579
294
|
|
|
580
295
|
## Archive extraction
|
|
581
296
|
|
|
@@ -606,20 +321,13 @@ Extraction stages into a private directory and merges through the same safe-open
|
|
|
606
321
|
|
|
607
322
|
## Advanced path scopes
|
|
608
323
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
```ts
|
|
613
|
-
import { pathScope } from "@openclaw/fs-safe/advanced";
|
|
614
|
-
|
|
615
|
-
const uploads = pathScope("/safe/uploads", { label: "uploads directory" });
|
|
616
|
-
const files = await uploads.files(["photo.jpg"]);
|
|
617
|
-
const target = await uploads.writable("report.pdf");
|
|
618
|
-
```
|
|
324
|
+
Use [`pathScope()`](docs/path-scope.md) for lower-level boundary validation over
|
|
325
|
+
a trusted absolute path.
|
|
619
326
|
|
|
620
327
|
## Errors
|
|
621
328
|
|
|
622
|
-
|
|
329
|
+
Boundary and policy failures use `FsSafeError` with a closed `code` union.
|
|
330
|
+
Parsing, callbacks, and underlying I/O can also throw other error types:
|
|
623
331
|
|
|
624
332
|
```ts
|
|
625
333
|
import { FsSafeError } from "@openclaw/fs-safe/errors";
|
|
@@ -634,32 +342,15 @@ try {
|
|
|
634
342
|
}
|
|
635
343
|
```
|
|
636
344
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (err instanceof FsSafeError) {
|
|
641
|
-
if (err.category === "policy") {
|
|
642
|
-
// Unsafe caller input or filesystem state rejected by a safety policy.
|
|
643
|
-
} else {
|
|
644
|
-
// Routine filesystem outcome or runtime/environment problem.
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
Routine filesystem outcomes such as `not-found`, `not-empty`, and
|
|
650
|
-
`not-removable`, plus runtime failures such as `read-failed`, are operational;
|
|
651
|
-
they do not indicate that a filesystem
|
|
652
|
-
boundary policy was violated.
|
|
653
|
-
|
|
654
|
-
Current `FsSafeErrorCode` values are `already-exists`, `denied-path`, `device-path`, `hardlink`, `helper-failed`, `helper-unavailable`, `invalid-path`, `insecure-permissions`, `not-empty`, `not-file`, `not-found`, `not-owned`, `not-removable`, `outside-workspace`, `path-alias`, `path-mismatch`, `permission-unverified`, `read-failed`, `secret-exists`, `store-reentrant-update`, `symlink`, `timeout`, `too-large`, and `unsupported-platform`.
|
|
345
|
+
For `FsSafeError`, `category` distinguishes policy rejections from operational
|
|
346
|
+
filesystem or runtime failures. See [Errors](docs/errors.md) for codes, receipts,
|
|
347
|
+
and other error families; check the error type before branching on its code.
|
|
655
348
|
|
|
656
349
|
## Safety model
|
|
657
350
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
- `remove`, `mkdir`, `move`, `stat`, and `list` retain guarded JavaScript implementations with pre/post identity checks
|
|
662
|
-
- archive extraction stages into a private directory and merges through the same boundary checks used by direct writes
|
|
351
|
+
Root operations combine confinement, no-follow opens, and identity checks.
|
|
352
|
+
The [security model](docs/security-model.md) describes guarantees and race limits
|
|
353
|
+
for native and JavaScript mechanisms on each platform.
|
|
663
354
|
|
|
664
355
|
## Limitations
|
|
665
356
|
|