@openclaw/signal 2026.9.6 → 2026.9.7

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 (279) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/.setup/{accounts-BV6tDOTc.mjs → accounts-DMtj2E-4.mjs} +6 -35
  3. package/dist/.setup/api-DEB0Obj8.mjs +16 -0
  4. package/dist/.setup/{api-CGh4eWR8.mjs → api-DmCb6e7E.mjs} +10 -10
  5. package/dist/.setup/{approval-auth-BBI9BTRm.mjs → approval-auth-CszS7gwf.mjs} +7 -13
  6. package/dist/.setup/{approval-handler.runtime-C3vN5OWd.mjs → approval-handler.runtime-D9ofZcZW.mjs} +10 -29
  7. package/dist/.setup/{approval-reactions-DDQvPP_z.mjs → approval-reactions-BKkjIAoi.mjs} +25 -36
  8. package/dist/.setup/{channel-DhzTcFsK.mjs → channel-DrUxv5cf.mjs} +567 -136
  9. package/dist/.setup/{client-BseAt7F7.mjs → client-AXcDteBZ.mjs} +63 -152
  10. package/dist/.setup/client-adapter-c_O1ouxZ.mjs +30 -0
  11. package/dist/.setup/{config-schema-aQwwMGUj.mjs → config-schema-DvxZ18SD.mjs} +3 -13
  12. package/dist/.setup/{doctor-contract-api-Rbq_sRTI.mjs → doctor-contract-api-Db9V9gsY.mjs} +212 -91
  13. package/dist/.setup/{identity-YXPmgFMu.mjs → identity-B3JyPqGO.mjs} +2 -6
  14. package/dist/.setup/{install-signal-cli-BDbzFcU2.mjs → install-signal-cli-ChvjzM6X.mjs} +0 -4
  15. package/dist/.setup/legacy-transport-QIpaMNST.mjs +18 -0
  16. package/dist/.setup/{monitor-Dnz6Ygz5.mjs → monitor-BfwQr78f.mjs} +95 -124
  17. package/dist/.setup/{probe-DXT2eLnw.mjs → probe-BYLzra6D.mjs} +9 -9
  18. package/dist/.setup/{reaction-runtime-api-BnQGHcrB.mjs → reaction-runtime-api-DDi5x2wC.mjs} +3 -17
  19. package/dist/.setup/{question-reactions-C8cSEjVf.mjs → reaction-targets-CEdyjtrC.mjs} +11 -8
  20. package/dist/.setup/{rpc-context-CwwADZ1b.mjs → rpc-context-D26RxFIm.mjs} +1 -1
  21. package/dist/.setup/runtime-8sxUF217.mjs +8 -0
  22. package/dist/.setup/{send-B78M_eFp.mjs → send-DEjrbpIC.mjs} +25 -55
  23. package/dist/.setup/{channel.runtime-CG79z_Vy.mjs → setup-surface-Drt-4t3R.mjs} +4 -6
  24. package/dist/.setup/transport-detection-BVUDE42e.mjs +25 -0
  25. package/dist/.setup/{transport-policy-5VSnj5ma.mjs → transport-policy-B6C_LogW.mjs} +19 -1
  26. package/dist/.setup/transport-probes.runtime-DwXEHnXr.mjs +2 -0
  27. package/dist/api.js +10 -10
  28. package/dist/channel-config-api.js +1 -1
  29. package/dist/channel-plugin-api.js +1 -1
  30. package/dist/contract-api.js +2 -2
  31. package/dist/doctor-contract-api.js +1 -1
  32. package/dist/reaction-runtime-api.js +1 -1
  33. package/dist/runtime-api.js +2 -19
  34. package/dist/setup-entry.js +1 -1
  35. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +102 -0
  36. package/node_modules/@openclaw/fs-safe/README.md +24 -6
  37. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +6 -1
  38. package/node_modules/@openclaw/fs-safe/dist/advanced.js +3 -1
  39. package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
  40. package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +1 -2
  41. package/node_modules/@openclaw/fs-safe/dist/archive-plan.d.ts +2 -7
  42. package/node_modules/@openclaw/fs-safe/dist/archive-read.js +9 -18
  43. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -2
  44. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +3 -3
  45. package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.js +4 -0
  46. package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.d.ts +11 -11
  47. package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.js +3 -35
  48. package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.d.ts +2 -2
  49. package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.js +2 -12
  50. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.d.ts +7 -3
  51. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +13 -10
  52. package/node_modules/@openclaw/fs-safe/dist/archive-zip-manifest.js +3 -1
  53. package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.d.ts +2 -3
  54. package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.js +16 -8
  55. package/node_modules/@openclaw/fs-safe/dist/archive.d.ts +6 -3
  56. package/node_modules/@openclaw/fs-safe/dist/archive.js +22 -19
  57. package/node_modules/@openclaw/fs-safe/dist/atomic.d.ts +1 -1
  58. package/node_modules/@openclaw/fs-safe/dist/copy-publication.d.ts +1 -3
  59. package/node_modules/@openclaw/fs-safe/dist/copy-publication.js +2 -6
  60. package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.d.ts +2 -0
  61. package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.js +71 -0
  62. package/node_modules/@openclaw/fs-safe/dist/deny-mutations.js +4 -3
  63. package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +0 -1
  64. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +0 -3
  65. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +20 -2
  66. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +77 -1
  67. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +5 -7
  68. package/node_modules/@openclaw/fs-safe/dist/effective-uid.js +1 -4
  69. package/node_modules/@openclaw/fs-safe/dist/errors.d.ts +3 -1
  70. package/node_modules/@openclaw/fs-safe/dist/errors.js +3 -2
  71. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +10 -2
  72. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-acquisition.js +1 -1
  73. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +5 -1
  74. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.d.ts +4 -11
  75. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.js +1 -4
  76. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.d.ts +3 -6
  77. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +1 -1
  78. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-options.d.ts +2 -10
  79. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.d.ts +2 -4
  80. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +2 -2
  81. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.d.ts +3 -7
  82. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +7 -10
  83. package/node_modules/@openclaw/fs-safe/dist/file-store-prune.js +7 -6
  84. package/node_modules/@openclaw/fs-safe/dist/file-store.d.ts +4 -7
  85. package/node_modules/@openclaw/fs-safe/dist/file-store.js +19 -21
  86. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +23 -31
  87. package/node_modules/@openclaw/fs-safe/dist/guest.js +21 -12
  88. package/node_modules/@openclaw/fs-safe/dist/json-document-store.d.ts +4 -9
  89. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.d.ts +1 -5
  90. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.d.ts +0 -1
  91. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.js +2 -7
  92. package/node_modules/@openclaw/fs-safe/dist/local-file-access.js +2 -5
  93. package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.d.ts +2 -5
  94. package/node_modules/@openclaw/fs-safe/dist/local-roots.d.ts +2 -7
  95. package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.js +4 -4
  96. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +5 -0
  97. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +51 -14
  98. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +8 -2
  99. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.d.ts +13 -0
  100. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +303 -0
  101. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.d.ts +1 -0
  102. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.js +54 -0
  103. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.d.ts +5 -0
  104. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +64 -0
  105. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
  106. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +3 -0
  107. package/node_modules/@openclaw/fs-safe/dist/path.js +17 -1
  108. package/node_modules/@openclaw/fs-safe/dist/permission-exec.d.ts +6 -0
  109. package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +3 -6
  110. package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +3 -1
  111. package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
  112. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +3 -6
  113. package/node_modules/@openclaw/fs-safe/dist/permissions.d.ts +3 -10
  114. package/node_modules/@openclaw/fs-safe/dist/permissions.js +0 -1
  115. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.d.ts +0 -1
  116. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +4 -2
  117. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-shared-route.d.ts +2 -8
  118. package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +1 -1
  119. package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +2 -3
  120. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +4 -0
  121. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -5
  122. package/node_modules/@openclaw/fs-safe/dist/regular-file.js +3 -3
  123. package/node_modules/@openclaw/fs-safe/dist/replace-directory.js +5 -17
  124. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +4 -0
  125. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +36 -0
  126. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +4 -10
  127. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +66 -38
  128. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +7 -11
  129. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +10 -2
  130. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +21 -0
  131. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +123 -0
  132. package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.d.ts +26 -0
  133. package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.js +47 -0
  134. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +2 -2
  135. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +16 -4
  136. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +55 -0
  137. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.js +1 -0
  138. package/node_modules/@openclaw/fs-safe/dist/replace-file.d.ts +3 -55
  139. package/node_modules/@openclaw/fs-safe/dist/replace-file.js +31 -11
  140. package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.d.ts +1 -0
  141. package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.js +1 -1
  142. package/node_modules/@openclaw/fs-safe/dist/retained-file-types.d.ts +50 -0
  143. package/node_modules/@openclaw/fs-safe/dist/retained-file-types.js +1 -0
  144. package/node_modules/@openclaw/fs-safe/dist/retained-file.d.ts +3 -0
  145. package/node_modules/@openclaw/fs-safe/dist/retained-file.js +121 -0
  146. package/node_modules/@openclaw/fs-safe/dist/root-context.js +3 -2
  147. package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +9 -0
  148. package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +28 -0
  149. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +9 -1
  150. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +88 -37
  151. package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.d.ts +1 -1
  152. package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +1 -6
  153. package/node_modules/@openclaw/fs-safe/dist/root-file.js +2 -5
  154. package/node_modules/@openclaw/fs-safe/dist/root-handle-context.d.ts +4 -0
  155. package/node_modules/@openclaw/fs-safe/dist/root-handle-context.js +12 -0
  156. package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +3 -3
  157. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +8 -5
  158. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +2 -7
  159. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +3 -3
  160. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +0 -1
  161. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -3
  162. package/node_modules/@openclaw/fs-safe/dist/root-path-observation.d.ts +4 -11
  163. package/node_modules/@openclaw/fs-safe/dist/root-path.js +7 -10
  164. package/node_modules/@openclaw/fs-safe/dist/root-paths.d.ts +2 -6
  165. package/node_modules/@openclaw/fs-safe/dist/root-remove-identity.d.ts +1 -3
  166. package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +19 -12
  167. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +56 -20
  168. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +1 -6
  169. package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.d.ts +2 -0
  170. package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.js +1 -1
  171. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +1 -0
  172. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +8 -2
  173. package/node_modules/@openclaw/fs-safe/dist/secret-file.d.ts +6 -2
  174. package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -16
  175. package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.js +1 -1
  176. package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +5 -14
  177. package/node_modules/@openclaw/fs-safe/dist/secure-file.js +3 -2
  178. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +2 -7
  179. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +4 -4
  180. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission-parser.d.ts +1 -2
  181. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.d.ts +2 -8
  182. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.d.ts +2 -7
  183. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +2 -2
  184. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +11 -12
  185. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-stale-admission.d.ts +1 -5
  186. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +9 -7
  187. package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.d.ts +39 -0
  188. package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.js +1 -0
  189. package/node_modules/@openclaw/fs-safe/dist/stat-observation.js +3 -10
  190. package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.d.ts +2 -0
  191. package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.js +6 -6
  192. package/node_modules/@openclaw/fs-safe/dist/symlink-parents.js +58 -7
  193. package/node_modules/@openclaw/fs-safe/dist/temp-target.js +8 -10
  194. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +28 -33
  195. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
  196. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +16 -17
  197. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +1 -2
  198. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +4 -9
  199. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +8 -0
  200. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +52 -0
  201. package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +5 -1
  202. package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +2 -1
  203. package/node_modules/@openclaw/fs-safe/dist/text-atomic.js +2 -0
  204. package/node_modules/@openclaw/fs-safe/dist/trash.js +27 -1
  205. package/node_modules/@openclaw/fs-safe/dist/walk.d.ts +2 -5
  206. package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +6 -0
  207. package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +88 -0
  208. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +9 -0
  209. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +93 -0
  210. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +36 -0
  211. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +73 -0
  212. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +28 -0
  213. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +300 -0
  214. package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +8 -0
  215. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +32 -0
  216. package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +60 -0
  217. package/node_modules/@openclaw/fs-safe/dist/watch-types.js +1 -0
  218. package/node_modules/@openclaw/fs-safe/dist/watch.d.ts +5 -0
  219. package/node_modules/@openclaw/fs-safe/dist/watch.js +530 -0
  220. package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +3 -7
  221. package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +0 -1
  222. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +6 -6
  223. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +78 -3
  224. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.d.ts +8 -0
  225. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +66 -15
  226. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +4 -0
  227. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +6 -2
  228. package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -2
  229. package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
  230. package/node_modules/@openclaw/fs-safe/docs/atomic.md +93 -3
  231. package/node_modules/@openclaw/fs-safe/docs/contributing.md +102 -4
  232. package/node_modules/@openclaw/fs-safe/docs/copy.md +2 -0
  233. package/node_modules/@openclaw/fs-safe/docs/durability.md +7 -0
  234. package/node_modules/@openclaw/fs-safe/docs/file-store.md +5 -0
  235. package/node_modules/@openclaw/fs-safe/docs/guest.md +7 -1
  236. package/node_modules/@openclaw/fs-safe/docs/index.md +1 -0
  237. package/node_modules/@openclaw/fs-safe/docs/install.md +30 -0
  238. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +20 -4
  239. package/node_modules/@openclaw/fs-safe/docs/native.md +63 -1
  240. package/node_modules/@openclaw/fs-safe/docs/permissions.md +66 -0
  241. package/node_modules/@openclaw/fs-safe/docs/public-api.md +30 -3
  242. package/node_modules/@openclaw/fs-safe/docs/retained-file.md +115 -0
  243. package/node_modules/@openclaw/fs-safe/docs/root.md +35 -9
  244. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +19 -0
  245. package/node_modules/@openclaw/fs-safe/docs/security-model.md +4 -1
  246. package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +5 -1
  247. package/node_modules/@openclaw/fs-safe/docs/staged-symlink.md +124 -0
  248. package/node_modules/@openclaw/fs-safe/docs/store.md +3 -1
  249. package/node_modules/@openclaw/fs-safe/docs/temp.md +24 -4
  250. package/node_modules/@openclaw/fs-safe/docs/testing.md +210 -0
  251. package/node_modules/@openclaw/fs-safe/docs/types.md +6 -0
  252. package/node_modules/@openclaw/fs-safe/docs/walk.md +34 -1
  253. package/node_modules/@openclaw/fs-safe/docs/watch.md +251 -0
  254. package/node_modules/@openclaw/fs-safe/docs/writing.md +21 -0
  255. package/node_modules/@openclaw/fs-safe/package.json +13 -9
  256. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  257. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  258. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  259. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  260. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  261. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  262. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  263. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  264. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  265. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  266. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  267. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  268. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  269. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  270. package/package.json +5 -5
  271. package/dist/.setup/api-B9-B1L7Q.mjs +0 -17
  272. package/dist/.setup/client-adapter-dvFa47YL.mjs +0 -63
  273. package/dist/.setup/rolldown-runtime-8H4AJuhK.mjs +0 -14
  274. package/dist/.setup/send.runtime-CLnCWvrM.mjs +0 -2
  275. package/dist/.setup/transport-detection-CoiRl7Gx.mjs +0 -576
  276. package/dist/.setup/transport-detection.runtime-Tlhuv3T_.mjs +0 -2
  277. package/dist/.setup/transport-probes.runtime-WprAyBbL.mjs +0 -2
  278. package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.d.ts +0 -20
  279. package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.js +0 -78
