@openclaw/matrix 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 (313) hide show
  1. package/dist/.setup/{approval-handler.runtime-rxLn8CbP.mjs → approval-handler.runtime-CsqMdj7h.mjs} +9 -9
  2. package/dist/.setup/{approval-reactions-DhB6gNPU.mjs → approval-reactions-D2pEUrFS.mjs} +1 -1
  3. package/dist/.setup/{channel-BcwGspPw.mjs → channel-Dw4_imdd.mjs} +132 -111
  4. package/dist/.setup/{channel.runtime-CUc6rbu1.mjs → channel.runtime-2EEtIlyu.mjs} +6 -6
  5. package/dist/.setup/{channel.setup-CwuYD_CT.mjs → channel.setup-Be8TYKMA.mjs} +8 -3
  6. package/dist/.setup/{cli-mbL9jCro.mjs → cli-CEmWh7wh.mjs} +99 -37
  7. package/dist/.setup/{cli-metadata-6MOGuyHq.mjs → cli-metadata-CBLGwJT6.mjs} +1 -1
  8. package/dist/.setup/{client-BlTvpEWC.mjs → client-BKiiI29H.mjs} +3 -3
  9. package/dist/.setup/{client-vPPbaxNp.mjs → client-C4Gibhzl.mjs} +94 -120
  10. package/dist/.setup/{client-bootstrap-D05xy1HB.mjs → client-bootstrap-kOIDV4z_.mjs} +3 -3
  11. package/dist/.setup/{create-client-CroC4BVc.mjs → create-client-BS8UYx6N.mjs} +23 -15
  12. package/dist/.setup/{credentials-Bf9PQ7xs.mjs → credentials-CPnJZaZB.mjs} +3 -5
  13. package/dist/.setup/{credentials-read-CqRiEAQa.mjs → credentials-read-D2ULV55A.mjs} +12 -4
  14. package/dist/.setup/{crypto-runtime-CpJ0b7vY.mjs → crypto-runtime-D8yY7kjD.mjs} +168 -290
  15. package/dist/.setup/crypto-state-store-D4wrCvFs.mjs +185 -0
  16. package/dist/.setup/crypto-store-ownership-Z_Y5WRKr.mjs +174 -0
  17. package/dist/.setup/crypto-unsafe-state-doctor-C8szCYmf.mjs +54 -0
  18. package/dist/.setup/{directory-live-B1WbfSHo.mjs → directory-live-BpSz-FQB.mjs} +1 -1
  19. package/dist/.setup/{draft-stream-Bdrh7dRt.mjs → draft-stream-DYf0th1n.mjs} +1 -1
  20. package/dist/.setup/{exec-approvals-BgNWWJg1.mjs → exec-approvals-DaM-5OA5.mjs} +33 -43
  21. package/dist/.setup/{file-sync-store-BdKx_gMi.mjs → file-sync-store-CUH8EAJq.mjs} +3 -3
  22. package/dist/.setup/idb-persistence-BHTYPRe_.mjs +233 -0
  23. package/dist/.setup/idb-persistence-lock-Bh3ZLIsE.mjs +14 -0
  24. package/dist/.setup/{messages-B1CZqU8u.mjs → messages-_XHVg3do.mjs} +3 -18
  25. package/dist/.setup/{monitor-Dk_fQL1k.mjs → monitor-DRUSJ_Ev.mjs} +31 -39
  26. package/dist/.setup/{onboarding-CrSexor_.mjs → onboarding-CK9Ksgio.mjs} +3 -3
  27. package/dist/.setup/{outbound-LR3SqPIq.mjs → outbound-D5Z_4FBg.mjs} +28 -36
  28. package/dist/.setup/{profile-update-C7bjaGR6.mjs → profile-update--Pf3jCXD.mjs} +2 -2
  29. package/dist/.setup/{reaction-events-CTSvOlXO.mjs → reaction-events-BG9spedG.mjs} +6 -6
  30. package/dist/.setup/{resolve-targets-BVVbbvP6.mjs → resolve-targets-BJ5sIKeM.mjs} +1 -1
  31. package/dist/.setup/{retired-state-DW5pLH05.mjs → retired-state-DOCruzWd.mjs} +10 -10
  32. package/dist/.setup/{room-info-9Uqlpt1V.mjs → room-info-BE7bbX_c.mjs} +3 -3
  33. package/dist/.setup/runtime-setter-api-i2MOXoT3.mjs +2 -0
  34. package/dist/.setup/{sdk-Cfc6m7fY.mjs → sdk-ByU8So5S.mjs} +242 -139
  35. package/dist/.setup/secret-contract-DxyzKSzf.mjs +90 -0
  36. package/dist/.setup/secret-contract-api-Dukgvike.mjs +2 -0
  37. package/dist/.setup/{send-LCOuTVc1.mjs → send-BprM-Wmx.mjs} +19 -42
  38. package/dist/.setup/{setup-bootstrap-CV-hHBXu.mjs → setup-bootstrap-DmznRqYe.mjs} +2 -6
  39. package/dist/.setup/{setup-core-CKnNTfgD.mjs → setup-core-DcCk1_Tv.mjs} +4 -7
  40. package/dist/.setup/setup-plugin-api-BU6UGksW.mjs +2 -0
  41. package/dist/.setup/{startup-verification-CgfUb4yJ.mjs → startup-verification-UU8DUvi4.mjs} +5 -13
  42. package/dist/.setup/state-layout-walk-BQMgMykU.mjs +27 -0
  43. package/dist/.setup/{storage-DkC6bHgA.mjs → storage-l8V0XsGM.mjs} +7 -166
  44. package/dist/.setup/{subagent-hooks-D8jyeQsO.mjs → subagent-hooks-CSY4iwa_.mjs} +7 -6
  45. package/dist/.setup/{sync-cache-state-D4G-fi3t.mjs → sync-cache-state-Cnm4TCEI.mjs} +1 -1
  46. package/dist/.setup/thread-bindings-rrwQAS6R.mjs +620 -0
  47. package/dist/.setup/{thread-bindings-shared-CbsFNbAY.mjs → thread-bindings-shared-CPjUA0Ib.mjs} +26 -5
  48. package/dist/.setup/{tool-actions-DAp6_XSJ.mjs → tool-actions-DBrk6Yei.mjs} +9 -11
  49. package/dist/.setup/{verification-B3VswneT.mjs → verification--qUbmRd-.mjs} +2 -2
  50. package/dist/api.js +6 -6
  51. package/dist/auth-presence.js +16 -2
  52. package/dist/channel-plugin-api.js +1 -2
  53. package/dist/cli-metadata.js +1 -2
  54. package/dist/contract-api.js +2 -2
  55. package/dist/doctor-contract-api.js +20 -27
  56. package/dist/index.js +38 -12
  57. package/dist/plugin-entry.handlers.runtime.js +32 -5
  58. package/dist/runtime-api.js +3 -3
  59. package/dist/runtime-setter-api.js +1 -2
  60. package/dist/secret-contract-api.js +1 -2
  61. package/dist/session-binding-contract-api.js +1 -1
  62. package/dist/setup-entry.js +3 -3
  63. package/dist/setup-plugin-api.js +1 -2
  64. package/dist/subagent-hooks-api.js +1 -1
  65. package/dist/thread-binding-api.js +1 -2
  66. package/dist/thread-bindings-runtime.js +1 -2
  67. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +148 -0
  68. package/node_modules/@openclaw/fs-safe/README.md +20 -4
  69. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -1
  70. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  71. package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +28 -3
  72. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  73. package/node_modules/@openclaw/fs-safe/dist/archive-read.js +11 -5
  74. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +4 -2
  75. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -2
  76. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +2 -2
  77. package/node_modules/@openclaw/fs-safe/dist/bounded-read.d.ts +1 -0
  78. package/node_modules/@openclaw/fs-safe/dist/bounded-read.js +9 -2
  79. package/node_modules/@openclaw/fs-safe/dist/clone-metadata.d.ts +2 -0
  80. package/node_modules/@openclaw/fs-safe/dist/clone-metadata.js +2 -1
  81. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +6 -0
  82. package/node_modules/@openclaw/fs-safe/dist/config.js +5 -0
  83. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +3 -1
  84. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +8 -7
  85. package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
  86. package/node_modules/@openclaw/fs-safe/dist/copy-link.js +170 -0
  87. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.d.ts +9 -0
  88. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.js +39 -0
  89. package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +11 -2
  90. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +2 -4
  91. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +4 -2
  92. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +3 -6
  93. package/node_modules/@openclaw/fs-safe/dist/copy.js +3 -3
  94. package/node_modules/@openclaw/fs-safe/dist/create.js +1 -1
  95. package/node_modules/@openclaw/fs-safe/dist/creation-boundary.js +7 -4
  96. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.d.ts +12 -0
  97. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.js +29 -0
  98. package/node_modules/@openclaw/fs-safe/dist/directory-durability.d.ts +7 -4
  99. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +20 -5
  100. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +3 -0
  101. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +27 -2
  102. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.d.ts +7 -7
  103. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +9 -4
  104. package/node_modules/@openclaw/fs-safe/dist/durability.d.ts +2 -1
  105. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +2 -2
  106. package/node_modules/@openclaw/fs-safe/dist/exact-identity.d.ts +17 -0
  107. package/node_modules/@openclaw/fs-safe/dist/exact-identity.js +29 -0
  108. package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
  109. package/node_modules/@openclaw/fs-safe/dist/file-hash.d.ts +1 -1
  110. package/node_modules/@openclaw/fs-safe/dist/file-hash.js +11 -9
  111. package/node_modules/@openclaw/fs-safe/dist/file-identity.d.ts +7 -1
  112. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +27 -5
  113. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +1 -2
  114. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -1
  115. package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +1 -0
  116. package/node_modules/@openclaw/fs-safe/dist/file-lock.js +1 -0
  117. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.d.ts +9 -0
  118. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.js +42 -0
  119. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +1 -0
  120. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +22 -15
  121. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
  122. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.d.ts +1 -0
  123. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.js +5 -0
  124. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -0
  125. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -0
  126. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +16 -3
  127. package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.js +24 -13
  128. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +85 -18
  129. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +5 -0
  130. package/node_modules/@openclaw/fs-safe/dist/native-config.js +11 -0
  131. package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +6 -4
  132. package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.d.ts +2 -1
  133. package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.js +26 -2
  134. package/node_modules/@openclaw/fs-safe/dist/native-guidance.d.ts +3 -0
  135. package/node_modules/@openclaw/fs-safe/dist/native-guidance.js +21 -0
  136. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +1 -1
  137. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +2 -0
  138. package/node_modules/@openclaw/fs-safe/dist/native-operations.js +8 -6
  139. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +44 -4
  140. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +85 -15
  141. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +3 -3
  142. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +33 -6
  143. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +4 -1
  144. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +22 -3
  145. package/node_modules/@openclaw/fs-safe/dist/native-policy-directory-observation.js +4 -3
  146. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +1 -1
  147. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +3 -0
  148. package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.d.ts +3 -0
  149. package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.js +16 -0
  150. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
  151. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +15 -5
  152. package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.d.ts +3 -0
  153. package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.js +7 -0
  154. package/node_modules/@openclaw/fs-safe/dist/native-status.d.ts +17 -0
  155. package/node_modules/@openclaw/fs-safe/dist/native-status.js +30 -0
  156. package/node_modules/@openclaw/fs-safe/dist/native.d.ts +31 -4
  157. package/node_modules/@openclaw/fs-safe/dist/native.js +107 -9
  158. package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +9 -1
  159. package/node_modules/@openclaw/fs-safe/dist/overwrite-file-handle.js +4 -1
  160. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +1 -1
  161. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -1
  162. package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +9 -1
  163. package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +1 -0
  164. package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
  165. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +83 -47
  166. package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +2 -0
  167. package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +8 -3
  168. package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +13 -0
  169. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +10 -8
  170. package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
  171. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +7 -5
  172. package/node_modules/@openclaw/fs-safe/dist/pipe.d.ts +9 -0
  173. package/node_modules/@openclaw/fs-safe/dist/pipe.js +26 -0
  174. package/node_modules/@openclaw/fs-safe/dist/private-directory.js +1 -1
  175. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +2 -5
  176. package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.d.ts +3 -2
  177. package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +38 -10
  178. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
  179. package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts +3 -0
  180. package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.js +3 -0
  181. package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +5 -3
  182. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +26 -20
  183. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -0
  184. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.js +3 -4
  185. package/node_modules/@openclaw/fs-safe/dist/realpath.js +32 -0
  186. package/node_modules/@openclaw/fs-safe/dist/regular-file.d.ts +4 -0
  187. package/node_modules/@openclaw/fs-safe/dist/regular-file.js +11 -15
  188. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +3 -2
  189. package/node_modules/@openclaw/fs-safe/dist/root-copy.d.ts +18 -0
  190. package/node_modules/@openclaw/fs-safe/dist/root-copy.js +128 -0
  191. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +8 -13
  192. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +252 -27
  193. package/node_modules/@openclaw/fs-safe/dist/root-directory-creation.js +5 -1
  194. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +11 -8
  195. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +35 -13
  196. package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.d.ts +8 -0
  197. package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.js +108 -0
  198. package/node_modules/@openclaw/fs-safe/dist/root-file.d.ts +7 -0
  199. package/node_modules/@openclaw/fs-safe/dist/root-file.js +15 -0
  200. package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +2 -0
  201. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +154 -175
  202. package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.d.ts +2 -0
  203. package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.js +239 -0
  204. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +37 -15
  205. package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.d.ts +32 -0
  206. package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.js +56 -0
  207. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
  208. package/node_modules/@openclaw/fs-safe/dist/root-public-types.d.ts +1 -0
  209. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +95 -16
  210. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +6 -4
  211. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +23 -19
  212. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -6
  213. package/node_modules/@openclaw/fs-safe/dist/root-write-publication.js +5 -1
  214. package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +3 -1
  215. package/node_modules/@openclaw/fs-safe/dist/secure-file.d.ts +2 -0
  216. package/node_modules/@openclaw/fs-safe/dist/secure-file.js +10 -4
  217. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
  218. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +2 -2
  219. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +1 -1
  220. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-target.js +8 -1
  221. package/node_modules/@openclaw/fs-safe/dist/staged-directory.d.ts +13 -0
  222. package/node_modules/@openclaw/fs-safe/dist/staged-directory.js +29 -0
  223. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +5 -3
  224. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.d.ts +8 -0
  225. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +13 -2
  226. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +71 -3
  227. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +2 -1
  228. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +22 -0
  229. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-types.d.ts +3 -0
  230. package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +1 -0
  231. package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +1 -0
  232. package/node_modules/@openclaw/fs-safe/dist/trash.js +2 -2
  233. package/node_modules/@openclaw/fs-safe/dist/types.d.ts +4 -0
  234. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +2 -2
  235. package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.d.ts +5 -0
  236. package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.js +39 -0
  237. package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +1 -0
  238. package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +25 -36
  239. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +24 -5
  240. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +2 -1
  241. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +4 -0
  242. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.d.ts +2 -0
  243. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
  244. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.js +7 -0
  245. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.d.ts +16 -0
  246. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.js +51 -0
  247. package/node_modules/@openclaw/fs-safe/dist/write-file-handle.d.ts +1 -0
  248. package/node_modules/@openclaw/fs-safe/dist/write-file-handle.js +12 -10
  249. package/node_modules/@openclaw/fs-safe/docs/advanced.md +2 -1
  250. package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
  251. package/node_modules/@openclaw/fs-safe/docs/config.md +71 -1
  252. package/node_modules/@openclaw/fs-safe/docs/contributing.md +45 -5
  253. package/node_modules/@openclaw/fs-safe/docs/copy.md +45 -5
  254. package/node_modules/@openclaw/fs-safe/docs/durability.md +34 -7
  255. package/node_modules/@openclaw/fs-safe/docs/errors.md +8 -0
  256. package/node_modules/@openclaw/fs-safe/docs/guest.md +2 -0
  257. package/node_modules/@openclaw/fs-safe/docs/install.md +47 -27
  258. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -6
  259. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.6.md +1 -1
  260. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +79 -6
  261. package/node_modules/@openclaw/fs-safe/docs/native.md +279 -8
  262. package/node_modules/@openclaw/fs-safe/docs/permissions.md +64 -15
  263. package/node_modules/@openclaw/fs-safe/docs/pipe.md +81 -0
  264. package/node_modules/@openclaw/fs-safe/docs/platform-capabilities.md +63 -0
  265. package/node_modules/@openclaw/fs-safe/docs/public-api.md +10 -0
  266. package/node_modules/@openclaw/fs-safe/docs/queue-link-free-design.md +237 -0
  267. package/node_modules/@openclaw/fs-safe/docs/quickstart.md +23 -3
  268. package/node_modules/@openclaw/fs-safe/docs/reading.md +56 -2
  269. package/node_modules/@openclaw/fs-safe/docs/regular-file.md +2 -0
  270. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
  271. package/node_modules/@openclaw/fs-safe/docs/secure-file.md +1 -1
  272. package/node_modules/@openclaw/fs-safe/docs/security-model.md +35 -0
  273. package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
  274. package/node_modules/@openclaw/fs-safe/docs/staged-file.md +18 -0
  275. package/node_modules/@openclaw/fs-safe/docs/store.md +15 -0
  276. package/node_modules/@openclaw/fs-safe/docs/temp.md +39 -0
  277. package/node_modules/@openclaw/fs-safe/docs/testing.md +13 -0
  278. package/node_modules/@openclaw/fs-safe/docs/types.md +51 -81
  279. package/node_modules/@openclaw/fs-safe/docs/walk.md +4 -1
  280. package/node_modules/@openclaw/fs-safe/docs/watch.md +3 -2
  281. package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
  282. package/node_modules/@openclaw/fs-safe/package.json +21 -12
  283. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  284. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  285. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  286. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  287. package/node_modules/@openclaw/fs-safe-freebsd-arm64/fs-safe-native.node +0 -0
  288. package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +29 -0
  289. package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
  290. package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +29 -0
  291. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  292. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  293. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  294. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  295. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  296. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  297. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  298. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  299. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/fs-safe-native.node +0 -0
  300. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +29 -0
  301. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  302. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  303. package/node_modules/typebox/build/value/clean/from_object.mjs +1 -1
  304. package/node_modules/typebox/package.json +1 -1
  305. package/openclaw.plugin.json +3 -0
  306. package/package.json +7 -6
  307. package/dist/.setup/runtime-setter-api-DtxDAM-x.mjs +0 -3
  308. package/dist/.setup/secret-contract-DSBRvhdZ.mjs +0 -95
  309. package/dist/.setup/secret-contract-api-DzEJ-X7n.mjs +0 -3
  310. package/dist/.setup/setup-plugin-api-BDAYk1a5.mjs +0 -3
  311. package/dist/.setup/thread-bindings-CT4P-OSZ.mjs +0 -365
  312. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +0 -336
  313. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +0 -90
