@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
export function isWindowsSeparator(value, offset) {
|
|
2
3
|
const code = value.charCodeAt(offset);
|
|
3
4
|
return code === 0x2f || code === 0x5c;
|
|
@@ -19,3 +20,53 @@ export function rootedWindowsDriveColonIndex(value) {
|
|
|
19
20
|
: windowsNamespaceMarker(value) !== undefined && hasWindowsDrivePrefix(value, 4) ? 5 : -1;
|
|
20
21
|
return colon >= 0 && isWindowsSeparator(value, colon + 1) ? colon : -1;
|
|
21
22
|
}
|
|
23
|
+
function asciiLowercase(value) {
|
|
24
|
+
return value.replace(/[A-Z]/g, letter => String.fromCharCode(letter.charCodeAt(0) + 0x20));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* True when dot-like components climb above the start of `segments`. Win32
|
|
28
|
+
* trims trailing dots and spaces, so any all-dot or all-space component other
|
|
29
|
+
* than `.` is counted as a parent step.
|
|
30
|
+
*/
|
|
31
|
+
export function windowsSegmentsClimbAbove(segments) {
|
|
32
|
+
let depth = 0;
|
|
33
|
+
for (const segment of segments) {
|
|
34
|
+
if (segment === "" || segment === ".")
|
|
35
|
+
continue;
|
|
36
|
+
depth += /^[. ]+$/.test(segment) ? -1 : 1;
|
|
37
|
+
if (depth < 0)
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Comparable share or device root of a path spelled with two leading
|
|
44
|
+
* separators, excluding namespaced drive roots such as `\\?\C:\`.
|
|
45
|
+
* Returns undefined for drive, rooted, and relative spellings, and null when
|
|
46
|
+
* the spelling alone cannot establish which share or device it reaches.
|
|
47
|
+
*/
|
|
48
|
+
export function windowsShareOrDeviceRoot(value) {
|
|
49
|
+
if (!isWindowsSeparator(value, 0) || !isWindowsSeparator(value, 1))
|
|
50
|
+
return undefined;
|
|
51
|
+
const spelled = value.replaceAll("/", "\\");
|
|
52
|
+
if (windowsNamespaceMarker(spelled) === undefined) {
|
|
53
|
+
return asciiLowercase(path.win32.parse(spelled).root.replace(/\\+$/, ""));
|
|
54
|
+
}
|
|
55
|
+
// Node passes namespace spellings to Win32 unchanged, where `..` climbs out
|
|
56
|
+
// of a drive or share (`\\.\C:\..\UNC\host`) and trailing dots, spaces and
|
|
57
|
+
// empty components are rewritten. GLOBALROOT and Global expose whole object
|
|
58
|
+
// namespaces, so none of these identify a single share or device.
|
|
59
|
+
const segments = spelled.slice(4).split("\\");
|
|
60
|
+
const head = asciiLowercase(segments[0] ?? "");
|
|
61
|
+
const authority = head === "unc" ? segments.slice(0, 3) : segments.slice(0, 1);
|
|
62
|
+
if (head === "globalroot" || head === "global" || authority.length < (head === "unc" ? 3 : 1) ||
|
|
63
|
+
authority.some(segment => segment === "" || /[. ]$/.test(segment)) ||
|
|
64
|
+
windowsSegmentsClimbAbove(segments.slice(authority.length))) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (rootedWindowsDriveColonIndex(value) === 5)
|
|
68
|
+
return undefined;
|
|
69
|
+
return asciiLowercase(head === "unc"
|
|
70
|
+
? `\\\\${authority[1]}\\${authority[2]}`
|
|
71
|
+
: `${spelled.slice(0, 4)}${authority[0]}`);
|
|
72
|
+
}
|
|
@@ -63,6 +63,9 @@ supplies a path that must stay under a root.
|
|
|
63
63
|
The helper returns `{ ok: false, code, error }` for path-policy failures such as
|
|
64
64
|
relative paths, symlinks, non-directories, or directory swaps during creation.
|
|
65
65
|
Operational filesystem failures such as permissions or I/O errors are rethrown.
|
|
66
|
+
Directory guards retain exact bigint identities, including Windows file IDs
|
|
67
|
+
above JavaScript's integer precision. These checks remain best-effort against
|
|
68
|
+
concurrent namespace changes.
|
|
66
69
|
|
|
67
70
|
### Files and identity
|
|
68
71
|
|
|
@@ -77,6 +80,7 @@ Operational filesystem failures such as permissions or I/O errors are rethrown.
|
|
|
77
80
|
| `overwriteFileHandle`, `OverwriteFileHandleOptions` | [in-place-write.md](in-place-write.md) | Overwrite a borrowed read/write handle with prefix-only preparation and best-effort rollback; preserves its inode, cursor, and caller-owned lifetime. |
|
|
78
81
|
| `openRootFile`, `openRootFileSync`, `canUseRootFileOpen`, `matchRootFileOpenFailure`, related types | – | Low-level root-bounded open; rejects every symlink component by default, with `symlinks: "follow-parents-within-root"` for contained parent aliases or `"follow-within-root"` for final links too. |
|
|
79
82
|
| `appendRegularFile`, `appendRegularFileSync`, `readRegularFile`, `readRegularFileSync`, `statRegularFile`, `statRegularFileSync`, `resolveRegularFileAppendFlags`, `AppendRegularFileOptions`, `RegularFileStatResult` | [regular-file.md](regular-file.md) | Type-checked regular-file I/O. |
|
|
83
|
+
| `retainEntryForPublication`, `RetainedEntryPublication`, `RetainEntryForPublicationOptions`, `EntryPublicationReceipt`, `EntryPublicationResult`, `EntryPublicationIssue`, `PublicationIdentity`, `PublicationParent` | [One-way entry publication](entry-publication.md) | Native no-replace directory/regular-file/symlink export, including Windows NTFS junctions, under caller-exclusive namespace; explicit transition receipts, close-only disposal, no source CAS. |
|
|
80
84
|
| `retainFileInDirectory`, `RetainedFile`, related receipt/result types | [Retained Windows files](retained-file.md) | Existing-file native handle custody and explicit removal; local NTFS, producer authority required, no persistence guarantee. |
|
|
81
85
|
| `sameFileIdentity`, `FileIdentityStat` | – | Compare two stats for same-inode equality. |
|
|
82
86
|
| `readDirectoryIdentity`, `assertDirectoryIdentitySync`, `DirectoryIdentity` | [directory-identity.md](directory-identity.md) | Observe exact bigint directory identity and synchronously check a caller-selected path, optionally retaining its canonical path. |
|
|
@@ -233,6 +233,9 @@ synchronous throws and rejected promises from custom asynchronous adapters.
|
|
|
233
233
|
|
|
234
234
|
The best-effort parent-directory synchronization helper also ignores either form
|
|
235
235
|
of close failure. Parent-directory mode admission and its close remain fail-closed.
|
|
236
|
+
If parent preparation and its descriptor close both fail, sync and async replacement
|
|
237
|
+
report an `AggregateError` retaining the preparation failure first and the close
|
|
238
|
+
failure second. A close failure alone is propagated unchanged.
|
|
236
239
|
A compatibility-publication handle that was not adopted also receives one
|
|
237
240
|
best-effort close, preserving the selected verification or previous-handle close
|
|
238
241
|
failure. The retained owner's close failures remain reportable.
|
|
@@ -601,7 +604,7 @@ type ReplaceFileAtomicSyncFileSystem = {
|
|
|
601
604
|
};
|
|
602
605
|
```
|
|
603
606
|
|
|
604
|
-
The async interface already requires `open()`, whose `FileHandle` supplies `chmod()`, so injecting `node:fs` or another conforming adapter needs no new async member. The async temp owner consumes its retained handle before awaiting `close()` during publication handoff and terminal settlement: if a custom adapter releases the resource and then rejects, that rejection is reported without calling `close()` on the same retained handle again. If publication verification opened a replacement handle before the previous retained handle failed to close, the replacement receives one best-effort close attempt. On POSIX, `open()` must support no-follow directory descriptors as Node does. A custom synchronous filesystem that passes `mode`, `dirMode`, or `preserveExistingMode` must supply `fchmodSync`; omission fails before any file or directory is created and never falls back to a pathname `chmod`. Existing synchronous adapters that request none of those options may omit it; their parent is still opened and identity-checked through a no-follow directory descriptor. Injecting plain `node:fs` supports explicit file and directory modes.
|
|
607
|
+
The async interface already requires `open()`, whose `FileHandle` supplies `chmod()`, so injecting `node:fs` or another conforming adapter needs no new async member. The async temp owner consumes its retained handle before awaiting `close()` during publication handoff and terminal settlement: if a custom adapter releases the resource and then rejects, that rejection is reported without calling `close()` on the same retained handle again. If publication verification opened a replacement handle before the previous retained handle failed to close, the replacement receives one best-effort close attempt. On POSIX, `open()` must support no-follow directory descriptors as Node does. A custom synchronous filesystem that passes `mode`, `dirMode`, or `preserveExistingMode` must supply `fchmodSync`; omission fails before any file or directory is created and never falls back to a pathname `chmod`. Existing synchronous adapters that request none of those options may omit it; their parent is still opened and identity-checked through a no-follow directory descriptor. Injecting plain `node:fs` supports explicit file and directory modes. Copy fallback applies the file mode through its pinned destination descriptor as well, preserving exact modes despite the process umask.
|
|
605
608
|
|
|
606
609
|
## See also
|
|
607
610
|
|
|
@@ -112,20 +112,12 @@ FS_SAFE_NATIVE_MODE=auto # auto | off | require | true | false | on | 1 | 0
|
|
|
112
112
|
|
|
113
113
|
`OPENCLAW_FS_SAFE_NATIVE_MODE` is accepted as an alias. Programmatic overrides via `configureFsSafeNative` always win.
|
|
114
114
|
|
|
115
|
-
### Python-helper
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
same native mode; interpreter paths are ignored. The deprecated
|
|
122
|
-
`configureFsSafePython()` export behaves the same way.
|
|
123
|
-
|
|
124
|
-
Replace these inputs with `configureFsSafeNative()` or
|
|
125
|
-
`FS_SAFE_NATIVE_MODE` during the 0.5 upgrade. The bridge exists only so shipped
|
|
126
|
-
0.4 configuration fails loudly and maps predictably; it is not a supported
|
|
127
|
-
Python execution path. Follow the [0.5 migration checklist](migrating-to-0.5.md)
|
|
128
|
-
for the full upgrade.
|
|
115
|
+
### Removed Python-helper configuration
|
|
116
|
+
|
|
117
|
+
The deprecated Python configuration bridge and its environment variables have
|
|
118
|
+
been removed. Use `configureFsSafeNative()` or `FS_SAFE_NATIVE_MODE`. See the
|
|
119
|
+
[0.5 migration checklist](migrating-to-0.5.md#2-replace-python-helper-configuration)
|
|
120
|
+
for the migration path.
|
|
129
121
|
|
|
130
122
|
## Related pages
|
|
131
123
|
|
|
@@ -336,20 +336,10 @@ Small, focused PRs land faster. The general shape:
|
|
|
336
336
|
|
|
337
337
|
## Releases
|
|
338
338
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
artifact and provenance statement, and then creates the GitHub release.
|
|
344
|
-
|
|
345
|
-
Each package needs its own npm trusted-publisher configuration for
|
|
346
|
-
`openclaw/fs-safe` and `release.yml`. A new platform package must be created and
|
|
347
|
-
configured on npm before the first tag that references it; npm trust is
|
|
348
|
-
package-specific and cannot be bootstrapped by the tag workflow itself.
|
|
349
|
-
|
|
350
|
-
External contributors do not need to do anything beyond getting the pull
|
|
351
|
-
request merged. Maintainers must not publish locally or add npm automation
|
|
352
|
-
tokens.
|
|
339
|
+
Releases use protected `vX.Y.Z` tags on `main` and npm trusted publishing.
|
|
340
|
+
Contributors finish at PR merge; maintainers follow the
|
|
341
|
+
[release checklist](https://github.com/openclaw/fs-safe/blob/main/RELEASE-PREREQS.md).
|
|
342
|
+
Do not publish locally or add npm automation tokens.
|
|
353
343
|
|
|
354
344
|
## Reporting security issues
|
|
355
345
|
|
|
@@ -217,35 +217,12 @@ clone or `copy_file_range` transparently continues down the fallback chain.
|
|
|
217
217
|
|
|
218
218
|
## Recoverable atomic-replace fallback
|
|
219
219
|
|
|
220
|
-
`replaceFileAtomic()` normally publishes a
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
```ts
|
|
228
|
-
await replaceFileAtomic({
|
|
229
|
-
filePath: statePath,
|
|
230
|
-
content: nextState,
|
|
231
|
-
syncTempFile: true,
|
|
232
|
-
syncParentDir: true,
|
|
233
|
-
copyFallbackOnPermissionError: true,
|
|
234
|
-
copyFallbackRestore: "restore-original",
|
|
235
|
-
maxRestoreBytes: 4 * 1024 * 1024,
|
|
236
|
-
destinationHardlinks: "reject",
|
|
237
|
-
});
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
The existing regular-file destination is pinned before its link count is
|
|
241
|
-
accepted. Its original bytes are read within `maxRestoreBytes`, then the new
|
|
242
|
-
bytes are written and synchronized through the same descriptor. If a write or
|
|
243
|
-
sync tears, fs-safe rewrites the snapshot and fsyncs it before throwing. Inspect
|
|
244
|
-
`details.cleanup`: `"restored"` means the original bytes were put back and
|
|
245
|
-
synchronized; `"restore-failed"` means the replacement and recovery both
|
|
246
|
-
failed, so the destination must be treated as indeterminate. This is recovery
|
|
247
|
-
from a live-process I/O failure, not a transaction or a substitute for an
|
|
248
|
-
application backup protocol.
|
|
220
|
+
`replaceFileAtomic()` normally publishes a sibling temp with an atomic rename;
|
|
221
|
+
file and directory synchronization are opt-in. Its permission-error copy fallback
|
|
222
|
+
remains non-atomic even with opt-in `copyFallbackRestore: "restore-original"`.
|
|
223
|
+
Recovery requires an explicit `maxRestoreBytes` budget and inspection of the
|
|
224
|
+
`details.cleanup` receipt. See [Atomic writes](atomic.md#eperm-and-copy-fallback)
|
|
225
|
+
for restoration limits, identity/authority refusals, and retained-inode semantics.
|
|
249
226
|
|
|
250
227
|
## Streaming SHA-256
|
|
251
228
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: One-way entry publication
|
|
3
|
+
description: Retained directory, regular-file and symlink publication with atomic destination absence, under caller-owned namespace stability.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# One-way retained entry publication
|
|
7
|
+
|
|
8
|
+
`retainEntryForPublication` from `@openclaw/fs-safe/advanced` admits an existing
|
|
9
|
+
directory, single-link regular file or single-link symlink for a **one-way, native no-replace rename**.
|
|
10
|
+
It retains both parent directories and the source object. It never reverses a
|
|
11
|
+
move, unlinks a name, removes a tree, or copies across filesystems.
|
|
12
|
+
|
|
13
|
+
This is an advanced cooperative primitive, **not a sandbox or source-identity
|
|
14
|
+
compare-and-swap**. The caller must exclude source namespace writers from its
|
|
15
|
+
original observation through publication, and keep the admitted physical parent
|
|
16
|
+
and ancestor topology stable through all later pathname consumers. Descriptors
|
|
17
|
+
prevent identity reuse and bind the native rename to retained parents; they do
|
|
18
|
+
not pin the source name or confine the operation to the parents' current paths.
|
|
19
|
+
Observed substitutions refuse; substitutions after the last check remain outside
|
|
20
|
+
this contract. A callback, lockfile, random directory or mode `0700` does not
|
|
21
|
+
exclude arbitrary same-user nonparticipants.
|
|
22
|
+
|
|
23
|
+
## Inputs and admission
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import {
|
|
27
|
+
retainEntryForPublication,
|
|
28
|
+
type RetainEntryForPublicationOptions,
|
|
29
|
+
type EntryPublicationResult,
|
|
30
|
+
} from "@openclaw/fs-safe/advanced";
|
|
31
|
+
|
|
32
|
+
function publishManagedEntry(options: RetainEntryForPublicationOptions): EntryPublicationResult {
|
|
33
|
+
const publication = retainEntryForPublication(options);
|
|
34
|
+
return publication.publish();
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`options` contains:
|
|
39
|
+
|
|
40
|
+
- `source.parent` and `destination.parent`: `{ path, identity: { dev, ino } }`.
|
|
41
|
+
Paths must already be absolute canonical physical spellings, with no symlink,
|
|
42
|
+
case or lexical aliases. Preserve caller-captured original identities; never
|
|
43
|
+
substitute a new observation merely to make a stale admission succeed.
|
|
44
|
+
- `source.basename` and `destination.basename`: nonempty direct-child names.
|
|
45
|
+
Dot entries, separators, colons, NUL and control characters are refused.
|
|
46
|
+
- `source.expected`: `{ dev, ino, kind: "directory" | "file" | "symlink" }`. Identities must
|
|
47
|
+
be exact unsigned bigint observations with a known nonzero inode. File contents
|
|
48
|
+
are not hashed, frozen or made read-only. Regular files and symlinks must have one link;
|
|
49
|
+
directories are not recursively inspected. Source and destination must not
|
|
50
|
+
overlap. Special entries are not supported.
|
|
51
|
+
- `assertBeforeMutation`: required synchronous authority callback. Throw to refuse.
|
|
52
|
+
Promises and generators refuse. The callback runs once in `publish()`, followed
|
|
53
|
+
by fresh source and parent checks. It cannot dispose or reenter the resource.
|
|
54
|
+
It supplies authorization, not namespace isolation.
|
|
55
|
+
|
|
56
|
+
Admission can throw `FsSafeError`. `cause` retains the original admission error;
|
|
57
|
+
`details.result` reports `not-published`, descriptor settlement and ordered issues.
|
|
58
|
+
Admission never changes either namespace. Caller cleanup responsibilities do not
|
|
59
|
+
transfer to this resource.
|
|
60
|
+
|
|
61
|
+
Symlink publication moves the link inode, never its payload. Link target bytes are
|
|
62
|
+
not decoded, resolved or rewritten; relative, dangling and non-UTF-8 targets are
|
|
63
|
+
preserved. Relative targets resolve from the final parent after publication, so
|
|
64
|
+
the caller must prepare the correct final layout. External payloads remain owned
|
|
65
|
+
by their existing owner, and the caller must hold any target/ancestor stability
|
|
66
|
+
needed by subsequent consumers. Source symlink basenames must match the physical
|
|
67
|
+
directory entry spelling; parent aliases remain refused. No recursive symlink
|
|
68
|
+
policy is imposed on the contents of a published directory.
|
|
69
|
+
|
|
70
|
+
## Results and lifetime
|
|
71
|
+
|
|
72
|
+
`publish()` is synchronous, one-shot, and closes every retained descriptor/handle
|
|
73
|
+
before returning an immutable `EntryPublicationResult`. Inspect **all** fields:
|
|
74
|
+
|
|
75
|
+
| Field | Meaning |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| `transition: "committed"` | Native rename returned success. Recorded before verification or close. |
|
|
78
|
+
| `transition: "not-published"` | Refused before dispatch, or a determinate native rejection. Both entries are preserved by this operation. |
|
|
79
|
+
| `transition: "indeterminate"` | Native reply was lost/malformed or the error did not prove rejection. Retain both locations; do not infer a result from later path observations. |
|
|
80
|
+
| `verification` | `verified`, `failed`, or `not-performed`. A failed postcheck never changes committed to not-published. |
|
|
81
|
+
| `resources` | `closed` or `close-failed`. Every owned close is attempted once, even if an earlier close failed; ambiguous closes are never retried. |
|
|
82
|
+
| `issues` | Ordered `{ phase, cause }` failures. The first is primary, including falsy thrown values; later close failures do not mask it. |
|
|
83
|
+
|
|
84
|
+
A committed result with issues is not an error-free publication. The immutable
|
|
85
|
+
`receipt` records original source/destination observations and capability facts:
|
|
86
|
+
`destinationAbsence: "atomic"`,
|
|
87
|
+
`sourceIdentity: "observed-under-caller-exclusive-namespace"`, and
|
|
88
|
+
`parentBinding: "retained-object"`, plus admitted filesystem names.
|
|
89
|
+
|
|
90
|
+
`dispose()` only closes. It does not delete unpublished staging or published
|
|
91
|
+
names. Subsequent `publish()`/`dispose()` return the same terminal result without
|
|
92
|
+
another effect. `Symbol.dispose` closes too, throwing with `details.result` if
|
|
93
|
+
closure reported a failure. Retain an unused resource only while its namespace
|
|
94
|
+
contract is held, then explicitly dispose it; there is no GC cleanup guarantee.
|
|
95
|
+
|
|
96
|
+
Results are in-memory syscall dispositions, **not a durable transaction journal**.
|
|
97
|
+
For crash recovery record intent before dispatch in the caller's own durable
|
|
98
|
+
owner, then persist the result. A crash before receipt persistence is unresolved.
|
|
99
|
+
Several publications are not one atomic transaction: if a later child collides,
|
|
100
|
+
keep prior exposed children, newer destination writes and remaining staging. This
|
|
101
|
+
API deliberately provides no automatic compensation or retry.
|
|
102
|
+
|
|
103
|
+
## Platform and filesystem contract
|
|
104
|
+
|
|
105
|
+
Native support is mandatory even in `auto` mode. There is no Node pathname
|
|
106
|
+
rename or copy fallback. This API supports local APFS/HFS on macOS and
|
|
107
|
+
ext-family/XFS/Btrfs/tmpfs on Linux, subject to the kernel/filesystem's native
|
|
108
|
+
no-replace operation (`renameatx_np(RENAME_EXCL)` or `renameat2(RENAME_NOREPLACE)`).
|
|
109
|
+
Network, FUSE, overlay and unknown filesystem types refuse before dispatch;
|
|
110
|
+
On Windows, fixed local NTFS volumes support handle-relative
|
|
111
|
+
`FileRenameInformationEx` without replacement. Other Windows filesystems, remote
|
|
112
|
+
paths and namespace aliases refuse; cross-device moves refuse without copying.
|
|
113
|
+
Other platforms are unsupported.
|
|
114
|
+
Unsupported syscall/flag errors do not trigger another rename implementation.
|
|
115
|
+
|
|
116
|
+
A preexisting **or raced** empty directory, file or symlink at the destination is
|
|
117
|
+
never overwritten by a successful no-replace call. Admission also rejects a
|
|
118
|
+
destination alias of the source, including Darwin case-only rename exceptions.
|
|
119
|
+
For distinct destination entries this is the syscall guarantee;
|
|
120
|
+
POSIX source selection still occurs by basename. Moving admitted A away and installing
|
|
121
|
+
B after the native source check can cause POSIX to move B. Postchecks may detect
|
|
122
|
+
that only after commitment. Applications needing protection from that schedule
|
|
123
|
+
must use a stronger namespace owner, not treat this API as source CAS.
|
|
124
|
+
|
|
125
|
+
### Windows entries and resources
|
|
126
|
+
|
|
127
|
+
`kind: "symlink"` includes Windows file/directory symbolic links
|
|
128
|
+
(`IO_REPARSE_TAG_SYMLINK`) and directory junctions (`IO_REPARSE_TAG_MOUNT_POINT`).
|
|
129
|
+
The original entry handle is renamed relative to the retained destination parent;
|
|
130
|
+
the source is not reopened to select the object for mutation. The complete opaque
|
|
131
|
+
reparse buffer is checked before and after publication, never decoded or rebuilt.
|
|
132
|
+
Other reparse tags fail closed. Relative symbolic-link targets and absolute
|
|
133
|
+
junction targets keep their original bytes. Reparse ancestors are not admitted.
|
|
134
|
+
The caller's existing-empty destination directory and distinct sibling runtime
|
|
135
|
+
stores need not be replaced or removed.
|
|
136
|
+
|
|
137
|
+
All ancestors and both parents are opened component-by-component without following
|
|
138
|
+
reparse points, checked against physical spellings, and retained until close.
|
|
139
|
+
Original exact same-volume source/parent identities are required. Named source
|
|
140
|
+
observations are checked against the retained handle, including native file ID.
|
|
141
|
+
Windows handle selection does not upgrade the cross-platform receipt into a
|
|
142
|
+
namespace lock, current-path confinement or a durable transaction. Keep the same
|
|
143
|
+
caller-exclusive namespace and stable-topology contract through later consumers.
|
|
144
|
+
Every handle, including partial admission and temporary observation handles, is
|
|
145
|
+
consumed once by explicit close; a lost native reply leaves the transition or
|
|
146
|
+
resource settlement unknown rather than inferring success from pathnames.
|
|
@@ -46,6 +46,16 @@ code and `policy` category for compatibility, along with the original `cause`;
|
|
|
46
46
|
they do not expose native message text or paths. Already-classified `FsSafeError`
|
|
47
47
|
instances and missing-path errors keep their existing classification.
|
|
48
48
|
|
|
49
|
+
Descriptor exhaustion during writes uses `helper-failed` / `operational` and
|
|
50
|
+
names `EMFILE` (process descriptor limit) or `ENFILE` (system descriptor limit)
|
|
51
|
+
in its message. The original error remains in `cause`, including any
|
|
52
|
+
`SuppressedError` linking publication and disposal failures. When native rename
|
|
53
|
+
has an uncertain outcome, the message explicitly reports indeterminate
|
|
54
|
+
publication and a preserved stage; `details.publication` and `details.cleanup`
|
|
55
|
+
retain that receipt. Descriptors are still closed. An errno alone does not prove
|
|
56
|
+
that publication never happened, so this diagnostic does not authorize removing
|
|
57
|
+
the stage or retrying the write. Existing boundary/policy errors keep their codes.
|
|
58
|
+
|
|
49
59
|
`details` is an operation-specific receipt, not an alternate error code. For
|
|
50
60
|
example, `publishFileExclusive()` uses it to report the failing phase, created
|
|
51
61
|
target identity, cleanup decision, and failed directory-sync outcome. Narrow
|
|
@@ -115,6 +125,12 @@ type FsSafeErrorCode =
|
|
|
115
125
|
|
|
116
126
|
## Code reference
|
|
117
127
|
|
|
128
|
+
Create-only Root writes to an existing regular file or directory report
|
|
129
|
+
`already-exists` in every native mode, including atomic and streamed creation.
|
|
130
|
+
Policy failures such as an explicit symlink rejection or a denied path retain
|
|
131
|
+
their precedence. A non-directory ancestor still reports its path/type failure;
|
|
132
|
+
it is not an existing destination.
|
|
133
|
+
|
|
118
134
|
| Code | When it fires | Common causes |
|
|
119
135
|
|---|---|---|
|
|
120
136
|
| `already-exists` | `create()`, `createJson()`, `move({ overwrite: false })`. | Target file or directory already at the destination. |
|
|
@@ -42,11 +42,6 @@ const cache = fileStore({
|
|
|
42
42
|
|
|
43
43
|
Store and per-call `maxBytes` values must be non-negative safe integers or positive `Infinity`. Zero is an active zero-byte cap; `Infinity` disables the cap. An omitted or explicitly `undefined` per-call value preserves the store-level limit. The same rule applies to buffer writes, streams, copies, async reads, and synchronous reads/writes.
|
|
44
44
|
|
|
45
|
-
Use `private: true` for credentials, auth profiles, tokens, and other private
|
|
46
|
-
state. Private mode keeps the same `FileStore` shape but routes writes through
|
|
47
|
-
the secret-file atomic path, refusing symlink parent components and re-asserting
|
|
48
|
-
mode after rename.
|
|
49
|
-
|
|
50
45
|
Returns a `FileStore`:
|
|
51
46
|
|
|
52
47
|
```ts
|
|
@@ -116,6 +111,34 @@ therefore does not imply that no filesystem access or serialization occurred.
|
|
|
116
111
|
|
|
117
112
|
`root()` returns a [`Root`](root.md) handle for the same directory when you need the full surface (move, list, mkdir). It's a fresh handle per call and is safe to call frequently.
|
|
118
113
|
|
|
114
|
+
## Private mode
|
|
115
|
+
|
|
116
|
+
Use `fileStore({ private: true })` for credentials, auth profiles, tokens, and
|
|
117
|
+
other private state. It keeps the same `FileStore` shape and mode defaults above.
|
|
118
|
+
Async writes use the secret-file atomic path, refusing symlink parent components
|
|
119
|
+
and re-asserting file mode after rename. Existing directories must already have
|
|
120
|
+
the requested mode; async writes reject unsuitable permissions rather than
|
|
121
|
+
repairing them. New-directory initialization requires guarded descriptor
|
|
122
|
+
authority and can fail closed under restrictive platform/umask combinations;
|
|
123
|
+
see the [secret-directory policy](secret-file.md#parameters).
|
|
124
|
+
|
|
125
|
+
Private locked JSON mutations prepare directories before sidecar acquisition
|
|
126
|
+
and bind the lock to the admitted parent identity. Lock normalization is
|
|
127
|
+
read-only: a deleted or replaced parent is rejected, not recreated. The writer
|
|
128
|
+
revalidates directory admission afterward. Reads never create directories and
|
|
129
|
+
retain the shared [read semantics](#reads).
|
|
130
|
+
|
|
131
|
+
For boot paths or sync-only integrations:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { fileStoreSync } from "@openclaw/fs-safe/store";
|
|
135
|
+
|
|
136
|
+
fileStoreSync({ rootDir: "/var/lib/app", private: true }).writeJson("config.json", config);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Sync directory modes remain repair-compatible on POSIX through verified
|
|
140
|
+
descriptors, with the platform limitations described under [Writes](#writes).
|
|
141
|
+
|
|
119
142
|
## Writes
|
|
120
143
|
|
|
121
144
|
Writes use guarded sibling-temp publication: apply file and directory modes,
|
|
@@ -48,37 +48,8 @@ await fs.remove("notes/archive/today.txt");
|
|
|
48
48
|
|
|
49
49
|
## What you get
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
| [`root()`](root.md) | One boundary for read/write/move/remove and bounded recursive walking inside a trusted directory. |
|
|
54
|
-
| [`@openclaw/fs-safe/config`](config.md) | Process-global native helper and lock-option defaults. |
|
|
55
|
-
| [`@openclaw/fs-safe/guest`](guest.md) | Python filesystem source for caller-owned guest transports, with admitted roots and descriptor-relative operations. |
|
|
56
|
-
| [Native helper policy](native-helper.md) | Choose `auto`, `off`, or `require` for platform-native primitives. |
|
|
57
|
-
| [Native architecture](native.md) | Understand the thin syscall layer, beneath model, platform mechanisms, and fallback boundary. |
|
|
58
|
-
| [`replaceFileAtomic`](atomic.md) | Sibling-temp + rename, fsync hooks, mode preservation, copy fallback. |
|
|
59
|
-
| [`@openclaw/fs-safe/durability`](durability.md) | Pinned directory identities, durable creation, exclusive publication, streaming SHA-256, provenance receipts, and sync-failure policy. |
|
|
60
|
-
| [`writeExternalFileWithinRoot`](output.md) | Stage external-library file output in private temp storage, then finalize under a root. |
|
|
61
|
-
| [`writeJson` / `readJson*`](json.md) | JSON state files with strict and lenient read variants. |
|
|
62
|
-
| [`@openclaw/fs-safe/store`](store.md) | Overview of `fileStore`, `fileStoreSync`, and `jsonStore`. |
|
|
63
|
-
| [`jsonStore`](json-store.md) | Single JSON state file with explicit fallback, atomic writes, and optional locking. |
|
|
64
|
-
| [`fileStore`](file-store.md) | Managed multi-file/blob store with modes, stream writes, copy-in, pruning, and private mode. |
|
|
65
|
-
| [Private file-store mode](private-file-store.md) | `fileStore({ private: true })` for private JSON/text state at 0600 under 0700 dirs. |
|
|
66
|
-
| [`tempWorkspace`](temp.md) | 0700 scratch dir with auto-cleanup. |
|
|
67
|
-
| [`readSecureFile`](secure-file.md) | Absolute file reads with fd pinning, permissions, owner, size, and timeout checks. |
|
|
68
|
-
| [`watch`](watch.md) | Guarded filesystem observation with advisory native hints and polling. |
|
|
69
|
-
| [`walkDirectory` / `Root.walk`](walk.md) | Standalone inventories plus root-bounded pruning, budgets, and partial-error reporting. |
|
|
70
|
-
| [`Root.entries`](entries.md) | Guarded nonrecursive entries, bounded name collection, and caller-owned symlink validation. |
|
|
71
|
-
| [`extractArchive`](archive.md) | Policy-driven ZIP/TAR extraction with clamp/filter, metadata/path-depth, link, count, and byte limits. |
|
|
72
|
-
| [Secret files](secret-file.md) | Mode-0600 credentials with size and TOCTOU defense. |
|
|
73
|
-
| [Permissions](permissions.md) | POSIX mode helpers plus Windows ACL inspection, raw owner/ACE facts, remediation, and private-directory creation. |
|
|
74
|
-
| [`acquireFileLock`](sidecar-lock.md) | Cross-process file lock with retry and fail-closed stale-lock handling. |
|
|
75
|
-
| [`FsSafeError`](errors.md) | Closed code union (with `policy` / `operational` category) you can branch on. |
|
|
76
|
-
| [Public API inventory](public-api.md) | Complete runtime/type export cross-check against the generated declarations. |
|
|
77
|
-
| [`pathScope()`](path-scope.md) | Lower-level absolute-path boundary helper; lives behind `@openclaw/fs-safe/advanced`. |
|
|
78
|
-
| [`@openclaw/fs-safe/advanced`](advanced.md) | Directory of lower-level composition helpers (path scopes, regular-file I/O, install paths, sibling-temp writes, …). |
|
|
79
|
-
| [`@openclaw/fs-safe/test-hooks`](test-hooks.md) | Test-only injection hooks for reproducing open/lstat races. |
|
|
80
|
-
| [Migrating to 0.5](migrating-to-0.5.md) | End-to-end checklist for Python removal and 0.4 API behavior changes. |
|
|
81
|
-
| [Migrating to 0.6](migrating-to-0.6.md) | Installer-policy checklist for the platform-native package split. |
|
|
51
|
+
Browse the [subpath catalogue](install.md#subpath-exports) for entry points and
|
|
52
|
+
the [public API inventory](public-api.md) for individual runtime and type exports.
|
|
82
53
|
|
|
83
54
|
## Status
|
|
84
55
|
|
|
@@ -98,6 +98,8 @@ Use the main entry for the common surface, or the focused subpaths when you want
|
|
|
98
98
|
|---|---|
|
|
99
99
|
| `@openclaw/fs-safe` | Common root, config, output, lock, native-mode, and error exports. |
|
|
100
100
|
| `@openclaw/fs-safe/root` | `root()`, `Root`, `RootDefaults`, and root-walk types. |
|
|
101
|
+
| `@openclaw/fs-safe/copy` | [Directory copying and cloning](copy.md), clone-source creation, filesystem probes, and clone metadata. |
|
|
102
|
+
| `@openclaw/fs-safe/guest` | [Guest filesystem protocol](guest.md): Python source and exit constants for caller-owned transports. |
|
|
101
103
|
| `@openclaw/fs-safe/config` | Process-global native helper and lock defaults. |
|
|
102
104
|
| `@openclaw/fs-safe/path` | `isPathInside`, `safeRealpathSync`, `isWithinDir`, error helpers. |
|
|
103
105
|
| `@openclaw/fs-safe/output` | Guarded staging/finalization for libraries that require an absolute output path. |
|
|
@@ -107,15 +109,17 @@ Use the main entry for the common surface, or the focused subpaths when you want
|
|
|
107
109
|
| `@openclaw/fs-safe/atomic` | `replaceFileAtomic`, `writeTextAtomic`, `replaceDirectoryAtomic`, `movePathWithCopyFallback`. |
|
|
108
110
|
| `@openclaw/fs-safe/durability` | Pinned directories, strict sync, durable directory creation, exclusive publication, and streaming SHA-256. |
|
|
109
111
|
| `@openclaw/fs-safe/temp` | `tempWorkspace`, `withTempWorkspace`, sync variants, `resolveSecureTempRoot`. |
|
|
112
|
+
| `@openclaw/fs-safe/secure-temp-root` | [Secure temp root](temp.md#secure-temp-root) resolution without workspace/store imports. |
|
|
110
113
|
| `@openclaw/fs-safe/secure-file` | `readSecureFile` for pinned absolute file reads with permissions checks. |
|
|
111
114
|
| `@openclaw/fs-safe/file-lock` | `acquireFileLock`, `withFileLock`, `createFileLockManager`, and related lock types. |
|
|
115
|
+
| `@openclaw/fs-safe/watch` | [Guarded filesystem observation](watch.md) with advisory native hints and polling. |
|
|
112
116
|
| `@openclaw/fs-safe/permissions` | POSIX mode helpers, Windows ACL inspection/remediation, raw owner/ACE facts, and private-directory creation. |
|
|
113
117
|
| `@openclaw/fs-safe/walk` | `walkDirectory`, `walkDirectorySync`, related types. Budget-bounded, not root-bounded. |
|
|
114
118
|
| `@openclaw/fs-safe/archive` | `extractArchive`, `readArchiveEntry`, kind resolution, policy types, limits, and preflight helpers. |
|
|
115
119
|
| `@openclaw/fs-safe/advanced` | Lower-level composition helpers: path scopes, root-file open, install paths, local-root readers, temp-file targets, sibling-temp writes, regular-file helpers, `pathExists`, `withTimeout`, and related advanced types. This surface is less stable than the focused public subpaths. |
|
|
116
120
|
| `@openclaw/fs-safe/errors` | `FsSafeError`, `FsSafeErrorCode`. |
|
|
117
121
|
| `@openclaw/fs-safe/types` | Shared types: `DirEntry`, `PathStat`, `BasePathOptions`, … |
|
|
118
|
-
| `@openclaw/fs-safe/test-hooks` | Test-only hooks for injecting races
|
|
122
|
+
| `@openclaw/fs-safe/test-hooks` | [Test-only hooks](testing.md#hooks-api) for injecting races; registration requires `NODE_ENV=test` or `VITEST=true`. |
|
|
119
123
|
|
|
120
124
|
## Runtime dependencies
|
|
121
125
|
|
|
@@ -191,36 +195,11 @@ the cause.
|
|
|
191
195
|
|
|
192
196
|
### Loading modes
|
|
193
197
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
import { configureFsSafeNative } from "@openclaw/fs-safe/config";
|
|
202
|
-
|
|
203
|
-
configureFsSafeNative({ mode: "auto" }); // default
|
|
204
|
-
configureFsSafeNative({ mode: "off" }); // disable the addon; reject native-only operations
|
|
205
|
-
configureFsSafeNative({ mode: "require" }); // fail closed if unavailable
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Environment variables are read at runtime:
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
FS_SAFE_NATIVE_MODE=off # auto | off | require
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
`OPENCLAW_FS_SAFE_NATIVE_MODE` is also accepted.
|
|
215
|
-
|
|
216
|
-
Disabling native loading keeps fallback-capable operations working through Node path
|
|
217
|
-
operations guarded by lexical and canonical checks plus identity verification.
|
|
218
|
-
Use `require` when native-backed operations must fail instead of falling back.
|
|
219
|
-
Temp workspaces retain compatible JavaScript quarantine cleanup in `auto` and
|
|
220
|
-
`off`. Set `cleanupSafety: "require-bounded"` to reject before child creation
|
|
221
|
-
unless native no-replace quarantine and descriptor-bounded tree removal are
|
|
222
|
-
available. See the [temp workspace contract](temp.md#private-temp-workspaces). The exact boundary
|
|
223
|
-
for other operations is documented in [native helper policy](native-helper.md).
|
|
198
|
+
Configure `auto` (default), `off`, or `require` before first use with
|
|
199
|
+
`configureFsSafeNative()` or `FS_SAFE_NATIVE_MODE`. See
|
|
200
|
+
[Native helper policy](native-helper.md#modes) for mode selection, loader lifetime,
|
|
201
|
+
and strict failure behavior. Temp workspace `cleanupSafety` is a separate policy;
|
|
202
|
+
see the [temp workspace contract](temp.md#private-temp-workspaces).
|
|
224
203
|
|
|
225
204
|
## Verify the install
|
|
226
205
|
|
|
@@ -221,5 +221,5 @@ const state = await readJsonIfExists<State>("./state.json");
|
|
|
221
221
|
- [JSON store](json-store.md) — a single-file state wrapper with explicit per-call fallback (`readOr` / `updateOr`) and optional sidecar locking.
|
|
222
222
|
- [Atomic writes](atomic.md) — lower-level sibling-temp replacement helpers.
|
|
223
223
|
- [Secret files](secret-file.md) — JSON-or-text writes with mode 0600 in mode 0700 dirs.
|
|
224
|
-
- [Private file-store mode](
|
|
224
|
+
- [Private file-store mode](file-store.md#private-mode) — root-bounded JSON+text state stores.
|
|
225
225
|
- [File lock](sidecar-lock.md) — cross-process coordination.
|
|
@@ -90,7 +90,6 @@ candidate, so replacing options while a read is pending cannot weaken admission.
|
|
|
90
90
|
type ReadLocalFileFromRootsOptions = LocalRootsInputOptions & {
|
|
91
91
|
hardlinks?: "reject" | "allow";
|
|
92
92
|
maxBytes?: number;
|
|
93
|
-
nonBlockingRead?: boolean;
|
|
94
93
|
symlinks?: "reject" | "follow-within-root" | "follow-parents-within-root";
|
|
95
94
|
};
|
|
96
95
|
|
|
@@ -29,7 +29,9 @@ resolved outside the root. `root()` handles were not affected. See the
|
|
|
29
29
|
|
|
30
30
|
## 2. Replace Python helper configuration
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
The deprecated `configureFsSafePython()` / `FsSafePythonConfig` bridge and its
|
|
33
|
+
six Python environment variables have been removed in current releases.
|
|
34
|
+
Configure native mode before the first filesystem operation:
|
|
33
35
|
|
|
34
36
|
```ts
|
|
35
37
|
import { configureFsSafeNative } from "@openclaw/fs-safe/config";
|
|
@@ -37,17 +39,15 @@ import { configureFsSafeNative } from "@openclaw/fs-safe/config";
|
|
|
37
39
|
configureFsSafeNative({ mode: "auto" });
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Python is never spawned. Treat that warning as an upgrade diagnostic, not as a
|
|
50
|
-
second supported helper path.
|
|
42
|
+
Replace `FS_SAFE_PYTHON_MODE` with `FS_SAFE_NATIVE_MODE`, or
|
|
43
|
+
`OPENCLAW_FS_SAFE_PYTHON_MODE` with `OPENCLAW_FS_SAFE_NATIVE_MODE`. Remove
|
|
44
|
+
`pythonPath`, `FS_SAFE_PYTHON`, `OPENCLAW_FS_SAFE_PYTHON`,
|
|
45
|
+
`OPENCLAW_PINNED_PYTHON`, and `OPENCLAW_PINNED_WRITE_PYTHON`; prebuilt native
|
|
46
|
+
binaries need no interpreter. Python environment settings are now ignored
|
|
47
|
+
silently: a deployment that set `FS_SAFE_PYTHON_MODE=require` or `off` runs in
|
|
48
|
+
`auto` after upgrading unless it sets `FS_SAFE_NATIVE_MODE` (or calls
|
|
49
|
+
`configureFsSafeNative`) first. Programmatic native configuration wins, followed by
|
|
50
|
+
`FS_SAFE_NATIVE_MODE`, `OPENCLAW_FS_SAFE_NATIVE_MODE`, and the default `auto`.
|
|
51
51
|
|
|
52
52
|
Choose the production mode deliberately:
|
|
53
53
|
|