@@ -0,0 +1,25 @@
1
+ import { o as __exportAll } from "./doctor-contract-api-Db9V9gsY.mjs";
2
+ import { a as normalizeSignalTransportUrl } from "./transport-url-CAC0JcuI.mjs";
3
+ //#region extensions/signal/src/transport-detection.ts
4
+ var transport_detection_exports = /* @__PURE__ */ __exportAll({ detectSignalTransport: () => detectSignalTransport });
5
+ const DEFAULT_PROBE_TIMEOUT_MS = 1e4;
6
+ async function detectSignalTransport(params) {
7
+ const url = normalizeSignalTransportUrl(params.url);
8
+ const timeoutMs = params.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
9
+ const probes = params.probeNative && params.probeContainer ? void 0 : await import("./transport-probes.runtime-DwXEHnXr.mjs");
10
+ const probeNative = params.probeNative ?? probes?.nativeCheck;
11
+ const probeContainer = params.probeContainer ?? probes?.containerCheck;
12
+ if (!probeNative || !probeContainer) throw new Error("Signal transport probes are unavailable");
13
+ const [native, container] = await Promise.all([probeNative(url, timeoutMs).catch(() => ({ ok: false })), probeContainer(url, timeoutMs, params.account).catch(() => ({ ok: false }))]);
14
+ if (native.ok) return {
15
+ kind: "external-native",
16
+ url
17
+ };
18
+ if (container.ok) return {
19
+ kind: "container",
20
+ url
21
+ };
22
+ throw new Error(`Signal transport not reachable at ${url}`);
23
+ }
24
+ //#endregion
25
+ export { transport_detection_exports as n, detectSignalTransport as t };
@@ -17,6 +17,24 @@ function isSignalLocalEndpointHost(hostname) {
17
17
  function isValidSignalManagedNativePort(value) {
18
18
  return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 65535;
19
19
  }
20
+ function reserveSignalTransportPorts(transport, reservedPorts) {
21
+ if (transport?.kind === "managed-native") {
22
+ if (transport.socketPath !== void 0) return false;
23
+ const httpPort = transport.httpPort;
24
+ if (httpPort !== void 0) reservedPorts.add(httpPort);
25
+ if (transport.url && !isSignalManagedNativeConnectionUrlForBind(transport)) {
26
+ const localConnectionPort = resolveLocalSignalTransportPort(transport.url);
27
+ if (localConnectionPort !== void 0) reservedPorts.add(localConnectionPort);
28
+ }
29
+ return httpPort === void 0;
30
+ }
31
+ if (transport?.kind === "external-native" || transport?.kind === "container") {
32
+ const localPort = resolveLocalSignalTransportPort(transport.url);
33
+ if (localPort !== void 0) reservedPorts.add(localPort);
34
+ return false;
35
+ }
36
+ return true;
37
+ }
20
38
  function allocateSignalManagedNativePort(params) {
21
39
  if (params.preferredPort !== void 0) {
22
40
  if (!isValidSignalManagedNativePort(params.preferredPort)) throw new Error("Signal managed native port must be an integer between 1 and 65535.");
@@ -65,4 +83,4 @@ function assignSignalManagedNativePort(transport, httpPort) {
65
83
  };
66
84
  }
67
85
  //#endregion
68
- export { isSignalManagedNativeConnectionUrlForBind as a, assignSignalManagedNativePort as i, DEFAULT_SIGNAL_MANAGED_NATIVE_PORT as n, isValidSignalManagedNativePort as o, allocateSignalManagedNativePort as r, resolveLocalSignalTransportPort as s, DEFAULT_SIGNAL_MANAGED_NATIVE_HOST as t };
86
+ export { isSignalManagedNativeConnectionUrlForBind as a, resolveLocalSignalTransportPort as c, assignSignalManagedNativePort as i, DEFAULT_SIGNAL_MANAGED_NATIVE_PORT as n, isValidSignalManagedNativePort as o, allocateSignalManagedNativePort as r, reserveSignalTransportPorts as s, DEFAULT_SIGNAL_MANAGED_NATIVE_HOST as t };
@@ -0,0 +1,2 @@
1
+ import { n as signalCheck, s as containerCheck } from "./client-AXcDteBZ.mjs";
2
+ export { containerCheck, signalCheck as nativeCheck };
package/dist/api.js CHANGED
@@ -1,12 +1,12 @@
1
- import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./.setup/accounts-BV6tDOTc.mjs";
1
+ import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./.setup/accounts-DMtj2E-4.mjs";
2
2
  import { n as normalizeSignalMessagingTarget, t as looksLikeSignalTargetId } from "./.setup/normalize-l_b99hap.mjs";
3
- import { a as normalizeSignalAllowRecipient, c as resolveSignalSender, i as isSignalSenderAllowed, l as looksLikeUuid, n as formatSignalSenderDisplay, o as resolveSignalPeerId, r as formatSignalSenderId, s as resolveSignalRecipient, t as formatSignalPairingIdLine } from "./.setup/identity-YXPmgFMu.mjs";
4
- import { c as signalMessageActions, d as markdownToSignalText, f as markdownToSignalTextChunks, l as resolveSignalReactionLevel, t as detectSignalTransport } from "./.setup/transport-detection-CoiRl7Gx.mjs";
5
- import { n as sendReactionSignal, t as removeReactionSignal } from "./.setup/reaction-runtime-api-BnQGHcrB.mjs";
6
- import { a as normalizeSignalAccountInput, d as probeSignalTransport, f as writeSignalAccountTransport, p as resolveSignalOutboundTarget, t as signalPlugin, u as prepareSignalManagedNativeTransport } from "./.setup/channel-DhzTcFsK.mjs";
7
- import { t as signalSetupPlugin } from "./.setup/api-B9-B1L7Q.mjs";
8
- import { r as installSignalCli } from "./.setup/install-signal-cli-BDbzFcU2.mjs";
9
- import { t as monitorSignalProvider } from "./.setup/monitor-Dnz6Ygz5.mjs";
10
- import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./.setup/send-B78M_eFp.mjs";
11
- import { t as probeSignal } from "./.setup/probe-DXT2eLnw.mjs";
3
+ import { a as normalizeSignalAllowRecipient, c as resolveSignalSender, i as isSignalSenderAllowed, l as looksLikeUuid, n as formatSignalSenderDisplay, o as resolveSignalPeerId, r as formatSignalSenderId, s as resolveSignalRecipient, t as formatSignalPairingIdLine } from "./.setup/identity-B3JyPqGO.mjs";
4
+ import { _ as resolveSignalReactionLevel, a as normalizeSignalAccountInput, b as markdownToSignalTextChunks, d as probeSignalTransport, f as writeSignalAccountTransport, g as signalMessageActions, h as resolveSignalOutboundTarget, t as signalPlugin, u as prepareSignalManagedNativeTransport, y as markdownToSignalText } from "./.setup/channel-DrUxv5cf.mjs";
5
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./.setup/reaction-runtime-api-DDi5x2wC.mjs";
6
+ import { t as detectSignalTransport } from "./.setup/transport-detection-BVUDE42e.mjs";
7
+ import { t as signalSetupPlugin } from "./.setup/api-DEB0Obj8.mjs";
8
+ import { r as installSignalCli } from "./.setup/install-signal-cli-ChvjzM6X.mjs";
9
+ import { t as monitorSignalProvider } from "./.setup/monitor-BfwQr78f.mjs";
10
+ import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./.setup/send-DEjrbpIC.mjs";
11
+ import { t as probeSignal } from "./.setup/probe-BYLzra6D.mjs";
12
12
  export { detectSignalTransport, formatSignalPairingIdLine, formatSignalSenderDisplay, formatSignalSenderId, installSignalCli, isSignalSenderAllowed, listEnabledSignalAccounts, listSignalAccountIds, looksLikeSignalTargetId, looksLikeUuid, markdownToSignalText, markdownToSignalTextChunks, monitorSignalProvider, normalizeSignalAccountInput, normalizeSignalAllowRecipient, normalizeSignalMessagingTarget, prepareSignalManagedNativeTransport, probeSignal, probeSignalTransport, removeReactionSignal, resolveDefaultSignalAccountId, resolveSignalAccount, resolveSignalOutboundTarget, resolveSignalPeerId, resolveSignalReactionLevel, resolveSignalRecipient, resolveSignalSender, sendMessageSignal, sendReactionSignal, sendReadReceiptSignal, sendTypingSignal, signalMessageActions, signalPlugin, signalSetupPlugin, writeSignalAccountTransport };
@@ -1,2 +1,2 @@
1
- import { t as SignalChannelConfigSchema } from "./.setup/config-schema-aQwwMGUj.mjs";
1
+ import { t as SignalChannelConfigSchema } from "./.setup/config-schema-DvxZ18SD.mjs";
2
2
  export { SignalChannelConfigSchema };
@@ -1,2 +1,2 @@
1
- import { t as signalPlugin } from "./.setup/channel-DhzTcFsK.mjs";
1
+ import { t as signalPlugin } from "./.setup/channel-DrUxv5cf.mjs";
2
2
  export { signalPlugin };
@@ -1,4 +1,4 @@
1
1
  import { n as normalizeSignalMessagingTarget, r as normalizeSignalReactionRecipient, t as looksLikeSignalTargetId } from "./.setup/normalize-l_b99hap.mjs";
2
- import { i as isSignalSenderAllowed } from "./.setup/identity-YXPmgFMu.mjs";
3
- import { a as looksLikeArchive, i as installSignalCliFromRelease, n as extractSignalCliArchive, o as pickAsset, r as installSignalCli, t as downloadToFile } from "./.setup/install-signal-cli-BDbzFcU2.mjs";
2
+ import { i as isSignalSenderAllowed } from "./.setup/identity-B3JyPqGO.mjs";
3
+ import { a as looksLikeArchive, i as installSignalCliFromRelease, n as extractSignalCliArchive, o as pickAsset, r as installSignalCli, t as downloadToFile } from "./.setup/install-signal-cli-ChvjzM6X.mjs";
4
4
  export { downloadToFile, extractSignalCliArchive, installSignalCli, installSignalCliFromRelease, isSignalSenderAllowed, looksLikeArchive, looksLikeSignalTargetId, normalizeSignalMessagingTarget, normalizeSignalReactionRecipient, pickAsset };
@@ -1,2 +1,2 @@
1
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./.setup/doctor-contract-api-Rbq_sRTI.mjs";
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./.setup/doctor-contract-api-Db9V9gsY.mjs";
2
2
  export { legacyConfigRules, normalizeCompatibilityConfig };
@@ -1,2 +1,2 @@
1
- import { n as sendReactionSignal, t as removeReactionSignal } from "./.setup/reaction-runtime-api-BnQGHcrB.mjs";
1
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./.setup/reaction-runtime-api-DDi5x2wC.mjs";
2
2
  export { removeReactionSignal, sendReactionSignal };
@@ -1,19 +1,2 @@
1
- import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./.setup/accounts-BV6tDOTc.mjs";
2
- import { n as normalizeSignalMessagingTarget, t as looksLikeSignalTargetId } from "./.setup/normalize-l_b99hap.mjs";
3
- import { c as signalMessageActions, l as resolveSignalReactionLevel, o as setSignalRuntime } from "./.setup/transport-detection-CoiRl7Gx.mjs";
4
- import { n as sendReactionSignal, t as removeReactionSignal } from "./.setup/reaction-runtime-api-BnQGHcrB.mjs";
5
- import { n as SignalConfigSchema } from "./.setup/config-schema-aQwwMGUj.mjs";
6
- import { r as installSignalCli } from "./.setup/install-signal-cli-BDbzFcU2.mjs";
7
- import { t as monitorSignalProvider } from "./.setup/monitor-Dnz6Ygz5.mjs";
8
- import { t as sendMessageSignal } from "./.setup/send-B78M_eFp.mjs";
9
- import { t as probeSignal } from "./.setup/probe-DXT2eLnw.mjs";
10
- import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/account-helpers";
11
- import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
12
- import { buildBaseAccountStatusSnapshot, buildBaseChannelStatusSummary, collectStatusIssuesFromLastError, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
13
- import { normalizeE164 } from "openclaw/plugin-sdk/text-utility-runtime";
14
- import { DEFAULT_ACCOUNT_ID, applyAccountNameToChannelSection, deleteAccountFromConfigSection, emptyPluginConfigSchema, formatPairingApproveHint, getChatChannelMeta, migrateBaseNameToDefaultAccount, normalizeAccountId, setAccountEnabledInConfigSection } from "openclaw/plugin-sdk/core";
15
- import { detectBinary, formatCliCommand, formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
16
- import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
17
- import { chunkText } from "openclaw/plugin-sdk/reply-runtime";
18
- import { resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
19
- export { DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE, SignalConfigSchema, applyAccountNameToChannelSection, buildBaseAccountStatusSnapshot, buildBaseChannelStatusSummary, buildChannelConfigSchema, chunkText, collectStatusIssuesFromLastError, createDefaultChannelRuntimeState, deleteAccountFromConfigSection, detectBinary, emptyPluginConfigSchema, formatCliCommand, formatDocsLink, formatPairingApproveHint, getChatChannelMeta, installSignalCli, listEnabledSignalAccounts, listSignalAccountIds, looksLikeSignalTargetId, migrateBaseNameToDefaultAccount, monitorSignalProvider, normalizeAccountId, normalizeE164, normalizeSignalMessagingTarget, probeSignal, removeReactionSignal, resolveAllowlistProviderRuntimeGroupPolicy, resolveChannelMediaMaxBytes, resolveDefaultGroupPolicy, resolveDefaultSignalAccountId, resolveSignalAccount, resolveSignalReactionLevel, sendMessageSignal, sendReactionSignal, setAccountEnabledInConfigSection, setSignalRuntime, signalMessageActions };
1
+ import { r as setSignalRuntime } from "./.setup/runtime-8sxUF217.mjs";
2
+ export { setSignalRuntime };
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  var setup_entry_default = defineBundledChannelSetupEntry({
5
5
  importMetaUrl: import.meta.url,
6
6
  plugin: {
7
- specifier: fileURLToPath(new URL(".setup/api-CGh4eWR8.mjs", import.meta.url).href),
7
+ specifier: fileURLToPath(new URL(".setup/api-DmCb6e7E.mjs", import.meta.url).href),
8
8
  exportName: "signalSetupPlugin"
9
9
  }
10
10
  });
@@ -2,6 +2,108 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.21.1 - 2026-09-27
6
+
7
+ ### Fixes
8
+
9
+ - **Linux without `openat2`:** native opens now follow in-root relative symlinks exactly as `RESOLVE_BENEATH` does, rejecting absolute links, `..` escapes, procfs and `nosymfollow` links, and chains longer than 40. Denial and symlink policies report the same errors as the `openat2` path. Links inside sticky world-writable directories are refused. ([#717](https://github.com/openclaw/fs-safe/pull/717))
10
+ - **Deny policies on case-insensitive filesystems:** `denyMutations` now denies creating a not-yet-existing case or Unicode-normalization alias of a denied path (for example `case.txt` when `Case.txt` is denied on APFS, NTFS or casefold ext4). Where a filesystem's sensitivity cannot be proven, Unicode-equivalent names are denied conservatively. ([#720](https://github.com/openclaw/fs-safe/pull/720))
11
+ - **Directory listings:** `list()`, `entries()` and `walk()` reject names that are not valid UTF-8 with `invalid-path` instead of decoding them lossily, which could collide with a real `U+FFFD` name and report another entry's metadata. ([#715](https://github.com/openclaw/fs-safe/pull/715))
12
+ - **ZIP admission:** entries that differ only by case or Unicode normalization are rejected during preflight and member reads, matching extraction's collision policy. ([#716](https://github.com/openclaw/fs-safe/pull/716))
13
+ - **Atomic restore:** when a metadata call fails with `EIO` after the destination was truncated, restore-original writes the original bytes back through the retained, identity-verified descriptor instead of leaving the file empty. ([#718](https://github.com/openclaw/fs-safe/pull/718))
14
+ - **Lock and prune cleanup:** cleanup decisions compare exact bigint file identities. Legacy numeric receipts with IDs above 2^53 are refused instead of risking removal of the wrong file. ([#719](https://github.com/openclaw/fs-safe/pull/719))
15
+ - **Watch overflow:** subscriptions no longer publish spurious whole-scope overflow invalidations for excluded or unselected paths or for slow reconcile passes. macOS streams use OS-level exclusion paths and anchor-scoped streams, and Windows uses a 1 MiB change buffer on local volumes. Genuine OS event drops still invalidate. ([#724](https://github.com/openclaw/fs-safe/pull/724))
16
+ - **Mutation authority:** reject synchronous and asynchronous generator callback results before mutation, so lazy authority checks cannot be skipped. ([#711](https://github.com/openclaw/fs-safe/pull/711))
17
+ - **Synchronous atomic writes:** reject deferred `beforeRename` hooks before publication and clean up the owned stage. ([#728](https://github.com/openclaw/fs-safe/pull/728))
18
+ - **Copy publication callbacks:** reject deferred generator observers in `Root.copyIn()` while preserving the completed destination and source bytes. ([#726](https://github.com/openclaw/fs-safe/pull/726))
19
+ - **Native move diagnostics:** no-clobber `Root.move()` keeps the original native loader error as its cause, including missing libraries or incompatible glibc versions. ([#709](https://github.com/openclaw/fs-safe/pull/709))
20
+
21
+ ### Features
22
+
23
+ - **Watch polling interval:** `pollIntervalMs` sets the scan interval whenever polling is the selected transport, including `mode: "auto"` falling back to polling, without changing the events reconcile interval. ([#723](https://github.com/openclaw/fs-safe/pull/723))
24
+
25
+ ### Performance
26
+
27
+ - **Linux directory cloning:** reuse the admitted filesystem type when creating clone sources and cloning trees, avoiding a duplicate filesystem probe and allocation per operation. ([#732](https://github.com/openclaw/fs-safe/pull/732))
28
+
29
+ ## 0.21.0 - 2026-09-26
30
+
31
+ ### Highlights
32
+
33
+ - **Guarded filesystem observation:** `@openclaw/fs-safe/watch` observes literal entry and tree scopes beneath an admitted Root and delivers bounded advisory invalidations; guarded scans stay authoritative. One shared Rust event thread per process uses inotify, FSEvents and ReadDirectoryChangesW on Linux, macOS and Windows, sleeps while idle, and falls back to portable polling under `mode: "auto"`. Subscriptions become ready after one baseline scan, stay available under sustained writes, fence stale generations on `setScopes()`, join native work on `close()`, and support `persistent: false` so one-shot Node commands can exit. Thanks @vincentkoc. ([#690](https://github.com/openclaw/fs-safe/pull/690), [#695](https://github.com/openclaw/fs-safe/pull/695), [#697](https://github.com/openclaw/fs-safe/pull/697), [#703](https://github.com/openclaw/fs-safe/pull/703), [#707](https://github.com/openclaw/fs-safe/pull/707))
34
+ - **Linux user namespaces:** private temp workspaces now work beneath unmapped ancestors, including systemd user services with `PrivateUsers=true`, while UID/GID mappings and ancestor permissions are still rechecked. ([#693](https://github.com/openclaw/fs-safe/pull/693))
35
+
36
+ ### Features
37
+
38
+ - **Revocable atomic writes:** atomic replacement can recheck caller authority before new effects and report retained destination identities for partial writes and publication, without treating receipts as permission to roll back. ([#694](https://github.com/openclaw/fs-safe/pull/694))
39
+ - **Root walking:** `symlinkPolicy: "include"` reports links without following their targets, preserving sorted traversal, entry budgets, and the existing skip/follow result types. Directory-to-symlink substitutions fail before descent. ([#692](https://github.com/openclaw/fs-safe/pull/692))
40
+ - **Windows file retirement:** the advanced surface can retire existing NTFS files through a retained handle, with exact producer identity, current authority, and separate disposition and settlement facts. There is no persistence guarantee. ([#706](https://github.com/openclaw/fs-safe/pull/706))
41
+
42
+ ### Performance
43
+
44
+ - **Temporary filename sanitization:** skip duplicate normalization after reserved-device suffixing while preserving filename admission and fallback rules. ([#689](https://github.com/openclaw/fs-safe/pull/689))
45
+
46
+ ### Compatibility
47
+
48
+ - Supplied temp workspace roots must be owned by the effective user and must not be group- or world-writable; use a private per-user root instead of shared `/tmp`. A one-time warning reports host ownership that cannot be verified. ([#693](https://github.com/openclaw/fs-safe/pull/693))
49
+ - On Windows, an events-mode watch holds one handle on each watched Root, so the Root's own ancestor directories cannot be renamed while it is open; renaming inside the Root is unaffected. Use `mode: "poll"` where that matters. ([#697](https://github.com/openclaw/fs-safe/pull/697))
50
+ - `watch()` requires an explicit `mode` (`"auto"`, `"events"` or `"poll"`); `"events"` fails readiness with `helper-unavailable` when no native backend is available, and Bun currently selects polling under `"auto"`. ([#690](https://github.com/openclaw/fs-safe/pull/690))
51
+
52
+ ## 0.20.0 - 2026-09-25
53
+
54
+ ### Highlights
55
+
56
+ - **Older Linux systems:** when `openat2` is absent (Linux before 5.6) or blocked by seccomp, native operations fall back to a guarded no-follow walk instead of failing with `ENOSYS`. Nested no-clobber moves keep working, and GNU x64/arm64 bindings now target glibc 2.28, so native operations load on RHEL 8-family systems. ([#686](https://github.com/openclaw/fs-safe/pull/686), [#685](https://github.com/openclaw/fs-safe/pull/685); fixes [#572](https://github.com/openclaw/fs-safe/issues/572), [#511](https://github.com/openclaw/fs-safe/issues/511), [#548](https://github.com/openclaw/fs-safe/issues/548))
57
+ - **Path hardening:** trash moves stay inside allowed roots, symlink-parent checks inspect raw segments before dotdot normalization, relative-escape checks recognize either Windows separator and nested escapes, and safe path segments reject Windows reserved device names. Thanks @SebTardif. ([#611](https://github.com/openclaw/fs-safe/pull/611), [#615](https://github.com/openclaw/fs-safe/pull/615), [#614](https://github.com/openclaw/fs-safe/pull/614), [#612](https://github.com/openclaw/fs-safe/pull/612))
58
+ - **ZIP admission per operation:** portable ZIP reads and extraction bind entry selection to the admitted archive, name, and decoder object, and verify payloads against the admitted CRC and size. Substituted, renamed, or replaced decoder entries are rejected. ([#660](https://github.com/openclaw/fs-safe/pull/660))
59
+
60
+ ### Features
61
+
62
+ - **Retained symlink publication:** `retainSymlinkInDirectory()` on the advanced surface holds an explicitly identified POSIX symlink through exact-slot no-replace publication and explicit recovery, preserving observed foreign replacements and uncertain outcomes. ([#682](https://github.com/openclaw/fs-safe/pull/682))
63
+ - **Batched Windows ACL facts:** `readOwnerAndDaclBatch()` inspects ordered paths in one isolated native worker or one PowerShell process, with a configurable whole-batch timeout and bounded output. Results are bounded during collection; oversized batches reject with `too-large` before later paths are queried. ([#669](https://github.com/openclaw/fs-safe/pull/669), [#680](https://github.com/openclaw/fs-safe/pull/680))
64
+ - **Atomic text options:** `writeTextAtomic()` forwards the existing `beforeRename` hook and `tempPrefix` option to atomic replacement, keeping its validation and identity checks. ([#580](https://github.com/openclaw/fs-safe/pull/580))
65
+
66
+ ### Fixes
67
+
68
+ - **Lock exit cleanup:** leave raw sidecars in place when Windows reports an unknown device or inode, while keeping token-owned cleanup working when the descriptor and path identities legitimately differ (for example on VirtioFS). Thanks @SebTardif. ([#617](https://github.com/openclaw/fs-safe/pull/617))
69
+ - **Retained symlink errors:** preserve uncertain publication outcomes and cached cleanup/recovery failures when inspecting thrown error metadata fails, retaining the original cause without retrying mutations, callbacks, or descriptor closes. ([#684](https://github.com/openclaw/fs-safe/pull/684))
70
+ - **Create collision cleanup:** atomic and streamed creates remove their private stage when the JavaScript fallback observes a competing destination before publication; failures after a link attempt retain their existing recovery evidence. ([#683](https://github.com/openclaw/fs-safe/pull/683))
71
+
72
+ ### Compatibility
73
+
74
+ - Safe path segments now reject Windows reserved device names (`CON`, `NUL`, `COM1`, `CON.json`, …) on every platform, because segments are portable identifiers. Temporary filename sanitization still suffixes them (`CON.txt` becomes `CON_.txt`). ([#612](https://github.com/openclaw/fs-safe/pull/612))
75
+ - `assertNoSymlinkParents()` and guarded appends with `rejectSymlinkParents` reject raw spellings whose dotdot segments would cancel a symlink or re-enter the root after leaving it. ([#615](https://github.com/openclaw/fs-safe/pull/615))
76
+ - Without `openat2`, beneath opens report `best-effort` containment instead of `kernel-atomic`, anonymous `O_TMPFILE` opens report `ENOTSUP`, and bounded tree cleanup fails closed with `helper-unavailable` because it requires `RESOLVE_NO_XDEV`. ([#686](https://github.com/openclaw/fs-safe/pull/686))
77
+ - The Linux GNU binding floor is glibc 2.28 for both x64 and arm64 (arm64 was previously built against 2.17), matching Node 22's own Linux baseline. ([#685](https://github.com/openclaw/fs-safe/pull/685))
78
+
79
+ ## 0.19.0 - 2026-09-24
80
+
81
+ ### Highlights
82
+
83
+ - **Strict secret-file durability:** `createSecretFileAtomic()` accepts `durable: "file"` to require every file flush to succeed, including on `EPERM`; parent-directory synchronization remains best effort. ([#644](https://github.com/openclaw/fs-safe/pull/644))
84
+ - **Literal `~` names:** keep FileStore keys, absolute Root reads, discovered walk entries, and ZIP/TAR entries literal instead of treating them as home-directory shorthand. Reads, writes, removal, pruning, extraction, and durable publication select the intended entry. ([#633](https://github.com/openclaw/fs-safe/pull/633))
85
+ - **Native Unix descriptor safety:** reject negative descriptors in low-level root, query, hash, copy, clone, staging, and cleanup calls without Rust panics or working-directory operations. Modern macOS beneath opens report `EBADF` for negative roots instead of `EIO`. ([#640](https://github.com/openclaw/fs-safe/pull/640), [#643](https://github.com/openclaw/fs-safe/pull/643), [#646](https://github.com/openclaw/fs-safe/pull/646))
86
+
87
+ ### Fixes
88
+
89
+ - **Guest directory permissions:** cross-device moves preserve mode `000`, including nested directories, without changing other modes' umask behavior. Restore top-level permissions through the retained directory descriptor and preserve published entries on failure. Thanks @SebTardif. ([#616](https://github.com/openclaw/fs-safe/pull/616))
90
+ - **ReFS clone cleanup:** remove ordinary partial output when Windows rejects the ignore-readonly deletion flag. Keep readonly attributes intact and include cleanup failures in the original clone error; readonly files or other processes' open handles can still leave output behind. ([#648](https://github.com/openclaw/fs-safe/pull/648))
91
+
92
+ ### Compatibility and documentation
93
+
94
+ - Secret creation's default and boolean durability options retain their behavior, and `writeSecretFileAtomic()` remains boolean-only. Strict file synchronization preserves existing publication and cleanup semantics: a failure after publication can leave a complete file present. ([#644](https://github.com/openclaw/fs-safe/pull/644))
95
+ - `Root.walk("~")` and `Root.walk("~/dir")` expand home shorthand when iteration starts and return admitted canonical paths relative to the Root. Use `./~/dir` for a literal tilde directory, and prefix returned entry paths with `./` when passing them to another Root method. Relative Root `~/name` inputs still expand home; FileStore keys do not. ([#633](https://github.com/openclaw/fs-safe/pull/633))
96
+ - Guest moves still require OS permission to read mode-000 source directories; they do not widen source permissions. If permission restoration fails after publication, the source and published copy remain for caller reconciliation. ([#616](https://github.com/openclaw/fs-safe/pull/616))
97
+ - Clarify that `Root.append()` and `openWritable()` creation modes remain subject to the process umask and do not chmod existing files. This documents existing behavior. ([#636](https://github.com/openclaw/fs-safe/pull/636))
98
+
99
+ ## 0.18.2 - 2026-09-22
100
+
101
+ ### Fixes
102
+
103
+ - **Lock errors:** preserve Root stale-removal rejections and `null` or `undefined` retry failures instead of masking them with timeouts or `TypeError`s. ([#624](https://github.com/openclaw/fs-safe/pull/624))
104
+ - **Secret-file reads:** report caught `null` or `undefined` inspection and read failures as structured errors instead of internal `TypeError`s. ([#623](https://github.com/openclaw/fs-safe/pull/623))
105
+ - **Secret-file creation:** preserve `null` or `undefined` thrown by parameter getters instead of masking them with an internal `TypeError`. ([#625](https://github.com/openclaw/fs-safe/pull/625))
106
+
5
107
  ## 0.18.1 - 2026-09-22
6
108
 
7
109
  ### Fixes
@@ -10,7 +10,7 @@
10
10
 
11
11
  Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.
12
12
 
13
- 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 native opens are kernel-atomic; macOS, Windows, and JavaScript paths are best-effort.
13
+ 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
14
 
15
15
  ```ts
16
16
  import { root } from "@openclaw/fs-safe";
@@ -45,7 +45,7 @@ The same idea has landed in other languages. Go [added `os.Root` and `OpenInRoot
45
45
  | `path.resolve().startsWith()` | string check only | – | – | – | – |
46
46
  | [`write-file-atomic`](https://www.npmjs.com/package/write-file-atomic) | – | ✓ | – | – | – |
47
47
  | Go [`os.Root`](https://go.dev/blog/osroot) / Rust [`cap-std`](https://github.com/bytecodealliance/cap-std) | ✓ | platform | ✓ | ✓ | – |
48
- | **`@openclaw/fs-safe`** | **✓** | **✓** | **✓** | **Linux atomic; others best-effort** | **✓ (ZIP/TAR/gzip/zstd/bzip2)** |
48
+ | **`@openclaw/fs-safe`** | **✓** | **✓** | **✓** | **Linux openat2 atomic; others best-effort** | **✓ (ZIP/TAR/gzip/zstd/bzip2)** |
49
49
 
50
50
  ## Not a sandbox
51
51
 
@@ -84,7 +84,9 @@ reports the escape. Use `require` when hostile concurrent mutation is in scope.
84
84
 
85
85
  Equivalent env var: `FS_SAFE_NATIVE_MODE=auto|off|require`. The seven bindings
86
86
  ship as exact-version optional packages filtered by OS, CPU, and Linux libc, so
87
- a normal install receives only its matching binary. There are no postinstall
87
+ a normal install receives only its matching binary. Linux GNU x64/arm64 bindings
88
+ support [glibc 2.28 or newer](docs/install.md#supported-native-platforms), including
89
+ RHEL 8-family systems. There are no postinstall
88
90
  steps, runtime downloads, or consumer Rust builds. On a platform without a
89
91
  published binding, or when optional dependencies are omitted, `auto` silently retains lexical and canonical root
90
92
  checks, no-follow opens, guarded temp+rename writes, and post-write identity
@@ -94,8 +96,11 @@ tradeoff, and [native architecture](docs/native.md) for the platform mechanisms
94
96
  and policy ownership model.
95
97
 
96
98
  Open results report the mechanism's containment class as `"kernel-atomic"` or
97
- `"best-effort"`. Linux native `openBeneath()` is kernel-atomic; macOS, Windows,
98
- and guarded JavaScript results are best-effort. See the [security model](docs/security-model.md#containment-guarantees-by-platform) before using that fact in higher-level policy.
99
+ `"best-effort"`. Linux native `openBeneath()` is kernel-atomic when `openat2`
100
+ is available. Older kernels and syscall-filtered containers use a guarded
101
+ descriptor-relative walk reporting best-effort; nested no-clobber moves keep
102
+ atomic `renameat2(RENAME_NOREPLACE)`. macOS, Windows, and guarded JavaScript
103
+ results are best-effort. See [Linux compatibility](docs/native.md#linux-without-openat2). See the [security model](docs/security-model.md#containment-guarantees-by-platform) before using that fact in higher-level policy.
99
104
 
100
105
  ## Migrating from the Python helper
101
106
 
@@ -274,6 +279,7 @@ contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are
274
279
  | `@openclaw/fs-safe/secure-file` | fd-pinned absolute file reads with owner, mode, ACL, trusted-dir, size, and timeout checks |
275
280
  | `@openclaw/fs-safe/file-lock` | async/sync sidecar locks, root-bounded sidecars, ownership verification, and stale policy |
276
281
  | `@openclaw/fs-safe/permissions` | POSIX mode and Windows ACL inspection, raw owner/ACE facts, private-directory creation, and remediation helpers |
282
+ | [`@openclaw/fs-safe/watch`](docs/watch.md) | Guarded observation with native event hints, bounded scans, and joined close |
277
283
  | `@openclaw/fs-safe/walk` | budget-bounded directory walking with symlink policy, filters, and truncation accounting; not root-bounded |
278
284
  | `@openclaw/fs-safe/copy` | directory copying with `clone: "auto"`, `"always"`, or `"never"`; native APFS, Btrfs, ReFS, XFS, and ZFS cloning, portable byte copying, and clone metadata; see [directory copying](docs/copy.md) |
279
285
  | `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits, gzip/zstd/bzip2 support, and bounded entry reads |
@@ -342,6 +348,11 @@ original directory on Linux/macOS and requires native support for this operation
342
348
  It offers atomic replace/no-replace publication, not expected-inode replacement
343
349
  or a crash-durability promise; application checks and coordination remain yours.
344
350
 
351
+ For an already staged POSIX symlink, [`retainSymlinkInDirectory()`](docs/staged-symlink.md)
352
+ admits caller-captured identity and retains that exact inode through no-replace
353
+ publication or explicit recovery. Same-target foreign replacements are not adopted.
354
+ Staging, cooperative locking and crash recovery remain application responsibilities.
355
+
345
356
  `replaceFileAtomic()` writes a sibling temp file, applies its exact mode through the still-open descriptor, optionally fsyncs it, and renames it over the destination. It never follows the published destination path to set file permissions. Mode preservation inherits only rwx bits from an existing non-symlink regular file; special bits, ownership, ACLs, and extended attributes are not copied. Pinned-destination hardlink rejection, rename retry / copy fallback on `EPERM`, bounded original-content restoration after a torn fallback, parent-directory fsync, and a `beforeRename` hook for backup or observer flows are all opt-in. `movePathWithCopyFallback()` stages cross-device moves before commit and removes only the copied source entries, so concurrent source additions or replacements are preserved. Its optional synchronous `assertBeforeMutation` hook rechecks caller authority before renames and each source removal; `onDestinationPublished` reports an exact bigint destination identity before later checks or cleanup can fail. See [mutation authority and publication receipts](docs/atomic.md#mutation-authority-and-publication-receipts).
346
357
 
347
358
  ```ts
@@ -358,6 +369,12 @@ await replaceFileAtomic({
358
369
 
359
370
  `replaceFileAtomicSync()` covers the synchronous case with the same options shape. Both accept an injectable `fileSystem` for tests. Async adapters use `chmod()` on the `FileHandle` returned by their required `open()` operation; custom sync adapters using `mode` or `preserveExistingMode` provide the optional descriptor-bound `fchmodSync` operation.
360
371
 
372
+ Both variants accept `assertBeforeMutation` for revocable caller authority and
373
+ `onDestinationState` for observed removal, partial-write, and publication facts.
374
+ The observer receives exact bigint identities from retained descriptors, including
375
+ when later completion fails. These facts do not authorize rollback; the caller
376
+ still owns current authority and content checks. See [atomic write authority](docs/atomic.md#atomic-write-authority-and-destination-state).
377
+
361
378
  ## External outputs
362
379
 
363
380
  Use `writeExternalFileWithinRoot()` when a browser download, renderer, media
@@ -549,7 +566,8 @@ Check `scan.truncated` before treating the result as complete, and `scan.failedD
549
566
  `walkDirectory()` accepts asynchronous `include` and `descend` callbacks through `AsyncWalkDirectoryOptions`, so a marker lookup can prune a directory before its children are read. Decisions remain serial and retain the options object as their `this` receiver; `walkDirectorySync()` and its options remain synchronous. See [Directory walking](docs/walk.md) for callback timing, JavaScript result compatibility, and error handling.
550
567
 
551
568
  For caller-controlled paths, `Root.walk()` is the root-bounded async iterator.
552
- It supports entry/depth budgets, in-root symlink following, cancellation, and a
569
+ It supports entry/depth budgets, including links without following their targets,
570
+ in-root symlink following, cancellation, and a
553
571
  truncation marker (or typed error) when a budget is reached. Its `entryFilter`
554
572
  accepts `"include"`, `"skip"`, or `"skip-subtree"`, directly or through a Promise.
555
573
  After an awaited decision resolves, the walk rechecks cancellation and the
@@ -8,6 +8,8 @@ export { resolvePathPrefixSync, type ResolvedPathPrefix } from "./path-prefix.js
8
8
  export { probePathSuffixAliasesSync, type ProbePathSuffixAliasesOptions } from "./path-suffix-aliases.js";
9
9
  export { readDirectoryIdentity, assertDirectoryIdentitySync, type DirectoryIdentity, } from "./directory-guard.js";
10
10
  export { stageFileInDirectory, } from "./native-staged-file.js";
11
+ export { retainSymlinkInDirectory } from "./native-staged-symlink.js";
12
+ export type { StagedSymlink, StagedSymlinkExpected, StagedSymlinkReceipt, PublishedSymlinkReceipt, StagedSymlinkPublication, StagedSymlinkRemoval, StagedSymlinkCleanupReceipt, StagedSymlinkFailureDetails, } from "./staged-symlink-types.js";
11
13
  export type { StagedFile, StagedFileReceipt, PublishedFileReceipt, StagedFilePublication, StagedFileCleanupReceipt, StagedFileFailureDetails, } from "./staged-file-types.js";
12
14
  export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
13
15
  export { readFileWindowFully, readFileWindowFullySync, type ReadFileWindowOptions, } from "./positional-read.js";
@@ -33,4 +35,7 @@ export { resolveHomeRelativePath } from "./home-dir.js";
33
35
  export { appendRegularFile, appendRegularFileSync, readRegularFile, readRegularFileSync, resolveRegularFileAppendFlags, statRegularFile, statRegularFileSync, type AppendRegularFileOptions, type RegularFileStatResult, } from "./regular-file.js";
34
36
  export { buildRandomTempFilePath, sanitizeTempFileName, type TempFile, tempFile, withTempFile, } from "./temp-target.js";
35
37
  export { writeSiblingTempFile, writeViaSiblingTempPath, type WriteSiblingTempFileOptions, type WriteSiblingTempFileResult, } from "./sibling-temp.js";
36
- export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, type IcaclsResetCommandOptions, type PermissionCommandFailure, type PermissionExec, type WindowsAclEntry, type WindowsAclSummary, } from "./permissions.js";
38
+ export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, type IcaclsResetCommandOptions, type PermissionExec, type WindowsAclEntry, type WindowsAclSummary, } from "./permissions-windows.js";
39
+ export type { PermissionCommandFailure } from "./permission-exec.js";
40
+ export { retainFileInDirectory } from "./retained-file.js";
41
+ export type { RetainedFile, RetainedFileAdmission, RetainedFileExpected, RetainedFileIssue, RetainedFileReceipt, RetainedFileResult, RetainFileInDirectoryOptions, } from "./retained-file-types.js";
@@ -11,6 +11,7 @@ export { resolvePathPrefixSync } from "./path-prefix.js";
11
11
  export { probePathSuffixAliasesSync } from "./path-suffix-aliases.js";
12
12
  export { readDirectoryIdentity, assertDirectoryIdentitySync, } from "./directory-guard.js";
13
13
  export { stageFileInDirectory, } from "./native-staged-file.js";
14
+ export { retainSymlinkInDirectory } from "./native-staged-symlink.js";
14
15
  export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
15
16
  export { readFileWindowFully, readFileWindowFullySync, } from "./positional-read.js";
16
17
  export { writeFileWindowFully } from "./write-file-handle.js";
@@ -35,4 +36,5 @@ export { resolveHomeRelativePath } from "./home-dir.js";
35
36
  export { appendRegularFile, appendRegularFileSync, readRegularFile, readRegularFileSync, resolveRegularFileAppendFlags, statRegularFile, statRegularFileSync, } from "./regular-file.js";
36
37
  export { buildRandomTempFilePath, sanitizeTempFileName, tempFile, withTempFile, } from "./temp-target.js";
37
38
  export { writeSiblingTempFile, writeViaSiblingTempPath, } from "./sibling-temp.js";
38
- export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions.js";
39
+ export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
40
+ export { retainFileInDirectory } from "./retained-file.js";
@@ -78,7 +78,7 @@ export async function finalizeArchivePublication(params) {
78
78
  const env_1 = { stack: [], error: void 0, hasError: false };
79
79
  try {
80
80
  await assertGuards(file.guards);
81
- const opened = __addDisposableResource(env_1, await params.targetRoot.open(file.relativePath, { hardlinks: "reject", symlinks: "reject" })
81
+ const opened = __addDisposableResource(env_1, await params.targetRoot.open(`./${file.relativePath}`, { hardlinks: "reject", symlinks: "reject" })
82
82
  .catch((error) => {
83
83
  if (error instanceof FsSafeError && (error.code === "hardlink" || error.code === "path-alias")) {
84
84
  throw createArchiveSymlinkTraversalError(file.relativePath);
@@ -3,7 +3,6 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { ownExtractionDestinationMutation } from "./archive-deadline.js";
5
5
  import { assertDirectoryIdentityGuard, assertResolvedInsideDestination, createDirectoryIdentityGuard, createArchiveSymlinkTraversalError, preparePrivateArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
6
- import {} from "./directory-mode-owner.js";
7
6
  import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
8
7
  import { assertSyncDirectoryGuard, inspectDirectoryIdentitySync } from "./directory-guard.js";
9
8
  import { FsSafeError } from "./errors.js";
@@ -251,7 +250,7 @@ async function mergeTree(params, publication, durable = true, entryUmask = 0) {
251
250
  }
252
251
  };
253
252
  }
254
- await targetRoot.copyIn(relPath, sourcePath, options);
253
+ await targetRoot.copyIn(`./${relPath}`, sourcePath, options);
255
254
  check();
256
255
  await assertGuards();
257
256
  await assertResolvedInsideDestination({
@@ -54,14 +54,9 @@ export type TarEntryInfo = {
54
54
  mode?: number;
55
55
  };
56
56
  export declare function createTarEntryPlanner(params: ArchivePlanOptions): (entry: TarEntryInfo) => ArchivePlanEntry | null;
57
- export declare function createTarEntryPreflightChecker(params: {
57
+ export declare function createTarEntryPreflightChecker(params: Omit<ArchivePlanOptions & {
58
58
  rootDir: string;
59
- stripComponents?: number;
60
- limits?: ArchiveExtractLimits;
61
- escapeLabel?: string;
62
- entryFilter?: ArchiveEntryFilter;
63
- onFiltered?: ArchiveFilteredEntryPolicy;
64
- }): (entry: TarEntryInfo) => boolean;
59
+ }, "entryModes">): (entry: TarEntryInfo) => boolean;
65
60
  export type ArchiveLogger = {
66
61
  info?: (message: string) => void;
67
62
  warn?: (message: string) => void;
@@ -11,9 +11,8 @@ import { resolveArchiveKind } from "./archive-kind.js";
11
11
  import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP, ArchiveLimitError, ARCHIVE_LIMIT_ERROR_CODE, } from "./archive-limits.js";
12
12
  import { isGzipBuffer } from "./archive-gzip-tail.js";
13
13
  import { inspectTar, replayTar } from "./archive-tar-stream.js";
14
- import { loadAdmittedZipArchive } from "./archive-zip-loader.js";
14
+ import { loadAdmittedZipArchive, assertZipEntryBinding } from "./archive-zip-loader.js";
15
15
  import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
16
- import { isZipSymlinkEntry, zipEntryKind } from "./archive-zip-entry.js";
17
16
  import { FsSafeError } from "./errors.js";
18
17
  import { inspectFileIdentity } from "./strict-file-identity.js";
19
18
  import { resolveReadOpenFlags } from "./read-open-flags.js";
@@ -93,27 +92,19 @@ async function readArchiveInput(archivePath) {
93
92
  }
94
93
  }
95
94
  async function readZipEntry(buffer, entryPath, maxBytes, admitted) {
96
- const archive = await loadAdmittedZipArchive(buffer, admitted);
97
- let entry;
98
- // JSZip keys retain some aliases and may use Unicode Path metadata. Scan the
99
- // effective entries once, after raw ZIP admission has rejected collisions.
100
- for (const candidate of Object.values(archive.files)) {
101
- if (canonicalEntryPath(candidate.name) !== entryPath)
102
- continue;
103
- if (entry) {
104
- throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(entryPath)}`);
105
- }
106
- entry = candidate;
107
- }
108
- if (!entry || entry.dir) {
95
+ const { archive, entries } = await loadAdmittedZipArchive(buffer, admitted);
96
+ const record = entries.get(entryPath);
97
+ if (!record || record.entry.dir) {
109
98
  throw new Error(`archive entry not found: ${formatErrorDetail(entryPath)}`);
110
99
  }
111
- if (isZipSymlinkEntry(entry)) {
100
+ assertZipEntryBinding(archive, record, entryPath);
101
+ const { entry, kind } = record;
102
+ if (kind === "symlink") {
112
103
  throw new Error(`archive entry is a link: ${formatErrorDetail(entryPath)}`);
113
104
  }
114
- if (zipEntryKind(entry) !== "file")
105
+ if (kind !== "file")
115
106
  throw new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
116
- const integrity = createZipIntegrityTransform(entry);
107
+ const integrity = createZipIntegrityTransform(record);
117
108
  const stream = typeof entry.nodeStream === "function"
118
109
  ? entry.nodeStream()
119
110
  : Readable.from(await entry.async("nodebuffer"));
@@ -3,7 +3,6 @@ import { type AsyncDirectoryGuard } from "./directory-guard.js";
3
3
  import { type ExtractionDeadline } from "./archive-deadline.js";
4
4
  import { ArchiveSecurityError } from "./archive-errors.js";
5
5
  export type ArchiveDirectoryGuard = AsyncDirectoryGuard<BigIntStats>;
6
- export { ArchiveSecurityError, type ArchiveSecurityErrorCode } from "./archive-errors.js";
7
6
  export declare function createDirectoryIdentityGuard(dir: string): Promise<ArchiveDirectoryGuard>;
8
7
  export declare function assertDirectoryIdentityGuard(guard: ArchiveDirectoryGuard): Promise<void>;
9
8
  export declare function prepareArchiveDestinationGuard(destDir: string): Promise<ArchiveDirectoryGuard>;
@@ -29,5 +28,5 @@ export declare function withStagedArchiveDestination<T>(params: {
29
28
  stagingDirPrefix?: string;
30
29
  run: (stagingDir: string) => Promise<T>;
31
30
  }): Promise<T>;
32
- export { mergeExtractedTreeIntoDestination } from "./archive-merge.js";
33
31
  export declare function createArchiveSymlinkTraversalError(originalPath: string): ArchiveSecurityError;
32
+ export {};
@@ -21,7 +21,6 @@ const ARCHIVE_STAGING_MODE = 0o700;
21
21
  function checkExtractionDeadline(deadline) {
22
22
  deadline?.check();
23
23
  }
24
- export { ArchiveSecurityError } from "./archive-errors.js";
25
24
  function symlinkTraversalError(originalPath) {
26
25
  return new ArchiveSecurityError("destination-symlink-traversal", `${ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK}: ${formatErrorDetail(originalPath)}`);
27
26
  }
@@ -134,7 +133,9 @@ export async function assertResolvedInsideDestination(params) {
134
133
  }
135
134
  async function mkdirArchiveOutput(params) {
136
135
  try {
137
- await params.targetRoot.mkdir(params.relativePath);
136
+ const relativePath = params.relativePath;
137
+ // Archive names are literal; retain Root.mkdir's admission without home expansion.
138
+ await params.targetRoot.mkdir(relativePath === "~" || relativePath.startsWith("~/") ? `./${relativePath}` : relativePath);
138
139
  }
139
140
  catch (error) {
140
141
  if (error instanceof FsSafeError) {
@@ -302,7 +303,6 @@ export async function withStagedArchiveDestination(params) {
302
303
  }
303
304
  }
304
305
  }
305
- export { mergeExtractedTreeIntoDestination } from "./archive-merge.js";
306
306
  export function createArchiveSymlinkTraversalError(originalPath) {
307
307
  return symlinkTraversalError(originalPath);
308
308
  }
@@ -1,5 +1,6 @@
1
1
  import { assertArchiveEntryCountWithinLimit, } from "./archive-limits.js";
2
2
  import { admitZipNames, zipExtraFields, zipFormat, zipUInt64 } from "./archive-zip-names.js";
3
+ import { createArchiveOutputPathTracker } from "./archive-entry.js";
3
4
  function entryKind(attributes, directory) {
4
5
  const type = (attributes >>> 16) & 0o170000;
5
6
  // High-word symlinks remain links regardless of the creator or directory bits.
@@ -186,6 +187,7 @@ export function* scanZipDirectory(size, limits, onEntry) {
186
187
  const directory = yield* layout(size);
187
188
  assertArchiveEntryCountWithinLimit(directory.count, limits);
188
189
  const seen = new Set();
190
+ const trackKnownPath = createArchiveOutputPathTracker();
189
191
  const spans = [];
190
192
  let at = directory.start;
191
193
  let count = 0;
@@ -226,6 +228,8 @@ export function* scanZipDirectory(size, limits, onEntry) {
226
228
  const localNames = yield* read(localAt + 30, localNameLength + localExtraLength, directory.start);
227
229
  const localExtra = zipExtraFields(localNames.subarray(localNameLength));
228
230
  const admittedName = admitZipNames({ central: centralName, local: localNames.subarray(0, localNameLength), flags, centralExtra, localExtra, seen });
231
+ if (admittedName.path !== undefined)
232
+ trackKnownPath(admittedName.path, admittedName.path);
229
233
  const localValues = wideValues(local, localExtra, false);
230
234
  const crc = central.readUInt32LE(16);
231
235
  if (!(flags & 8) && (local.readUInt32LE(14) !== crc || localValues.compressed !== values.compressed || localValues.uncompressed !== values.uncompressed)) {
@@ -1,4 +1,4 @@
1
- import { type ArchiveEntryModePolicy, type ArchiveEntryKind } from "./archive-plan.js";
1
+ import type { ArchiveEntryKind } from "./archive-plan.js";
2
2
  import type { ZipDirectoryEntry } from "./archive-zip-directory.js";
3
3
  export type ZipEntry = {
4
4
  name: string;
@@ -12,13 +12,13 @@ export type ZipEntry = {
12
12
  nodeStream?: () => NodeJS.ReadableStream;
13
13
  async: (type: "nodebuffer") => Promise<Buffer>;
14
14
  };
15
- /** Internal: register only after the complete decoder/admission association. */
16
- export declare function registerAdmittedZipEntry(entry: ZipEntry, physical: ZipDirectoryEntry): void;
17
- export declare function zipEntryIntegrityMetadata(entry: ZipEntry): {
18
- crc32?: number;
19
- uncompressedSize?: number;
20
- } | undefined;
21
- export declare function isZipSymlinkEntry(entry: ZipEntry): boolean;
22
- export declare function zipEntryKind(entry: ZipEntry): ArchiveEntryKind;
23
- export declare function zipEntryMode(entry: ZipEntry, policy: ArchiveEntryModePolicy | undefined): number;
24
- export declare function zipEntryDeclaredSize(entry: ZipEntry): number;
15
+ export type AdmittedZipEntry = Readonly<{
16
+ entry: ZipEntry;
17
+ name: string;
18
+ kind: ArchiveEntryKind;
19
+ mode: number | undefined;
20
+ size: number;
21
+ crc32: number;
22
+ }>;
23
+ /** Internal: create only after the complete decoder/admission association. */
24
+ export declare function createAdmittedZipEntry(entry: ZipEntry, name: string, physical: ZipDirectoryEntry): AdmittedZipEntry;