@openclaw/feishu 2026.9.8 → 2026.10.1-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 (225) hide show
  1. package/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
  2. package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
  3. package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
  4. package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
  5. package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
  6. package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
  7. package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
  8. package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
  9. package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
  10. package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
  11. package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
  12. package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
  13. package/dist/api.js +234 -297
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/doctor-contract-api.js +2 -2
  16. package/dist/session-binding-contract-api.js +1 -1
  17. package/dist/setup-api.js +1 -1
  18. package/dist/setup-entry.js +1 -1
  19. package/dist/subagent-hooks-api.js +1 -1
  20. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
  21. package/node_modules/@openclaw/fs-safe/README.md +70 -379
  22. package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
  23. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
  24. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  25. package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
  26. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  27. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
  28. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
  29. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
  30. package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
  31. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
  32. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
  33. package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
  34. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
  35. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
  36. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
  37. package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
  38. package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
  39. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
  40. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
  41. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
  42. package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
  43. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
  44. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
  45. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
  46. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
  47. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
  48. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
  49. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
  50. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
  51. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
  52. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
  53. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
  54. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
  55. package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
  56. package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
  57. package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
  58. package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
  59. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
  60. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
  61. package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
  62. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
  63. package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
  64. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
  65. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
  66. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
  67. package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
  68. package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
  69. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
  70. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
  71. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
  72. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
  73. package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
  74. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
  75. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
  76. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
  77. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
  78. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
  79. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
  80. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
  81. package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
  82. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
  83. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
  84. package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
  85. package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
  86. package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
  87. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
  88. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
  89. package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
  90. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
  91. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
  92. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
  93. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
  94. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
  95. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
  96. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
  97. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
  98. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
  99. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
  100. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
  101. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
  102. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
  103. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
  104. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
  105. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
  106. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
  107. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
  108. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
  109. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
  110. package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
  111. package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
  112. package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
  113. package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
  114. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
  115. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
  116. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
  117. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
  118. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
  119. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
  120. package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
  121. package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
  122. package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
  123. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
  124. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
  125. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
  126. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
  127. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
  128. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
  129. package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
  130. package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
  131. package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
  132. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
  133. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
  134. package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
  135. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
  136. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
  137. package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
  139. package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
  140. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
  141. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
  142. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
  143. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
  144. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
  145. package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
  146. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
  147. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
  148. package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
  149. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
  150. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
  151. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
  152. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
  153. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
  154. package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
  155. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
  156. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
  157. package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
  158. package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
  159. package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
  160. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
  161. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
  162. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
  163. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
  164. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
  165. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
  166. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
  167. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
  168. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
  169. package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
  170. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
  171. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
  172. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
  173. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
  174. package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
  175. package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
  176. package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
  177. package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
  178. package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
  179. package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
  180. package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
  181. package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
  182. package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
  183. package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
  184. package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
  185. package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
  186. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
  187. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
  188. package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
  189. package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
  190. package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
  191. package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
  192. package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
  193. package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
  194. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
  195. package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
  196. package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
  197. package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
  198. package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
  199. package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
  200. package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
  201. package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
  202. package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
  203. package/node_modules/@openclaw/fs-safe/package.json +10 -10
  204. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  205. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  206. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  207. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  208. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  209. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  210. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  211. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  212. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  213. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  214. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  215. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  216. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  217. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  218. package/package.json +5 -5
  219. package/skills/feishu-wiki/SKILL.md +1 -1
  220. package/dist/.setup/accounts-wRqItHug.mjs +0 -206
  221. package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
  222. package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
  223. package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
  224. package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
  225. package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +0 -110
@@ -64,28 +64,50 @@ This does not promise delivery for a differently spelled alias that appears and
64
64
  disappears entirely between scans: without an observed identity, it cannot be
65
65
  admitted as the selected path. Observation is not a complete transient history.
66
66
  Raw event names remain private: detail comes from guarded scans, prior guarded
67
- snapshots, or explicitly configured targets. Unclassifiable hints inside selected,
68
- non-excluded territory lose detail. Hints for excluded or unselected paths are
67
+ snapshots, or explicitly configured targets. A non-target name absent from both
68
+ snapshots is ignored only when its parent directory has the same device/inode in
69
+ both guarded passes. Otherwise the unclassifiable selected hint loses detail.
70
+ Hints for excluded or unselected paths are
69
71
  ignored. Exclusion callbacks are synchronous; excluded directories are recorded
70
72
  without descent. Bounded exclusion records recognize late deletion hints.
