@openclaw/openshell-sandbox 2026.10.5-beta.4 → 2026.10.10-beta.2

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 (240) hide show
  1. package/README.md +1 -1
  2. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +148 -0
  3. package/node_modules/@openclaw/fs-safe/README.md +20 -4
  4. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -1
  5. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  6. package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +28 -3
  7. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  8. package/node_modules/@openclaw/fs-safe/dist/archive-read.js +11 -5
  9. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +4 -2
  10. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -2
  11. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +2 -2
  12. package/node_modules/@openclaw/fs-safe/dist/bounded-read.d.ts +1 -0
  13. package/node_modules/@openclaw/fs-safe/dist/bounded-read.js +9 -2
  14. package/node_modules/@openclaw/fs-safe/dist/clone-metadata.d.ts +2 -0
  15. package/node_modules/@openclaw/fs-safe/dist/clone-metadata.js +2 -1
  16. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +6 -0
  17. package/node_modules/@openclaw/fs-safe/dist/config.js +5 -0
  18. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +3 -1
  19. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +8 -7
  20. package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
  21. package/node_modules/@openclaw/fs-safe/dist/copy-link.js +170 -0
  22. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.d.ts +9 -0
  23. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.js +39 -0
  24. package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +11 -2
  25. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +2 -4
  26. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +4 -2
  27. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +3 -6
  28. package/node_modules/@openclaw/fs-safe/dist/copy.js +3 -3
  29. package/node_modules/@openclaw/fs-safe/dist/create.js +1 -1
  30. package/node_modules/@openclaw/fs-safe/dist/creation-boundary.js +7 -4
  31. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.d.ts +12 -0
  32. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.js +29 -0
  33. package/node_modules/@openclaw/fs-safe/dist/directory-durability.d.ts +7 -4
  34. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +20 -5
  35. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +3 -0
  36. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +27 -2
  37. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.d.ts +7 -7
  38. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +9 -4
  39. package/node_modules/@openclaw/fs-safe/dist/durability.d.ts +2 -1
  40. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +2 -2
  41. package/node_modules/@openclaw/fs-safe/dist/exact-identity.d.ts +17 -0
  42. package/node_modules/@openclaw/fs-safe/dist/exact-identity.js +29 -0
  43. package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
  44. package/node_modules/@openclaw/fs-safe/dist/file-hash.d.ts +1 -1
  45. package/node_modules/@openclaw/fs-safe/dist/file-hash.js +11 -9
  46. package/node_modules/@openclaw/fs-safe/dist/file-identity.d.ts +7 -1
  47. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +27 -5
  48. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +1 -2
  49. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -1
  50. package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +1 -0
  51. package/node_modules/@openclaw/fs-safe/dist/file-lock.js +1 -0
  52. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.d.ts +9 -0
  53. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.js +42 -0
  54. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +1 -0
  55. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +22 -15
  56. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
  57. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.d.ts +1 -0
  58. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.js +5 -0
  59. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -0
  60. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -0
  61. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +16 -3
  62. package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.js +24 -13
  63. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +85 -18
  64. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +5 -0
  65. package/node_modules/@openclaw/fs-safe/dist/native-config.js +11 -0
  66. package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +6 -4
  67. package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.d.ts +2 -1
  68. package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.js +26 -2
  69. package/node_modules/@openclaw/fs-safe/dist/native-guidance.d.ts +3 -0
  70. package/node_modules/@openclaw/fs-safe/dist/native-guidance.js +21 -0
  71. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +1 -1
  72. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +2 -0
  73. package/node_modules/@openclaw/fs-safe/dist/native-operations.js +8 -6
  74. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +44 -4
  75. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +85 -15
  76. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +3 -3
  77. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +33 -6
  78. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +4 -1
  79. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +22 -3
  80. package/node_modules/@openclaw/fs-safe/dist/native-policy-directory-observation.js +4 -3
  81. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +1 -1
  82. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +3 -0
  83. package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.d.ts +3 -0
  84. package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.js +16 -0
  85. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
  86. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +15 -5
  87. package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.d.ts +3 -0
  88. package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.js +7 -0
  89. package/node_modules/@openclaw/fs-safe/dist/native-status.d.ts +17 -0
  90. package/node_modules/@openclaw/fs-safe/dist/native-status.js +30 -0
  91. package/node_modules/@openclaw/fs-safe/dist/native.d.ts +31 -4
  92. package/node_modules/@openclaw/fs-safe/dist/native.js +107 -9
  93. package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +9 -1
  94. package/node_modules/@openclaw/fs-safe/dist/overwrite-file-handle.js +4 -1
  95. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +1 -1
  96. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -1
  97. package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +9 -1
  98. package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +1 -0
  99. package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
  100. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +83 -47
  101. package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +2 -0
  102. package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +8 -3
  103. package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +13 -0
  104. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +10 -8
  105. package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
  106. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +7 -5
  107. package/node_modules/@openclaw/fs-safe/dist/pipe.d.ts +9 -0
  108. package/node_modules/@openclaw/fs-safe/dist/pipe.js +26 -0
  109. package/node_modules/@openclaw/fs-safe/dist/private-directory.js +1 -1
  110. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +2 -5
  111. package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.d.ts +3 -2
  112. package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +38 -10
  113. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
  114. package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts +3 -0
  115. package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.js +3 -0
  116. package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +5 -3
  117. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +26 -20
  118. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -0
  119. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.js +3 -4
  120. package/node_modules/@openclaw/fs-safe/dist/realpath.js +32 -0
  121. package/node_modules/@openclaw/fs-safe/dist/regular-file.d.ts +4 -0
  122. package/node_modules/@openclaw/fs-safe/dist/regular-file.js +11 -15
  123. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +3 -2
  124. package/node_modules/@openclaw/fs-safe/dist/root-copy.d.ts +18 -0
  125. package/node_modules/@openclaw/fs-safe/dist/root-copy.js +128 -0
  126. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +8 -13
  127. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +252 -27
  128. package/node_modules/@openclaw/fs-safe/dist/root-directory-creation.js +5 -1
  129. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +11 -8
  130. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +35 -13
  131. package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.d.ts +8 -0
  132. package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.js +108 -0
  133. package/node_modules/@openclaw/fs-safe/dist/root-file.d.ts +7 -0
  134. package/node_modules/@openclaw/fs-safe/dist/root-file.js +15 -0
  135. package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +2 -0
  136. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +154 -175
  137. package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.d.ts +2 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.js +239 -0
  139. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +37 -15
  140. package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.d.ts +32 -0
  141. package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.js +56 -0
  142. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
  143. package/node_modules/@openclaw/fs-safe/dist/root-public-types.d.ts +1 -0
  144. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +95 -16
  145. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +6 -4
  146. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +23 -19
  147. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -6
  148. package/node_modules/@openclaw/fs-safe/dist/root-write-publication.js +5 -1
  149. package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +3 -1
  150. package/node_modules/@openclaw/fs-safe/dist/secure-file.d.ts +2 -0
  151. package/node_modules/@openclaw/fs-safe/dist/secure-file.js +10 -4
  152. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
  153. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +2 -2
  154. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +1 -1
  155. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-target.js +8 -1
  156. package/node_modules/@openclaw/fs-safe/dist/staged-directory.d.ts +13 -0
  157. package/node_modules/@openclaw/fs-safe/dist/staged-directory.js +29 -0
  158. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +5 -3
  159. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.d.ts +8 -0
  160. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +13 -2
  161. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +71 -3
  162. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +2 -1
  163. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +22 -0
  164. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-types.d.ts +3 -0
  165. package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +1 -0
  166. package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +1 -0
  167. package/node_modules/@openclaw/fs-safe/dist/trash.js +2 -2
  168. package/node_modules/@openclaw/fs-safe/dist/types.d.ts +4 -0
  169. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +2 -2
  170. package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.d.ts +5 -0
  171. package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.js +39 -0
  172. package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +1 -0
  173. package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +25 -36
  174. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +24 -5
  175. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +2 -1
  176. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +4 -0
  177. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.d.ts +2 -0
  178. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
  179. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.js +7 -0
  180. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.d.ts +16 -0
  181. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.js +51 -0
  182. package/node_modules/@openclaw/fs-safe/dist/write-file-handle.d.ts +1 -0
  183. package/node_modules/@openclaw/fs-safe/dist/write-file-handle.js +12 -10
  184. package/node_modules/@openclaw/fs-safe/docs/advanced.md +2 -1
  185. package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
  186. package/node_modules/@openclaw/fs-safe/docs/config.md +71 -1
  187. package/node_modules/@openclaw/fs-safe/docs/contributing.md +45 -5
  188. package/node_modules/@openclaw/fs-safe/docs/copy.md +45 -5
  189. package/node_modules/@openclaw/fs-safe/docs/durability.md +34 -7
  190. package/node_modules/@openclaw/fs-safe/docs/errors.md +8 -0
  191. package/node_modules/@openclaw/fs-safe/docs/guest.md +2 -0
  192. package/node_modules/@openclaw/fs-safe/docs/install.md +47 -27
  193. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -6
  194. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.6.md +1 -1
  195. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +79 -6
  196. package/node_modules/@openclaw/fs-safe/docs/native.md +279 -8
  197. package/node_modules/@openclaw/fs-safe/docs/permissions.md +64 -15
  198. package/node_modules/@openclaw/fs-safe/docs/pipe.md +81 -0
  199. package/node_modules/@openclaw/fs-safe/docs/platform-capabilities.md +63 -0
  200. package/node_modules/@openclaw/fs-safe/docs/public-api.md +10 -0
  201. package/node_modules/@openclaw/fs-safe/docs/queue-link-free-design.md +237 -0
  202. package/node_modules/@openclaw/fs-safe/docs/quickstart.md +23 -3
  203. package/node_modules/@openclaw/fs-safe/docs/reading.md +56 -2
  204. package/node_modules/@openclaw/fs-safe/docs/regular-file.md +2 -0
  205. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
  206. package/node_modules/@openclaw/fs-safe/docs/secure-file.md +1 -1
  207. package/node_modules/@openclaw/fs-safe/docs/security-model.md +35 -0
  208. package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
  209. package/node_modules/@openclaw/fs-safe/docs/staged-file.md +18 -0
  210. package/node_modules/@openclaw/fs-safe/docs/store.md +15 -0
  211. package/node_modules/@openclaw/fs-safe/docs/temp.md +39 -0
  212. package/node_modules/@openclaw/fs-safe/docs/testing.md +13 -0
  213. package/node_modules/@openclaw/fs-safe/docs/types.md +51 -81
  214. package/node_modules/@openclaw/fs-safe/docs/walk.md +4 -1
  215. package/node_modules/@openclaw/fs-safe/docs/watch.md +3 -2
  216. package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
  217. package/node_modules/@openclaw/fs-safe/package.json +21 -12
  218. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  219. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  220. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  221. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  222. package/node_modules/@openclaw/fs-safe-freebsd-arm64/fs-safe-native.node +0 -0
  223. package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +29 -0
  224. package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
  225. package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +29 -0
  226. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  227. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  228. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  229. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  230. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  231. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  232. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  233. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  234. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/fs-safe-native.node +0 -0
  235. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +29 -0
  236. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  237. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  238. package/package.json +5 -5
  239. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +0 -336
  240. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +0 -90
