@openclaw/fs-safe 0.9.0 → 0.11.0

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 (326) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/LICENSE +1 -0
  3. package/README.md +52 -4
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +3 -2
  6. package/dist/advanced.d.ts +5 -0
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +5 -0
  9. package/dist/archive-crc32.d.ts.map +1 -1
  10. package/dist/archive-crc32.js +6 -1
  11. package/dist/archive-deadline.d.ts.map +1 -1
  12. package/dist/archive-deadline.js +3 -4
  13. package/dist/archive-durability.d.ts +6 -6
  14. package/dist/archive-durability.d.ts.map +1 -1
  15. package/dist/archive-durability.js +1 -1
  16. package/dist/archive-entry.d.ts.map +1 -1
  17. package/dist/archive-entry.js +4 -5
  18. package/dist/archive-gzip-tail.d.ts +3 -0
  19. package/dist/archive-gzip-tail.d.ts.map +1 -1
  20. package/dist/archive-gzip-tail.js +23 -3
  21. package/dist/archive-input.d.ts.map +1 -1
  22. package/dist/archive-input.js +4 -2
  23. package/dist/archive-merge.d.ts +5 -1
  24. package/dist/archive-merge.d.ts.map +1 -1
  25. package/dist/archive-merge.js +16 -13
  26. package/dist/archive-native.d.ts.map +1 -1
  27. package/dist/archive-native.js +7 -6
  28. package/dist/archive-parser.wasm +0 -0
  29. package/dist/archive-read.d.ts.map +1 -1
  30. package/dist/archive-read.js +83 -74
  31. package/dist/archive-staging.d.ts +6 -3
  32. package/dist/archive-staging.d.ts.map +1 -1
  33. package/dist/archive-staging.js +42 -22
  34. package/dist/archive-tar-stream.d.ts +11 -4
  35. package/dist/archive-tar-stream.d.ts.map +1 -1
  36. package/dist/archive-tar-stream.js +23 -10
  37. package/dist/archive-tar-wasm.d.ts.map +1 -1
  38. package/dist/archive-tar-wasm.js +18 -14
  39. package/dist/archive-zip-admission.d.ts +1 -1
  40. package/dist/archive-zip-admission.d.ts.map +1 -1
  41. package/dist/archive-zip-admission.js +48 -12
  42. package/dist/archive-zip-loader.d.ts +6 -0
  43. package/dist/archive-zip-loader.d.ts.map +1 -0
  44. package/dist/archive-zip-loader.js +38 -0
  45. package/dist/archive-zip-names.d.ts.map +1 -1
  46. package/dist/archive-zip-names.js +13 -8
  47. package/dist/archive-zip-preflight.d.ts +2 -3
  48. package/dist/archive-zip-preflight.d.ts.map +1 -1
  49. package/dist/archive-zip-preflight.js +2 -34
  50. package/dist/archive.d.ts.map +1 -1
  51. package/dist/archive.js +12 -10
  52. package/dist/bounded-read.d.ts +12 -0
  53. package/dist/bounded-read.d.ts.map +1 -1
  54. package/dist/bounded-read.js +82 -45
  55. package/dist/clone-metadata.d.ts +19 -0
  56. package/dist/clone-metadata.d.ts.map +1 -0
  57. package/dist/clone-metadata.js +32 -0
  58. package/dist/copy-file-input.d.ts +22 -0
  59. package/dist/copy-file-input.d.ts.map +1 -0
  60. package/dist/copy-file-input.js +69 -0
  61. package/dist/copy-policy.d.ts +3 -0
  62. package/dist/copy-policy.d.ts.map +1 -0
  63. package/dist/copy-policy.js +8 -0
  64. package/dist/copy-publication.d.ts +10 -1
  65. package/dist/copy-publication.d.ts.map +1 -1
  66. package/dist/copy-publication.js +27 -0
  67. package/dist/copy-tree-portable.d.ts +9 -0
  68. package/dist/copy-tree-portable.d.ts.map +1 -0
  69. package/dist/copy-tree-portable.js +222 -0
  70. package/dist/copy.d.ts +16 -0
  71. package/dist/copy.d.ts.map +1 -0
  72. package/dist/copy.js +125 -0
  73. package/dist/directory-durability.d.ts.map +1 -1
  74. package/dist/directory-durability.js +5 -4
  75. package/dist/directory-guard.d.ts +11 -1
  76. package/dist/directory-guard.d.ts.map +1 -1
  77. package/dist/directory-guard.js +53 -11
  78. package/dist/durability.d.ts +1 -1
  79. package/dist/durability.d.ts.map +1 -1
  80. package/dist/durability.js +1 -1
  81. package/dist/error-detail.d.ts.map +1 -1
  82. package/dist/error-detail.js +4 -1
  83. package/dist/file-handle-transfer.d.ts +14 -0
  84. package/dist/file-handle-transfer.d.ts.map +1 -0
  85. package/dist/file-handle-transfer.js +64 -0
  86. package/dist/file-hash.d.ts +9 -2
  87. package/dist/file-hash.d.ts.map +1 -1
  88. package/dist/file-hash.js +135 -39
  89. package/dist/file-lock-sync.d.ts.map +1 -1
  90. package/dist/file-lock-sync.js +8 -4
  91. package/dist/file-store-boundary.d.ts.map +1 -1
  92. package/dist/file-store-boundary.js +7 -5
  93. package/dist/file-store-path.d.ts +3 -0
  94. package/dist/file-store-path.d.ts.map +1 -0
  95. package/dist/file-store-path.js +27 -0
  96. package/dist/file-store-prune.d.ts.map +1 -1
  97. package/dist/file-store-prune.js +6 -4
  98. package/dist/file-store-sync-write.d.ts.map +1 -1
  99. package/dist/file-store-sync-write.js +56 -44
  100. package/dist/file-store.d.ts.map +1 -1
  101. package/dist/file-store.js +2 -18
  102. package/dist/filename.d.ts.map +1 -1
  103. package/dist/filename.js +4 -13
  104. package/dist/guarded-mkdir.d.ts +1 -0
  105. package/dist/guarded-mkdir.d.ts.map +1 -1
  106. package/dist/guarded-mkdir.js +4 -2
  107. package/dist/guarded-mutation.d.ts +2 -0
  108. package/dist/guarded-mutation.d.ts.map +1 -1
  109. package/dist/guarded-mutation.js +8 -4
  110. package/dist/guest-dispatch-python.d.ts +2 -0
  111. package/dist/guest-dispatch-python.d.ts.map +1 -0
  112. package/dist/guest-dispatch-python.js +117 -0
  113. package/dist/guest-native-python.d.ts +4 -0
  114. package/dist/guest-native-python.d.ts.map +1 -0
  115. package/dist/guest-native-python.js +135 -0
  116. package/dist/guest.d.ts +9 -0
  117. package/dist/guest.d.ts.map +1 -0
  118. package/dist/guest.js +413 -0
  119. package/dist/home-dir.d.ts.map +1 -1
  120. package/dist/home-dir.js +9 -7
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/install-path.d.ts.map +1 -1
  124. package/dist/install-path.js +5 -8
  125. package/dist/json-durable-queue-directory.js +3 -3
  126. package/dist/json-durable-queue.d.ts.map +1 -1
  127. package/dist/json-durable-queue.js +19 -18
  128. package/dist/json.d.ts.map +1 -1
  129. package/dist/json.js +2 -1
  130. package/dist/local-roots.d.ts.map +1 -1
  131. package/dist/local-roots.js +24 -25
  132. package/dist/move-path-stage.d.ts +8 -0
  133. package/dist/move-path-stage.d.ts.map +1 -0
  134. package/dist/move-path-stage.js +56 -0
  135. package/dist/move-path.d.ts.map +1 -1
  136. package/dist/move-path.js +30 -34
  137. package/dist/mutation-authority.d.ts +9 -0
  138. package/dist/mutation-authority.d.ts.map +1 -0
  139. package/dist/mutation-authority.js +36 -0
  140. package/dist/native-binding.d.ts +29 -1
  141. package/dist/native-binding.d.ts.map +1 -1
  142. package/dist/native-operations.d.ts +1 -1
  143. package/dist/native-operations.d.ts.map +1 -1
  144. package/dist/native-operations.js +11 -5
  145. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  146. package/dist/native-pinned-write-windows.js +19 -7
  147. package/dist/native-pinned-write.d.ts.map +1 -1
  148. package/dist/native-pinned-write.js +3 -1
  149. package/dist/native-staged-file.d.ts +3 -3
  150. package/dist/native-staged-file.d.ts.map +1 -1
  151. package/dist/native-staged-file.js +35 -8
  152. package/dist/opened-realpath.d.ts.map +1 -1
  153. package/dist/opened-realpath.js +5 -4
  154. package/dist/output.d.ts +2 -0
  155. package/dist/output.d.ts.map +1 -1
  156. package/dist/output.js +2 -0
  157. package/dist/overwrite-file-handle.d.ts +8 -0
  158. package/dist/overwrite-file-handle.d.ts.map +1 -0
  159. package/dist/overwrite-file-handle.js +42 -0
  160. package/dist/path-case.d.ts +7 -0
  161. package/dist/path-case.d.ts.map +1 -0
  162. package/dist/path-case.js +136 -0
  163. package/dist/path.d.ts.map +1 -1
  164. package/dist/path.js +5 -2
  165. package/dist/permissions-windows.d.ts +1 -1
  166. package/dist/permissions-windows.d.ts.map +1 -1
  167. package/dist/permissions-windows.js +85 -53
  168. package/dist/pinned-open.d.ts.map +1 -1
  169. package/dist/pinned-open.js +3 -1
  170. package/dist/pinned-operation.js +1 -1
  171. package/dist/pinned-write.d.ts +7 -3
  172. package/dist/pinned-write.d.ts.map +1 -1
  173. package/dist/pinned-write.js +51 -30
  174. package/dist/positional-read.d.ts +9 -0
  175. package/dist/positional-read.d.ts.map +1 -0
  176. package/dist/positional-read.js +36 -0
  177. package/dist/private-temp-workspace.d.ts.map +1 -1
  178. package/dist/private-temp-workspace.js +6 -4
  179. package/dist/publish-copy-stage.d.ts +13 -0
  180. package/dist/publish-copy-stage.d.ts.map +1 -0
  181. package/dist/publish-copy-stage.js +47 -0
  182. package/dist/publish-file.d.ts.map +1 -1
  183. package/dist/publish-file.js +6 -5
  184. package/dist/realpath.d.ts +4 -0
  185. package/dist/realpath.d.ts.map +1 -0
  186. package/dist/realpath.js +43 -0
  187. package/dist/recursive-mkdir-path.d.ts +3 -0
  188. package/dist/recursive-mkdir-path.d.ts.map +1 -0
  189. package/dist/recursive-mkdir-path.js +8 -0
  190. package/dist/replace-directory.d.ts.map +1 -1
  191. package/dist/replace-directory.js +2 -1
  192. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  193. package/dist/replace-file-copy-fallback.js +23 -31
  194. package/dist/replace-file-copy-source.d.ts.map +1 -1
  195. package/dist/replace-file-copy-source.js +7 -12
  196. package/dist/replace-file-mode.d.ts +3 -0
  197. package/dist/replace-file-mode.d.ts.map +1 -0
  198. package/dist/replace-file-mode.js +10 -0
  199. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  200. package/dist/replace-file-temp-owner.js +6 -2
  201. package/dist/replace-file.d.ts +1 -0
  202. package/dist/replace-file.d.ts.map +1 -1
  203. package/dist/replace-file.js +14 -8
  204. package/dist/root-context.d.ts +3 -0
  205. package/dist/root-context.d.ts.map +1 -1
  206. package/dist/root-context.js +53 -28
  207. package/dist/root-create-input.d.ts +10 -0
  208. package/dist/root-create-input.d.ts.map +1 -0
  209. package/dist/root-create-input.js +80 -0
  210. package/dist/root-directory-list.d.ts +26 -0
  211. package/dist/root-directory-list.d.ts.map +1 -0
  212. package/dist/root-directory-list.js +219 -0
  213. package/dist/root-entries.d.ts +11 -0
  214. package/dist/root-entries.d.ts.map +1 -0
  215. package/dist/root-entries.js +61 -0
  216. package/dist/root-errors.d.ts +6 -5
  217. package/dist/root-errors.d.ts.map +1 -1
  218. package/dist/root-errors.js +16 -12
  219. package/dist/root-file.d.ts +2 -0
  220. package/dist/root-file.d.ts.map +1 -1
  221. package/dist/root-file.js +12 -4
  222. package/dist/root-impl.d.ts +17 -51
  223. package/dist/root-impl.d.ts.map +1 -1
  224. package/dist/root-impl.js +358 -234
  225. package/dist/root-options.d.ts +77 -0
  226. package/dist/root-options.d.ts.map +1 -0
  227. package/dist/root-options.js +18 -0
  228. package/dist/root-path-existing.d.ts +5 -0
  229. package/dist/root-path-existing.d.ts.map +1 -1
  230. package/dist/root-path-existing.js +59 -3
  231. package/dist/root-path-symlink.d.ts.map +1 -1
  232. package/dist/root-path-symlink.js +3 -2
  233. package/dist/root-path.d.ts +1 -0
  234. package/dist/root-path.d.ts.map +1 -1
  235. package/dist/root-path.js +74 -162
  236. package/dist/root-paths.d.ts.map +1 -1
  237. package/dist/root-paths.js +13 -9
  238. package/dist/root-remove.d.ts +5 -0
  239. package/dist/root-remove.d.ts.map +1 -0
  240. package/dist/root-remove.js +286 -0
  241. package/dist/root-symlink-policy.d.ts +14 -0
  242. package/dist/root-symlink-policy.d.ts.map +1 -0
  243. package/dist/root-symlink-policy.js +34 -0
  244. package/dist/root-walk.d.ts +5 -4
  245. package/dist/root-walk.d.ts.map +1 -1
  246. package/dist/root-walk.js +99 -50
  247. package/dist/root-write-mode.d.ts.map +1 -1
  248. package/dist/root-write-mode.js +3 -5
  249. package/dist/root.d.ts +4 -1
  250. package/dist/root.d.ts.map +1 -1
  251. package/dist/secret-file.d.ts.map +1 -1
  252. package/dist/secret-file.js +2 -1
  253. package/dist/secret-read-async.d.ts.map +1 -1
  254. package/dist/secret-read-async.js +2 -1
  255. package/dist/secure-file.d.ts.map +1 -1
  256. package/dist/secure-file.js +25 -8
  257. package/dist/secure-temp-dir.d.ts.map +1 -1
  258. package/dist/secure-temp-dir.js +2 -1
  259. package/dist/sibling-staged-file.d.ts +1 -0
  260. package/dist/sibling-staged-file.d.ts.map +1 -1
  261. package/dist/sibling-staged-file.js +42 -8
  262. package/dist/sibling-temp.d.ts +2 -0
  263. package/dist/sibling-temp.d.ts.map +1 -1
  264. package/dist/sibling-temp.js +6 -4
  265. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  266. package/dist/sidecar-lock-acquire.js +17 -5
  267. package/dist/sidecar-lock-policy.d.ts +2 -0
  268. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  269. package/dist/sidecar-lock-policy.js +20 -1
  270. package/dist/staged-directory.d.ts.map +1 -1
  271. package/dist/staged-directory.js +4 -3
  272. package/dist/temp-cleanup.d.ts.map +1 -1
  273. package/dist/temp-cleanup.js +3 -2
  274. package/dist/temp-target.d.ts +14 -12
  275. package/dist/temp-target.d.ts.map +1 -1
  276. package/dist/temp-target.js +12 -6
  277. package/dist/timing.d.ts +1 -0
  278. package/dist/timing.d.ts.map +1 -1
  279. package/dist/timing.js +25 -6
  280. package/dist/trash.d.ts.map +1 -1
  281. package/dist/trash.js +9 -7
  282. package/dist/unicode-path.d.ts +3 -0
  283. package/dist/unicode-path.d.ts.map +1 -0
  284. package/dist/unicode-path.js +13 -0
  285. package/dist/walk.d.ts.map +1 -1
  286. package/dist/walk.js +9 -28
  287. package/dist/windows-owner.d.ts +9 -12
  288. package/dist/windows-owner.d.ts.map +1 -1
  289. package/dist/windows-owner.js +24 -58
  290. package/dist/write-file-handle.d.ts +7 -0
  291. package/dist/write-file-handle.d.ts.map +1 -0
  292. package/dist/write-file-handle.js +26 -0
  293. package/docs/advanced.md +22 -4
  294. package/docs/archive.md +44 -9
  295. package/docs/atomic.md +24 -1
  296. package/docs/config.md +1 -0
  297. package/docs/contributing.md +36 -1
  298. package/docs/copy.md +155 -0
  299. package/docs/directory-identity.md +85 -0
  300. package/docs/durability.md +53 -3
  301. package/docs/entries.md +109 -0
  302. package/docs/errors.md +4 -4
  303. package/docs/file-store.md +15 -0
  304. package/docs/guest.md +141 -0
  305. package/docs/in-place-write.md +81 -0
  306. package/docs/index.md +2 -0
  307. package/docs/install.md +31 -0
  308. package/docs/local-roots.md +8 -1
  309. package/docs/native-helper.md +10 -3
  310. package/docs/native.md +18 -1
  311. package/docs/output.md +32 -6
  312. package/docs/path-case.md +64 -0
  313. package/docs/path-scope.md +1 -1
  314. package/docs/permissions.md +29 -10
  315. package/docs/positional-read.md +63 -0
  316. package/docs/public-api.md +31 -2
  317. package/docs/root.md +196 -6
  318. package/docs/secure-file.md +2 -0
  319. package/docs/security-model.md +14 -0
  320. package/docs/sidecar-lock.md +12 -3
  321. package/docs/temp.md +35 -6
  322. package/docs/timing.md +2 -0
  323. package/docs/types.md +19 -12
  324. package/docs/walk.md +54 -5
  325. package/docs/writing.md +173 -5
  326. package/package.json +19 -8