71
- Genuine backend event loss and detail-budget exhaustion still invalidate every
72
- scope, including when an excluded subtree caused the underlying event pressure.
73
+ Pending hint pressure folds filenames into directory-level subtree hints, coarsening
74
+ toward the Root as needed. Unrelated folds are ignored after guarded alias admission;
75
+ relevant or uncertain folds trigger a full guarded pass and publish its snapshot
76
+ diff. The hint directory itself is never published without observation. Genuine
77
+ backend event loss and snapshot-diff budget exhaustion still invalidate every
78
+ scope, including when an excluded subtree caused genuine kernel loss.
79
+
80
+ On Linux and macOS, an undecodable child name triggers a structural hint for its
81
+ containing directory. If that directory's children are selected by a tree scope
82
+ with remaining depth, a guarded scan fails closed with `invalid-path` while the
83
+ name remains present. An undecodable sibling beside an ancestor or missing-scope
84
+ anchor is ignored: it cannot match a validated literal scope component. Raw or
85
+ lossily decoded names are never published or used for child I/O. Windows already
86
+ fails closed by reconciling after an undecodable UTF-16 notification.
87
+
88
+ Linux nameless self-events (including directory chmod, rename and deletion) retain
89
+ structural detail for the watched directory. Self-events for the Root itself,
90
+ kernel queue overflow and malformed transport buffers still lose detail.
73
91
 
74
92
  ## Transport and mode
75
93
 
76
94
  | Platform/runtime | `auto` | Event transport / limitation |
77
95
  | --- | --- | --- |
78
96
  | Node.js on Linux with addon | `events` | One shared Rust thread and inotify instance; a nonrecursive watch per distinct directory inode. |
79
- | Node.js on macOS with addon | `events` | One FSEvents stream per subscription on a shared serial dispatch queue. Pathname activity after a swap remains advisory. |
97
+ | Node.js on macOS with addon | `events` | Entry scopes use descriptor-bound kqueue watches; tree scopes share one FSEvents stream per subscription. Pathname activity after a swap remains advisory. |
80
98
  | Node.js on Windows with addon | `events` | One recursive ReadDirectoryChangesW Root handle per subscription on the shared IOCP hub; the open handle prevents ordinary renames of the Root's ancestors. |
81
99
  | Bun / other unsupported runtimes | `poll` | TSFN lifetime and shutdown have not been qualified; `events` rejects. |
82
100
  | Missing/disabled addon | `poll` | `events` rejects with `FsSafeError("helper-unavailable")`. |
83
101
 
84
102
  The shared hub sleeps until a filesystem event, command, or callback acknowledgement:
85
- Linux blocks on inotify plus eventfd, Windows on IOCP, and macOS on its command
86
- channel (FSEvents wakes it from the serial dispatch queue). There is no native
103
+ Linux blocks on inotify plus eventfd, Windows on IOCP, and macOS on kqueue with a
104
+ command wake (FSEvents wakes it from the serial dispatch queue). There is no native
87
105
  polling timer; the independent JS reconciliation interval remains authoritative.
88
106
 
107
+ Linux discards queued events for watches retired by fs-safe, including their
108
+ `IN_IGNORED` echoes, without invalidating unrelated subscriptions. A genuinely
109
+ unknown descriptor or kernel queue overflow still invalidates every subscription.
110
+
89
111
  `mode` is required. `poll` never starts or loads the watch hub; guarded scans
90
112
  may still use the existing addon. Existing `FS_SAFE_NATIVE_MODE=off` and
91
113
  `require` policies apply: `require` plus an unavailable event backend rejects
@@ -104,15 +126,29 @@ Nonblocking TSFN batches cannot block the hub on JavaScript, and per-owner
104
126
  pending detail and queued batches are bounded. The last removal stops and joins
105
127
  the native thread. No Worker threads, eval programs, or JS `fs.watch` are used.
106
128
 
107
- macOS uses FileEvents, NoDefer and WatchRoot with a 30 ms FSEvents latency.
108
- After guarded admission, streams use selected tree anchors and entry parents,
129
+ macOS entry scopes use nonrecursive `EVFILT_VNODE` watches on the admitted parent
130
+ and, when present, the entry itself. Parent activity requests a guarded scan
131
+ without supplying filenames. The entry descriptor covers content and attribute
132
+ changes and is replaced when a guarded scan admits a new identity. Missing paths
133
+ use their nearest admitted ancestor. Descriptors are opened without following
134
+ symlinks; a symlink entry uses `O_SYMLINK` to observe the link itself. Every
135
+ descriptor's device/inode must match the guarded observation. There are at most
136
+ two retained descriptors per entry scope (128 scopes maximum), counted in health
137
+ `directories`; descriptor exhaustion fails registration with `EMFILE`. Removal
138
+ closes these descriptors on the hub before returning. Deep unselected traffic
139
+ does not reach an entry-only subscription.
140
+
141
+ Tree scopes of every depth retain FileEvents, NoDefer and WatchRoot with a 30 ms
142
+ FSEvents latency. After guarded admission, streams use only selected tree anchors,
109
143
  falling back to the nearest admitted ancestor for missing paths. Nested anchors
110
144
  are deduplicated, with at most 128 paths. The eight shallowest non-overlapping
111
145
  excluded directories are also passed to `FSEventStreamSetExclusionPaths`.
112
146
  When the stream paths change, the old stream is stopped, invalidated and released
