@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
@@ -0,0 +1,350 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import fs from "node:fs";
54
+ import { randomUUID } from "node:crypto";
55
+ import fsAsync from "node:fs/promises";
56
+ import path from "node:path";
57
+ import { assertSyncDirectoryGuard } from "./directory-guard.js";
58
+ import { nodeDirectorySearchOnlyFlags } from "./directory-mode-node.js";
59
+ import { assertDarwinCreationAcl, assertPrivateDirectory } from "./creation-boundary.js";
60
+ import { assertMutationNotDenied } from "./deny-mutations.js";
61
+ import { FsSafeError } from "./errors.js";
62
+ import { getNativeBinding } from "./native.js";
63
+ import { captureNativeFdClose } from "./native-binding.js";
64
+ import { openNativeRootAdmission } from "./native-parent-admission.js";
65
+ import { capturePolicyAwareNativeParent } from "./native-policy-parent.js";
66
+ import { isNotFoundPathError, isPathInside } from "./path.js";
67
+ import { preparePinnedWriteMutationAdmission } from "./pinned-mutation-admission.js";
68
+ import { resolvePathViaExistingAncestor } from "./root-path-existing.js";
69
+ import { admitPathInsideRoot } from "./root-boundary.js";
70
+ import { assertRootIdentityCurrentSync } from "./root-context.js";
71
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
72
+ import { getFsSafeTestHooks } from "./test-hooks.js";
73
+ import { createSuppressedError } from "./suppressed-error.js";
74
+ export function rootWriteQueueKey(root, relativePath) {
75
+ return `${root.rootReal}\0${relativePath}`;
76
+ }
77
+ export function buildAtomicWriteTempPath(targetPath) {
78
+ return path.join(path.dirname(targetPath), `.fs-safe-${randomUUID()}.tmp`);
79
+ }
80
+ function unavailable() {
81
+ throw new FsSafeError("helper-unavailable", "native confined creation is unavailable");
82
+ }
83
+ async function withNativeDirectory(params, operation, discard) {
84
+ const binding = getNativeBinding();
85
+ if (!binding?.openBeneath || !binding.mkdirChildBeneath || (params.private && process.platform === "win32"))
86
+ return unavailable();
87
+ const directoryTarget = params.directory === params.target;
88
+ const canonicalTarget = async (target) => directoryTarget
89
+ ? await resolvePathViaExistingAncestor(target)
90
+ : path.join(await resolvePathViaExistingAncestor(path.dirname(target)), path.basename(target));
91
+ const resolveCurrent = params.resolveCurrent;
92
+ params = { ...params,
93
+ directory: await resolvePathViaExistingAncestor(params.directory),
94
+ target: await canonicalTarget(params.target),
95
+ resolveCurrent: async () => await canonicalTarget(await resolveCurrent()),
96
+ };
97
+ if (!isPathInside(params.root.rootReal, params.directory)) {
98
+ throw new FsSafeError("outside-workspace", "creation parent is outside root");
99
+ }
100
+ const close = captureNativeFdClose(binding);
101
+ let pending;
102
+ try {
103
+ const env_1 = { stack: [], error: void 0, hasError: false };
104
+ try {
105
+ const admitted = await openNativeRootAdmission(binding, {
106
+ rootPath: params.root.rootReal, rootIdentity: params.root.rootIdentity, operation: "create", reportCloseErrors: true, searchOnly: true,
107
+ });
108
+ const rootOwner = __addDisposableResource(env_1, admitted.root, true);
109
+ const descriptors = [];
110
+ const directoryOwner = __addDisposableResource(env_1, { [Symbol.dispose]() {
111
+ const errors = [];
112
+ for (const fd of descriptors.reverse())
113
+ try {
114
+ close(fd);
115
+ }
116
+ catch (error) {
117
+ errors.push(error);
118
+ }
119
+ if (errors.length)
120
+ throw new AggregateError(errors, "creation directory close failed");
121
+ } }, false);
122
+ const guards = [];
123
+ function assertCurrent() {
124
+ assertRootIdentityCurrentSync(params.root);
125
+ for (const guard of guards)
126
+ assertSyncDirectoryGuard(guard);
127
+ }
128
+ async function authorize(mutation) {
129
+ if (path.relative(params.target, await params.resolveCurrent()) !== "") {
130
+ throw new FsSafeError("path-mismatch", "creation route changed during admission");
131
+ }
132
+ await assertMutationNotDenied(params.target, params.policy?.denyMutations);
133
+ await assertMutationNotDenied(mutation, params.policy?.denyMutations);
134
+ assertCurrent();
135
+ }
136
+ let current = rootOwner.fd;
137
+ let currentPath = params.root.rootReal;
138
+ const flags = (nodeDirectorySearchOnlyFlags()?.flags ?? fs.constants.O_RDONLY) |
139
+ (fs.constants.O_DIRECTORY ?? 0) | (fs.constants.O_NOFOLLOW ?? 0);
140
+ if (!params.private && !(directoryTarget && params.directory === params.root.rootReal)) {
141
+ if (!admitPathInsideRoot({ rootPath: params.root.rootReal, candidatePath: params.target, rootIdentity: params.root.rootIdentity })) {
142
+ throw new FsSafeError("outside-workspace", "creation target is outside root");
143
+ }
144
+ const relativeParentPath = path.relative(params.root.rootReal, params.directory).split(path.sep).join("/");
145
+ const prepared = await preparePinnedWriteMutationAdmission({
146
+ rootReal: params.root.rootReal, rootIdentity: params.root.rootIdentity,
147
+ resolvedTargetPath: params.target, defaultRelativeParentPath: relativeParentPath,
148
+ originalPath: params.originalPath, policy: params.policy ?? {},
149
+ resolveCurrent: async () => ({ resolved: await params.resolveCurrent() }),
150
+ });
151
+ const parent = await capturePolicyAwareNativeParent(binding, {
152
+ rootPath: params.root.rootReal, rootIdentity: params.root.rootIdentity,
153
+ relativeParentPath, basename: directoryTarget ? "" : path.basename(params.target),
154
+ mkdir: params.mkdir, mode: 0o600, input: { kind: "buffer", data: "" },
155
+ mutationAdmission: prepared.mutationAdmission, assertBeforeMutation: params.assertBeforeMutation,
156
+ }, admitted, process.platform === "win32", flags, true);
157
+ descriptors.push(parent.fd);
158
+ guards.push(parent.guard);
159
+ assertCurrent();
160
+ pending = { value: await operation(binding, parent.fd, assertCurrent) };
161
+ return pending.value;
162
+ }
163
+ for (const segment of path.relative(currentPath, params.directory).split(path.sep).filter(Boolean)) {
164
+ const childPath = path.join(currentPath, segment);
165
+ let child;
166
+ let created = false;
167
+ try {
168
+ child = binding.openBeneath(current, segment, flags).fd;
169
+ }
170
+ catch (error) {
171
+ if (!params.mkdir || !isNotFoundPathError(error))
172
+ throw error;
173
+ await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", childPath);
174
+ await authorize(childPath);
175
+ if (params.private)
176
+ assertDarwinCreationAcl(current, "directory");
177
+ params.assertBeforeMutation?.();
178
+ assertCurrent();
179
+ if (binding.mkdirOpenChildBeneath) {
180
+ const opened = binding.mkdirOpenChildBeneath(current, segment, params.private ? 0o700 : 0o777, flags);
181
+ created = opened.created;
182
+ child = opened.fd;
183
+ }
184
+ else {
185
+ created = binding.mkdirChildBeneath(current, segment, params.private ? 0o700 : 0o777);
186
+ child = binding.openBeneath(current, segment, flags).fd;
187
+ }
188
+ }
189
+ descriptors.push(child);
190
+ const stat = inspectFileIdentitySync(() => fs.fstatSync(child, { bigint: true }));
191
+ if (!stat.isDirectory())
192
+ throw new FsSafeError("not-file", "creation parent is not a directory");
193
+ guards.push({ dir: childPath, realPath: childPath, stat });
194
+ assertCurrent();
195
+ if (created && params.private)
196
+ await assertPrivateDirectory(childPath);
197
+ current = child;
198
+ currentPath = childPath;
199
+ }
200
+ await authorize(params.target);
201
+ if (params.private) {
202
+ await assertPrivateDirectory(currentPath);
203
+ assertCurrent();
204
+ }
205
+ pending = { value: await operation(binding, current, assertCurrent) };
206
+ return pending.value;
207
+ }
208
+ catch (e_1) {
209
+ env_1.error = e_1;
210
+ env_1.hasError = true;
211
+ }
212
+ finally {
213
+ const result_1 = __disposeResources(env_1);
214
+ if (result_1)
215
+ await result_1;
216
+ }
217
+ }
218
+ catch (error) {
219
+ if (pending && discard) {
220
+ try {
221
+ await discard(pending.value);
222
+ }
223
+ catch (cleanupError) {
224
+ throw createSuppressedError(cleanupError, error, "creation handoff and cleanup failed");
225
+ }
226
+ }
227
+ throw error;
228
+ }
229
+ }
230
+ export async function tryMkdirRootNative(params) {
231
+ // The protected Windows creator already verifies the admitted parent identity
232
+ // and uses handle-relative NtCreateFile with its protected security descriptor.
233
+ if (params.private && process.platform === "win32")
234
+ return false;
235
+ return await withNativeDirectory(params, async (_binding, _fd, assertCurrent) => { assertCurrent(); return true; });
236
+ }
237
+ export async function tryOpenCreateRootNative(params) {
238
+ const binding = getNativeBinding();
239
+ if (!binding?.removeStagedFile || !binding.openCreateBeneath)
240
+ return unavailable();
241
+ const access = (params.existingFlags & fs.constants.O_RDWR) ? 0o600 : 0o200;
242
+ if (typeof params.mode !== "number")
243
+ throw Object.assign(new TypeError("mode must be a number"), { code: "ERR_INVALID_ARG_TYPE" });
244
+ if (!Number.isInteger(params.mode) || params.mode < 0 || params.mode > 0xffffffff) {
245
+ throw Object.assign(new RangeError("mode must be an unsigned 32-bit integer"), { code: "ERR_OUT_OF_RANGE" });
246
+ }
247
+ const creationMode = () => {
248
+ const mask = process.umask();
249
+ const desired = params.mode & 0o7777 & ~mask;
250
+ return (desired & access) === access ? desired : undefined;
251
+ };
252
+ // A public FileHandle reopen must not require widening creation permissions.
253
+ if (creationMode() === undefined)
254
+ return unavailable();
255
+ return await withNativeDirectory(params, async (binding, parent, assertCurrent) => {
256
+ const env_2 = { stack: [], error: void 0, hasError: false };
257
+ try {
258
+ params.assertBeforeMutation?.();
259
+ assertCurrent();
260
+ const mode = creationMode();
261
+ if (mode === undefined)
262
+ throw new FsSafeError("helper-unavailable", "native creation cannot preserve the requested permissions during handoff");
263
+ // Native creation owns the only O_CREAT dispatch. The subsequent Node open
264
+ // carries neither O_CREAT nor O_TRUNC and must match this retained inode.
265
+ const close = captureNativeFdClose(binding);
266
+ const fd = binding.openCreateBeneath(parent, path.basename(params.target), params.flags, params.mode & 0o7777);
267
+ let rawOpen = true;
268
+ const closeRaw = () => { if (rawOpen) {
269
+ rawOpen = false;
270
+ close(fd);
271
+ } };
272
+ const owner = __addDisposableResource(env_2, { [Symbol.dispose]: closeRaw }, false);
273
+ try {
274
+ const identity = inspectFileIdentitySync(() => fs.fstatSync(fd, { bigint: true }));
275
+ if (!identity.isFile() || identity.nlink !== 1n)
276
+ throw new FsSafeError("path-mismatch", "created file changed");
277
+ // Keep the kernel's creation-time ACL and umask decisions. chmod here
278
+ // could widen inherited ACL restrictions, even for ordinary mode bits.
279
+ if (process.platform !== "win32" && (Number(identity.mode) & access) !== access) {
280
+ throw new FsSafeError("helper-unavailable", "created permissions do not permit a FileHandle handoff");
281
+ }
282
+ assertCurrent();
283
+ const handle = await fsAsync.open(params.target, params.existingFlags);
284
+ let validatedHandle = false;
285
+ try {
286
+ inspectFileIdentitySync(() => fs.fstatSync(handle.fd, { bigint: true }), identity);
287
+ validatedHandle = true;
288
+ assertCurrent();
289
+ // Close failures still belong to this scope, before transferring either owner.
290
+ closeRaw();
291
+ // Empty relative paths duplicate the admitted descriptor without reopening its name.
292
+ const retainedParent = binding.openBeneath(parent, "", (nodeDirectorySearchOnlyFlags()?.flags ?? fs.constants.O_RDONLY) | (fs.constants.O_DIRECTORY ?? 0)).fd;
293
+ let held = true;
294
+ return { handle, async cleanupCreated() {
295
+ if (!held)
296
+ return;
297
+ inspectFileIdentitySync(() => fs.fstatSync(handle.fd, { bigint: true }), identity);
298
+ binding.removeStagedFile(retainedParent, path.basename(params.target), handle.fd);
299
+ }, releaseCreationParent() {
300
+ if (!held)
301
+ return;
302
+ held = false;
303
+ close(retainedParent);
304
+ } };
305
+ }
306
+ catch (error) {
307
+ if (!rawOpen && validatedHandle) {
308
+ try {
309
+ binding.removeStagedFile(parent, path.basename(params.target), handle.fd);
310
+ }
311
+ catch { /* Preserve handoff failure. */ }
312
+ }
313
+ await handle.close();
314
+ throw error;
315
+ }
316
+ }
317
+ catch (error) {
318
+ if (rawOpen)
319
+ try {
320
+ binding.removeStagedFile(parent, path.basename(params.target), fd);
321
+ }
322
+ catch { /* Preserve the admission failure. */ }
323
+ throw error;
324
+ }
325
+ }
326
+ catch (e_2) {
327
+ env_2.error = e_2;
328
+ env_2.hasError = true;
329
+ }
330
+ finally {
331
+ __disposeResources(env_2);
332
+ }
333
+ }, async (created) => {
334
+ const env_3 = { stack: [], error: void 0, hasError: false };
335
+ try {
336
+ const handle = __addDisposableResource(env_3, created.handle, true);
337
+ const parent = __addDisposableResource(env_3, { [Symbol.dispose]: created.releaseCreationParent }, false);
338
+ await created.cleanupCreated();
339
+ }
340
+ catch (e_3) {
341
+ env_3.error = e_3;
342
+ env_3.hasError = true;
343
+ }
344
+ finally {
345
+ const result_2 = __disposeResources(env_3);
346
+ if (result_2)
347
+ await result_2;
348
+ }
349
+ });
350
+ }
@@ -0,0 +1,35 @@
1
+ import type { ExactStatIdentity } from "./stat-observation.js";
2
+ import type { DirEntry } from "./types.js";
3
+ /** Strings only: cached spellings never replace an observation or its identity fences. */
4
+ export type RootDirectoryListingPaths = {
5
+ directory: string;
6
+ names: Map<string, string>;
7
+ };
8
+ export type RootDirectoryListing = {
9
+ paths: RootDirectoryListingPaths;
10
+ assertCurrent(): Promise<void>;
11
+ next(): Promise<{
12
+ kind: "entry";
13
+ entry: DirEntry;
14
+ identity?: ExactStatIdentity;
15
+ } | {
16
+ kind: "limit";
17
+ name: string;
18
+ } | undefined>;
19
+ [Symbol.asyncDispose](): Promise<void>;
20
+ };
21
+ export type RootDirectoryListingOptions = {
22
+ order: "sorted" | "filesystem";
23
+ signal?: AbortSignal;
24
+ snapshot: boolean;
25
+ maxNames?: number;
26
+ metadataBatchSize?: number;
27
+ /** Internal watch lane: exact identities and bounded synchronous name reads on Node. */
28
+ exactIdentity?: boolean;
29
+ /** Advisory scans may omit vanished leaves after revalidating their parent. */
30
+ skipVanished?: boolean;
31
+ previousPaths?: RootDirectoryListingPaths;
32
+ /** Internal owner receives cleanup failures, including acquisition rollback. */
33
+ onCleanupFailure?: (error: unknown) => void;
34
+ admitEntry(): boolean;
35
+ };
@@ -2,9 +2,10 @@ import type { BigIntStats, Stats } from "node:fs";
2
2
  import { type AsyncDirectoryGuard } from "./directory-guard.js";
