@openclaw/openshell-sandbox 2026.9.6 → 2026.9.8
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/assets/icon.png +0 -0
- package/dist/index.js +75 -229
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +102 -0
- package/node_modules/@openclaw/fs-safe/README.md +24 -6
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +6 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-plan.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +9 -18
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.d.ts +11 -11
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.js +3 -35
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.js +2 -12
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.d.ts +7 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +13 -10
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-manifest.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.d.ts +2 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.js +16 -8
- package/node_modules/@openclaw/fs-safe/dist/archive.d.ts +6 -3
- package/node_modules/@openclaw/fs-safe/dist/archive.js +22 -19
- package/node_modules/@openclaw/fs-safe/dist/atomic.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-publication.d.ts +1 -3
- package/node_modules/@openclaw/fs-safe/dist/copy-publication.js +2 -6
- package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.js +71 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutations.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +0 -3
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +20 -2
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +77 -1
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +5 -7
- package/node_modules/@openclaw/fs-safe/dist/effective-uid.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/errors.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/errors.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +10 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-acquisition.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.d.ts +4 -11
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.d.ts +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-options.d.ts +2 -10
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.d.ts +2 -4
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.d.ts +3 -7
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +7 -10
- package/node_modules/@openclaw/fs-safe/dist/file-store-prune.js +7 -6
- package/node_modules/@openclaw/fs-safe/dist/file-store.d.ts +4 -7
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +19 -21
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +23 -31
- package/node_modules/@openclaw/fs-safe/dist/guest.js +21 -12
- package/node_modules/@openclaw/fs-safe/dist/json-document-store.d.ts +4 -9
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.d.ts +1 -5
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.js +2 -7
- package/node_modules/@openclaw/fs-safe/dist/local-file-access.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/local-roots.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +51 -14
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +8 -2
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +303 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.js +54 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +64 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +17 -1
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +3 -6
- package/node_modules/@openclaw/fs-safe/dist/permissions.d.ts +3 -10
- package/node_modules/@openclaw/fs-safe/dist/permissions.js +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-shared-route.d.ts +2 -8
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +2 -3
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/regular-file.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/replace-directory.js +5 -17
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +36 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +4 -10
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +66 -38
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +7 -11
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +10 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +123 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.js +47 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +16 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +55 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file.d.ts +3 -55
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +31 -11
- package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/retained-file-types.d.ts +50 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file.js +121 -0
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +28 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +9 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +88 -37
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +1 -6
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/root-handle-context.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-handle-context.js +12 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +8 -5
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-observation.d.ts +4 -11
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +7 -10
- package/node_modules/@openclaw/fs-safe/dist/root-paths.d.ts +2 -6
- package/node_modules/@openclaw/fs-safe/dist/root-remove-identity.d.ts +1 -3
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +19 -12
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +56 -20
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +1 -6
- package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +8 -2
- package/node_modules/@openclaw/fs-safe/dist/secret-file.d.ts +6 -2
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -16
- package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +5 -14
- package/node_modules/@openclaw/fs-safe/dist/secure-file.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +2 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission-parser.d.ts +1 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.d.ts +2 -8
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +11 -12
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-stale-admission.d.ts +1 -5
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +9 -7
- package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.d.ts +39 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/stat-observation.js +3 -10
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.js +6 -6
- package/node_modules/@openclaw/fs-safe/dist/symlink-parents.js +58 -7
- package/node_modules/@openclaw/fs-safe/dist/temp-target.js +8 -10
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +28 -33
- 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 +16 -17
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +4 -9
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +52 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +5 -1
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.js +27 -1
- package/node_modules/@openclaw/fs-safe/dist/walk.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +88 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +93 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +36 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +73 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +28 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +300 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +60 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.js +530 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +3 -7
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +0 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +6 -6
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +78 -3
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +66 -15
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +6 -2
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -2
- package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +93 -3
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +102 -4
- package/node_modules/@openclaw/fs-safe/docs/copy.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/durability.md +7 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +5 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +7 -1
- package/node_modules/@openclaw/fs-safe/docs/index.md +1 -0
- package/node_modules/@openclaw/fs-safe/docs/install.md +30 -0
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +20 -4
- package/node_modules/@openclaw/fs-safe/docs/native.md +63 -1
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +66 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +30 -3
- package/node_modules/@openclaw/fs-safe/docs/retained-file.md +115 -0
- package/node_modules/@openclaw/fs-safe/docs/root.md +35 -9
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +19 -0
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +5 -1
- package/node_modules/@openclaw/fs-safe/docs/staged-symlink.md +124 -0
- package/node_modules/@openclaw/fs-safe/docs/store.md +3 -1
- package/node_modules/@openclaw/fs-safe/docs/temp.md +24 -4
- package/node_modules/@openclaw/fs-safe/docs/testing.md +210 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +6 -0
- package/node_modules/@openclaw/fs-safe/docs/walk.md +34 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +251 -0
- package/node_modules/@openclaw/fs-safe/docs/writing.md +21 -0
- package/node_modules/@openclaw/fs-safe/package.json +13 -9
- 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/node_modules/p-limit/index.d.ts +2 -0
- package/node_modules/p-limit/index.js +17 -1
- package/node_modules/p-limit/package.json +1 -1
- package/node_modules/p-limit/readme.md +2 -0
- package/package.json +6 -6
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.d.ts +0 -20
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.js +0 -78
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Guarded filesystem observation
|
|
2
|
+
|
|
3
|
+
`@openclaw/fs-safe/watch` observes literal paths under an admitted `Root`.
|
|
4
|
+
Notifications are advisory invalidations, not a transaction log, stable content,
|
|
5
|
+
or authority to read a reported pathname. Guarded metadata scans through the
|
|
6
|
+
original Root determine what may be published. Use the Root again to read data.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { root } from "@openclaw/fs-safe/root";
|
|
10
|
+
import { watch } from "@openclaw/fs-safe/watch";
|
|
11
|
+
|
|
12
|
+
const workspace = await root("/trusted/workspace");
|
|
13
|
+
const subscription = watch(workspace, {
|
|
14
|
+
mode: "auto",
|
|
15
|
+
scopes: [
|
|
16
|
+
{ path: "config.json", kind: "entry" },
|
|
17
|
+
{ path: "skills", kind: "tree", depth: 8 },
|
|
18
|
+
],
|
|
19
|
+
exclude: entry => entry.kind === "directory" && entry.path.endsWith("node_modules"),
|
|
20
|
+
onInvalidate(invalidation) {
|
|
21
|
+
// Schedule application-owned settling/reload work; this callback is synchronous.
|
|
22
|
+
console.log(invalidation.reason, invalidation.changes);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
await subscription.ready;
|
|
26
|
+
await subscription.setScopes([{ path: "skills", kind: "tree" }]);
|
|
27
|
+
await subscription.reconcile();
|
|
28
|
+
await subscription.close();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Contract
|
|
32
|
+
|
|
33
|
+
The subpath exports `watch` and the types `WatchScope`, `WatchEntry`,
|
|
34
|
+
`WatchChange`, `WatchInvalidation`, `WatchFailure`, `WatchHealth`, `WatchOptions`,
|
|
35
|
+
and `WatchSubscription`.
|
|
36
|
+
|
|
37
|
+
An `entry` scope observes only that entry, including its identity and metadata.
|
|
38
|
+
A `tree` also observes descendants to its depth (default 32, maximum 128).
|
|
39
|
+
Depth zero observes only the entry. The empty string and `.` select the Root;
|
|
40
|
+
trailing separators are accepted and normalized after validation. Scopes are
|
|
41
|
+
literal names, never globs or home expansion. Absolute paths, traversal, NULs,
|
|
42
|
+
and platform namespace aliases reject. Windows scopes also reject reserved
|
|
43
|
+
device components and trailing dots/spaces that Win32 would silently alias. Filesystem identity determines ordinary
|
|
44
|
+
case/Unicode aliases; names are not compared by lowercasing.
|
|
45
|
+
|
|
46
|
+
Symlinks are observed as entries and never followed. A symbolic parent fails
|
|
47
|
+
admission: separately admit a caller-trusted link target if needed. Missing
|
|
48
|
+
entries and ordinary blocking files can become directories in later scans.
|
|
49
|
+
Replacing, renaming, or losing the admitted Root fails observation; it never
|
|
50
|
+
silently adopts a new Root. Directory entry scopes ignore child-only mtime/size
|
|
51
|
+
changes, but include permission-mode changes to the directory itself.
|
|
52
|
+
|
|
53
|
+
Each invalidation has `reason: "event" | "reconcile" | "overflow"` and optional
|
|
54
|
+
bounded `changes: { path, type: "content" | "structural" }[]`. Missing detail
|
|
55
|
+
means invalidate **every configured scope**. Initial admission and every
|
|
56
|
+
successful `setScopes` publish one undetailed `reconcile` invalidation. A rename
|
|
57
|
+
or identity replacement is structural; metadata changes to the same ordinary
|
|
58
|
+
file may be content changes. Neither means the file is settled or readable.
|
|
59
|
+
An admitted native hint can invalidate an entry even when its before/after scan
|
|
60
|
+
metadata is identical: the path may have changed and been restored between scans
|
|
61
|
+
(ABA), or content may have changed without a distinguishable metadata change.
|
|
62
|
+
Structural hints remain conservative in that case.
|
|
63
|
+
This does not promise delivery for a differently spelled alias that appears and
|
|
64
|
+
disappears entirely between scans: without an observed identity, it cannot be
|
|
65
|
+
admitted as the selected path. Observation is not a complete transient history.
|
|
66
|
+
Raw event names remain private: detail comes from guarded scans, prior guarded
|
|
67
|
+
snapshots, or explicitly configured targets. Unclassifiable hints inside selected,
|
|
68
|
+
non-excluded territory lose detail. Hints for excluded or unselected paths are
|
|
69
|
+
ignored. Exclusion callbacks are synchronous; excluded directories are recorded
|
|
70
|
+
without descent. Bounded exclusion records recognize late deletion hints.
|
|
71
|
+
Genuine backend event loss and detail-budget exhaustion still invalidate every
|
|
72
|
+
scope, including when an excluded subtree caused the underlying event pressure.
|
|
73
|
+
|
|
74
|
+
## Transport and mode
|
|
75
|
+
|
|
76
|
+
| Platform/runtime | `auto` | Event transport / limitation |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| Node.js on Linux with addon | `events` | One shared Rust thread and inotify instance; a nonrecursive watch per distinct directory inode. |
|
|
79
|
+
| Node.js on macOS with addon | `events` | One FSEvents stream per subscription on a shared serial dispatch queue. Pathname activity after a swap remains advisory. |
|
|
80
|
+
| Node.js on Windows with addon | `events` | One recursive ReadDirectoryChangesW Root handle per subscription on the shared IOCP hub; the open handle prevents ordinary renames of the Root's ancestors. |
|
|
81
|
+
| Bun / other unsupported runtimes | `poll` | TSFN lifetime and shutdown have not been qualified; `events` rejects. |
|
|
82
|
+
| Missing/disabled addon | `poll` | `events` rejects with `FsSafeError("helper-unavailable")`. |
|
|
83
|
+
|
|
84
|
+
The shared hub sleeps until a filesystem event, command, or callback acknowledgement:
|
|
85
|
+
Linux blocks on inotify plus eventfd, Windows on IOCP, and macOS on its command
|
|
86
|
+
channel (FSEvents wakes it from the serial dispatch queue). There is no native
|
|
87
|
+
polling timer; the independent JS reconciliation interval remains authoritative.
|
|
88
|
+
|
|
89
|
+
`mode` is required. `poll` never starts or loads the watch hub; guarded scans
|
|
90
|
+
may still use the existing addon. Existing `FS_SAFE_NATIVE_MODE=off` and
|
|
91
|
+
`require` policies apply: `require` plus an unavailable event backend rejects
|
|
92
|
+
`auto` too. Health reports the selected `events` or `poll` mode and failures
|
|
93
|
+
from selection have `operation: "watch"`.
|
|
94
|
+
|
|
95
|
+
Linux installs each watch **before** enumerating children. It opens directories
|
|
96
|
+
beneath the Root using the shared guarded native open (openat2, or its checked
|
|
97
|
+
openat fallback), checks exact identities, verifies the procfs namespace, and
|
|
98
|
+
registers through `/proc/self/fd/N/.`. The final `/.` makes `IN_DONT_FOLLOW` apply
|
|
99
|
+
to the directory instead of rejecting the procfs magic symlink. The descriptor
|
|
100
|
+
closes immediately: inotify retains the inode reference. Reconciliation replaces
|
|
101
|
+
registrations when the directory inventory changes. Queue overflow invalidates
|
|
102
|
+
all owners; exhausted watch capacity reports `failure.code: "watch-limit"`.
|
|
103
|
+
Nonblocking TSFN batches cannot block the hub on JavaScript, and per-owner
|
|
104
|
+
pending detail and queued batches are bounded. The last removal stops and joins
|
|
105
|
+
the native thread. No Worker threads, eval programs, or JS `fs.watch` are used.
|
|
106
|
+
|
|
107
|
+
macOS uses FileEvents, NoDefer and WatchRoot with a 30 ms FSEvents latency.
|
|
108
|
+
After guarded admission, streams use selected tree anchors and entry parents,
|
|
109
|
+
falling back to the nearest admitted ancestor for missing paths. Nested anchors
|
|
110
|
+
are deduplicated, with at most 128 paths. The eight shallowest non-overlapping
|
|
111
|
+
excluded directories are also passed to `FSEventStreamSetExclusionPaths`.
|
|
112
|
+
When the stream paths change, the old stream is stopped, invalidated and released
|
|
113
|
+
on its dispatch queue, then its replacement starts before another guarded pass
|
|
114
|
+
covers the handover. Native exclusions reduce traffic but cannot eliminate real
|
|
115
|
+
FSEvents drops, including during recursive deletion.
|
|
116
|
+
Absolute hints are reduced lexically against the admitted canonical Root;
|
|
117
|
+
outside paths never become detail. Dropped/wrapped streams, RootChanged and
|
|
118
|
+
Unmount trigger guarded reconciliation. Pathname hints can reflect activity
|
|
119
|
+
after a swap, but the Root is never replaced and names require guarded admission.
|
|
120
|
+
Removal synchronously stops, invalidates and releases the stream on its queue.
|
|
121
|
+
|
|
122
|
+
Windows opens one identity-checked Root handle per subscription with
|
|
123
|
+
READ/WRITE/DELETE sharing, backup semantics and overlapped I/O. Each handle
|
|
124
|
+
observes the entire subtree; guarded scans filter hints to configured scopes.
|
|
125
|
+
No descendant watch handles are retained, so directories inside the Root can be
|
|
126
|
+
renamed while watching, including directories containing selected scopes.
|
|
127
|
+
Completed buffers are copied and the
|
|
128
|
+
read re-armed before names are examined. Zero-byte / enumeration-loss completions
|
|
129
|
+
invalidate every scope. Buffers are 1 MiB on confirmed local volumes, and 64 KiB
|
|
130
|
+
on network/UNC or unclassified volumes. Cancellation waits for IOCP completion before closing
|
|
131
|
+
the handle or freeing its buffer; there are no detached retirement waits.
|
|
132
|
+
|
|
133
|
+
On Windows, an open file handle—including fs-safe's pinned reads, editors, and
|
|
134
|
+
antivirus—blocks renaming that file's ancestor directories, even with delete
|
|
135
|
+
sharing. The watch backend retains only the Root's `ReadDirectoryChangesW` handle,
|
|
136
|
+
which permits renames beneath the Root. Callers renaming directories concurrently
|
|
137
|
+
with reads should use bounded retries, as Windows tools do.
|
|
138
|
+
|
|
139
|
+
Windows prevents ordinary renames of the Root's own ancestors while its directory
|
|
140
|
+
handle is open, even with DELETE sharing. Use `mode: "poll"` when callers must not
|
|
141
|
+
retain that handle. Renaming or replacing the Root still fails guarded observation;
|
|
142
|
+
the subscription never adopts another location. Subscriptions have independent
|
|
143
|
+
handles and delivery queues, and closing one does not retire another's observation.
|
|
144
|
+
|
|
145
|
+
## Budgets and lifecycle
|
|
146
|
+
|
|
147
|
+
| Option | Default / bound |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `scopes` | At most 128 literal scopes |
|
|
150
|
+
| `persistent` | `true`; `false` lets Node exit with the subscription still open |
|
|
151
|
+
| `intervalMs` | 30000 with events; 1000 with poll; minimum 20 ms |
|
|
152
|
+
| `pollIntervalMs` | Optional polling override; minimum 20 ms, maximum 2147483647 ms (same as `intervalMs`) |
|
|
153
|
+
| `maxDirectories` | 4096 observed directories, including scope ancestors |
|
|
154
|
+
| `maxEntries` | 100000 examined entries per pass, including excluded entries |
|
|
155
|
+
| `maxPendingPaths` | 256; maximum 4096 |
|
|
156
|
+
| Reconciliation | One active pass and one coalesced pending pass; no convergence/pass budget |
|
|
157
|
+
|
|
158
|
+
Periodic guarded reconciliation runs without needing an event. It catches
|
|
159
|
+
missed events and works on filesystems where native hints are incomplete.
|
|
160
|
+
When polling is selected, the interval is `pollIntervalMs`, then `intervalMs`,
|
|
161
|
+
then 1000 ms, in that order. This applies to explicit `mode: "poll"`, `auto`
|
|
162
|
+
selecting polling, and `auto` falling back after an unsupported event backend.
|
|
163
|
+
`pollIntervalMs` does not change the events reconciliation interval, which
|
|
164
|
+
remains `intervalMs` or 30000 ms. For example, `mode: "auto", pollIntervalMs: 25`
|
|
165
|
+
uses 25 ms polling when needed and retains the 30-second events reconciliation.
|
|
166
|
+
Scans are metadata comparisons: content changes preserving all compared
|
|
167
|
+
metadata may be missed in polling mode. No mode promises transactional
|
|
168
|
+
snapshots, complete history, or hard real-time delivery.
|
|
169
|
+
|
|
170
|
+
`ready` resolves after the first complete guarded scan establishes the baseline,
|
|
171
|
+
even while writes continue. Events mode installs each directory registration
|
|
172
|
+
before listing it (FSEvents and recursive RDCW anchors cover the crawl). A changed
|
|
173
|
+
registration/listing identity is retried up to three times per directory; further
|
|
174
|
+
churn invalidates that subtree. Poll mode starts with its first scan and detects
|
|
175
|
+
changes during the crawl on the next comparison. Neither mode waits for two
|
|
176
|
+
agreeing scans.
|
|
177
|
+
`ready` and `reconcile()` do not drain the operating system's event queue.
|
|
178
|
+
For example, FSEvents can deliver coalesced setup creation activity after `ready`,
|
|
179
|
+
even with its stream starting at the current event ID. Consumers must tolerate
|
|
180
|
+
these advisory invalidations; tests measuring a quiet interval should first
|
|
181
|
+
observe a selected sentinel edit and drain its trailing events.
|
|
182
|
+
|
|
183
|
+
Each later pass compares with the previous snapshot, publishes bounded differences,
|
|
184
|
+
and adopts its result as the next snapshot. Vanishing entries, kind changes, and
|
|
185
|
+
transient descendant scan errors produce structural invalidations, preserving the
|
|
186
|
+
Root identity checks. Events during a pass coalesce into one pending pass and
|
|
187
|
+
retain bounded detail regardless of how long the scan takes. The 25 ms hint
|
|
188
|
+
coalescing window does not impose a scan deadline. A full native callback queue
|
|
189
|
+
retains its bounded pending batch for retry. Genuine backend loss, exhausted
|
|
190
|
+
detail capacity, or an unclassifiable selected hint emits `overflow` without
|
|
191
|
+
detail. Sustained writes cannot exhaust a pass budget or disable observation.
|
|
192
|
+
|
|
193
|
+
`reconcile()` resolves after a complete pass that **started after the call**. Calls
|
|
194
|
+
waiting for the same future pass coalesce; an earlier in-flight pass cannot satisfy
|
|
195
|
+
a new call. It rejects only when observation becomes unavailable or is closed.
|
|
196
|
+
`setScopes` fences the old generation immediately and resolves after the new
|
|
197
|
+
baseline scan; superseded scope calls reject `AbortError`.
|
|
198
|
+
By default, an open subscription keeps the Node event loop alive, matching
|
|
199
|
+
`fs.watch`. Set `persistent: false` for caches used by one-shot commands:
|
|
200
|
+
the subscription's timers and native delivery handle do not keep Node alive,
|
|
201
|
+
including during startup or reconciliation. Invalidations still arrive while
|
|
202
|
+
other work keeps the process alive. Persistent and non-persistent subscriptions
|
|
203
|
+
have independent lifetimes; closing the last persistent one lets Node exit.
|
|
204
|
+
Native environment cleanup retires any remaining event registrations and joins
|
|
205
|
+
the hub at exit. `signal` triggers close;
|
|
206
|
+
await `close()` or `[Symbol.asyncDispose]()` to join owned work.
|
|
207
|
+
|
|
208
|
+
`health()` returns `starting`, `ready`, `reconciling`, `unavailable`, or `closed`,
|
|
209
|
+
the actual mode, observed `directories`, and optional `{ operation, code, error }`
|
|
210
|
+
failure. A running reconciliation reports `reconciling`, then returns to `ready`.
|
|
211
|
+
Observation becomes `unavailable` on loss of Root authority (removed, replaced,
|
|
212
|
+
or inaccessible), fatal native backend/registration failures such as `watch-limit`,
|
|
213
|
+
deterministic size limits (`too-large`, operation `scan`), or callback contract
|
|
214
|
+
violations. Invalid scope admission, including symbolic parents, still rejects;
|
|
215
|
+
it never grants authority through a link. Transient descendant churn does not
|
|
216
|
+
make an admitted subscription unavailable. Callbacks may synchronously retire
|
|
217
|
+
the owner. Returning a thenable or a synchronous or asynchronous generator object
|
|
218
|
+
from `onInvalidate`, `onHealth`, or `exclude` rejects observation without advancing
|
|
219
|
+
generators. Application async
|
|
220
|
+
work remains application-owned and is not joined by the subscription.
|
|
221
|
+
|
|
222
|
+
`close()` is terminal, idempotent, and joined. Observation failures remain in
|
|
223
|
+
health but do not make successful retirement reject. Retirement failures do
|
|
224
|
+
reject, with a `SuppressedError` retaining an earlier observation failure when
|
|
225
|
+
both exist. No new generation or callback can be admitted after close.
|
|
226
|
+
|
|
227
|
+
The stress harness also provides `node scripts/watch-stress.mjs --scenario soak-short`,
|
|
228
|
+
a two-minute lifecycle and collected-memory smoke. It is separate from the
|
|
229
|
+
full one-hour `soak` scenario and its stronger memory-growth qualification.
|
|
230
|
+
|
|
231
|
+
## Seeded consumer-cache stress test
|
|
232
|
+
|
|
233
|
+
After building the package and host binding, run the model against real temporary
|
|
234
|
+
Roots in both modes:
|
|
235
|
+
|
|
236
|
+
```sh
|
|
237
|
+
node scripts/watch-stress/model-runner.mjs --seeds 2000 --mode both --output watch-model-results.json
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Each seed generates file and directory edits, renames, replacements, deep trees,
|
|
241
|
+
symlink retargets, bursts, scope changes, and subscription retirement. A consumer
|
|
242
|
+
queues refreshes only from `onInvalidate`; checkpoints drain those requests after
|
|
243
|
+
quiescence and `reconcile()`, then compare its cache with a guarded Root walk and
|
|
244
|
+
an independent mutation model. Native hints never authorize consumer reads.
|
|
245
|
+
|
|
246
|
+
`--seed`, `--steps`, `--settle` (milliseconds), and `--concurrency` control a run.
|
|
247
|
+
Failures are shrunk by fast-check and saved beside the report as `.<mode>-<seed>.failure.json`;
|
|
248
|
+
replay one with `--replay <failure.json>`. Event mode requires a working native
|
|
249
|
+
binding and never silently falls back to polling. The small
|
|
250
|
+
`test/watch-model.test.ts` corpus runs in ordinary CI; native-event cases also run
|
|
251
|
+
when `FS_SAFE_TEST_WATCH_EVENTS=1`. Keep fixtures on normal `os.tmpdir()` storage.
|
|
@@ -211,6 +211,11 @@ can establish them; native disposal can retain them inside a `SuppressedError`
|
|
|
211
211
|
cause. Preserve those details when handling errors: a rejection can follow
|
|
212
212
|
complete publication, and an indeterminate link or native rename must preserve names for
|
|
213
213
|
recovery. A cleanup or close failure also retains the original operation failure.
|
|
214
|
+
The JavaScript fallback checks the destination again immediately before attempting
|
|
215
|
+
publication. A collision observed there leaves publication unattempted and cleans
|
|
216
|
+
the owned stage. A later collision or other error from the link call remains
|
|
217
|
+
indeterminate, including `EEXIST`; the error code alone does not prove that the
|
|
218
|
+
filesystem left both names unchanged.
|
|
214
219
|
No later verification, mode, or synchronization failure authorizes deleting an
|
|
215
220
|
already published complete destination. See [receipt meanings](staged-file.md).
|
|
216
221
|
|
|
@@ -301,6 +306,12 @@ type RootWriteJsonOptions = RootWriteOptions & {
|
|
|
301
306
|
|
|
302
307
|
Open in append mode, write, sync the file handle, and close. Honors `mkdir` for the parent directory and syncs the parent directory when the append creates the file. `durable: false` skips both syncs. Pass `prependNewlineIfNeeded: true` to insert a `\n` if the file does not already end in one.
|
|
303
308
|
|
|
309
|
+
`mode` selects the creation mode, defaulting to `0o600` when neither the call nor
|
|
310
|
+
the Root supplies it. On POSIX, the process umask can further restrict that mode;
|
|
311
|
+
for example, `mode: 0o640` with umask `0o077` creates a `0o600` file. Existing
|
|
312
|
+
files are not chmodded, even when an explicit `mode` is supplied. Empty appends
|
|
313
|
+
use the same creation rules.
|
|
314
|
+
|
|
304
315
|
```ts
|
|
305
316
|
await fs.append("logs/today.log", `[${ts}] ${line}\n`);
|
|
306
317
|
await fs.append("notes/scratch.md", "* new bullet", { prependNewlineIfNeeded: true });
|
|
@@ -348,6 +359,11 @@ replacing rename. After dispatch it rechecks both parent identities, so a
|
|
|
348
359
|
post-operation rejection can mean the no-replace rename completed. Directory
|
|
349
360
|
moves continue to require `overwrite: true`.
|
|
350
361
|
|
|
362
|
+
Linux without `openat2` uses the [guarded native parent walk](native.md#linux-without-openat2).
|
|
363
|
+
The move still uses `renameat2(RENAME_NOREPLACE)` and preserves collisions;
|
|
364
|
+
parent resolution reports the documented `best-effort` containment class.
|
|
365
|
+
Disabling the addon still makes no-clobber moves unavailable.
|
|
366
|
+
|
|
351
367
|
Both selected canonical endpoints are admitted inside the retained Root after
|
|
352
368
|
native parent admission. With `mutationSymlinks: "reject"`, both full operation
|
|
353
369
|
paths are rechecked after the live mutation-authority callback and before
|
|
@@ -522,6 +538,11 @@ destination — there is no atomic-rename step. For exclusive publication of a
|
|
|
522
538
|
complete stream, use [`create()`](#streamed-creation). For streamed replacement,
|
|
523
539
|
the [`atomic`](atomic.md) helpers provide a staged writer.
|
|
524
540
|
|
|
541
|
+
For all three write modes, `mode` only selects new-file creation permissions,
|
|
542
|
+
defaulting to `0o600` when neither the call nor the Root supplies it. POSIX
|
|
543
|
+
permissions remain subject to the process umask; existing files are not chmodded.
|
|
544
|
+
The returned numeric `stat` records the admitted descriptor before caller writes.
|
|
545
|
+
|
|
525
546
|
On POSIX, existing-target opens use `O_NONBLOCK` as an admission safeguard so
|
|
526
547
|
a no-reader FIFO cannot stall regular-file validation. This does not change
|
|
527
548
|
ordinary regular-file write semantics. `replace` and `update` remain write-only
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/fs-safe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"filesystem",
|
|
@@ -95,6 +95,10 @@
|
|
|
95
95
|
"types": "./dist/file-lock.d.ts",
|
|
96
96
|
"default": "./dist/file-lock.js"
|
|
97
97
|
},
|
|
98
|
+
"./watch": {
|
|
99
|
+
"types": "./dist/watch.d.ts",
|
|
100
|
+
"default": "./dist/watch.js"
|
|
101
|
+
},
|
|
98
102
|
"./walk": {
|
|
99
103
|
"types": "./dist/walk.d.ts",
|
|
100
104
|
"default": "./dist/walk.js"
|
|
@@ -169,18 +173,18 @@
|
|
|
169
173
|
"archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
|
|
170
174
|
},
|
|
171
175
|
"optionalDependencies": {
|
|
172
|
-
"@openclaw/fs-safe-darwin-arm64": "0.
|
|
173
|
-
"@openclaw/fs-safe-darwin-x64": "0.
|
|
174
|
-
"@openclaw/fs-safe-linux-arm64-gnu": "0.
|
|
175
|
-
"@openclaw/fs-safe-linux-arm64-musl": "0.
|
|
176
|
-
"@openclaw/fs-safe-linux-x64-gnu": "0.
|
|
177
|
-
"@openclaw/fs-safe-linux-x64-musl": "0.
|
|
178
|
-
"@openclaw/fs-safe-win32-x64-msvc": "0.
|
|
176
|
+
"@openclaw/fs-safe-darwin-arm64": "0.21.1",
|
|
177
|
+
"@openclaw/fs-safe-darwin-x64": "0.21.1",
|
|
178
|
+
"@openclaw/fs-safe-linux-arm64-gnu": "0.21.1",
|
|
179
|
+
"@openclaw/fs-safe-linux-arm64-musl": "0.21.1",
|
|
180
|
+
"@openclaw/fs-safe-linux-x64-gnu": "0.21.1",
|
|
181
|
+
"@openclaw/fs-safe-linux-x64-musl": "0.21.1",
|
|
182
|
+
"@openclaw/fs-safe-win32-x64-msvc": "0.21.1",
|
|
179
183
|
"jszip": "^3.10.2"
|
|
180
184
|
},
|
|
181
185
|
"devDependencies": {
|
|
182
186
|
"@emnapi/runtime": "2.0.0-alpha.5",
|
|
183
|
-
"@napi-rs/cli": "3.10.
|
|
187
|
+
"@napi-rs/cli": "3.10.5",
|
|
184
188
|
"@types/node": "^26.6.1",
|
|
185
189
|
"@vitest/coverage-v8": "5.0.1",
|
|
186
190
|
"fast-check": "^4.10.1",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -31,6 +31,8 @@ export type LimitFunction = {
|
|
|
31
31
|
|
|
32
32
|
The mapper function receives the item value and its index.
|
|
33
33
|
|
|
34
|
+
If the iterable throws during iteration, the returned promise rejects with that error. Mapper functions that were already scheduled still run, and their results are discarded.
|
|
35
|
+
|
|
34
36
|
This is a convenience function for processing inputs that arrive in batches. For more complex use cases, see [p-map](https://github.com/sindresorhus/p-map).
|
|
35
37
|
|
|
36
38
|
@param iterable - An iterable containing an argument for the given function.
|
|
@@ -105,7 +105,23 @@ export default function pLimit(concurrency) {
|
|
|
105
105
|
},
|
|
106
106
|
map: {
|
|
107
107
|
async value(iterable, function_) {
|
|
108
|
-
const promises =
|
|
108
|
+
const promises = [];
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
Array.from(iterable, (value, index) => {
|
|
112
|
+
const promise = generator(function_, value, index);
|
|
113
|
+
promises.push(promise);
|
|
114
|
+
return promise;
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
// The iterable threw, so `Promise.all` never observes the already scheduled promises. Observe them here to avoid unhandled rejections.
|
|
118
|
+
for (const promise of promises) {
|
|
119
|
+
promise.catch(() => {}); // eslint-disable-line promise/prefer-await-to-then
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
|
|
109
125
|
return Promise.all(promises);
|
|
110
126
|
},
|
|
111
127
|
},
|
|
@@ -83,6 +83,8 @@ The mapper function receives the item value and its index.
|
|
|
83
83
|
|
|
84
84
|
Returns a promise equivalent to `Promise.all(Array.from(iterable, (item, index) => limit(mapperFunction, item, index)))`.
|
|
85
85
|
|
|
86
|
+
If the iterable throws during iteration, the returned promise rejects with that error. Mapper functions that were already scheduled still run, and their results are discarded.
|
|
87
|
+
|
|
86
88
|
This is a convenience function for processing inputs that arrive in batches. For more complex use cases, see [p-map](https://github.com/sindresorhus/p-map).
|
|
87
89
|
|
|
88
90
|
### limit.activeCount
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/openshell-sandbox",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.8",
|
|
4
4
|
"description": "OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@openclaw/fs-safe": "0.
|
|
12
|
-
"p-limit": "7.3.
|
|
11
|
+
"@openclaw/fs-safe": "0.21.1",
|
|
12
|
+
"p-limit": "7.3.3",
|
|
13
13
|
"zod": "4.6.5"
|
|
14
14
|
},
|
|
15
15
|
"openclaw": {
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"minHostVersion": ">=2026.5.12-beta.1"
|
|
24
24
|
},
|
|
25
25
|
"compat": {
|
|
26
|
-
"pluginApi": ">=2026.9.
|
|
26
|
+
"pluginApi": ">=2026.9.8"
|
|
27
27
|
},
|
|
28
28
|
"build": {
|
|
29
|
-
"openclawVersion": "2026.9.
|
|
29
|
+
"openclawVersion": "2026.9.8",
|
|
30
30
|
"bundledDist": false
|
|
31
31
|
},
|
|
32
32
|
"release": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"assets/activity.svg"
|
|
46
46
|
],
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"openclaw": ">=2026.9.
|
|
48
|
+
"openclaw": ">=2026.9.8"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
51
51
|
"openclaw": {
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { BigIntStats, Stats } from "node:fs";
|
|
2
|
-
export type DirectoryModeChecks = {
|
|
3
|
-
check?: () => void;
|
|
4
|
-
beforeChmod?: () => Promise<void>;
|
|
5
|
-
};
|
|
6
|
-
export type DirectoryModeOwner = {
|
|
7
|
-
verify(check?: () => void): Promise<void>;
|
|
8
|
-
apply(mode: number, checks?: DirectoryModeChecks): Promise<void>;
|
|
9
|
-
close(): Promise<void>;
|
|
10
|
-
};
|
|
11
|
-
export declare function assertOwnedDirectory(expected: Stats | BigIntStats, actual: Stats | BigIntStats): void;
|
|
12
|
-
/** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
|
|
13
|
-
export declare function ownDirectoryMode(params: {
|
|
14
|
-
inspect: () => Promise<number>;
|
|
15
|
-
chmod: (mode: number) => Promise<void>;
|
|
16
|
-
prepareChmod?: () => Promise<void>;
|
|
17
|
-
verifyChmod?: () => Promise<void>;
|
|
18
|
-
close: () => Promise<void>;
|
|
19
|
-
ignoreChmodError?: boolean;
|
|
20
|
-
}): DirectoryModeOwner;
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { FsSafeError } from "./errors.js";
|
|
2
|
-
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
3
|
-
export function assertOwnedDirectory(expected, actual) {
|
|
4
|
-
if (actual.isSymbolicLink() || !actual.isDirectory()) {
|
|
5
|
-
throw new FsSafeError("not-file", "directory mode target must be a real directory");
|
|
6
|
-
}
|
|
7
|
-
if (!sameFileIdentityForCleanup(expected, actual)) {
|
|
8
|
-
throw new FsSafeError("path-mismatch", "directory changed before its mode could be applied");
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
/** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
|
|
12
|
-
export function ownDirectoryMode(params) {
|
|
13
|
-
let pending = Promise.resolve();
|
|
14
|
-
let closing;
|
|
15
|
-
const enqueue = (run) => {
|
|
16
|
-
if (closing)
|
|
17
|
-
return Promise.reject(new FsSafeError("path-mismatch", "directory mode owner is closed"));
|
|
18
|
-
const operation = pending.then(run);
|
|
19
|
-
pending = operation.catch(() => undefined);
|
|
20
|
-
return operation;
|
|
21
|
-
};
|
|
22
|
-
return {
|
|
23
|
-
verify: (check) => enqueue(async () => {
|
|
24
|
-
check?.();
|
|
25
|
-
await params.inspect();
|
|
26
|
-
check?.();
|
|
27
|
-
}),
|
|
28
|
-
apply: (mode, checks = {}) => enqueue(async () => {
|
|
29
|
-
// inspect() reports permission bits only; chmod ignores file-type bits,
|
|
30
|
-
// so tolerate raw stat modes (e.g. S_IFDIR | 0o755) by masking up front.
|
|
31
|
-
mode &= 0o7777;
|
|
32
|
-
checks.check?.();
|
|
33
|
-
const currentMode = await params.inspect();
|
|
34
|
-
checks.check?.();
|
|
35
|
-
if (currentMode === mode && !checks.beforeChmod && !checks.check)
|
|
36
|
-
return;
|
|
37
|
-
if (currentMode !== mode) {
|
|
38
|
-
await params.prepareChmod?.();
|
|
39
|
-
checks.check?.();
|
|
40
|
-
}
|
|
41
|
-
await checks.beforeChmod?.();
|
|
42
|
-
checks.check?.();
|
|
43
|
-
// Hooks/ancestor checks can yield; recheck the original named association.
|
|
44
|
-
await params.inspect();
|
|
45
|
-
checks.check?.();
|
|
46
|
-
let dispatchDeadlineFailure;
|
|
47
|
-
if (currentMode !== mode) {
|
|
48
|
-
checks.check?.();
|
|
49
|
-
try {
|
|
50
|
-
await params.chmod(mode);
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
if (!params.ignoreChmodError)
|
|
54
|
-
throw error;
|
|
55
|
-
}
|
|
56
|
-
// Expiry cannot release the fd while post-dispatch verification is pending.
|
|
57
|
-
try {
|
|
58
|
-
checks.check?.();
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
dispatchDeadlineFailure = { error };
|
|
62
|
-
}
|
|
63
|
-
await params.verifyChmod?.();
|
|
64
|
-
}
|
|
65
|
-
const finalMode = await params.inspect();
|
|
66
|
-
if (dispatchDeadlineFailure)
|
|
67
|
-
throw dispatchDeadlineFailure.error;
|
|
68
|
-
checks.check?.();
|
|
69
|
-
if (!params.ignoreChmodError && finalMode !== mode) {
|
|
70
|
-
throw new FsSafeError("path-mismatch", "directory final mode could not be verified");
|
|
71
|
-
}
|
|
72
|
-
}),
|
|
73
|
-
close() {
|
|
74
|
-
closing ??= pending.then(params.close);
|
|
75
|
-
return closing;
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|