113
147
  on its dispatch queue, then its replacement starts before another guarded pass
114
148
  covers the handover. Native exclusions reduce traffic but cannot eliminate real
115
- FSEvents drops, including during recursive deletion.
149
+ FSEvents drops, including during recursive deletion. A shallow tree anchored above
150
+ a busy unselected subtree still receives recursive traffic; pending hints fold
151
+ under pressure, while genuine FSEvents drops can still overflow.
116
152
  Absolute hints are reduced lexically against the admitted canonical Root;
117
153
  outside paths never become detail. Dropped/wrapped streams, RootChanged and
118
154
  Unmount trigger guarded reconciliation. Pathname hints can reflect activity
@@ -157,6 +193,20 @@ handles and delivery queues, and closing one does not retire another's observati
157
193
 
158
194
  Periodic guarded reconciliation runs without needing an event. It catches
159
195
  missed events and works on filesystems where native hints are incomplete.
196
+ Hint validation, scope relevance, and spelling-alias admission share the internal
197
+ hint module. Bounded change merging preserves structural precedence and insertion
198
+ order; nameless children require remaining tree depth, while folded subtrees also
199
+ cover ancestors of selected scopes.
200
+ Detailed native batches first pass guarded scope and spelling-alias admission;
201
+ proven unrelated activity does not schedule a scan. Relevant entry hints refresh
202
+ the entry scopes, while tree hints refresh the affected directory and its identity
203
+ chain, retaining unchanged sibling subtrees. Namespace changes, coarse directory
204
+ hints, vanished or hard-linked leaves, changed topology, uncertain identities,
205
+ backend loss, and batches without filenames still receive a full guarded pass.
206
+ `reconcile()`, initial admission, and scope replacement always reconcile every scope.
207
+ The periodic timer is independent of event traffic, so frequent hints cannot defer
208
+ the full missed-event check. Repeated passes reuse bounded pathname strings, never
209
+ cached metadata or filesystem authority.
160
210
  When polling is selected, the interval is `pollIntervalMs`, then `intervalMs`,
161
211
  then 1000 ms, in that order. This applies to explicit `mode: "poll"`, `auto`
162
212
  selecting polling, and `auto` falling back after an unsupported event backend.
@@ -166,6 +216,11 @@ uses 25 ms polling when needed and retains the 30-second events reconciliation.
166
216
  Scans are metadata comparisons: content changes preserving all compared
167
217
  metadata may be missed in polling mode. No mode promises transactional
168
218
  snapshots, complete history, or hard real-time delivery.
219
+ On Node.js, directory name reads avoid a thread-pool round trip per entry. Scans yield to
220
+ the event loop between bounded groups of at most 32 names so cancellation and
221
+ other work can progress; every entry still receives the same identity checks
222
+ and entry-budget admission before its metadata is read.
223
+ Bun and Deno retain asynchronous name reads.
169
224
 
170
225
  `ready` resolves after the first complete guarded scan establishes the baseline,
171
226
  even while writes continue. Events mode installs each directory registration
@@ -186,9 +241,9 @@ transient descendant scan errors produce structural invalidations, preserving th
186
241
  Root identity checks. Events during a pass coalesce into one pending pass and
187
242
  retain bounded detail regardless of how long the scan takes. The 25 ms hint
188
243
  coalescing window does not impose a scan deadline. A full native callback queue
189
- retains its bounded pending batch for retry. Genuine backend loss, exhausted
190
- detail capacity, or an unclassifiable selected hint emits `overflow` without
191
- detail. Sustained writes cannot exhaust a pass budget or disable observation.
244
+ retains its bounded pending batch for retry. Pending native and JavaScript hint queues degrade to coarse subtree hints when
245
+ full. Genuine backend loss, exhausted snapshot-diff capacity, or an unclassifiable
246
+ selected hint emits `overflow` without detail. Sustained writes cannot exhaust a pass budget or disable observation.
192
247
 
193
248
  `reconcile()` resolves after a complete pass that **started after the call**. Calls
194
249
  waiting for the same future pass coalesce; an earlier in-flight pass cannot satisfy
@@ -202,7 +257,8 @@ including during startup or reconciliation. Invalidations still arrive while
202
257
  other work keeps the process alive. Persistent and non-persistent subscriptions
203
258
  have independent lifetimes; closing the last persistent one lets Node exit.
204
259
  Native environment cleanup retires any remaining event registrations and joins
205
- the hub at exit. `signal` triggers close;
260
+ the hub at exit. `signal` triggers close, including when a caller's abort
261
+ listener stops event propagation;
206
262
  await `close()` or `[Symbol.asyncDispose]()` to join owned work.
207
263
 
208
264
  `health()` returns `starting`, `ready`, `reconciling`, `unavailable`, or `closed`,
@@ -249,3 +305,29 @@ replay one with `--replay <failure.json>`. Event mode requires a working native
249
305
  binding and never silently falls back to polling. The small