3
3
  import { type RootContext } from "./root-context.js";
4
4
  import type { RootPathDirectoryObservationGuard, RootPathObservationReceipt, RootPathParentObservationReceipt } from "./root-path.js";
5
- import { type ExactStatIdentity } from "./stat-observation.js";
6
5
  import type { DirEntry, PathStat } from "./types.js";
6
+ import type { RootDirectoryListing, RootDirectoryListingOptions } from "./root-directory-list-types.js";
7
7
  export declare function pathStatFromStats(stat: Stats | BigIntStats): PathStat;
8
+ export declare function pathStatFromStats(stat: Stats | BigIntStats, name: string): DirEntry;
8
9
  export type RootDirectoryObservationGuard = AsyncDirectoryGuard<BigIntStats>;
9
10
  export declare function createRootDirectoryObservationGuard(root: RootContext, directory: string): Promise<RootDirectoryObservationGuard>;
10
11
  export declare function assertRootDirectoryObservationGuard(root: RootContext, guard: RootDirectoryObservationGuard | RootPathDirectoryObservationGuard): Promise<void>;
@@ -17,30 +18,4 @@ export declare function assertRootDirectoryObservationGuard(root: RootContext, g
17
18
  export declare function assertRootPathObservationReceiptCurrent(root: RootContext, receipt: RootPathObservationReceipt | RootPathParentObservationReceipt, finalTarget?: Stats | BigIntStats): void;
18
19
  export declare function listDirectoryPath(root: RootContext, directory: string, withFileTypes: true, receipt?: RootPathObservationReceipt): Promise<DirEntry[]>;
19
20
  export declare function listDirectoryPath(root: RootContext, directory: string, withFileTypes: boolean, receipt?: RootPathObservationReceipt): Promise<string[] | DirEntry[]>;
20
- export type RootDirectoryListing = {
21
- assertCurrent(): Promise<void>;
22
- next(): Promise<{
23
- kind: "entry";
24
- entry: DirEntry;
25
- identity?: ExactStatIdentity;
26
- } | {
27
- kind: "limit";
28
- name: string;
29
- } | undefined>;
30
- [Symbol.asyncDispose](): Promise<void>;
31
- };
32
- export type RootDirectoryListingOptions = {
33
- order: "sorted" | "filesystem";
34
- signal?: AbortSignal;
35
- snapshot: boolean;
36
- maxNames?: number;
37
- metadataBatchSize?: number;
38
- /** Internal exact metadata lane, supported by streaming filesystem order. */
39
- exactIdentity?: boolean;
40
- /** Advisory scans may omit vanished leaves after revalidating their parent. */
41
- skipVanished?: boolean;
42
- /** Internal owner receives cleanup failures, including acquisition rollback. */
43
- onCleanupFailure?: (error: unknown) => void;
44
- admitEntry(): boolean;
45
- };
46
21
  export declare function openRootDirectoryListing(root: RootContext, directory: string, options: RootDirectoryListingOptions, receipt?: RootPathObservationReceipt): Promise<RootDirectoryListing>;
@@ -21,8 +21,8 @@ function directoryEntryName(bytes) {
21
21
  }
22
22
  return bytes.toString("utf8");
23
23
  }
