@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.
Files changed (225) hide show
  1. package/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
  2. package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
  3. package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
  4. package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
  5. package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
  6. package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
  7. package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
  8. package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
  9. package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
  10. package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
  11. package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
  12. package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
  13. package/dist/api.js +234 -297
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/doctor-contract-api.js +2 -2
  16. package/dist/session-binding-contract-api.js +1 -1
  17. package/dist/setup-api.js +1 -1
  18. package/dist/setup-entry.js +1 -1
  19. package/dist/subagent-hooks-api.js +1 -1
  20. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
  21. package/node_modules/@openclaw/fs-safe/README.md +70 -379
  22. package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
  23. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
  24. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  25. package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
  26. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  27. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
  28. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
  29. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
  30. package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
  31. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
  32. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
  33. package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
  34. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
  35. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
  36. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
  37. package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
  38. package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
  39. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
  40. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
  41. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
  42. package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
  43. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
  44. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
  45. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
  46. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
  47. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
  48. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
  49. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
  50. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
  51. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
  52. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
  53. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
  54. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
  55. package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
  56. package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
  57. package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
  58. package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
  59. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
  60. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
  61. package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
  62. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
  63. package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
  64. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
  65. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
  66. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
  67. package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
  68. package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
  69. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
  70. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
  71. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
  72. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
  73. package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
  74. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
  75. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
  76. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
  77. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
  78. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
  79. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
  80. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
  81. package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
  82. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
  83. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
  84. package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
  85. package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
  86. package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
  87. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
  88. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
  89. package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
  90. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
  91. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
  92. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
  93. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
  94. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
  95. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
  96. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
  97. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
  98. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
  99. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
  100. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
  101. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
  102. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
  103. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
  104. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
  105. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
  106. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
  107. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
  108. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
  109. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
  110. package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
  111. package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
  112. package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
  113. package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
  114. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
  115. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
  116. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
  117. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
  118. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
  119. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
  120. package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
  121. package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
  122. package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
  123. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
  124. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
  125. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
  126. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
  127. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
  128. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
  129. package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
  130. package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
  131. package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
  132. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
  133. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
  134. package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
  135. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
  136. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
  137. package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
  139. package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
  140. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
  141. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
  142. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
  143. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
  144. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
  145. package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
  146. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
  147. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
  148. package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
  149. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
  150. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
  151. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
  152. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
  153. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
  154. package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
  155. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
  156. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
  157. package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
  158. package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
  159. package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
  160. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
  161. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
  162. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
  163. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
  164. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
  165. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
  166. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
  167. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
  168. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
  169. package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
  170. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
  171. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
  172. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
  173. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
  174. package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
  175. package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
  176. package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
  177. package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
  178. package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
  179. package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
  180. package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
  181. package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
  182. package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
  183. package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
  184. package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
  185. package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
  186. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
  187. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
  188. package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
  189. package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
  190. package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
  191. package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
  192. package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
  193. package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
  194. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
  195. package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
  196. package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
  197. package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
  198. package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
  199. package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
  200. package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
  201. package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
  202. package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
  203. package/node_modules/@openclaw/fs-safe/package.json +10 -10
  204. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  205. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  206. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  207. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  208. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  209. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  210. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  211. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  212. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  213. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  214. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  215. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  216. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  217. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  218. package/package.json +5 -5
  219. package/skills/feishu-wiki/SKILL.md +1 -1
  220. package/dist/.setup/accounts-wRqItHug.mjs +0 -206
  221. package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
  222. package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
  223. package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
  224. package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
  225. 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. Older adapter literals may continue to include `chmod` or `chmodSync` for source compatibility, but those operations are ignored. Copy fallback applies the file mode through its pinned destination descriptor as well, preserving exact modes despite the process umask.
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 migration bridge
116
-
117
- Version 0.5 detects the former `FS_SAFE_PYTHON_MODE`, `FS_SAFE_PYTHON`,
118
- `OPENCLAW_FS_SAFE_PYTHON_MODE`, `OPENCLAW_FS_SAFE_PYTHON`,
119
- `OPENCLAW_PINNED_PYTHON`, and `OPENCLAW_PINNED_WRITE_PYTHON` names. It emits one
120
- `FS_SAFE_PYTHON_DEPRECATED` warning and maps `auto`, `off`, or `require` to the
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
- Maintainers publish from a protected `vX.Y.Z` tag on `main` through
340
- `.github/workflows/release.yml`. The workflow requires the package version and a
341
- dated `CHANGELOG.md` section to match the tag. It builds and publishes all seven
342
- platform packages before publishing `@openclaw/fs-safe`, verifies every registry
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 synchronized sibling temp with an
221
- atomic rename. Some Windows filesystems and file owners reject that rename with
222
- `EPERM` or `EEXIST`; `copyFallbackOnPermissionError: true` permits a non-atomic
223
- copy fallback.
224
-
225
- Callers that cannot tolerate a torn in-place fallback can add:
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
- | Surface | Use it for |
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. Active under `NODE_ENV=test`. |
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
- The platform native binaries provide fd-relative open/link/mkdir primitives,
195
- atomic no-replace rename, and file identity checks. The default is `auto`: use
196
- the matching binary when it loads, otherwise use the guarded JavaScript path
197
- where a safe fallback exists. Native-only operations fail with
198
- `helper-unavailable`.
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](private-file-store.md) — root-bounded JSON+text state stores.
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
- Change startup configuration before the first filesystem operation:
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
- | Remove from 0.4 | Use in 0.5 |
41
- |---|---|
42
- | `configureFsSafePython({ mode })` | `configureFsSafeNative({ mode })` |
43
- | `FS_SAFE_PYTHON_MODE` | `FS_SAFE_NATIVE_MODE` |
44
- | `OPENCLAW_FS_SAFE_PYTHON_MODE` | `OPENCLAW_FS_SAFE_NATIVE_MODE` |
45
- | `pythonPath`, `FS_SAFE_PYTHON`, pinned-Python aliases | Nothing; prebuilt native binaries do not use an interpreter |
46
-
47
- The old names warn once and map `auto`, `off`, or `require` so a shipped 0.4
48
- deployment does not silently change policy. Interpreter paths are ignored and
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