250
306
  `test/watch-model.test.ts` corpus runs in ordinary CI; native-event cases also run
251
307
  when `FS_SAFE_TEST_WATCH_EVENTS=1`. Keep fixtures on normal `os.tmpdir()` storage.
308
+
309
+ The nightly watch-stress workflow includes 500 seeds per mode with the finite
310
+ state corpus on all five runners. Add `--transitions` to exercise that corpus
311
+ before each random sequence in a local run.
312
+ It varies `maxPendingPaths` from 2 through 8, creates missing tree descendants
313
+ beside concurrent sibling churn, uses filesystem-proven case and Unicode aliases,
314
+ and covers atomic-save names, excluded subtrees, Linux undecodable siblings,
315
+ directory chmod/rename/deletion, scope replacement, and subscription retirement.
316
+ A separate quiet subscription must remain unaffected. The checker records genuine
317
+ backend loss and rejects overflow without that loss or a selected diff exceeding
318
+ the configured budget. Each checkpoint permits at most four guarded passes and
319
+ refreshes the consumer cache only in response to invalidation.
320
+ Volumes without Unicode-normalization aliases also receive a distinct spelling
321
+ beside the missing target. On Linux a selected undecodable child must fail its
322
+ owner closed with `invalid-path` while the other subscription stays healthy.
323
+
324
+ ```sh
325
+ node scripts/watch-stress/model-runner.mjs --transitions --seeds 500 --mode both --output watch-transition-results.json
326
+ ```
327
+
328
+ For transport diagnosis, add `--native-only`: event-mode checkpoints then wait up
329
+ to 400 intervals of 25 ms without calling `reconcile()`. This stronger diagnostic
330
+ depends on native event delivery; it is separate from the periodic-reconciliation
331
+ guarantee and must not turn an unavailable transport into a passing run. Transition
332
+ failures retain their seed and mode in the report; replay them with `--seed N
333
+ --seeds 1 --mode events --transitions` and the same diagnostic options.
@@ -64,6 +64,35 @@ Failed-write cleanup compares exact parent and file identities, including large
64
64
  Windows file indexes. Replaced paths and paths whose ownership cannot be verified
65
65
  are preserved.
66
66
 