@@ -10,6 +10,9 @@
10
10
 
11
11
  Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.
12
12
 
13
+ Platform-specific additions include [Linux write leases, Darwin ACL inspection,
14
+ and Windows test fixtures](docs/platform-capabilities.md).
15
+
13
16
  Think Go's `os.Root` / `OpenInRoot` or Rust's [`cap-std`](https://github.com/bytecodealliance/cap-std), but for Node. Hand `root()` a trusted directory and you get back a handle whose every method resolves relative paths against it and defends against `..`, symlink swaps, hardlink aliases, and TOCTOU rename races. The exact containment strength is reported per mechanism: Linux `openat2` opens are kernel-atomic; guarded Linux fallback, macOS, Windows, and JavaScript paths are best-effort.
14
17
 
15
18
  ```ts
@@ -81,6 +84,12 @@ kernel-atomic. Read the [native helper policy](docs/native-helper.md) and
81
84
  [operation/platform matrix](docs/security-model.md#native-root-mutation-capabilities)
82
85
  when concurrent mutation is in scope.
83
86
 
87
+ For doctor/startup checks, use
88
+ [`await getFsSafeNativeStatus({ probe: true })`](docs/config.md#getfssafenativestatus-options)
89
+ from `@openclaw/fs-safe/config` to report whether the native addon is active,
90
+ why it is unavailable, and what to do next. Calling it without options only reads
91
+ the cache. Status is per module instance; Workers must be checked separately.
92
+
84
93
  ## Migrating from the Python helper
85
94
 
86
95
  Version 0.5 replaced the Python worker with prebuilt native bindings. Follow the
@@ -113,9 +122,14 @@ operation; per-call options handle exceptions. See the [Root reference](docs/roo
113
122
 
114
123
  `write()` replaces contents by default. Use `create()` or `overwrite: false`
115
124
  when an existing destination should be an error. `move()` defaults to no clobber
116
- and requires native support for the atomic collision decision; it fails with
117
- `helper-unavailable` when unavailable. Pass `overwrite: true` when replacement
118
- is intended.
125
+ and requires native support. On Linux, `auto` can replace a concurrently created
126
+ empty directory if no-replace rename is unsupported; use native `require` for
127
+ strict refusal. See the [move fallback contract](docs/writing.md#fs-move-from-to-options).
128
+ Pass `overwrite: true` when replacement is intended.
129
+
130
+ Staged JavaScript copies and creates also work when hardlinks are refused
131
+ (including Android/Termux), using a verified empty placeholder before atomic
132
+ replacement. See the [visibility and concurrency tradeoffs](docs/writing.md#publication-without-hardlinks).
119
133
 
120
134
  See [Writing](docs/writing.md) for copy sources, mutation authority, symlink
121
135
  policy, writable handles, and bounded removal, and [Creation](docs/creation.md)
@@ -159,6 +173,8 @@ and error exports. Prefer focused subpaths when a consumer needs a narrower
159
173
  contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are grouped under
160
174
  `@openclaw/fs-safe/advanced` instead of being separate public leaf contracts.
161
175
 
176
+ Use [`@openclaw/fs-safe/pipe`](docs/pipe.md) for native-owned, reopenable anonymous pipes and one-shot child input.
177
+
162
178
  See the [complete subpath catalogue](docs/install.md#subpath-exports) for every entry point and its contents.
163
179
 
164
180
  ## Failure semantics in the name
@@ -236,7 +252,7 @@ updates:
236
252
  import { fileStore } from "@openclaw/fs-safe/store";
237
253
 
238
254
  const files = fileStore({ rootDir: "/safe/workspace/state", private: true });
239
- const store = files.json("settings.json", { lock: true });
255
+ const store = files.json<{ enabled: boolean }>("settings.json", { lock: true });
240
256
 
241
257
  await store.updateOr({ enabled: false }, (current) => ({ ...current, enabled: true }));
242
258
  ```
@@ -17,7 +17,7 @@ export { readFileWindowFully, readFileWindowFullySync, type ReadFileWindowOption
17
17
  export { writeFileWindowFully, type WriteFileWindowOptions } from "./write-file-handle.js";
18
18
  export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, type UnsafeDeviceReadPathMatch, type UnsafeDeviceReadPathOptions, type UnsafeDeviceReadPathReason, } from "./device-path.js";
19
19
  export { assertAbsolutePathInput, canonicalPathFromExistingAncestor, ensureAbsoluteDirectory, findExistingAncestor, resolveAbsolutePathForRead, resolveAbsolutePathForWrite, type AbsolutePathSymlinkPolicy, type EnsureAbsoluteDirectoryOptions, type EnsureAbsoluteDirectoryResult, type ResolvedAbsolutePath, type ResolvedWritableAbsolutePath, } from "./absolute-path.js";
20
- export { sameFileIdentity, type FileIdentityStat } from "./file-identity.js";
20
+ export { sameFileIdentity, type FileIdentityStat, type FileIdentityInput, type ExactFileIdentity, type ExactIdentityReceipt } from "./file-identity.js";
21
21
  export { sanitizeUntrustedFileName } from "./filename.js";
22
22
  export { pathExists, pathExistsSync } from "./fs.js";
23
23
  export { resolveLocalPathFromRootsSync, readLocalFileFromRoots, type LocalRootsInputOptions, type LocalRootsPathResult, type LocalRootsReadResult, type ReadLocalFileFromRootsOptions, type ResolveLocalPathFromRootsSyncOptions, } from "./local-roots.js";
@@ -26,6 +26,7 @@ export { formatPosixMode } from "./permissions.js";
26
26
  export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
27
27
  export { assertNoHardlinkedFinalPath, assertNoPathAliasEscape, PATH_ALIAS_POLICIES, type PathAliasPolicy, } from "./path-policy.js";
28
28
  export { openRootFile, openRootFileSync, canUseRootFileOpen, matchRootFileOpenFailure, type OpenRootFileParams, type OpenRootFileSyncParams, type RootFileOpenFailure, type RootFileOpenFailureReason, type RootFileOpenResult, } from "./root-file.js";
29
+ export { createRootFileOpenBatchSync, type RootFileOpenBatchSync } from "./root-file-open-batch-sync.js";
29
30
  export { ROOT_PATH_ALIAS_POLICIES, resolvePathViaExistingAncestorSync, resolveRootPath, resolveRootPathSync, type ResolvedRootPath, type RootPathAliasPolicy, } from "./root-path.js";
30
31
  export { ensureDirectoryWithinRoot, pathScope, resolveExistingPathsWithinRoot, resolvePathWithinRoot, resolvePathsWithinRoot, resolveStrictExistingPathsWithinRoot, resolveWritablePathWithinRoot, type PathScope, type PathScopeOptions, type PathScopeResolveOptions, } from "./root-paths.js";
31
32
  export { safeDirName, safePathSegmentHashed, safePathSegmentHashedV2, resolveSafeInstallDir, assertCanonicalPathWithinBase, } from "./install-path.js";
@@ -27,6 +27,7 @@ export { formatPosixMode } from "./permissions.js";
27
27
  export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
28
28
  export { assertNoHardlinkedFinalPath, assertNoPathAliasEscape, PATH_ALIAS_POLICIES, } from "./path-policy.js";
29
29
  export { openRootFile, openRootFileSync, canUseRootFileOpen, matchRootFileOpenFailure, } from "./root-file.js";
30
+ export { createRootFileOpenBatchSync } from "./root-file-open-batch-sync.js";
30
31
  export { ROOT_PATH_ALIAS_POLICIES, resolvePathViaExistingAncestorSync, resolveRootPath, resolveRootPathSync, } from "./root-path.js";
31
32
  export { ensureDirectoryWithinRoot, pathScope, resolveExistingPathsWithinRoot, resolvePathWithinRoot, resolvePathsWithinRoot, resolveStrictExistingPathsWithinRoot, resolveWritablePathWithinRoot, } from "./root-paths.js";
32
33
  export { safeDirName, safePathSegmentHashed, safePathSegmentHashedV2, resolveSafeInstallDir, assertCanonicalPathWithinBase, } from "./install-path.js";
@@ -17,6 +17,7 @@ import { syncFileBestEffortSync } from "./file-sync.js";
17
17
  import { finalizeArchivePublication } from "./archive-durability.js";
18
18
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
19
19
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
20
+ import { ArchiveSecurityError } from "./archive-errors.js";
20
21
  export async function withStagedArchivePublication(params, extract) {
21
22
  await withStagedArchiveDestination({
22
23
  destinationRealDir: params.destinationGuard.realPath,
@@ -101,6 +102,23 @@ async function mergeTree(params, publication, durable = true, entryUmask = 0) {
101
102
  assertDirectoryIdentityGuard(destinationGuard);
102
103
  check();
103
104
  for (const ancestor of ancestors) {
105
+ // Verify every retained ancestor, including its current name and physical path.
106
+ // Shallow chains cost less to verify directly than crossing the native bridge.
107
+ if (ancestors.length >= 4 && ancestor.owner.verifyCanonical) {
108
+ let verified;
109
+ try {
110
+ verified = await ancestor.owner.verifyCanonical();
111
+ }
112
+ catch (error) {
113
+ if (error instanceof FsSafeError || isNotFoundPathError(error)) {
114
+ throw createArchiveSymlinkTraversalError(path.relative(destinationDir, ancestor.guard.dir));
115
+ }
116
+ throw error;
117
+ }
118
+ check();
119
+ if (verified)
120
+ continue;
121
+ }
104
122
  assertDirectoryIdentityGuard(ancestor.guard);
105
123
  check();
106
124
  await ancestor.owner.verify(check);
@@ -156,15 +174,22 @@ async function mergeTree(params, publication, durable = true, entryUmask = 0) {
156
174
  await ownExtractionDestinationMutation(params.deadline, async () => {
157
175
  await assertGuards();
158
176
  assertSourceFrontier();
159
- const owner = await pinNodeDirectoryForMode(destinationPath).catch((error) => {
177
+ const guard = await createDirectoryIdentityGuard(destinationPath).catch((error) => {
178
+ if (error instanceof ArchiveSecurityError && error.code === "destination-symlink") {
179
+ throw createArchiveSymlinkTraversalError(originalPath);
180
+ }
181
+ throw error;
182
+ });
183
+ check();
184
+ const owner = await pinNodeDirectoryForMode(destinationPath, {
185
+ expectedIdentity: guard.stat, canonicalPath: guard.realPath,
186
+ }).catch((error) => {
160
187
  if (error instanceof FsSafeError && (error.code === "not-file" || error.code === "path-mismatch")) {
161
188
  throw createArchiveSymlinkTraversalError(originalPath);
162
189
  }
163
190
  throw error;
164
191
  });
165
192
  try {
166
- check();
167
- const guard = await createDirectoryIdentityGuard(destinationPath);
168
193
  check();
169
194
  await owner.verify(check);
170
195
  ancestors.push({ guard, owner });
@@ -17,7 +17,7 @@ import { FsSafeError } from "./errors.js";
17
17
  import { inspectFileIdentity } from "./strict-file-identity.js";
18
18
  import { resolveReadOpenFlags } from "./read-open-flags.js";
19
19
  import { realpathSync } from "./realpath.js";
20
- import { getNativeBinding } from "./native.js";
20
+ import { assertNativeCapabilities, getNativeBinding } from "./native.js";
21
21
  import { admitZipBuffer } from "./archive-zip-admission.js";
22
22
  import { validateNativeZipManifest } from "./archive-zip-manifest.js";
23
23
  import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
@@ -199,9 +199,15 @@ async function readNativeBufferEntry(native, buffer, kind, requested, displayPat
199
199
  try {
200
200
  const signal = new AbortController().signal;
201
201
  const limits = resolveTarMeterLimits();
202
- const reader = kind === "zip"
203
- ? await native.openZipBufferNative(buffer, limits, AbortSignal.any([signal]))
204
- : await native.openTarBufferNative(buffer, kind, limits, AbortSignal.any([signal]));
202
+ let reader;
203
+ if (kind === "zip") {
204
+ assertNativeCapabilities(native, "openZipBufferNative");
205
+ reader = await native.openZipBufferNative(buffer, limits, AbortSignal.any([signal]));
206
+ }
207
+ else {
208
+ assertNativeCapabilities(native, "openTarBufferNative");
209
+ reader = await native.openTarBufferNative(buffer, kind, limits, AbortSignal.any([signal]));
210
+ }
205
211
  const manifest = reader.entries;
206
212
  if (kind === "zip")
207
213
  validateNativeZipManifest(manifest, zipEntries);
@@ -227,7 +233,7 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
227
233
  const zipEntries = [];
228
234
  if (kind === "zip")
229
235
  admitZipBuffer(buffer, resolveExtractLimits(), entry => { zipEntries.push(entry); });
230
- const native = getNativeBinding();
236
+ const native = getNativeBinding(kind === "zip" ? "openZipBufferNative" : "openTarBufferNative");
231
237
  if (native)
232
238
  return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, maxBytes, zipEntries);
233
239
  return kind === "zip" ? await readZipEntry(buffer, requestedEntry, maxBytes, zipEntries)
@@ -118,11 +118,13 @@ export async function loadAdmittedZipArchive(buffer, admitted) {
118
118
  const entries = new Map();
119
119
  for (const [name, entry, physical] of normalized) {
120
120
  const appendSlash = physical.kind === "directory" && !name.endsWith("/");
121
- const normalizedName = appendSlash ? `${name}/` : name;
121
+ // JSZip reduces a relative root marker such as "./" to an empty name.
122
+ // Keep its directory spelling relative when restoring the terminal slash.
123
+ const normalizedName = appendSlash ? `${name || "."}/` : name;
122
124
  if (Object.hasOwn(files, normalizedName))
123
125
  collision();
124
126
  if (appendSlash) {
125
- entry.name = `${entry.name}/`;
127
+ entry.name = `${entry.name || "."}/`;
126
128
  }
127
129
  files[normalizedName] = entry;
128
130
  entries.set(physical.portableKey, createAdmittedZipEntry(entry, normalizedName, physical));
@@ -154,7 +154,7 @@ export async function extractArchive(params) {
154
154
  const label = kind === "zip" ? "extract zip" : "extract tar";
155
155
  const limits = resolveExtractLimits(params.limits);
156
156
  const tarLimits = resolveTarMeterLimits(limits);
157
- const native = getNativeBinding();
157
+ const native = getNativeBinding("inspectArchiveNative", "extractArchiveNative");
158
158
  assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
159
159
  assertNoWindowsPathAlias(destDir, "filesystem", "archive destination uses a Windows filesystem namespace alias");
160
160
  // Read the declared public fields before private adapters copy options: class
@@ -282,7 +282,7 @@ export async function inspectTarArchive(params) {
282
282
  const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
283
283
  const limits = resolveExtractLimits(params.limits);
284
284
  const tarLimits = resolveTarMeterLimits(limits);
285
- const native = getNativeBinding();
285
+ const native = getNativeBinding("inspectArchiveNative");
286
286
  assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
287
287
  return await withExtractionDeadline(params.timeoutMs, "inspect tar", async (deadline) => {
288
288
  const staged = await stageArchiveFileForExtraction({ archivePath, limits, deadline });
@@ -1,6 +1,6 @@
1
1
  import syncFs, {} from "node:fs";
2
2
  import fs, {} from "node:fs/promises";
3
- import { readBoundedSync } from "./bounded-read.js";
3
+ import { readBoundedSync, readFileHandleBounded } from "./bounded-read.js";
4
4
  import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
5
5
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
6
6
  import { sleep, sleepSync } from "./timing.js";
@@ -207,7 +207,7 @@ export class AtomicFile {
207
207
  }
208
208
  *readFile(sizeHint) {
209
209
  if (typeof this.resource !== "number")
210
- return yield* wait(this.resource.readFile());
210
+ return yield* wait(readFileHandleBounded(this.resource, Infinity));
211
211
  if (sizeHint === undefined)
212
212
  return this.io.syncFs.readFileSync(this.resource);
213
213
  const fd = this.resource;
@@ -13,6 +13,7 @@ export declare function readBoundedAsync(maxBytes: number, readChunk: (scratch:
13
13
  * force an unbounded allocation.
14
14
  */
15
15
  export declare function readFileHandleBounded(handle: ReadableFileHandle, maxBytes: number): Promise<Buffer>;
16
+ export declare function readFileHandleBoundedWithSize(handle: ReadableFileHandle, maxBytes: number, initialSize?: number): Promise<Buffer>;
16
17
  /** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
17
18
  export declare function readFileDescriptorBounded(fd: number, maxBytes: number): Promise<Buffer>;
18
19
  export declare function readBoundedSync(maxBytes: number, readChunk: (scratch: Buffer, length: number) => number, options?: {
@@ -73,11 +73,18 @@ export async function readBoundedAsync(maxBytes, readChunk, options = {}) {
73
73
  * force an unbounded allocation.
74
74
  */
75
75
  export async function readFileHandleBounded(handle, maxBytes) {
76
+ return await readFileHandleBoundedWithSize(handle, maxBytes);
77
+ }
78
+ // Internal: the admitted size bounds the first allocation, never the read.
79
+ export function readFileHandleBoundedWithSize(handle, maxBytes, initialSize) {
76
80
  normalizeMaxBytes(maxBytes);
77
81
  const fd = "fd" in handle && typeof handle.fd === "number" ? handle.fd : undefined;
78
- return await readBoundedAsync(maxBytes, async (scratch, length) => {
82
+ return readBoundedAsync(maxBytes, async (scratch, length) => {
79
83
  return (await handle.read(scratch, 0, length, null)).bytesRead;
80
- }, { observeRegularFileSize: fd === undefined ? undefined : () => regularFileSize(fd) });
84
+ }, {
85
+ initialSize,
86
+ observeRegularFileSize: fd === undefined ? undefined : () => regularFileSize(fd),
87
+ });
81
88
  }
82
89
  function regularFileSize(fd) {
83
90
  try {
@@ -1,4 +1,6 @@
1
+ import type { ExactFileIdentity } from "./exact-identity.js";
1
2
  export type CloneFileMetadata = {
3
+ exactIdentity?: ExactFileIdentity;
2
4
  dev: number;
3
5
  type: number;
4
6
  mtimeSec: number;
@@ -6,7 +6,7 @@ import { getNativeBinding, requireNativeBinding } from "./native.js";
6
6
  */
7
7
  export async function readCloneFileMetadata(files) {
8
8
  const paths = files.map(assertAbsolutePathInput);
9
- const native = process.platform === "darwin" ? requireNativeBinding() : getNativeBinding();
9
+ const native = process.platform === "darwin" ? requireNativeBinding("readCloneFileMetadata") : getNativeBinding("readCloneFileMetadata");
10
10
  if (!native)
11
11
  return paths.map(() => undefined);
12
12
  const results = await native.readCloneFileMetadata(paths);
@@ -19,6 +19,7 @@ export async function readCloneFileMetadata(files) {
19
19
  result.readUInt32LE(20) !== 0x100)
20
20
  return undefined;
21
21
  return {
22
+ exactIdentity: Object.freeze({ dev: BigInt(result.readUInt32LE(24)), ino: result.readBigInt64LE(76) }),
22
23
  dev: result.readUInt32LE(24),
23
24
  type: result.readUInt32LE(28),
24
25
  mtimeSec: Number(result.readBigInt64LE(32)),
@@ -1,2 +1,8 @@
1
+ import type { FsSafeNativeStatus } from "./native-status.js";
1
2
  export { configureFsSafeNative, getFsSafeNativeConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
3
+ export type { FsSafeNativeStatus, FsSafeNativeStatusReason, } from "./native-status.js";
2
4
  export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
5
+ /** Read native status without loading the native module on config import. */
6
+ export declare function getFsSafeNativeStatus(options?: {
7
+ probe?: boolean;
8
+ }): Promise<FsSafeNativeStatus>;
@@ -1,2 +1,7 @@
1
1
  export { configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
2
2
  export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
3
+ /** Read native status without loading the native module on config import. */
4
+ export async function getFsSafeNativeStatus(options) {
5
+ const { readFsSafeNativeStatus } = await import("./native-status.js");
6
+ return readFsSafeNativeStatus(options);
7
+ }
@@ -3,19 +3,21 @@ import type { FileHandle } from "node:fs/promises";
3
3
  import { type CopyCloneMode } from "./copy-policy.js";
4
4
  import { type NativeBinding } from "./native.js";
5
5
  import { type NativeFileCopyResult } from "./native-binding.js";
6
+ import type { CopyMetadata } from "./copy-metadata.js";
6
7
  export type CopyFileInput = {
7
8
  kind: "file";
8
9
  handle: FileHandle;
9
10
  size: number;
10
11
  clone: CopyCloneMode;
11
12
  signal?: AbortSignal;
13
+ metadata?: CopyMetadata;
12
14
  verifySource(): Promise<void>;
13
15
  };
14
16
  export declare function resolveFileCopyCloneMode(mode?: CopyCloneMode): CopyCloneMode;
15
17
  export declare function assertCopySourceCurrent(source: {
16
18
  handle: FileHandle;
17
19
  realPath: string;
18
- }, identity: BigIntStats): Promise<void>;
20
+ }, identity: BigIntStats): void;
19
21
  export declare function writeCopyFileToFd(fd: number, input: CopyFileInput, maxBytes?: number, assertBeforeMutation?: () => void): Promise<void>;
20
22
  export declare function createNativeCopyFile(native: NativeBinding, input: CopyFileInput, parentFd: number, basename: string, maxBytes: number | undefined): Promise<NativeFileCopyResult | undefined>;
21
23
  export declare function assertNativeCopyCompleted(input: CopyFileInput, copied?: NativeFileCopyResult): void;
@@ -1,9 +1,9 @@
1
1
  import fs, {} from "node:fs";
2
2
  import { resolveCopyCloneMode } from "./copy-policy.js";
3
3
  import { FsSafeError } from "./errors.js";
4
- import { getNativeBinding } from "./native.js";
4
+ import { getNativeBinding, selectNativeBinding } from "./native.js";
5
5
  import { captureNativeFdClose } from "./native-binding.js";
6
- import { inspectFileIdentity } from "./strict-file-identity.js";
6
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
7
7
  import { transferFileHandle } from "./file-handle-transfer.js";
8
8
  export function resolveFileCopyCloneMode(mode) {
9
9
  const clone = resolveCopyCloneMode(mode, "never");
@@ -12,9 +12,9 @@ export function resolveFileCopyCloneMode(mode) {
12
12
  }
13
13
  return clone;
14
14
  }
15
- export async function assertCopySourceCurrent(source, identity) {
16
- await inspectFileIdentity(() => fs.fstatSync(source.handle.fd, { bigint: true }), identity);
17
- await inspectFileIdentity(() => {
15
+ export function assertCopySourceCurrent(source, identity) {
16
+ inspectFileIdentitySync(() => fs.fstatSync(source.handle.fd, { bigint: true }), identity);
17
+ inspectFileIdentitySync(() => {
18
18
  const current = fs.lstatSync(source.realPath, { bigint: true });
19
19
  if (current.isSymbolicLink() || !current.isFile()) {
20
20
  throw new FsSafeError("path-mismatch", "copy source path changed");
@@ -29,7 +29,8 @@ export async function writeCopyFileToFd(fd, input, maxBytes, assertBeforeMutatio
29
29
  }
30
30
  export async function createNativeCopyFile(native, input, parentFd, basename, maxBytes) {
31
31
  input.signal?.throwIfAborted();
32
- if (!native.copyFileExclusive) {
32
+ const copier = selectNativeBinding(native, "copyFileExclusive");
33
+ if (!copier) {
33
34
  if (input.clone === "always") {
34
35
  throw new FsSafeError("helper-unavailable", "native file cloning is unavailable");
35
36
  }
@@ -39,7 +40,7 @@ export async function createNativeCopyFile(native, input, parentFd, basename, ma
39
40
  const nativeSignal = input.signal ? AbortSignal.any([input.signal]) : undefined;
40
41
  try {
41
42
  // The caller adopts this descriptor before observing a later cancellation.
42
- return await native.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
43
+ return await copier.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
43
44
  }
44
45
  catch (error) {
45
46
  const code = error?.code;
@@ -0,0 +1,26 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
3
+ import type { NativeBinding } from "./native-binding.js";
4
+ import type { PinnedWriteParams } from "./pinned-write-types.js";
5
+ export type CopyLinkInput = {
6
+ kind: "link";
7
+ stageBeforePublish: true;
8
+ sourcePath: string;
9
+ identity: BigIntStats;
10
+ target?: Buffer;
11
+ preserveMetadata: boolean;
12
+ signal?: AbortSignal;
13
+ verifySource(): Promise<void>;
14
+ };
15
+ /** Shared by tree byte copying and per-file copying; never resolves the target. */
16
+ export declare function readCopyLinkTarget(source: string): Promise<Buffer>;
17
+ export declare function assertCopyLinkAvailable(): void;
18
+ export declare function admitCopyLink(sourcePath: string, preserveMetadata: boolean, signal?: AbortSignal): Promise<CopyLinkInput | undefined>;
19
+ /** The caller has admitted the destination parent under Root's mutation policy. */
20
+ export declare function copyLinkAtParent(params: PinnedWriteParams, input: CopyLinkInput, parent: AnyAsyncDirectoryGuard, native?: {
21
+ binding: NativeBinding;
22
+ fd: number;
23
+ }): Promise<{
24
+ dev: bigint;
25
+ ino: bigint;
26
+ }>;
@@ -0,0 +1,170 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs, {} from "node:fs";
3
+ import fsp from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
6
+ import { FsSafeError } from "./errors.js";
7
+ import { statIdentityFromNative } from "./native-stat-identity.js";
8
+ import { getNativeBinding } from "./native.js";
9
+ import { isFsSafeNativeRequired } from "./native-config.js";
10
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
11
+ import { timestampSeconds } from "./copy-metadata.js";
12
+ import { syncDirectoryBestEffort } from "./directory-durability.js";
13
+ import { noReplaceUnavailable } from "./native-noreplace.js";
14
+ import { createSuppressedError } from "./suppressed-error.js";
15
+ /** Shared by tree byte copying and per-file copying; never resolves the target. */
16
+ export async function readCopyLinkTarget(source) {
17
+ return await fsp.readlink(source, { encoding: "buffer" });
18
+ }
19
+ export function assertCopyLinkAvailable() {
20
+ const native = getNativeBinding();
21
+ if (process.platform === "darwin" && !native?.createCopySymlink) {
22
+ throw new FsSafeError("helper-unavailable", "exclusive macOS link copying requires the native helper; link(2) follows its source");
23
+ }
24
+ if (process.platform === "win32" && (!native?.copyLinkExclusive || !native.publishCopyLink || !native.removeCopyLink)) {
25
+ throw new FsSafeError("helper-unavailable", "copying Windows links without following them requires the native helper");
26
+ }
27
+ if (native && process.platform !== "win32" && !native.createCopySymlink) {
28
+ throw new FsSafeError("helper-unavailable", "native link copying is unavailable");
29
+ }
30
+ }
31
+ export async function admitCopyLink(sourcePath, preserveMetadata, signal) {
32
+ const parent = await createAsyncDirectoryGuard(path.dirname(sourcePath), { bigint: true });
33
+ sourcePath = path.join(parent.realPath, path.basename(sourcePath));
34
+ const identity = fs.lstatSync(sourcePath, { bigint: true });
35
+ if (!identity.isSymbolicLink())
36
+ return undefined;
37
+ const target = process.platform === "win32" ? undefined : await readCopyLinkTarget(sourcePath);
38
+ const verifySource = async () => {
39
+ signal?.throwIfAborted();
40
+ await assertAsyncDirectoryGuard(parent);
41
+ const current = inspectFileIdentitySync(() => fs.lstatSync(sourcePath, { bigint: true }), identity);
42
+ if (!current.isSymbolicLink() || current.mtimeNs !== identity.mtimeNs || current.ctimeNs !== identity.ctimeNs) {
43
+ throw new FsSafeError("path-mismatch", "copy source link changed");
44
+ }
45
+ };
46
+ await verifySource();
47
+ return { kind: "link", stageBeforePublish: true, sourcePath, identity, target, preserveMetadata, signal, verifySource };
48
+ }
49
+ /** The caller has admitted the destination parent under Root's mutation policy. */
50
+ export async function copyLinkAtParent(params, input, parent, native) {
51
+ const temporaryName = `.fs-safe-${randomUUID()}.tmp`;
52
+ const temporaryPath = path.join(parent.realPath, temporaryName);
53
+ const destination = path.join(parent.realPath, params.basename);
54
+ let identity;
55
+ let sourceParent;
56
+ let failure;
57
+ const assertStage = () => {
58
+ assertSyncDirectoryGuard(parent);
59
+ const stat = inspectFileIdentitySync(() => fs.lstatSync(temporaryPath, { bigint: true }), identity);
60
+ if (!stat.isSymbolicLink() || stat.nlink !== 1n)
61
+ throw new FsSafeError("path-mismatch", "copy link stage changed");
62
+ };
63
+ try {
64
+ await input.verifySource();
65
+ await assertAsyncDirectoryGuard(parent);
66
+ if (process.platform === "win32") {
67
+ if (!native?.binding.copyLinkExclusive)
68
+ throw new FsSafeError("helper-unavailable", "native Windows link copying is unavailable");
69
+ const sourceGuard = await createAsyncDirectoryGuard(path.dirname(input.sourcePath), { bigint: true });
70
+ sourceParent = await fsp.open(sourceGuard.realPath, fs.constants.O_RDONLY);
71
+ inspectFileIdentitySync(() => fs.fstatSync(sourceParent.fd, { bigint: true }), sourceGuard.stat);
72
+ await input.verifySource();
73
+ params.assertBeforeMutation?.();
74
+ assertSyncDirectoryGuard(parent);
75
+ identity = native.binding.copyLinkExclusive(sourceParent.fd, path.basename(input.sourcePath), native.fd, temporaryName, input.identity.dev, input.identity.ino, input.preserveMetadata, params.mode);
76
+ }
77
+ else {
78
+ if (native && !native.binding.createCopySymlink)
79
+ throw new FsSafeError("helper-unavailable", "native link copying is unavailable");
80
+ params.assertBeforeMutation?.();
81
+ assertSyncDirectoryGuard(parent);
82
+ if (native?.binding.createCopySymlink) {
83
+ const created = native.binding.createCopySymlink(native.fd, temporaryName, input.target, params.mode, input.preserveMetadata ? input.identity.atimeNs : undefined, input.preserveMetadata ? input.identity.mtimeNs : undefined);
84
+ identity = { dev: statIdentityFromNative(created.dev), ino: statIdentityFromNative(created.ino) };
85
+ if (created.errorCode)
86
+ throw Object.assign(new Error(created.errorMessage), { code: created.errorCode });
87
+ }
88
+ else {
89
+ fs.symlinkSync(input.target, temporaryPath);
90
+ identity = fs.lstatSync(temporaryPath, { bigint: true });
91
+ }
92
+ if (!native && input.preserveMetadata) {
93
+ assertStage();
94
+ fs.lutimesSync(temporaryPath, timestampSeconds(input.identity.atimeNs), timestampSeconds(input.identity.mtimeNs));
95
+ }
96
+ }
97
+ assertStage();
98
+ await input.verifySource();
99
+ params.assertBeforeMutation?.();
100
+ input.signal?.throwIfAborted();
101
+ assertStage();
102
+ if (process.platform === "win32") {
103
+ native.binding.publishCopyLink(native.fd, temporaryName, params.basename, identity.dev, identity.ino);
104
+ }
105
+ else if (native) {
106
+ const renameNoReplace = native.binding.renameNoReplace;
107
+ if (!renameNoReplace)
108
+ throw new FsSafeError("helper-unavailable", "native link publication is unavailable");
109
+ try {
110
+ renameNoReplace(native.fd, temporaryName, native.fd, params.basename);
111
+ }
112
+ catch (error) {
113
+ const unavailable = noReplaceUnavailable(error, "link copy publication", true);
114
+ if (!unavailable || isFsSafeNativeRequired() || process.platform !== "linux")
115
+ throw error;
116
+ // Linux link(2) links the symlink inode itself, never its target.
117
+ assertStage();
118
+ fs.linkSync(temporaryPath, destination);
119
+ }
120
+ }
121
+ else {
122
+ fs.linkSync(temporaryPath, destination);
123
+ }
124
+ params.onPublished?.(identity);
125
+ assertSyncDirectoryGuard(parent);
126
+ const final = inspectFileIdentitySync(() => fs.lstatSync(destination, { bigint: true }), identity);
127
+ if (!final.isSymbolicLink())
128
+ throw new FsSafeError("path-mismatch", "published copy link changed");
129
+ if (params.sync !== false)
130
+ await syncDirectoryBestEffort(parent.realPath);
131
+ await input.verifySource();
132
+ return identity;
133
+ }
134
+ catch (error) {
135
+ failure = { error };
136
+ throw error;
137
+ }
138
+ finally {
139
+ let cleanupFailure;
140
+ try {
141
+ if (identity) {
142
+ assertSyncDirectoryGuard(parent);
143
+ const remaining = fs.lstatSync(temporaryPath, { bigint: true, throwIfNoEntry: false });
144
+ if (remaining) {
145
+ inspectFileIdentitySync(() => remaining, identity);
146
+ if (!remaining.isSymbolicLink())
147
+ throw new FsSafeError("path-mismatch", "copy link cleanup identity changed");
148
+ if (process.platform === "win32")
149
+ native.binding.removeCopyLink(native.fd, temporaryName, identity.dev, identity.ino);
150
+ else
151
+ fs.unlinkSync(temporaryPath);
152
+ }
153
+ }
154
+ }
155
+ catch (error) {
156
+ cleanupFailure = { error };
157
+ }
158
+ try {
159
+ await sourceParent?.close();
160
+ }
161
+ catch (error) {
162
+ cleanupFailure = { error: cleanupFailure
163
+ ? createSuppressedError(error, cleanupFailure.error, "copy link cleanup and close failed") : error };
164
+ }
165
+ if (cleanupFailure)
166
+ throw failure
167
+ ? createSuppressedError(cleanupFailure.error, failure.error, "copy link operation and cleanup failed")
168
+ : cleanupFailure.error;
169
+ }
170
+ }
@@ -0,0 +1,9 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ export type CopyMetadata = Readonly<{
3
+ atimeNs: bigint;
4
+ mtimeNs: bigint;
5
+ restoreNative?: (fd: number) => void;
6
+ }>;
7
+ export declare function timestampSeconds(nanoseconds: bigint): string;
8
+ export declare function captureCopyMetadata(fd: number, stat: BigIntStats): CopyMetadata;
9
+ export declare function restoreCopyMetadata(fd: number, metadata: CopyMetadata | undefined): void;
@@ -0,0 +1,39 @@
1
+ import fs, {} from "node:fs";
2
+ import { FsSafeError } from "./errors.js";
3
+ import { getNativeBinding } from "./native.js";
4
+ import { isFsSafeNativeRequired } from "./native-config.js";
5
+ export function timestampSeconds(nanoseconds) {
6
+ // Numeric strings retain fractional and pre-epoch times in Node's utimes API.
7
+ return String(Number(nanoseconds / 1000000000n) + Number(nanoseconds % 1000000000n) / 1e9);
8
+ }
9
+ export function captureCopyMetadata(fd, stat) {
10
+ let restoreNative;
11
+ const native = getNativeBinding();
12
+ if (process.platform === "win32") {
13
+ if (native?.readCopyMetadata && native.restoreCopyMetadata) {
14
+ const snapshot = native.readCopyMetadata(fd);
15
+ const restore = native.restoreCopyMetadata.bind(native);
16
+ restoreNative = target => restore(target, snapshot);
17
+ }
18
+ else if (isFsSafeNativeRequired()) {
19
+ throw new FsSafeError("helper-unavailable", "native Windows copy metadata preservation is unavailable");
20
+ }
21
+ }
22
+ else if (native?.restoreCopyFileTimes) {
23
+ const restore = native.restoreCopyFileTimes.bind(native);
24
+ const { atimeNs, mtimeNs } = stat;
25
+ restoreNative = target => restore(target, atimeNs, mtimeNs);
26
+ }
27
+ else if (isFsSafeNativeRequired()) {
28
+ throw new FsSafeError("helper-unavailable", "native copy timestamp preservation is unavailable");
29
+ }
30
+ return Object.freeze({ atimeNs: stat.atimeNs, mtimeNs: stat.mtimeNs, restoreNative });
31
+ }
32
+ export function restoreCopyMetadata(fd, metadata) {
33
+ if (!metadata)
34
+ return;
35
+ if (metadata.restoreNative)
36
+ metadata.restoreNative(fd);
37
+ else
38
+ fs.futimesSync(fd, timestampSeconds(metadata.atimeNs), timestampSeconds(metadata.mtimeNs));
39
+ }