@@ -1,6 +1,9 @@
1
+ import type { ExactFileIdentity } from "./exact-identity.js";
1
2
  export type FastPathMode = "auto" | "never" | "require";
2
3
  export type SafeEncoding = BufferEncoding | null;
3
4
  export type PathStat = {
5
+ /** Exact companion for identity checks; numeric fields are legacy metadata. */
6
+ exactIdentity?: ExactFileIdentity;
4
7
  dev: number;
5
8
  gid: number;
6
9
  ino: number;
@@ -20,3 +23,4 @@ export type BasePathOptions = {
20
23
  rootDir: string;
21
24
  relativePath: string;
22
25
  };
26
+ export type { ExactFileIdentity } from "./exact-identity.js";
@@ -5,9 +5,9 @@ import { watchStreamPaths } from "./watch-stream.js";
5
5
  export function watchBinding(mode) {
6
6
  if (mode === "poll")
7
7
  return;
8
- const binding = getNativeBinding(); // Preserves require + missing-addon failure.
8
+ const binding = getNativeBinding("watchRegister", "watchAdd", "watchUnregister", ...(process.platform === "darwin" ? ["watchConfigure", "watchEntries"] : []));
9
9
  // Bun TSFN teardown remains unqualified; guarded native scans remain usable.
10
- if (binding?.watchRegister && (process.platform !== "darwin" || (binding.watchConfigure && binding.watchEntries)) && !process.versions.bun && !process.versions.deno && ["linux", "darwin", "win32"].includes(process.platform))
10
+ if (binding && !process.versions.bun && !process.versions.deno && ["linux", "darwin", "win32"].includes(process.platform))
11
11
  return binding;
12
12
  if (mode === "events" || getFsSafeNativeConfig().mode === "require") {
13
13
  throw new FsSafeError("helper-unavailable", "native watch events are unavailable", { details: { operation: "watch" } });
@@ -0,0 +1,5 @@
1
+ import { type NativeBinding } from "./native.js";
2
+ /** The native owner caches immutable primary-token membership across Workers. */
3
+ export declare function isWindowsAppContainer(binding: NativeBinding): boolean | undefined;
4
+ export declare function assertWindowsHelperAllowed(): void;
5
+ export declare function assertWindowsAclRemediationAllowed(): void;
@@ -0,0 +1,39 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import { getNativeBinding } from "./native.js";
3
+ /** The native owner caches immutable primary-token membership across Workers. */
4
+ export function isWindowsAppContainer(binding) {
5
+ if (!binding.isWindowsAppContainer)
6
+ return undefined;
7
+ try {
8
+ const membership = binding.isWindowsAppContainer();
9
+ if (typeof membership !== "boolean")
10
+ throw new Error("Invalid AppContainer membership");
11
+ return membership;
12
+ }
13
+ catch (cause) {
14
+ throw new FsSafeError("permission-unverified", "Windows process token membership could not be verified", { cause });
15
+ }
16
+ }
17
+ export function assertWindowsHelperAllowed() {
18
+ if (process.platform !== "win32")
19
+ return;
20
+ const binding = getNativeBinding();
21
+ if (binding && isWindowsAppContainer(binding)) {
22
+ // Current libuv can hang while creating child-process pipes in a lowbox.
23
+ throw new FsSafeError("helper-unavailable", "External Windows security helpers are unavailable inside an AppContainer");
24
+ }
25
+ }
26
+ export function assertWindowsAclRemediationAllowed() {
27
+ if (process.platform !== "win32")
28
+ return;
29
+ let binding;
30
+ try {
31
+ binding = getNativeBinding();
32
+ }
33
+ catch {
34
+ return;
35
+ } // Command formatting remains available without an addon.
36
+ if (binding && isWindowsAppContainer(binding)) {
37
+ throw new FsSafeError("helper-unavailable", "Windows ACL reset commands are unavailable inside an AppContainer");
38
+ }
39
+ }
@@ -6,6 +6,7 @@ export type WindowsOwnerExec = (command: string, args: string[]) => Promise<{
6
6
  export type WindowsOwnerSummary = Omit<PermissionFailureFields & {
7
7
  sid?: string;
8
8
  currentUserSid?: string;
9
+ currentAppContainerSid?: string;
9
10
  daclPresent?: boolean;
10
11
  aces?: WindowsOwnerAce[];
11
12
  aclError?: string;
@@ -1,38 +1,16 @@
1
- import { formatCaughtPermissionFailure, getPermissionCommandFailure, } from "./permission-exec.js";
2
- import { resolveWindowsSystemCommand } from "./windows-command.js";
1
+ import { formatCaughtPermissionFailure, getPermissionCommandFailure, executePermissionCommand, } from "./permission-exec.js";
2
+ import { assertWindowsHelperAllowed } from "./windows-appcontainer.js";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { realpathSync } from "./realpath.js";
5
+ import { windowsSecurityHelperPath } from "./windows-security-helper.js";
3
6
  import { hasWindowsPathAlias } from "./windows-path-alias.js";
7
+ import { APP_CONTAINER_SID_RE } from "./windows-security-facts.js";
4
8
  const SID_RE = /^\*?s-\d+-\d+(-\d+)+$/i;
5
9
  const TRUSTED_OWNER_SIDS = new Set(["s-1-5-18", "s-1-5-32-544"]);
6
10
  function normalizeSid(value) {
7
11
  const normalized = value.trim().toLowerCase();
8
12
  return normalized.startsWith("*") ? normalized.slice(1) : normalized;
9
13
  }
10
- function encodePowerShellCommand(source) {
11
- return Buffer.from(source, "utf16le").toString("base64");
12
- }
13
- function windowsOwnerQueryCommand(targetPath) {
14
- const encodedPath = Buffer.from(targetPath, "utf8").toString("base64");
15
- return [
16
- "$ErrorActionPreference='Stop'",
17
- "$env:PSModulePath=[IO.Path]::Combine($PSHOME,'Modules')",
18
- `$p=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedPath}'))`,
19
- "$sections=[System.Security.AccessControl.AccessControlSections]::Access -bor [System.Security.AccessControl.AccessControlSections]::Owner",
20
- "$acl=if([IO.Directory]::Exists($p)){[IO.Directory]::GetAccessControl($p,$sections)}else{[IO.File]::GetAccessControl($p,$sections)}",
21
- "$ownerSid=$acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value",
22
- "$currentSid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
23
- "$root=[IO.Path]::GetPathRoot($p)",
24
- "$extendedDrive=$p.Length -ge 7 -and $p.StartsWith('\\\\?\\') -and [char]::IsLetter($p[4]) -and $p[5] -eq ':' -and $p[6] -eq '\\'",
25
- "$driveRoot=if($extendedDrive){$p.Substring(4,3)}else{$root}",
26
- "$namespacePath=$p.StartsWith('\\\\')",
27
- "$remote=($namespacePath -and -not $extendedDrive) -or ([IO.DriveInfo]::new($driveRoot).DriveType -eq [IO.DriveType]::Network)",
28
- // Emit only ASCII SID and numeric facts: console encodings can replace both
29
- // Unicode pathnames and account names before JavaScript receives the bytes.
30
- "$raw=[System.Security.AccessControl.RawSecurityDescriptor]::new($acl.GetSecurityDescriptorBinaryForm(),0)",
31
- "$dacl=$raw.DiscretionaryAcl;$complete=$true",
32
- "$aces=@(foreach($ace in $dacl){if($ace -isnot [System.Security.AccessControl.CommonAce] -or $ace.IsCallback -or [int]$ace.AceType -notin @(0,1)){$complete=$false;continue};@{sid=$ace.SecurityIdentifier.Value;mask=([long]$ace.AccessMask -band 4294967295);deny=([int]$ace.AceType -eq 1);inheritOnly=([int]$ace.AceFlags -band 8) -ne 0}})",
33
- "@{ownerSid=$ownerSid;currentUserSid=$currentSid;daclPresent=($null -ne $dacl);aces=$aces;complete=$complete;remote=$remote}|Microsoft.PowerShell.Utility\\ConvertTo-Json -Depth 4 -Compress",
34
- ].join(";");
35
- }
36
14
  function parseWindowsAclFacts(parsed) {
37
15
  if (parsed.complete !== true || typeof parsed.daclPresent !== "boolean" || !Array.isArray(parsed.aces)) {
38
16
  return { aclError: "Windows ACL query returned incomplete descriptor data" };
@@ -53,18 +31,24 @@ export async function inspectWindowsOwner(params) {
53
31
  const error = new Error("Path uses a Windows filesystem namespace alias");
54
32
  return { error: String(error), errorCause: error };
55
33
  }
34
+ if (process.platform === "win32" && params.exec === executePermissionCommand) {
35
+ assertWindowsHelperAllowed();
36
+ // Refuse a missing sandbox canonicalizer before starting a helper process.
37
+ // Other pathname failures retain the named-query reporting contract below.
38
+ try {
39
+ realpathSync.native(params.targetPath);
40
+ }
41
+ catch (error) {
42
+ if (error instanceof FsSafeError && error.code === "helper-unavailable")
43
+ throw error;
44
+ }
45
+ }
56
46
  let command = "";
57
47
  let startedAt = performance.now();
58
48
  try {
59
- command = resolveWindowsSystemCommand(String.raw `WindowsPowerShell\v1.0\powershell.exe`, params.env);
49
+ command = windowsSecurityHelperPath();
60
50
  startedAt = performance.now();
61
- const { stdout } = await params.exec(command, [
62
- "-NoLogo",
63
- "-NoProfile",
64
- "-NonInteractive",
65
- "-EncodedCommand",
66
- encodePowerShellCommand(windowsOwnerQueryCommand(params.targetPath)),
67
- ]);
51
+ const { stdout } = await params.exec(command, ["owner", params.targetPath]);
68
52
  const value = JSON.parse(stdout.trim());
69
53
  if (!value || typeof value !== "object" || Array.isArray(value)) {
70
54
  return { error: "Windows owner query returned invalid SID data" };
@@ -79,6 +63,10 @@ export async function inspectWindowsOwner(params) {
79
63
  if (!ownerSid || !currentUserSid) {
80
64
  return { error: "Windows owner query returned invalid SID data" };
81
65
  }
66
+ if (parsed.currentAppContainerSid != null &&
67
+ (typeof parsed.currentAppContainerSid !== "string" || !APP_CONTAINER_SID_RE.test(parsed.currentAppContainerSid.toLowerCase()))) {
68
+ return { error: "Windows owner query returned invalid AppContainer SID data" };
69
+ }
82
70
  const remote = parsed.remote;
83
71
  if (typeof remote !== "boolean") {
84
72
  return { error: "Windows owner query returned invalid locality data" };
@@ -86,6 +74,7 @@ export async function inspectWindowsOwner(params) {
86
74
  return {
87
75
  sid: ownerSid,
88
76
  currentUserSid,
77
+ ...(typeof parsed.currentAppContainerSid === "string" ? { currentAppContainerSid: normalizeSid(parsed.currentAppContainerSid) } : {}),
89
78
  ...parseWindowsAclFacts(parsed),
90
79
  trusted: !remote && (ownerSid === currentUserSid || TRUSTED_OWNER_SIDS.has(ownerSid)),
91
80
  };
@@ -1,9 +1,10 @@
1
+ import { assertWindowsHelperAllowed } from "./windows-appcontainer.js";
1
2
  import { spawn, spawnSync } from "node:child_process";
2
3
  import { fileURLToPath } from "node:url";
3
4
  import { FsSafeError } from "./errors.js";
4
5
  import { DEFAULT_PERMISSION_EXEC_TIMEOUT_MS, PermissionCommandError } from "./permission-exec.js";
5
- import { resolveWindowsSystemCommand } from "./windows-command.js";
6
6
  import { parseWindowsOwnerAndDaclFacts, parseWindowsSecurityCommandFacts, unverified } from "./windows-security-facts.js";
7
+ import { windowsSecurityHelperPath, explainWindowsSecurityHelperFailure } from "./windows-security-helper.js";
7
8
  const MAX_OUTPUT_BYTES = 1024 * 1024;
8
9
  const TERMINATION_GRACE_MS = 1_000;
9
10
  const FULL_IDENTITY = /^[0-9a-f]{16}:[0-9a-f]{32}$/;
@@ -35,10 +36,10 @@ function command(operation, params) {
35
36
  (!Number.isInteger(params.expectedLinks) || params.expectedLinks < 1 || params.expectedLinks > 0xffff_ffff)) {
36
37
  throw new TypeError("Windows security operation requires a positive uint32 link count");
37
38
  }
38
- const script = fileURLToPath(new URL("./windows-security-bridge.ps1", import.meta.url));
39
+ const helper = windowsSecurityHelperPath();
39
40
  return {
40
- file: resolveWindowsSystemCommand(String.raw `WindowsPowerShell\v1.0\powershell.exe`),
41
- args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-File", script, "-Operation", operation],
41
+ file: helper,
42
+ args: [operation],
42
43
  // Preserve Windows UTF-16 spelling and leave long paths out of the bounded
43
44
  // command line. This overrides any inherited value only in this child.
44
45
  env: {
@@ -85,14 +86,28 @@ function parseReplyValue(stdout, operation) {
85
86
  if (typeof response.code !== "string" || !codes.has(response.code) || typeof response.message !== "string") {
86
87
  unverified("Windows security command returned an invalid failure");
87
88
  }
88
- throw Object.assign(new Error(response.message), { code: response.code });
89
+ if (response.errno !== undefined &&
90
+ (typeof response.errno !== "number" || !Number.isInteger(response.errno) || response.errno < 0 || response.errno > 0xffff_ffff)) {
91
+ unverified("Windows security command returned an invalid native error number");
92
+ }
93
+ const failure = Object.assign(new Error(response.message), { code: response.code });
94
+ if (response.errno !== undefined) {
95
+ Object.defineProperty(failure, "errno", { value: response.errno, enumerable: true, configurable: true, writable: true });
96
+ }
97
+ throw failure;
89
98
  }
90
99
  if (operation === "create" && (!record(response.result) || response.result.created !== true || !isFullIdentity(response.result.identity))) {
91
100
  unverified("Windows security command did not verify private-directory creation");
92
101
  }
93
102
  return response.result;
94
103
  }
104
+ function explainHelperFailure(error, file) {
105
+ if (file.endsWith("windows-security-helper.exe")) {
106
+ explainWindowsSecurityHelperFailure(error);
107
+ }
108
+ }
95
109
  function executeSync(operation, params) {
110
+ assertWindowsHelperAllowed();
96
111
  const { file, args, env } = command(operation, params);
97
112
  const startedAt = performance.now();
98
113
  const result = spawnSync(file, args, {
@@ -107,6 +122,7 @@ function executeSync(operation, params) {
107
122
  killed: result.error?.code === "ETIMEDOUT",
108
123
  ...(operation === "create" ? { creationOutcome: "unconfirmed" } : {}),
109
124
  });
125
+ explainHelperFailure(error, file);
110
126
  if (operation === "create")
111
127
  Object.assign(error, { creationOutcome: "unconfirmed" });
112
128
  throw error;
@@ -120,6 +136,8 @@ class WindowsSecurityCommandError extends PermissionCommandError {
120
136
  constructor(file, durationMs, receipt, timedOut, timeoutMs = DEFAULT_PERMISSION_EXEC_TIMEOUT_MS) {
121
137
  super(file, durationMs, receipt, timeoutMs);
122
138
  this.timedOut = timedOut;
139
+ if (!timedOut)
140
+ explainHelperFailure(this, file);
123
141
  this.creationOutcome = receipt.creationOutcome;
124
142
  this.processExitConfirmed = receipt.processExitConfirmed;
125
143
  if (timedOut)
@@ -162,6 +180,7 @@ export function hasUnsettledWindowsSecurityCommand(error) {
162
180
  }
163
181
  function ignoreLateError() { }
164
182
  async function execute(operation, params, request) {
183
+ assertWindowsHelperAllowed();
165
184
  const selected = request ?? command(operation, params);
166
185
  const { file, args, env, input, timeoutMs = DEFAULT_PERMISSION_EXEC_TIMEOUT_MS, maxOutputBytes = MAX_OUTPUT_BYTES } = selected;
167
186
  const startedAt = performance.now();
@@ -1,5 +1,6 @@
1
1
  import type { NativeWindowsSecurityFacts } from "./native-binding.js";
2
- export type DescriptorFacts = Pick<NativeWindowsSecurityFacts, "ownerSid" | "currentUserSid" | "daclPresent" | "isLocal" | "aceListComplete" | "unsupportedAceTypes" | "aces">;
2
+ export declare const APP_CONTAINER_SID_RE: RegExp;
3
+ export type DescriptorFacts = Pick<NativeWindowsSecurityFacts, "ownerSid" | "currentUserSid" | "currentAppContainerSid" | "daclPresent" | "isLocal" | "aceListComplete" | "unsupportedAceTypes" | "aces">;
3
4
  export declare function unverified(message: string, cause?: unknown): never;
4
5
  /** Validate policy-free observations crossing the isolated batch transport. */
5
6
  export declare function parseWindowsOwnerAndDaclFacts(value: unknown): DescriptorFacts;
@@ -1,5 +1,6 @@
1
1
  import { FsSafeError } from "./errors.js";
2
2
  const SID_RE = /^s-\d+-\d+(?:-\d+)+$/;
3
+ export const APP_CONTAINER_SID_RE = /^s-1-15-2(?:-\d+){7,}$/;
3
4
  const SYSTEM_SID = "s-1-5-18";
4
5
  const ADMINISTRATORS_SID = "s-1-5-32-544";
5
6
  const WORLD_SIDS = new Set([
@@ -34,6 +35,7 @@ function validateAce(value, allowUnknownFlags) {
34
35
  function validateDescriptor(value, allowUnknownFlags) {
35
36
  if (!record(value) || typeof value.ownerSid !== "string" || !SID_RE.test(value.ownerSid) ||
36
37
  typeof value.currentUserSid !== "string" || !SID_RE.test(value.currentUserSid) ||
38
+ (value.currentAppContainerSid != null && (typeof value.currentAppContainerSid !== "string" || !APP_CONTAINER_SID_RE.test(value.currentAppContainerSid))) ||
37
39
  typeof value.daclPresent !== "boolean" || typeof value.isLocal !== "boolean" ||
38
40
  typeof value.aceListComplete !== "boolean" || !Array.isArray(value.unsupportedAceTypes) ||
39
41
  !value.unsupportedAceTypes.every(uint32) || !Array.isArray(value.aces) ||
@@ -59,6 +61,8 @@ function summarize(facts) {
59
61
  let groupReadable = false;
60
62
  let groupWritable = false;
61
63
  const trusted = new Set([facts.currentUserSid, SYSTEM_SID, ADMINISTRATORS_SID]);
64
+ if (facts.currentAppContainerSid)
65
+ trusted.add(facts.currentAppContainerSid);
62
66
  for (const ace of facts.aces) {
63
67
  if (ace.aceType !== "allow" || ace.flags.inheritOnly || trusted.has(ace.sid))
64
68
  continue;
@@ -0,0 +1,2 @@
1
+ export declare function windowsSecurityHelperPath(): string;
2
+ export declare function explainWindowsSecurityHelperFailure(error: Error): void;
@@ -0,0 +1,7 @@
1
+ import { fileURLToPath } from "node:url";
2
+ export function windowsSecurityHelperPath() {
3
+ return fileURLToPath(new URL("./windows-security-helper.exe", import.meta.url));
4
+ }
5
+ export function explainWindowsSecurityHelperFailure(error) {
6
+ error.message += "; Windows security helper could not complete; rebuild with pnpm build:windows-helper or reinstall @openclaw/fs-safe";
7
+ }
@@ -0,0 +1,16 @@
1
+ /** Test-only handle that omits delete sharing. */
2
+ export declare function holdWindowsSharingLock(filePath: string): Disposable & {
3
+ close(): void;
4
+ };
5
+ /** Test-only attribute updates preserve unspecified bits on the opened object. */
6
+ export declare function setWindowsFileAttributes(filePath: string, attrs: {
7
+ readOnly?: boolean;
8
+ hidden?: boolean;
9
+ system?: boolean;
10
+ }): void;
11
+ /** Test-only physical mapping reader; lcn -1 represents sparse clusters. */
12
+ export declare function readWindowsFileExtents(filePath: string): {
13
+ vcn: bigint;
14
+ lcn: bigint;
15
+ clusters: bigint;
16
+ }[];
@@ -0,0 +1,51 @@
1
+ import path from "node:path";
2
+ import { FsSafeError } from "./errors.js";
3
+ import { requireNativeBinding } from "./native.js";
4
+ function call(operation) {
5
+ if (process.platform !== "win32") {
6
+ throw new FsSafeError("unsupported-platform", "Windows test fixtures require Windows");
7
+ }
8
+ try {
9
+ return operation();
10
+ }
11
+ catch (cause) {
12
+ if (cause instanceof FsSafeError)
13
+ throw cause;
14
+ throw new FsSafeError("helper-failed", "Windows test fixture operation failed", { cause });
15
+ }
16
+ }
17
+ /** Test-only handle that omits delete sharing. */
18
+ export function holdWindowsSharingLock(filePath) {
19
+ return call(() => {
20
+ const native = requireNativeBinding();
21
+ if (!native.holdWindowsSharingLock)
22
+ throw new FsSafeError("helper-unavailable", "Windows sharing fixture unavailable");
23
+ const owner = native.holdWindowsSharingLock(path.toNamespacedPath(filePath));
24
+ let closed = false;
25
+ const close = () => {
26
+ if (closed)
27
+ return;
28
+ closed = true;
29
+ call(() => owner.close());
30
+ };
31
+ return Object.freeze({ close, [Symbol.dispose]: close });
32
+ });
33
+ }
34
+ /** Test-only attribute updates preserve unspecified bits on the opened object. */
35
+ export function setWindowsFileAttributes(filePath, attrs) {
36
+ call(() => {
37
+ const native = requireNativeBinding();
38
+ if (!native.setWindowsFileAttributes)
39
+ throw new FsSafeError("helper-unavailable", "Windows attribute fixture unavailable");
40
+ native.setWindowsFileAttributes(path.toNamespacedPath(filePath), attrs);
41
+ });
42
+ }
43
+ /** Test-only physical mapping reader; lcn -1 represents sparse clusters. */
44
+ export function readWindowsFileExtents(filePath) {
45
+ return call(() => {
46
+ const native = requireNativeBinding();
47
+ if (!native.readWindowsFileExtents)
48
+ throw new FsSafeError("helper-unavailable", "Windows extent fixture unavailable");
49
+ return native.readWindowsFileExtents(path.toNamespacedPath(filePath));
50
+ });
51
+ }
@@ -10,4 +10,5 @@ export declare function writeAllToFile(target: FileHandle | number, data: string
10
10
  encoding?: BufferEncoding;
11
11
  position?: number;
12
12
  assertBeforeMutation?: () => void;
13
+ synchronous?: boolean;
13
14
  }): Promise<void>;
@@ -31,16 +31,18 @@ export async function writeAllToFile(target, data, options = {}) {
31
31
  const length = Math.min(WRITE_CHUNK_BYTES, buffer.byteLength - offset);
32
32
  const position = options.position === undefined ? null : options.position + offset;
33
33
  options.assertBeforeMutation?.();
34
- const written = typeof target === "number"
35
- ? await new Promise((resolve, reject) => {
36
- fs.write(target, buffer, offset, length, position, (error, bytesWritten) => {
37
- if (error)
38
- reject(error);
39
- else
40
- resolve(bytesWritten);
41
- });
42
- })
43
- : (await target.write(buffer, offset, length, position)).bytesWritten;
34
+ const written = typeof target === "number" && options.synchronous
35
+ ? fs.writeSync(target, buffer, offset, length, position)
36
+ : typeof target === "number"
37
+ ? await new Promise((resolve, reject) => {
38
+ fs.write(target, buffer, offset, length, position, (error, bytesWritten) => {
39
+ if (error)
40
+ reject(error);
41
+ else
42
+ resolve(bytesWritten);
43
+ });
44
+ })
45
+ : (await target.write(buffer, offset, length, position)).bytesWritten;
44
46
  if (written <= 0) {
45
47
  throw new FsSafeError("helper-failed", "file write made no progress");
46
48
  }
@@ -79,11 +79,12 @@ concurrent namespace changes.
79
79
  | `sameFileContentsSync`, `SameFileContentsOptions` | [Exact file comparison](file-contents.md) | Compare borrowed regular-file descriptors byte for byte through EOF with bounded memory and an optional per-file byte limit, preserving both cursors and lifetimes. |
80
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. |
81
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. |
82
+ | `createRootFileOpenBatchSync`, `RootFileOpenBatchSync` | [Synchronous open batches](reading.md#synchronous-open-batches) | Reuse native Windows root handles for synchronous file opens while retaining fresh file checks. |
82
83
  | `appendRegularFile`, `appendRegularFileSync`, `readRegularFile`, `readRegularFileSync`, `statRegularFile`, `statRegularFileSync`, `resolveRegularFileAppendFlags`, `AppendRegularFileOptions`, `RegularFileStatResult` | [regular-file.md](regular-file.md) | Type-checked regular-file I/O. |
83
84
  | `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. |
84
85
  | `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. |
85
86
  | `copyRootFileSync`, `createRootFileCopyBatchSync`, `RootFileCopyBatchSync`, `CopyRootFileSyncOptions`, `CopiedRootFileSync` | [copy.md](copy.md#synchronous-guarded-file-copies) | Synchronous guarded exclusive file copy, optionally cloned and bound to an expected source identity, returning an owned destination descriptor and both identities. |
86
- | `sameFileIdentity`, `FileIdentityStat` | – | Compare two stats for same-inode equality. |
87
+ | `sameFileIdentity`, `FileIdentityStat` | – | Compare two stats for same-inode equality; unsafe Number components never match. Use bigint stats for exact Windows IDs. |
87
88
  | `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. |
88
89
  | `pathExists`, `pathExistsSync` | – | Boolean existence check that does not throw on `ENOENT`. |
89
90
  | `assertNoSymlinkParents`, `assertNoSymlinkParentsSync`, `AssertNoSymlinkParentsOptions` | – | Reject paths whose ancestor chain contains symlinks, inspecting raw segments before `..` normalization. A `..` may undo an inspected real directory, but cannot leave the root or undo an allowed root-child symlink. Raw paths outside the root that normalize inside are rejected. |
@@ -185,6 +185,15 @@ policy to the complete archive, including entries that extraction would strip
185
185
  or skip. Known UTF-8 and Unicode Path names are checked during physical admission;
186
186
  legacy names retain backend-selected decoding and undergo the same collision
187
187
  check before callbacks or selected bytes are returned.
188
+ Native legacy decoding is CP437 when neither the UTF-8 flag nor a valid Unicode
189
+ Path override declares UTF-8, even if the raw bytes also happen to form valid
190
+ UTF-8. Invalid flagged UTF-8 and invalid Unicode Path CRC/encoding fail closed;
191
+ the native metadata boundary enforces these checks before decoder normalization.
192
+ Native ZIP inspection also retains rejection of malformed Unicode Comment
193
+ checksums and UTF-8, including in entries unrelated to a requested payload.
194
+ Comment CRCs follow the native decoder's historical decoded-comment semantics;
195
+ valid sequential Unicode overrides retain their decoder order. Public physical
196
+ admission continues to reject duplicate critical Unicode Path fields.
188
197
 
189
198
  ZIP end-record admission searches the bounded comment window for signatures
190
199
  while retaining complete comment-length and ambiguity checks. Dense signature
@@ -244,6 +253,9 @@ remaining components are skipped before the filter callback, but still count
244
253
  toward `maxEntries` and undergo traversal validation. JavaScript TAR extraction
245
254
  copies the admitted payload range to this accepted output path, so depth checks,
246
255
  collision checks, writes, and mode application agree.
256
+ ZIP directories naming the relative archive root (for example `./`) follow the
257
+ same skip rule on both backends. Portable preflight retains their relative `./`
258
+ spelling; a literal absolute `/` entry is still rejected.
247
259
 
248
260
  An `entryFilter` sees the validated **canonical effective archive path before
249
261
  stripping**, entry kind, and declared size. On every JavaScript and native
@@ -348,6 +360,10 @@ A failure before publication preserves a pre-existing file, and rejection does
348
360
  not grant authority to delete a substituted file or alias. Failed extraction does not
349
361
  restore overwritten contents. Active destination mutations and their guarded
350
362
  cleanup still finish before rejection; no later destination mutation begins.
363
+ On supported native POSIX paths, each active destination ancestor uses its retained
364
+ descriptor plus fresh named-identity and physical-path observations at every guard
365
+ boundary. These checks are never cached or reduced to the deepest directory alone.
366
+ Other paths retain the full pathname checks.
351
367
  Portable ZIP output is not eligible for publication until its stream closes or
352
368
  the defensive `FileHandle` close succeeds. If that fallback close rejects,
353
369
  `extractArchive()` propagates the error and publishes no entry from the staged
@@ -12,10 +12,13 @@ import {
12
12
  configureFsSafeNative,
13
13
  configureFsSafeLocks,
14
14
  getFsSafeNativeConfig,
15
+ getFsSafeNativeStatus,
15
16
  getFsSafeLockConfig,
16
17
  type FsSafeLockConfig,
17
18
  type FsSafeNativeConfig,
18
19
  type FsSafeNativeMode,
20
+ type FsSafeNativeStatus,
21
+ type FsSafeNativeStatusReason,
19
22
  } from "@openclaw/fs-safe/config";
20
23
  ```
21
24
 
@@ -42,7 +45,7 @@ Set the process-global loading policy. Configure once at startup, before the fir
42
45
  | `require` | Operations that need the binding raise `FsSafeError("helper-unavailable")` when it cannot load. |
43
46
 
44
47
  Fallbacks include guarded JavaScript, the bundled TAR/gzip WASM parser, and the
45
- [packaged Windows security scripts](install.md#windows-security-fallback).
48
+ [packaged Windows security helper](install.md#windows-security-fallback).
46
49
  Windows command fallbacks remain subject to normal system execution policy.
47
50
 
48
51
  ## `getFsSafeNativeConfig()`
@@ -53,6 +56,73 @@ function getFsSafeNativeConfig(): FsSafeNativeConfig;
53
56
 
54
57
  Return the effective configuration: programmatic overrides win, then env vars, then the package default (`auto`).
55
58
 
59
+ ## `getFsSafeNativeStatus(options?)`
60
+
61
+ ```ts
62
+ function getFsSafeNativeStatus(options?: { probe?: boolean }): Promise<FsSafeNativeStatus>;
63
+
64
+ type FsSafeNativeStatusReason =
65
+ | "not-probed" | "loaded" | "disabled" | "unsupported-target"
66
+ | "package-missing" | "load-failed" | "capability-missing";
67
+
68
+ type FsSafeNativeStatus = {
69
+ mode: FsSafeNativeMode;
70
+ modeSource: "programmatic configuration" | "FS_SAFE_NATIVE_MODE" | "OPENCLAW_FS_SAFE_NATIVE_MODE" | "default";
71
+ reason: FsSafeNativeStatusReason;
72
+ loaded: boolean;
73
+ target: string;
74
+ packageName?: string;
75
+ packageVersion?: string;
76
+ loadErrorCode?: string;
77
+ guidance?: string;
78
+ };
79
+ ```
80
+
81
+ | Field | Meaning |
82
+ |---|---|
83
+ | `mode` | Effective `auto`, `off`, or `require` policy. |
84
+ | `modeSource` | The winning programmatic setting, environment variable **name**, or default. |
85
+ | `reason` | Cached loader outcome; `not-probed` until a load is attempted, unless the effective mode is `off` (`disabled`). |
86
+ | `loaded` | `true` exactly when `reason` is `loaded`. This confirms the loader's core capability check, not every operation's capabilities or kernel-atomic behavior. |
87
+ | `target` | Runtime platform/architecture/libc, such as `win32-x64-msvc` or `linux-x64-gnu`; also supplied for unsupported targets. |
88
+ | `packageName` | Expected native platform package; absent for unsupported targets. |
89
+ | `packageVersion` | Exact pinned version from fs-safe's manifest, when a platform package exists. |
90
+ | `loadErrorCode` | Present only for `load-failed`: an allowlisted loader code, or `UNKNOWN`. |
91
+ | `guidance` | Cause-specific next steps, shared with fallback warnings; absent for `not-probed` and `loaded`. |
92
+
93
+ The function returns a promise and loads its status module on the first call,
94
+ keeping the `config` import lightweight. The default, `{ probe: false }`, reads
95
+ the cached outcome without loading the addon. `{ probe: true }` attempts the same
96
+ lazy load as a native operation, unless
97
+ the mode is `off`. It never changes the configured mode or performs filesystem
98
+ mutations. Failed loads are reported even in `require` mode, without throwing.
99
+ `package-missing` identifies an unresolved platform package; a missing dependency
100
+ inside an installed package is `load-failed`. `capability-missing` means the
101
+ loader's core required capability is absent. Individual operations can still
102
+ lack capabilities when this API reports `loaded`.
103
+
104
+ For a doctor or startup health check:
105
+
106
+ ```ts
107
+ import { getFsSafeNativeStatus } from "@openclaw/fs-safe/config";
108
+
109
+ const native = await getFsSafeNativeStatus({ probe: true });
110
+ if (native.loaded) {
111
+ console.log(`Native filesystem support loaded for ${native.target}`);
112
+ } else {
113
+ console.log(`Native filesystem support: ${native.reason}`);
114
+ if (native.guidance) console.log(native.guidance);
115
+ }
116
+ ```
117
+
118
+ Status is per module instance. A real Worker has its own configuration, loader
119
+ cache, and status; checking the main thread does not probe its Workers. Restart
120
+ the process or Worker after repairing an installation: successes and failures
121
+ are cached, and neither repeated probes nor mode changes retry a failed load.
122
+ No field contains filesystem paths, raw native error messages, or environment
123
+ variable values. See [fallback troubleshooting](native-helper.md#troubleshooting-fallback-warnings)
124
+ for the individual causes and next steps.
125
+
56
126
  ## `configureFsSafeLocks(config)`
57
127
 
58
128
  ```ts