67
+ ### Windows link modes
68
+
69
+ With `mutationSymlinks` omitted, Windows buffered replacement `write()` and
70
+ `writeJson()` calls (`overwrite` omitted or `true`) currently differ by
71
+ implementation. The native pinned path rejects a final file
72
+ symlink with `path-alias`. The legacy JavaScript path can follow an unchanged
73
+ contained final link and replace its admitted target, preserving the link itself.
74
+ It reauthorizes the original link's target before staging and publication when
75
+ mutation policy is present, and retains its file/parent identity checks.
76
+ The legacy path is used by native mode `off`, by `auto` without a binding,
77
+ and by the explicit `renameIdentity: "verify-content-with-lock"` policy.
78
+
79
+ An omitted mutation policy also leaves parent-junction behavior implementation
80
+ dependent: native Windows parent admission refuses reparse traversal and can
81
+ report `invalid-path`, while the legacy writer can use the junction's admitted
82
+ contained target. This does not authorize an escaping target or bypass deny
83
+ policy. Set `mutationSymlinks: "reject"` explicitly for uniform link rejection
84
+ across these implementations, either in Root defaults or per call:
85
+
86
+ ```ts
87
+ import { root } from "@openclaw/fs-safe";
88
+
89
+ const files = await root("C:/workspace", { mutationSymlinks: "reject" });
90
+ await files.write("state.json", "{}");
91
+ ```
92
+
93
+ These are existing compatibility differences; omitted policy does not currently
94
+ provide uniform Windows link handling. See [write-side link policy](security-model.md#symlinks-write-side).
95
+
67
96
  ## Denying mutations
68
97
 
69
98
  All mutation verbs accept `denyMutations?: DenyMutationPolicy`, either as a root default or per-call option:
@@ -86,7 +115,10 @@ await fs.remove(".ssh/id_rsa"); // throws FsSafeError code "denied-path"
86
115
 
87
116
  ### `fs.write(rel, data, options?)`
88
117
 
89
- Overwrite or create. Always atomic.
118
+ Overwrite or create. Replacement is atomic; buffered `overwrite: false` can
119
+ expose incomplete content on the JavaScript fallback. Use
120
+ [`create()` with `atomic: true`](#atomic-buffered-creation) when create-only
121
+ publication must wait for complete content.
90
122
 
91
123
  ```ts
92
124
  await fs.write("state/last-run.json", JSON.stringify(run));
@@ -107,6 +139,11 @@ await fs.write("notes/today.txt", "hello\n", { encoding: "utf8" });
107
139
  | `overwrite` | `boolean` | `true`; `false` is create-only. |
108
140
  | `renameIdentity` | `RenameIdentityPolicy` | `"strict"`. |
109
141
 
142
+ `mkdir: false` requires existing parents and never creates a missing parent.
143
+ On POSIX, otherwise permitted relative in-root parent aliases remain available
144
+ to buffered and streamed writes and copies with native support enabled or
145
+ disabled. An explicit mutation symlink policy still applies.
146
+
110
147
  `write`, `create`, `writeJson`, `createJson`, `append`, and `copyIn` accept `durable`.
111
148
  Precedence is per-call option, then `Root.defaults.durable`, then `true`;
112
149
  an explicitly `undefined` call option preserves the root default.
@@ -150,6 +187,10 @@ alone is never proof that the name still refers to the expected file.
150
187
  ### `fs.create(rel, data, options?)`
151
188
 
152
189
  Don't-clobber variant of `write()`. Throws `already-exists` if the target is there.
190
+ An existing regular file or directory reports `already-exists` on every backend,
191
+ including buffered, atomic and streamed creation. Rejected directories and their
192
+ contents remain unchanged. Other non-regular types retain `not-file`; boundary,
193
+ explicit symlink policy, deny and hardlink failures retain their precedence.
153
194
  Create-only preflight preserves boundary, alias, hardlink, and type checks without
154
195
  opening an existing target to inherit its mode; a fresh file uses the requested
155
196
  mode or the normal new-file default. When the native binding is in use
@@ -304,7 +345,12 @@ type RootWriteJsonOptions = RootWriteOptions & {
304
345
 
305
346
  ### `fs.append(rel, data, options?)`
306
347
 
307
- Open in append mode, write, sync the file handle, and close. Honors `mkdir` for the parent directory and syncs the parent directory when the append creates the file. `durable: false` skips both syncs. Pass `prependNewlineIfNeeded: true` to insert a `\n` if the file does not already end in one.
348
+ Open in append mode, write, sync the file handle, and close. Honors `mkdir` for
349
+ the parent directory and syncs that directory when creating the file.
350
+ `durable: false` skips both syncs. Pass `prependNewlineIfNeeded: true` to separate
351
+ existing content from appended text when neither side supplies a newline. Strings use their `encoding` for the
352
+ newline check, including UTF-16LE; Buffers use a single LF byte. Empty strings
353
+ and Buffers add no separator; an empty append still creates a missing file.
308
354
 
309
355
  `mode` selects the creation mode, defaulting to `0o600` when neither the call nor
310
356
  the Root supplies it. On POSIX, the process umask can further restrict that mode;
@@ -317,23 +363,87 @@ await fs.append("logs/today.log", `[${ts}] ${line}\n`);
317
363
  await fs.append("notes/scratch.md", "* new bullet", { prependNewlineIfNeeded: true });
318
364
  ```
319
365
 
320
- For high-volume logging, consider [`openWritable`](#openwritable) and a long-lived append handle. Direct append-mode writes preserve kernel append semantics, but they are not atomic against external rotators that rename or unlink the target.
366
+ For high-volume logging, consider [`openWritable`](#openwritable-for-streaming) and a long-lived append handle. Direct append-mode writes preserve kernel append semantics, but they are not atomic against external rotators that rename or unlink the target.
321
367
 
322
368
  ### `fs.copyIn(rel, sourceAbsPath, options?)`
323
369
 
324
- Bring a file from outside the root into the root, atomically. The source path must be absolute. The library streams the source through the boundary, writes to a sibling temp, and renames over the destination.
370
+ `copyIn` accepts a `RootCopySource`: a trusted absolute source path or a file
371
+ within another Root. The guarded form supplies `root` with only its `open` and
372
+ `stat` read capabilities, plus `relativePath`:
325
373
 
326
374
  ```ts
327
- await fs.copyIn("inbox/upload.bin", "/tmp/incoming.bin", {
328
- maxBytes: 64 * 1024 * 1024,
375
+ const source = await root("/srv/templates");
376
+ const destination = await root("/srv/workspace");
377
+ await destination.copyIn("config/settings.json", {
378
+ root: source,
379
+ relativePath: "config/settings.json",
380
+ }, {
381
+ overwrite: false,
382
+ clone: "auto",
383
+ mode: 0o600,
384
+ signal: AbortSignal.timeout(30_000),
329
385
  });
330
386
  ```
331
387
 
332
- Options are `{ denyMutations?, durable?, maxBytes?, mkdir?, mode?, sourceHardlinks? }`.
333
- `durable` follows the root default and is `true` when omitted at both levels;
334
- set it to `false` to skip file and parent-directory syncs for reconstructible data.
335
- Use `sourceHardlinks: "reject"` to refuse if the source itself is a hardlinked
336
- alias. There is no encoding option: copying preserves source bytes.
388
+ The source Root applies its read policies, including confinement and symlink
389
+ handling. `sourceHardlinks` overrides its hardlink policy only when supplied;
390
+ otherwise the source Root default is retained. The admitted source
391
+ descriptor stays open through copying and source-identity verification; copying
392
+ does not consume its current file position. Both forms enforce `maxBytes` while
393
+ reading, including when a file grows after admission, and use bounded buffers.
394
+ Copies have independent file data; changing either file cannot change the other.
395
+ Set `preserveSourceMode: true` to select the mode from the admitted source
396
+ descriptor. An explicit numeric `mode`, including `Root.defaults.mode`, takes
397
+ precedence. By default, copying retains the existing destination-mode rules.
398
+ The operation verifies source identity, not a coherent snapshot of concurrent
399
+ in-place edits. Keep the source unchanged when snapshot consistency is required.
400
+
401
+ `overwrite` defaults to `true`, preserving the existing replacement behavior.
402
+ With `overwrite: false`, an existing destination produces `already-exists` and
403
+ is never altered. Copying prepares a private sibling file before publishing its
404
+ completed contents. Native mode uses no-replace rename. The guarded JavaScript
405
+ fallback links the completed stage and removes its temporary name in the same
406
+ JavaScript turn; the filesystem must support hardlinks. Other processes can
407
+ briefly observe both names. The source is never hardlinked to the destination.
408
+
409
+ `clone` chooses the file-data transfer strategy through `CopyCloneMode`, shared
410
+ with [`copyTree`](copy.md#api). File copies default to `"never"`; tree copies
411
+ default to `"auto"`:
412
+
413
+ | Value | Behavior |
414
+ | --- | --- |
415
+ | `never` | Copy regular file bytes using reads and writes, without explicit cloning or copy offload. |
416
+ | `auto` | Try native file cloning, then copy offload or ordinary byte copying when cloning is unavailable. |
417
+ | `always` | Require native cloning; fail when the binding or filesystem cannot provide it. |
418
+
419
+ Native file cloning supports APFS and supported Linux filesystems. Windows
420
+ currently uses byte copying for `never` and `auto`; `always` fails. Clone choice
421
+ does not change modes, durability, root confinement, or source and publication
422
+ identity checks. The shared strategy does not replace Root's guarded regular-file
423
+ contract with `copyTree`'s caller-owned immutable-tree and metadata contract.
424
+
425
+ An already aborted `signal` prevents I/O. Cancellation during copying waits for
426
+ admitted reads and native work to settle, then cleans only the owned unpublished
427
+ stage. The final authority check runs before publication. Once publication has
428
+ occurred, later cancellation or verification failure preserves the destination.
429
+ The synchronous optional `onDestinationPublished` callback receives a frozen
430
+ `RootCopyPublicationReceipt` containing `{ path, dev, ino }`, with exact bigint identity immediately after
431
+ publication, before later checks can fail. Callback errors also preserve the
432
+ published file and retain their original thrown value when cleanup succeeds,
433
+ including errors whose metadata cannot be inspected. Promise, thenable, and synchronous or asynchronous generator
434
+ results reject with `TypeError`; returned generators are never advanced. Other
435
+ synchronous return values are ignored. This receipt records an outcome; it does
436
+ not authorize removing a file that another actor may have edited. Application recovery and cooperative
437
+ locking remain caller-owned.
438
+
439
+ Existing `copyIn` callers must account for completed destinations retained after
440
+ a post-publication source-verification failure, even without the new options.
441
+ Recovery must inspect current destination state rather than assume a rejected
442
+ copy left no file.
443
+
444
+ `durable` follows the Root default (`true` when omitted at both levels);
445
+ `false` skips file and parent-directory syncs. Use `sourceHardlinks: "reject"`
446
+ to refuse hardlinked sources. There is no encoding option: copying preserves bytes.
337
447
 
338
448
  ### `fs.move(from, to, options?)`
339
449
 
@@ -370,7 +480,11 @@ paths are rechecked after the live mutation-authority callback and before
370
480
  dispatch. The Root and retained parents are fenced again after any such callback.
371
481
  These checks retain the documented final check-to-syscall race.
372
482
 
373
- For `{ overwrite: true }`, the JavaScript path checks both parent directories
483
+ For `{ overwrite: true }`, `require` mode retains both parents, checks the source
484
+ identity relative to its parent, and renames through those descriptors (or the
485
+ corresponding Windows handles). It then rechecks the parents and destination
486
+ identity. `require` rejects with `helper-unavailable` if this operation's native
487
+ entry point is absent. In `off` and default `auto`, the JavaScript path checks both parent directories
374
488
  before and after the rename. A failed post-operation check rejects even though
375
489
  the rename may already have completed; rejection does not imply rollback.
376
490
 
@@ -481,15 +595,35 @@ reasons, are propagated without adding or changing their details. Context is
481
595
  diagnostic; it is not permission to retry or mutate an entry.
482
596
 
483
597
  Removal is incremental, not atomic. A later budget, cancellation, identity, or
484
- filesystem failure does not restore already removed entries. As with existing
485
- `remove`, this is a guarded JavaScript operation in every native mode: pathname
486
- checks are best-effort against a hostile concurrent process and do not create
487
- an atomic check-and-delete syscall. Use OS isolation for that threat model.
598
+ filesystem failure does not restore already removed entries. Required-mode POSIX
599
+ removal checks the entry without following links and uses `unlinkat` relative
600
+ to a retained parent; empty directories use `AT_REMOVEDIR`. Recursive native
601
+ removal enumerates through retained directory descriptors and retains its
602
+ ordering, budgets, abort checks, and denied-descendant preflight. Linux descent
603
+ requires `openat2` with `RESOLVE_NO_XDEV`; macOS checks mount identity.
604
+
605
+ Windows nonrecursive removal checks the identity of the exact handle opened
606
+ relative to the parent and deletes that object with `FileDispositionInfoEx`,
607
+ without following a final reparse point. `require` rejects recursive removal
608
+ on Windows and on Linux without that mount-bounded capability. `off` and default
609
+ `auto` retain the JavaScript implementation even when the addon is loaded: its pathname checks are best-effort
610
+ against a hostile concurrent process and cannot prevent every outside side
611
+ effect. POSIX native deletion is also not an atomic expected-inode conditional
612
+ unlink. See the [platform matrix](security-model.md#native-root-mutation-capabilities)
613
+ for the precise parent-pinning guarantee and remaining same-call limitations.
488
614
 
489
615
  ### `fs.mkdir(rel)`
490
616
 
491
617
  `mkdir -p`. Creates missing parents.
492
618
 
619
+ `require` mode creates each missing component relative to a retained directory,
620
+ opens the child without following a final symlink, and checks its identity before
621
+ continuing. Windows private creation retains its protected native creator,
622
+ which verifies the admitted parent identity and calls handle-relative
623
+ `NtCreateFile` with a protected security descriptor. `require` refuses a missing
624
+ native capability; default `auto` retains its existing best-effort path and does
625
+ not confine mkdir under hostile concurrency.
626
+
493
627
  ```ts
494
628
  await fs.mkdir("snapshots/2026/05");
495
629
  ```
@@ -538,6 +672,28 @@ destination — there is no atomic-rename step. For exclusive publication of a
538
672
  complete stream, use [`create()`](#streamed-creation). For streamed replacement,
539
673
  the [`atomic`](atomic.md) helpers provide a staged writer.
540
674
 
675
+ When creating a missing file, `require` mode uses an exclusive no-follow open
676
+ beneath a retained parent and verifies the same inode during handoff to the
677
+ returned Node `FileHandle`. `append()` uses this path too. Required creation
678
+ fails with `helper-unavailable` with an incomplete addon, or when a
679
+ restrictive mode or umask prevents that handoff without widening initial permissions.
680
+ Default `auto` retains its existing JavaScript creation path even when the addon
681
+ is loaded, and does not confine creation under hostile concurrency. Required creation confinement
682
+ does not upgrade the returned `containment: "best-effort"` label or provide a
683
+ transaction around later caller writes.
684
+
685
+ On Windows, fallback creation rejects an observed dangling final symlink before
686
+ opening it, preserving the missing referent even when it is outside the Root.
687
+ Create-only writes and standalone exclusive creators apply the same preflight;
688
+ exclusive creation treats an existing symlink as a collision. This check does not close the race
689
+ between inspecting the leaf and opening it. An existing contained final symlink
690
+ still follows the omitted-policy behavior described above.
691
+
692
+ The native creation syscall applies the requested mode and inherited ACLs.
693
+ If those kernel-created permissions prevent the subsequent FileHandle handoff,
694
+ the operation fails and attempts identity-bound cleanup; it does not widen an
695
+ inherited ACL with a later `chmod`.
696
+
541
697
  For all three write modes, `mode` only selects new-file creation permissions,
542
698
  defaulting to `0o600` when neither the call nor the Root supplies it. POSIX
543
699
  permissions remain subject to the process umask; existing files are not chmodded.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
5
5
  "keywords": [
6
6
  "filesystem",
@@ -173,20 +173,20 @@
173
173
  "archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
174
174
  },
175
175
  "optionalDependencies": {
176
- "@openclaw/fs-safe-darwin-arm64": "0.21.1",
177
- "@openclaw/fs-safe-darwin-x64": "0.21.1",
178
- "@openclaw/fs-safe-linux-arm64-gnu": "0.21.1",
179
- "@openclaw/fs-safe-linux-arm64-musl": "0.21.1",
180
- "@openclaw/fs-safe-linux-x64-gnu": "0.21.1",
181
- "@openclaw/fs-safe-linux-x64-musl": "0.21.1",
182
- "@openclaw/fs-safe-win32-x64-msvc": "0.21.1",
176
+ "@openclaw/fs-safe-darwin-arm64": "0.23.0",
177
+ "@openclaw/fs-safe-darwin-x64": "0.23.0",
178
+ "@openclaw/fs-safe-linux-arm64-gnu": "0.23.0",
179
+ "@openclaw/fs-safe-linux-arm64-musl": "0.23.0",
180
+ "@openclaw/fs-safe-linux-x64-gnu": "0.23.0",
181
+ "@openclaw/fs-safe-linux-x64-musl": "0.23.0",
182
+ "@openclaw/fs-safe-win32-x64-msvc": "0.23.0",
183
183
  "jszip": "^3.10.2"
184
184
  },
185
185
  "devDependencies": {
186
186
  "@emnapi/runtime": "2.0.0-alpha.5",
187
187
  "@napi-rs/cli": "3.10.5",
188
188
  "@types/node": "^26.6.1",
189
- "@vitest/coverage-v8": "5.0.1",
189
+ "@vitest/coverage-v8": "5.0.2",
190
190
  "fast-check": "^4.10.1",
191
191
  "istanbul-lib-coverage": "3.2.2",
192
192
  "istanbul-lib-report": "3.0.1",
@@ -194,7 +194,7 @@
194
194
  "sigstore": "5.0.0",
195
195
  "tar": "7.5.22",
196
196
  "typescript": "^7.0.2",
197
- "vite": "8.3.0",
197
+ "vite": "8.3.1",
198
198
  "vitest": "^5.0.1"
199
199
  },
200
200
  "engines": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe-darwin-arm64",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "macOS arm64 native binding for @openclaw/fs-safe.",
5
5
  "license": "MIT",
6
6
  "author": "OpenClaw Team <dev@openclaw.ai>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe-darwin-x64",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "macOS x64 native binding for @openclaw/fs-safe.",
5
5
  "license": "MIT",
6
6
  "author": "OpenClaw Team <dev@openclaw.ai>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe-linux-arm64-gnu",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "Linux arm64 glibc native binding for @openclaw/fs-safe.",
5
5
  "license": "MIT",
6
6
  "author": "OpenClaw Team <dev@openclaw.ai>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe-linux-arm64-musl",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "Linux arm64 musl native binding for @openclaw/fs-safe.",
5
5
  "license": "MIT",
6
6
  "author": "OpenClaw Team <dev@openclaw.ai>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe-linux-x64-gnu",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "Linux x64 glibc native binding for @openclaw/fs-safe.",
5
5
  "license": "MIT",
6
6
  "author": "OpenClaw Team <dev@openclaw.ai>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe-linux-x64-musl",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "Linux x64 musl native binding for @openclaw/fs-safe.",
5
5
  "license": "MIT",
6
6
  "author": "OpenClaw Team <dev@openclaw.ai>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe-win32-x64-msvc",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "Windows x64 MSVC native binding for @openclaw/fs-safe.",
5
5
  "license": "MIT",
6
6
  "author": "OpenClaw Team <dev@openclaw.ai>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/feishu",
3
- "version": "2026.9.8",
3
+ "version": "2026.10.1-beta.2",
4
4
  "description": "OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,7 @@
9
9
  "type": "module",
10
10
  "dependencies": {
11
11
  "@larksuiteoapi/node-sdk": "1.74.0",
12
- "@openclaw/fs-safe": "0.21.1",
12
+ "@openclaw/fs-safe": "0.23.0",
13
13
  "mdast-util-from-markdown": "2.0.3",
14
14
  "mdast-util-gfm-table": "2.0.0",
15
15
  "micromark-extension-gfm-table": "2.1.2",
@@ -17,7 +17,7 @@
17
17
  "zod": "4.6.5"
18
18
  },
19
19
  "peerDependencies": {
20
- "openclaw": ">=2026.9.8"
20
+ "openclaw": ">=2026.10.1-beta.2"
21
21
  },
22
22
  "peerDependenciesMeta": {
23
23
  "openclaw": {
@@ -64,11 +64,11 @@
64
64
  "minHostVersion": ">=2026.5.29"
65
65
  },
66
66
  "compat": {
67
- "pluginApi": ">=2026.9.8"
67
+ "pluginApi": ">=2026.10.1-beta.2"
68
68
  },
69
69
  "build": {
70
70
  "bundledDist": false,
71
- "openclawVersion": "2026.9.8"
71
+ "openclawVersion": "2026.10.1-beta.2"
72
72
  },
73
73
  "release": {
74
74
  "publishToClawHub": true,
@@ -14,7 +14,7 @@ From `https://example.feishu.cn/wiki/ABC123def`, use `ABC123def` as `token`. Tre
14
14
 
15
15
  - Use `spaces` to enumerate accessible knowledge spaces and `nodes` for a space or parent node.
16
16
  - Continue pagination with the returned `page_token` while `has_more` is true, keeping the same space and parent.
17
- - Use `search` when the user provides a query but not an exact node.
17
+ - Search is unavailable. Use `nodes` to browse a known space or parent, or `get` when the user provides a wiki token.
18
18
  - Use `get` to resolve a wiki token to its `node_token`, `obj_token`, and `obj_type`.
19
19
 
20
20
  ## Create and organize