package/dist/guest.js ADDED
@@ -0,0 +1,413 @@
1
+ // Adapted from OpenClaw's guest filesystem program. Copyright OpenClaw Foundation; MIT.
2
+ import { GUEST_FILESYSTEM_DISPATCH_PYTHON } from "./guest-dispatch-python.js";
3
+ import { GUEST_FILESYSTEM_CREATE_EXCLUSIVE_PYTHON, GUEST_FILESYSTEM_CREATE_STAGING_PYTHON, GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON, } from "./guest-native-python.js";
4
+ /** Exclusive create found an existing destination, including a lost publication race. */
5
+ export const GUEST_FILESYSTEM_CREATE_EXISTS_EXIT_CODE = 17;
6
+ /** Read found a missing parent or file after the admitted root was opened. */
7
+ export const GUEST_FILESYSTEM_READ_NOT_FOUND_EXIT_CODE = 2;
8
+ /** Python 3 source for the descriptor-relative guest filesystem protocol. See docs/guest.md. */
9
+ export const GUEST_FILESYSTEM_PYTHON = [
10
+ `SANDBOX_CREATE_EXISTS_EXIT_CODE = ${GUEST_FILESYSTEM_CREATE_EXISTS_EXIT_CODE}`,
11
+ `SANDBOX_READ_NOT_FOUND_EXIT_CODE = ${GUEST_FILESYSTEM_READ_NOT_FOUND_EXIT_CODE}`,
12
+ "import ctypes",
13
+ "import errno",
14
+ "import json",
15
+ "import os",
16
+ "import secrets",
17
+ "import stat",
18
+ "import sys",
19
+ "",
20
+ "operation = sys.argv[1]",
21
+ "",
22
+ "DIR_FLAGS = os.O_RDONLY",
23
+ "if hasattr(os, 'O_DIRECTORY'):",
24
+ " DIR_FLAGS |= os.O_DIRECTORY",
25
+ "if hasattr(os, 'O_NOFOLLOW'):",
26
+ " DIR_FLAGS |= os.O_NOFOLLOW",
27
+ "",
28
+ "READ_FLAGS = os.O_RDONLY",
29
+ "if hasattr(os, 'O_NOFOLLOW'):",
30
+ " READ_FLAGS |= os.O_NOFOLLOW",
31
+ "if hasattr(os, 'O_NONBLOCK'):",
32
+ " READ_FLAGS |= os.O_NONBLOCK",
33
+ "",
34
+ "WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL",
35
+ "if hasattr(os, 'O_NOFOLLOW'):",
36
+ " WRITE_FLAGS |= os.O_NOFOLLOW",
37
+ "",
38
+ "def split_relative(path_value):",
39
+ " segments = []",
40
+ " for segment in path_value.split('/'):",
41
+ " if not segment or segment == '.':",
42
+ " continue",
43
+ " if segment == '..':",
44
+ " raise OSError(errno.EPERM, 'path traversal is not allowed', segment)",
45
+ " segments.append(segment)",
46
+ " return segments",
47
+ "",
48
+ "def open_dir(path_value, dir_fd=None):",
49
+ " return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)",
50
+ "",
51
+ "def walk_dir(root_fd, rel_path, mkdir_enabled):",
52
+ " current_fd = os.dup(root_fd)",
53
+ " try:",
54
+ " for segment in split_relative(rel_path):",
55
+ " try:",
56
+ " next_fd = open_dir(segment, dir_fd=current_fd)",
57
+ " except FileNotFoundError:",
58
+ " if not mkdir_enabled:",
59
+ " raise",
60
+ " os.mkdir(segment, 0o777, dir_fd=current_fd)",
61
+ " next_fd = open_dir(segment, dir_fd=current_fd)",
62
+ " os.close(current_fd)",
63
+ " current_fd = next_fd",
64
+ " return current_fd",
65
+ " except Exception:",
66
+ " os.close(current_fd)",
67
+ " raise",
68
+ "",
69
+ "def create_temp_file(parent_fd):",
70
+ " prefix = '.openclaw-write-'",
71
+ " for _ in range(128):",
72
+ " candidate = prefix + secrets.token_hex(6)",
73
+ " try:",
74
+ " fd = os.open(candidate, WRITE_FLAGS, 0o600, dir_fd=parent_fd)",
75
+ " return candidate, fd",
76
+ " except FileExistsError:",
77
+ " continue",
78
+ " raise RuntimeError('failed to allocate sandbox temp file')",
79
+ "",
80
+ "def create_temp_dir(parent_fd, mode):",
81
+ " prefix = '.openclaw-move-'",
82
+ " for _ in range(128):",
83
+ " candidate = prefix + secrets.token_hex(6)",
84
+ " try:",
85
+ " os.mkdir(candidate, mode, dir_fd=parent_fd)",
86
+ " return candidate",
87
+ " except FileExistsError:",
88
+ " continue",
89
+ " raise RuntimeError('failed to allocate sandbox temp directory')",
90
+ "",
91
+ GUEST_FILESYSTEM_CREATE_STAGING_PYTHON,
92
+ "",
93
+ "def existing_regular_file_mode(parent_fd, basename):",
94
+ " try:",
95
+ " target_stat = os.lstat(basename, dir_fd=parent_fd)",
96
+ " except FileNotFoundError:",
97
+ " return None",
98
+ " if stat.S_ISREG(target_stat.st_mode):",
99
+ " return stat.S_IMODE(target_stat.st_mode)",
100
+ " return None",
101
+ "",
102
+ "def write_all(file_fd, data):",
103
+ " view = memoryview(data)",
104
+ " while view:",
105
+ " written = os.write(file_fd, view)",
106
+ " if written <= 0:",
107
+ " raise OSError(errno.EIO, 'failed to write copied file')",
108
+ " view = view[written:]",
109
+ "",
110
+ GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON,
111
+ "",
112
+ "def copy_regular_file(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
113
+ " src_fd = os.open(src_basename, READ_FLAGS, dir_fd=src_parent_fd)",
114
+ " dst_fd = None",
115
+ " destination_created = False",
116
+ " copy_completed = False",
117
+ " try:",
118
+ " src_stat = os.fstat(src_fd)",
119
+ " if not stat.S_ISREG(src_stat.st_mode):",
120
+ " raise OSError(errno.EPERM, 'only regular files are allowed', src_basename)",
121
+ " if src_stat.st_nlink > 1:",
122
+ " raise OSError(errno.EPERM, 'hardlinked file is not allowed', src_basename)",
123
+ " dst_fd = os.open(dst_basename, WRITE_FLAGS, stat.S_IMODE(src_stat.st_mode), dir_fd=dst_parent_fd)",
124
+ " destination_created = True",
125
+ " while True:",
126
+ " chunk = os.read(src_fd, 65536)",
127
+ " if not chunk:",
128
+ " break",
129
+ " write_all(dst_fd, chunk)",
130
+ " try:",
131
+ " os.fchmod(dst_fd, stat.S_IMODE(src_stat.st_mode))",
132
+ " except AttributeError:",
133
+ " pass",
134
+ " os.fsync(dst_fd)",
135
+ " copy_completed = True",
136
+ " return entry_identity(src_stat)",
137
+ " finally:",
138
+ " if dst_fd is not None:",
139
+ " os.close(dst_fd)",
140
+ " if destination_created and not copy_completed:",
141
+ " try:",
142
+ " os.unlink(dst_basename, dir_fd=dst_parent_fd)",
143
+ " except FileNotFoundError:",
144
+ " pass",
145
+ " os.close(src_fd)",
146
+ "",
147
+ "def copy_file_atomic(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
148
+ " prefix = '.openclaw-copy-'",
149
+ " temp_name = None",
150
+ " try:",
151
+ " for _ in range(128):",
152
+ " candidate = prefix + secrets.token_hex(6)",
153
+ " try:",
154
+ " copy_regular_file(src_parent_fd, src_basename, dst_parent_fd, candidate)",
155
+ " temp_name = candidate",
156
+ " break",
157
+ " except FileExistsError:",
158
+ " continue",
159
+ " if temp_name is None:",
160
+ " raise RuntimeError('failed to allocate sandbox copy temp file')",
161
+ " os.replace(temp_name, dst_basename, src_dir_fd=dst_parent_fd, dst_dir_fd=dst_parent_fd)",
162
+ " temp_name = None",
163
+ " os.fsync(dst_parent_fd)",
164
+ " finally:",
165
+ " if temp_name is not None:",
166
+ " try:",
167
+ " os.unlink(temp_name, dir_fd=dst_parent_fd)",
168
+ " except FileNotFoundError:",
169
+ " pass",
170
+ "",
171
+ "def write_atomic(parent_fd, basename, stdin_buffer):",
172
+ " target_mode = existing_regular_file_mode(parent_fd, basename)",
173
+ " temp_fd = None",
174
+ " temp_name = None",
175
+ " try:",
176
+ " temp_name, temp_fd = create_temp_file(parent_fd)",
177
+ " while True:",
178
+ " chunk = stdin_buffer.read(65536)",
179
+ " if not chunk:",
180
+ " break",
181
+ " write_all(temp_fd, chunk)",
182
+ " if target_mode is not None:",
183
+ " try:",
184
+ " os.fchmod(temp_fd, target_mode)",
185
+ " except AttributeError:",
186
+ " pass",
187
+ " os.fsync(temp_fd)",
188
+ " os.close(temp_fd)",
189
+ " temp_fd = None",
190
+ " os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)",
191
+ " temp_name = None",
192
+ " os.fsync(parent_fd)",
193
+ " finally:",
194
+ " if temp_fd is not None:",
195
+ " os.close(temp_fd)",
196
+ " if temp_name is not None:",
197
+ " try:",
198
+ " os.unlink(temp_name, dir_fd=parent_fd)",
199
+ " except FileNotFoundError:",
200
+ " pass",
201
+ "",
202
+ GUEST_FILESYSTEM_CREATE_EXCLUSIVE_PYTHON,
203
+ "",
204
+ "def read_file_impl(parent_fd, basename, max_bytes):",
205
+ " file_fd = os.open(basename, READ_FLAGS, dir_fd=parent_fd)",
206
+ " try:",
207
+ " file_stat = os.fstat(file_fd)",
208
+ " if not stat.S_ISREG(file_stat.st_mode):",
209
+ " raise OSError(errno.EPERM, 'only regular files are allowed', basename)",
210
+ " if file_stat.st_nlink > 1:",
211
+ " raise OSError(errno.EPERM, 'hardlinked file is not allowed', basename)",
212
+ " if max_bytes is not None and file_stat.st_size > max_bytes:",
213
+ " raise OSError(errno.EFBIG, 'file exceeds bounded read limit', basename)",
214
+ " bytes_read = 0",
215
+ " while True:",
216
+ " read_size = 65536 if max_bytes is None else min(65536, max_bytes - bytes_read + 1)",
217
+ " chunk = os.read(file_fd, read_size)",
218
+ " if not chunk:",
219
+ " break",
220
+ " bytes_read += len(chunk)",
221
+ " if max_bytes is not None and bytes_read > max_bytes:",
222
+ " raise OSError(errno.EFBIG, 'file exceeds bounded read limit', basename)",
223
+ " write_all(1, chunk)",
224
+ " finally:",
225
+ " os.close(file_fd)",
226
+ "",
227
+ "def read_file(parent_fd, basename):",
228
+ " read_file_impl(parent_fd, basename, None)",
229
+ "",
230
+ "def read_file_bounded(parent_fd, basename, max_bytes):",
231
+ " if max_bytes < 0:",
232
+ " raise OSError(errno.EINVAL, 'read limit must be non-negative', basename)",
233
+ " read_file_impl(parent_fd, basename, max_bytes)",
234
+ "",
235
+ "def remove_tree(parent_fd, basename):",
236
+ " entry_stat = os.lstat(basename, dir_fd=parent_fd)",
237
+ " if not stat.S_ISDIR(entry_stat.st_mode) or stat.S_ISLNK(entry_stat.st_mode):",
238
+ " os.unlink(basename, dir_fd=parent_fd)",
239
+ " return",
240
+ " dir_fd = open_dir(basename, dir_fd=parent_fd)",
241
+ " try:",
242
+ " for child in os.listdir(dir_fd):",
243
+ " remove_tree(dir_fd, child)",
244
+ " finally:",
245
+ " os.close(dir_fd)",
246
+ " os.rmdir(basename, dir_fd=parent_fd)",
247
+ "",
248
+ "def entry_identity(entry_stat):",
249
+ " return (",
250
+ " entry_stat.st_dev,",
251
+ " entry_stat.st_ino,",
252
+ " entry_stat.st_mode,",
253
+ " entry_stat.st_size,",
254
+ " getattr(entry_stat, 'st_mtime_ns', int(entry_stat.st_mtime * 1000000000)),",
255
+ " getattr(entry_stat, 'st_ctime_ns', int(entry_stat.st_ctime * 1000000000)),",
256
+ " )",
257
+ "",
258
+ "def inode_identity(entry_stat):",
259
+ " return (entry_stat.st_dev, entry_stat.st_ino)",
260
+ "",
261
+ "def same_identity(expected, entry_stat):",
262
+ " return expected == entry_identity(entry_stat)",
263
+ "",
264
+ "def source_changed_error(basename):",
265
+ " return OSError(getattr(errno, 'ESTALE', errno.EIO), 'source changed during move fallback cleanup', basename)",
266
+ "",
267
+ "def copy_entry(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
268
+ " src_stat = os.lstat(src_basename, dir_fd=src_parent_fd)",
269
+ " if stat.S_ISDIR(src_stat.st_mode) and not stat.S_ISLNK(src_stat.st_mode):",
270
+ " os.mkdir(dst_basename, stat.S_IMODE(src_stat.st_mode) or 0o755, dir_fd=dst_parent_fd)",
271
+ " copied_children = []",
272
+ " src_dir_fd = None",
273
+ " dst_dir_fd = None",
274
+ " try:",
275
+ " src_dir_fd = open_dir(src_basename, dir_fd=src_parent_fd)",
276
+ " src_stat = os.fstat(src_dir_fd)",
277
+ " dst_dir_fd = open_dir(dst_basename, dir_fd=dst_parent_fd)",
278
+ " for child in os.listdir(src_dir_fd):",
279
+ " copied_children.append((child, copy_entry(src_dir_fd, child, dst_dir_fd, child)))",
280
+ " except Exception:",
281
+ " if dst_dir_fd is not None:",
282
+ " os.close(dst_dir_fd)",
283
+ " dst_dir_fd = None",
284
+ " try:",
285
+ " remove_tree(dst_parent_fd, dst_basename)",
286
+ " except FileNotFoundError:",
287
+ " pass",
288
+ " raise",
289
+ " finally:",
290
+ " if src_dir_fd is not None:",
291
+ " os.close(src_dir_fd)",
292
+ " if dst_dir_fd is not None:",
293
+ " os.close(dst_dir_fd)",
294
+ " return ('dir', entry_identity(src_stat), copied_children)",
295
+ " if stat.S_ISLNK(src_stat.st_mode):",
296
+ " link_target = os.readlink(src_basename, dir_fd=src_parent_fd)",
297
+ " os.symlink(link_target, dst_basename, dir_fd=dst_parent_fd)",
298
+ " return ('leaf', entry_identity(src_stat), None)",
299
+ " return ('leaf', copy_regular_file(src_parent_fd, src_basename, dst_parent_fd, dst_basename), None)",
300
+ "",
301
+ "def remove_copied_entry(parent_fd, basename, manifest):",
302
+ " kind, expected_identity, children = manifest",
303
+ " current_stat = os.lstat(basename, dir_fd=parent_fd)",
304
+ " if not same_identity(expected_identity, current_stat):",
305
+ " raise source_changed_error(basename)",
306
+ " if kind != 'dir':",
307
+ " os.unlink(basename, dir_fd=parent_fd)",
308
+ " return",
309
+ " dir_fd = open_dir(basename, dir_fd=parent_fd)",
310
+ " try:",
311
+ " for child, child_manifest in children:",
312
+ " remove_copied_entry(dir_fd, child, child_manifest)",
313
+ " finally:",
314
+ " os.close(dir_fd)",
315
+ " os.rmdir(basename, dir_fd=parent_fd)",
316
+ "",
317
+ "def move_entry(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
318
+ " try:",
319
+ " os.rename(src_basename, dst_basename, src_dir_fd=src_parent_fd, dst_dir_fd=dst_parent_fd)",
320
+ " os.fsync(dst_parent_fd)",
321
+ " os.fsync(src_parent_fd)",
322
+ " return",
323
+ " except OSError as err:",
324
+ " if err.errno != errno.EXDEV:",
325
+ " raise",
326
+ " src_stat = os.lstat(src_basename, dir_fd=src_parent_fd)",
327
+ " if stat.S_ISDIR(src_stat.st_mode) and not stat.S_ISLNK(src_stat.st_mode):",
328
+ " temp_dir_name = create_temp_dir(dst_parent_fd, stat.S_IMODE(src_stat.st_mode) or 0o755)",
329
+ " copied_children = []",
330
+ " temp_dir_fd = None",
331
+ " src_dir_fd = None",
332
+ " try:",
333
+ " temp_dir_fd = open_dir(temp_dir_name, dir_fd=dst_parent_fd)",
334
+ " src_dir_fd = open_dir(src_basename, dir_fd=src_parent_fd)",
335
+ " src_stat = os.fstat(src_dir_fd)",
336
+ " for child in os.listdir(src_dir_fd):",
337
+ " copied_children.append((child, copy_entry(src_dir_fd, child, temp_dir_fd, child)))",
338
+ " os.close(src_dir_fd)",
339
+ " src_dir_fd = None",
340
+ " os.close(temp_dir_fd)",
341
+ " temp_dir_fd = None",
342
+ " os.rename(temp_dir_name, dst_basename, src_dir_fd=dst_parent_fd, dst_dir_fd=dst_parent_fd)",
343
+ " except Exception:",
344
+ " if src_dir_fd is not None:",
345
+ " os.close(src_dir_fd)",
346
+ " if temp_dir_fd is not None:",
347
+ " os.close(temp_dir_fd)",
348
+ " try:",
349
+ " remove_tree(dst_parent_fd, temp_dir_name)",
350
+ " except FileNotFoundError:",
351
+ " pass",
352
+ " raise",
353
+ " remove_copied_entry(src_parent_fd, src_basename, ('dir', entry_identity(src_stat), copied_children))",
354
+ " os.fsync(dst_parent_fd)",
355
+ " os.fsync(src_parent_fd)",
356
+ " return",
357
+ " if stat.S_ISLNK(src_stat.st_mode):",
358
+ " link_target = os.readlink(src_basename, dir_fd=src_parent_fd)",
359
+ " try:",
360
+ " os.unlink(dst_basename, dir_fd=dst_parent_fd)",
361
+ " except FileNotFoundError:",
362
+ " pass",
363
+ " os.symlink(link_target, dst_basename, dir_fd=dst_parent_fd)",
364
+ " os.unlink(src_basename, dir_fd=src_parent_fd)",
365
+ " os.fsync(dst_parent_fd)",
366
+ " os.fsync(src_parent_fd)",
367
+ " return",
368
+ " src_fd = os.open(src_basename, READ_FLAGS, dir_fd=src_parent_fd)",
369
+ " temp_fd = None",
370
+ " temp_name = None",
371
+ " try:",
372
+ " src_file_stat = os.fstat(src_fd)",
373
+ " if not stat.S_ISREG(src_file_stat.st_mode):",
374
+ " raise OSError(errno.EPERM, 'only regular files are allowed', src_basename)",
375
+ " if src_file_stat.st_nlink > 1:",
376
+ " raise OSError(errno.EPERM, 'hardlinked file is not allowed', src_basename)",
377
+ " temp_name, temp_fd = create_temp_file(dst_parent_fd)",
378
+ " while True:",
379
+ " chunk = os.read(src_fd, 65536)",
380
+ " if not chunk:",
381
+ " break",
382
+ " write_all(temp_fd, chunk)",
383
+ " try:",
384
+ " os.fchmod(temp_fd, stat.S_IMODE(src_stat.st_mode))",
385
+ " except AttributeError:",
386
+ " pass",
387
+ " os.fsync(temp_fd)",
388
+ " os.close(temp_fd)",
389
+ " temp_fd = None",
390
+ " os.replace(temp_name, dst_basename, src_dir_fd=dst_parent_fd, dst_dir_fd=dst_parent_fd)",
391
+ " temp_name = None",
392
+ " os.unlink(src_basename, dir_fd=src_parent_fd)",
393
+ " os.fsync(dst_parent_fd)",
394
+ " os.fsync(src_parent_fd)",
395
+ " finally:",
396
+ " if temp_fd is not None:",
397
+ " os.close(temp_fd)",
398
+ " if temp_name is not None:",
399
+ " try:",
400
+ " os.unlink(temp_name, dir_fd=dst_parent_fd)",
401
+ " except FileNotFoundError:",
402
+ " pass",
403
+ " os.close(src_fd)",
404
+ "",
405
+ "basename_indexes = (4, 7) if operation in ('copy', 'rename') else (4,) if operation in ('read', 'write', 'create', 'remove') else ()",
406
+ "for basename_index in basename_indexes:",
407
+ " basename = sys.argv[basename_index]",
408
+ " if not basename or basename in ('.', '..') or '/' in basename or '\\x00' in basename:",
409
+ " raise OSError(errno.EINVAL, 'invalid basename', basename)",
410
+ "",
411
+ GUEST_FILESYSTEM_DISPATCH_PYTHON,
412
+ ].join("\n");
413
+ export { GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON };
@@ -1 +1 @@
1
- {"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"AAeA,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,GAAG,SAAS,CAGpB;AAyCD,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,CAER;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAYR;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAcR"}
1
+ {"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"AAoBA,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,GAAG,SAAS,CAGpB;AAwCD,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,CAER;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAYR;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAaR"}
package/dist/home-dir.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
3
  import { normalizeOptionalString } from "./string-coerce.js";
4
+ function hasHomePrefix(input) {
5
+ return input === "~" || input.startsWith("~/") ||
6
+ (path.sep === "\\" && input.startsWith("~\\"));
7
+ }
4
8
  function normalize(value) {
5
9
  const trimmed = normalizeOptionalString(value);
6
10
  if (!trimmed) {
@@ -20,8 +24,7 @@ function resolveRawHomeDir(env, homedir) {
20
24
  if (!explicitHome) {
21
25
  return resolveRawOsHomeDir(env, homedir);
22
26
  }
23
- const segments = path.normalize(explicitHome).split(path.sep);
24
- if (segments[0] !== "~") {
27
+ if (!hasHomePrefix(explicitHome)) {
25
28
  return explicitHome;
26
29
  }
27
30
  // OPENCLAW_HOME starts with "~"; expand against the os home dir. Fall
@@ -56,8 +59,7 @@ export function resolveRequiredHomeDir(env = process.env, homedir = os.homedir)
56
59
  return resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd());
57
60
  }
58
61
  export function expandHomePrefix(input, opts) {
59
- const segments = path.normalize(input).split(path.sep);
60
- if (segments[0] !== "~") {
62
+ if (!hasHomePrefix(input)) {
61
63
  return input;
62
64
  }
63
65
  const home = normalize(opts?.home) ??
@@ -65,14 +67,14 @@ export function expandHomePrefix(input, opts) {
65
67
  if (!home) {
66
68
  return input;
67
69
  }
68
- return path.join(home, ...segments.slice(1));
70
+ // Expand before normalizing so a following .. traverses the actual home.
71
+ return path.join(home, input.slice(2));
69
72
  }
70
73
  export function resolveHomeRelativePath(input, opts) {
71
74
  if (!input) {
72
75
  return input;
73
76
  }
74
- const segments = path.normalize(input).split(path.sep);
75
- if (segments[0] !== "~") {
77
+ if (!hasHomePrefix(input)) {
76
78
  return path.resolve(input);
77
79
  }
78
80
  const expanded = expandHomePrefix(input, {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { FsSafeError, categorizeFsSafeError, type FsSafeErrorCategory, type FsSafeErrorCode, type FsSafeErrorDetails, } from "./errors.js";
2
- export { DEFAULT_ROOT_MAX_BYTES, root, type DenyMutationPolicy, type HardlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCreateJsonOptions, type RootCreateOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type RootWalkDataEntry, type RootWalkDataEntryKind, type RootWalkDirectoryErrorBehavior, type RootWalkEntry, type RootWalkEntryFilter, type RootWalkEntryFilterResult, type RootWalkEntryKind, type RootWalkLimitBehavior, type RootWalkOptions, type RootWalkSymlinkPolicy, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root.js";
2
+ export { DEFAULT_ROOT_MAX_BYTES, root, type DenyMutationPolicy, type HardlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCopySource, type RootCopyPublicationReceipt, type CopyCloneMode, type RootCreateJsonOptions, type RootCreateOptions, type RootCreateStreamOptions, type RootDefaults, type RootEntriesOptions, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type RootWalkDataEntry, type RootWalkDataEntryKind, type RootWalkDirectoryErrorBehavior, type RootWalkEntry, type RootWalkEntryFilter, type RootWalkEntryFilterResult, type RootWalkEntryKind, type RootWalkLimitBehavior, type RootWalkOptions, type RootWalkSymlinkPolicy, type SymlinkPolicy, type MutationSymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root.js";
3
3
  export type { ContainmentGuarantee } from "./containment.js";
4
4
  export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, type FsSafePythonConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
5
5
  export { writeExternalFileWithinRoot, type ExternalFileWriteOptions, type ExternalFileWriteResult, } from "./output.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACnC,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,2BAA2B,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,0BAA0B,EAC/B,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACnC,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,2BAA2B,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"install-path.d.ts","sourceRoot":"","sources":["../src/install-path.ts"],"names":[],"mappings":"AAKA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAsB3D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAe5D;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDhB"}
1
+ {"version":3,"file":"install-path.d.ts","sourceRoot":"","sources":["../src/install-path.ts"],"names":[],"mappings":"AAMA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB3D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAe5D;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDhB"}
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import fsSync from "node:fs";
3
3
  import path from "node:path";
4
4
  import { isPathInside } from "./path.js";
5
+ import { realpathSync } from "./realpath.js";
5
6
  export function safeDirName(input) {
6
7
  const trimmed = input.trim();
7
8
  if (!trimmed) {
@@ -19,12 +20,8 @@ export function safePathSegmentHashed(input) {
19
20
  .replaceAll(/-+$/g, "");
20
21
  const normalized = base.length > 0 ? base : "skill";
21
22
  const safe = normalized === "." || normalized === ".." ? "skill" : normalized;
22
- const hash = createHash("sha256").update(trimmed).digest("hex").slice(0, 10);
23
- if (safe !== trimmed) {
24
- const prefix = safe.length > 50 ? safe.slice(0, 50) : safe;
25
- return `${prefix}-${hash}`;
26
- }
27
- if (safe.length > 60) {
23
+ if (safe !== trimmed || safe.length > 60) {
24
+ const hash = createHash("sha256").update(trimmed).digest("hex").slice(0, 10);
28
25
  return `${safe.slice(0, 50)}-${hash}`;
29
26
  }
30
27
  return safe;
@@ -59,7 +56,7 @@ export async function assertCanonicalPathWithinBase(params) {
59
56
  else if (!baseLstat.isDirectory()) {
60
57
  throw new Error(`Invalid ${params.boundaryLabel}: base directory must be a directory`);
61
58
  }
62
- const baseRealPath = fsSync.realpathSync.native(baseDir);
59
+ const baseRealPath = realpathSync.native(baseDir);
63
60
  const validateDirectory = async (dirPath) => {
64
61
  const resolvedDirPath = path.resolve(dirPath);
65
62
  const dirLstat = fsSync.lstatSync(dirPath);
@@ -75,7 +72,7 @@ export async function assertCanonicalPathWithinBase(params) {
75
72
  else if (!dirLstat.isDirectory()) {
76
73
  throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
77
74
  }
78
- const dirRealPath = fsSync.realpathSync.native(dirPath);
75
+ const dirRealPath = realpathSync.native(dirPath);
79
76
  if (!isPathInside(baseRealPath, dirRealPath)) {
80
77
  throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
81
78
  }
@@ -1,10 +1,10 @@
1
- import fsSync from "node:fs";
2
1
  import path from "node:path";
3
2
  import { syncDirectory } from "./directory-durability.js";
4
3
  import { isPathRelativeEscape } from "./path.js";
4
+ import { realpathSync } from "./realpath.js";
5
5
  export async function syncQueueDirectoryCreation(dir, validationBase) {
6
- const baseReal = fsSync.realpathSync.native(validationBase);
7
- const targetReal = fsSync.realpathSync.native(dir);
6
+ const baseReal = realpathSync.native(validationBase);
7
+ const targetReal = realpathSync.native(dir);
8
8
  const relative = path.relative(baseReal, targetReal);
9
9
  if (isPathRelativeEscape(relative)) {
10
10
  throw new Error(`durable queue directory escapes validation base: ${dir}`);
@@ -1 +1 @@
1
- {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAOF,eAAO,MAAM,0CAA0C,QAAmB,CAAC;AAM3E,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAO5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAiLD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AA6DD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CASZ;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/F;AAED,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE;IACzD,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAsBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CA8Dd;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}
1
+ {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAOF,eAAO,MAAM,0CAA0C,QAAmB,CAAC;AAM3E,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAO5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAiLD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAyDD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CASZ;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/F;AAED,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE;IACzD,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAsBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CA+Dd;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}
@@ -1,11 +1,14 @@
1
1
  import fs, {} from "node:fs";
2
2
  import path from "node:path";
3
3
  import { normalizeMaxBytes } from "./byte-budget.js";
4
+ import { readBoundedAsync } from "./bounded-read.js";
4
5
  import { syncDirectory } from "./directory-durability.js";
5
6
  import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
6
7
  import { getErrorCode, acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
7
8
  import { stringifyJsonDocument } from "./json-stringify.js";
8
9
  import { resolveReadOpenFlags } from "./read-open-flags.js";
10
+ import { realpathSync } from "./realpath.js";
11
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
9
12
  import { replaceFileAtomicWithDirectorySync } from "./replace-file.js";
10
13
  import { assertSafePathSegment } from "./safe-path-segment.js";
11
14
  import { inspectFileIdentity } from "./strict-file-identity.js";
@@ -59,7 +62,7 @@ async function ensureJsonDurableQueueDir(dir, validationRoot) {
59
62
  ? validationRoot
60
63
  : queueValidationRoot(dir);
61
64
  await assertNoSymlinkDirectorySegments(root, dir, true);
62
- await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
65
+ await fs.promises.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: 0o700 });
63
66
  await assertNoSymlinkDirectorySegments(root, dir, false);
64
67
  await chmodQueueDirectory(dir);
65
68
  await syncQueueDirectoryCreation(dir, root.path);
@@ -114,7 +117,7 @@ async function isDarwinSystemAlias(dir, stat) {
114
117
  return false;
115
118
  }
116
119
  try {
117
- return fs.realpathSync.native(resolved) === `/private${resolved}`;
120
+ return realpathSync.native(resolved) === `/private${resolved}`;
118
121
  }
119
122
  catch {
120
123
  return false;
@@ -130,7 +133,7 @@ async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissin
130
133
  if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
131
134
  throw new Error(`durable queue path is not a directory: ${dir}`);
132
135
  }
133
- base = fs.realpathSync.native(base);
136
+ base = realpathSync.native(base);
134
137
  target = path.join(base, ...relative.split(path.sep).filter(Boolean));
135
138
  current = base;
136
139
  baseStat = fs.lstatSync(base);
@@ -158,7 +161,7 @@ async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissin
158
161
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
159
162
  if (stat.isSymbolicLink() && validationRoot.allowSymlinkBase) {
160
163
  if (await isDarwinSystemAlias(current, stat)) {
161
- current = fs.realpathSync.native(current);
164
+ current = realpathSync.native(current);
162
165
  continue;
163
166
  }
164
167
  }
@@ -253,20 +256,12 @@ async function readBoundedUtf8File(params) {
253
256
  try {
254
257
  const openedStat = await inspectQueueEntry(() => fs.fstatSync(handle.fd, { bigint: true }), params.maxBytes, initialStat);
255
258
  await inspectQueueEntry(inspectPath, params.maxBytes, openedStat);
256
- const chunks = [];
257
- const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, params.maxBytes + 1));
258
- let total = 0;
259
- while (true) {
260
- const { bytesRead } = await handle.read(scratch, 0, scratch.length, null);
261
- if (bytesRead === 0) {
262
- return Buffer.concat(chunks, total).toString("utf8");
263
- }
264
- total += bytesRead;
265
- if (total > params.maxBytes) {
266
- throw new Error(`queue entry exceeds ${params.maxBytes} bytes`);
267
- }
268
- chunks.push(Buffer.from(scratch.subarray(0, bytesRead)));
269
- }
259
+ const bytes = await readBoundedAsync(params.maxBytes, async (buffer, length) => (await handle.read(buffer, 0, length, null)).bytesRead, {
260
+ initialSize: openedStat.size >= 0n && Number.isSafeInteger(Number(openedStat.size))
261
+ ? Number(openedStat.size) : undefined,
262
+ createLimitError: () => new Error(`queue entry exceeds ${params.maxBytes} bytes`),
263
+ });
264
+ return bytes.toString("utf8");
270
265
  }
271
266
  finally {
272
267
  await handle.close();
@@ -323,6 +318,12 @@ export async function loadPendingJsonDurableQueueEntries(options) {
323
318
  for (const file of files) {
324
319
  if (file.endsWith(".delivered")) {
325
320
  const id = file.slice(0, -".delivered".length);
321
+ try {
322
+ assertSafeQueueEntryId(id);
323
+ }
324
+ catch {
325
+ continue;
326
+ }
326
327
  await completeDeliveredQueueEntry(resolveJsonDurableQueueEntryPaths(options.queueDir, id));
327
328
  }
328
329
  else if (options.cleanupTmpMaxAgeMs !== undefined && file.endsWith(".tmp")) {