24
- async function readDirectoryEntryName(handle) {
25
- const entry = await handle.read();
24
+ async function readDirectoryEntryName(handle, synchronous = false) {
25
+ const entry = synchronous ? handle.readSync() : await handle.read();
26
26
  // Bun returns the raw Buffer directly; Node returns a Dirent whose name is a Buffer.
27
27
  // Node's Dir types still declare only string names.
28
28
  return entry === null ? undefined
@@ -31,25 +31,19 @@ async function readDirectoryEntryName(handle) {
31
31
  function openDirectoryNames(directory) {
32
32
  return fs.opendir(directory, { bufferSize: 1, encoding: "buffer" });
33
33
  }
34
- export function pathStatFromStats(stat) {
34
+ export function pathStatFromStats(stat, name) {
35
35
  const mtimeMs = typeof stat.mtimeMs === "bigint"
36
36
  ? "mtimeNs" in stat && typeof stat.mtimeNs === "bigint"
37
37
  ? Number(stat.mtimeNs) / 1_000_000
38
38
  : stat.mtime.getTime()
39
39
  : stat.mtimeMs;
40
- return {
41
- dev: Number(stat.dev),
42
- gid: Number(stat.gid),
43
- ino: Number(stat.ino),
44
- isDirectory: stat.isDirectory(),
45
- isFile: stat.isFile(),
46
- isSymbolicLink: stat.isSymbolicLink(),
47
- mode: Number(stat.mode),
48
- mtimeMs,
49
- nlink: Number(stat.nlink),
50
- size: Number(stat.size),
51
- uid: Number(stat.uid),
52
- };
40
+ const dev = Number(stat.dev), gid = Number(stat.gid), ino = Number(stat.ino);
41
+ const isDirectory = stat.isDirectory(), isFile = stat.isFile(), isSymbolicLink = stat.isSymbolicLink();
42
+ const mode = Number(stat.mode), nlink = Number(stat.nlink), size = Number(stat.size), uid = Number(stat.uid);
43
+ // Construct named metadata directly, preserving property order without a spread copy.
44
+ return name === undefined
45
+ ? { dev, gid, ino, isDirectory, isFile, isSymbolicLink, mode, mtimeMs, nlink, size, uid }
46
+ : { name, dev, gid, ino, isDirectory, isFile, isSymbolicLink, mode, mtimeMs, nlink, size, uid };
53
47
  }
54
48
  export async function createRootDirectoryObservationGuard(root, directory) {
55
49
  const guard = await createAsyncDirectoryGuard(directory, { bigint: true });
@@ -220,12 +214,14 @@ async function listGuardedDirectoryPath(root, guard, withFileTypes, receipt) {
220
214
  if (beforeObservation)
221
215
  await beforeObservation(guard.realPath, withFileTypes);
222
216
  const names = (await fs.readdir(guard.realPath, { encoding: "buffer" })).map(directoryEntryName).sort();
223
- entries = withFileTypes
224
- ? names.map(name => ({
225
- name,
226
- ...pathStatFromStats(fsSync.lstatSync(path.join(guard.realPath, name))),
227
- }))
228
- : names;
217
+ if (withFileTypes) {
218
+ // readdir supplies literal child names beneath the already-canonical directory.
219
+ const prefix = guard.realPath.endsWith(path.sep) ? guard.realPath : `${guard.realPath}${path.sep}`;
220
+ entries = names.map(name => pathStatFromStats(fsSync.lstatSync(`${prefix}${name}`), name));
221
+ }
222
+ else {
223
+ entries = names;
224
+ }
229
225
  }
230
226
  catch (error) {
231
227
  // Preserve ordinary observation errors only while the admitted directory is
@@ -268,12 +264,14 @@ export async function openRootDirectoryListing(root, directory, options, receipt
268
264
  await assertRootDirectoryObservationGuard(root, guard);
269
265
  options.signal?.throwIfAborted();
270
266
  };
267
+ const paths = { directory: guard.realPath, names: new Map() };
268
+ const previousPaths = options.previousPaths?.directory === guard.realPath ? options.previousPaths.names : undefined;
271
269
  let handle;
272
270
  let names;
273
271
  let snapshot;
274
- let index = 0;
272
+ let index = 0, preparedIndex = 0;
275
273
  let prepared = [];
276
- let preparedIndex = 0;
274
+ const synchronousNames = options.exactIdentity && !process.versions.bun && !process.versions.deno;
277
275
  let pendingFailure;
278
276
  let pendingLimit;
279
277
  let preparedBatch = false;
@@ -347,7 +345,7 @@ export async function openRootDirectoryListing(root, directory, options, receipt
347
345
  preparedIndex = 0;
348
346
  while (true) {
349
347
  try {
350
- const entry = { name, ...pathStatFromStats(fsSync.lstatSync(path.join(guard.realPath, name))) };
348
+ const entry = pathStatFromStats(fsSync.lstatSync(path.join(guard.realPath, name)), name);
351
349
  prepared.push(entry);
352
350
  // No later sibling can be observed before a possible recursive descent.
353
351
  if (entry.isDirectory || entry.isSymbolicLink || prepared.length >= (options.metadataBatchSize ?? METADATA_BATCH_SIZE))
@@ -376,6 +374,7 @@ export async function openRootDirectoryListing(root, directory, options, receipt
376
374
  }
377
375
  };
378
376
  return {
377
+ paths,
379
378
  assertCurrent,
380
379
  async next() {
381
380
  try {
@@ -400,15 +399,19 @@ export async function openRootDirectoryListing(root, directory, options, receipt
400
399
  return;
401
400
  }
402
401
  for (;;) {
402
+ if (synchronousNames && index++ % METADATA_BATCH_SIZE === METADATA_BATCH_SIZE - 1)
403
+ await yieldToEventLoop();
403
404
  await assertCurrent();
404
- const name = await readDirectoryEntryName(handle);
405
+ const name = await readDirectoryEntryName(handle, synchronousNames);
405
406
  await assertCurrent();
406
407
  if (name === undefined)
407
408
  return;
408
409
  if (!options.admitEntry())
409
410
  return { kind: "limit", name };
410
411
  // The stream's post-read fence is also the pre-stat fence in this owned operation.
411
- const pathname = path.join(guard.realPath, name);
412
+ const pathname = previousPaths?.get(name) ?? path.join(guard.realPath, name);
413
+ if (options.exactIdentity)
414
+ paths.names.set(name, pathname);
412
415
  let observed;
413
416
  try {
414
417
  observed = options.exactIdentity ? inspectStatObservationSync(bigint => bigint
@@ -421,7 +424,7 @@ export async function openRootDirectoryListing(root, directory, options, receipt
421
424
  throw error;
422
425
  }
423
426
  await assertCurrent();
424
- return { kind: "entry", entry: { name, ...pathStatFromStats(observed.stat) }, identity: observed.identity };
427
+ return { kind: "entry", entry: pathStatFromStats(observed.stat, name), identity: observed.identity };
425
428
  }
426
429
  }
427
430
  catch (error) {
@@ -5,11 +5,8 @@ import { formatErrorDetail } from "./error-detail.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { assertNoNulPathInput, hasNodeErrorCode, isNodeError, isNotFoundPathError, isPathInside, isPathRelativeEscape, } from "./path.js";
7
7
  import { realpathSync } from "./realpath.js";
8
- import { resolvePathWithinRoot } from "./root-paths-lexical.js";
8
+ import { invalidPath, resolvePathWithinRoot } from "./root-paths-lexical.js";
9
9
  import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
10
- function invalidPath(scopeLabel) {
11
- return { ok: false, error: `Invalid path: must stay within ${scopeLabel}` };
12
- }
13
10
  export function resolveNearestExistingPath(targetPath) {
14
11
  assertNoWindowsPathAlias(targetPath);
15
12
  let current = resolvePathPreservingWindowsRoot(targetPath);
@@ -29,7 +29,41 @@ export function hardlinkedPathNotAllowedError() {
29
29
  export function isAlreadyExistsError(error) {
30
30
  return hasNodeErrorCode(error, "EEXIST") || /File exists|EEXIST/i.test(String(error));
31
31
  }
32
+ function descriptorExhaustion(error) {
33
+ const pending = [error];
34
+ let details;
35
+ let code;
36
+ const seen = new Set();
37
+ while (pending.length) {
38
+ const current = pending.pop();
39
+ if (!(current instanceof Error) || seen.has(current))
40
+ continue;
41
+ seen.add(current);
42
+ // Keep boundary/policy failures primary, including combined failures.
43
+ if (current instanceof FsSafeError && current.code !== "helper-failed" && current.code !== "not-removable")
44
+ return;
45
+ const diagnostic = current;
46
+ if (current instanceof FsSafeError && (!details || current.details?.cleanup))
47
+ details = current.details ?? details;
48
+ if (diagnostic.code === "EMFILE" || diagnostic.code === "ENFILE")
49
+ code ??= diagnostic.code;
50
+ if (diagnostic.name === "SuppressedError")
51
+ pending.push(diagnostic.suppressed, diagnostic.error);
52
+ if (current instanceof AggregateError)
53
+ pending.push(...current.errors);
54
+ pending.push(current.cause);
55
+ }
56
+ if (!code)
57
+ return;
58
+ const cleanup = details?.cleanup;
59
+ const publication = details?.publication;
60
+ const preserved = cleanup?.status === "preserved" && publication?.status === "indeterminate";
61
+ return new FsSafeError("helper-failed", `filesystem write failed: too many open files (${code})${preserved ? "; publication outcome is indeterminate; staged file preserved" : ""}`, { cause: error, details });
62
+ }
32
63
  export function normalizePinnedWriteError(error) {
64
+ const exhausted = descriptorExhaustion(error);
65
+ if (exhausted)
66
+ return exhausted;
33
67
  if (error instanceof FsSafeError) {
34
68
  return error;
35
69
  }