@lousy-agents/mcp 5.19.1 → 5.19.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 (2) hide show
  1. package/dist/mcp-server.js +1141 -799
  2. package/package.json +1 -1
@@ -6559,7 +6559,7 @@ function escapeJsonPtr(str) {
6559
6559
 
6560
6560
 
6561
6561
  },
6562
- 7489(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6562
+ 4583(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6563
6563
  // NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
6564
6564
  var constructs_namespaceObject = {};
6565
6565
  __webpack_require__.r(constructs_namespaceObject);
@@ -26553,6 +26553,9 @@ var external_node_path_ = __webpack_require__(6760);
26553
26553
  const OPERATIONAL_CODES = new Set([
26554
26554
  "helper-failed",
26555
26555
  "helper-unavailable",
26556
+ "not-empty",
26557
+ "not-found",
26558
+ "not-removable",
26556
26559
  "permission-unverified",
26557
26560
  "timeout",
26558
26561
  "unsupported-platform",
@@ -26606,6 +26609,7 @@ function file_identity_sameFileIdentity(left, right, platform = process.platform
26606
26609
 
26607
26610
 
26608
26611
 
26612
+
26609
26613
  const NOT_FOUND_CODES = new Set(["ENOENT", "ENOTDIR"]);
26610
26614
  const SYMLINK_OPEN_CODES = new Set(["ELOOP", "EINVAL", "ENOTSUP"]);
26611
26615
  const POSIX_SEPARATOR_CHAR_CODE = 0x2f;
@@ -26723,6 +26727,9 @@ function splitSafeRelativePath(relativePath) {
26723
26727
  if (segment === "..") {
26724
26728
  throw new FsSafeError("invalid-path", "relative path must not contain '..'");
26725
26729
  }
26730
+ if (isDriveRelativePath(segment)) {
26731
+ throw new FsSafeError("invalid-path", "relative path must not contain a drive letter");
26732
+ }
26726
26733
  }
26727
26734
  return segments;
26728
26735
  }
@@ -26735,6 +26742,74 @@ function resolveSafeRelativePath(rootDir, relativePath) {
26735
26742
  return target;
26736
26743
  }
26737
26744
 
26745
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-errors.js
26746
+
26747
+
26748
+ const REMOVE_NOT_EMPTY_CODES = new Set(["ENOTEMPTY", "EEXIST"]);
26749
+ function fileNotFoundError(cause) {
26750
+ return cause === undefined
26751
+ ? new errors_FsSafeError("not-found", "file not found")
26752
+ : new errors_FsSafeError("not-found", "file not found", { cause });
26753
+ }
26754
+ function outsideWorkspaceError() {
26755
+ return new errors_FsSafeError("outside-workspace", "file is outside workspace root");
26756
+ }
26757
+ function root_errors_directoryComponentNotDirectoryError(cause) {
26758
+ return cause === undefined
26759
+ ? new errors_FsSafeError("not-file", "directory component must be a directory")
26760
+ : new errors_FsSafeError("not-file", "directory component must be a directory", { cause });
26761
+ }
26762
+ function hardlinkedPathNotAllowedError() {
26763
+ return new errors_FsSafeError("hardlink", "hardlinked path not allowed");
26764
+ }
26765
+ function isAlreadyExistsError(error) {
26766
+ return hasNodeErrorCode(error, "EEXIST") || /File exists|EEXIST/i.test(String(error));
26767
+ }
26768
+ function normalizePinnedWriteError(error) {
26769
+ if (error instanceof errors_FsSafeError) {
26770
+ return error;
26771
+ }
26772
+ if (path_isNotFoundPathError(error)) {
26773
+ return fileNotFoundError(error instanceof Error ? error : undefined);
26774
+ }
26775
+ return new errors_FsSafeError("invalid-path", "path is not a regular file under root", {
26776
+ cause: error instanceof Error ? error : undefined,
26777
+ });
26778
+ }
26779
+ function normalizePinnedPathError(error) {
26780
+ if (error instanceof errors_FsSafeError) {
26781
+ return error;
26782
+ }
26783
+ return new errors_FsSafeError("path-alias", "path is not under root", {
26784
+ cause: error instanceof Error ? error : undefined,
26785
+ });
26786
+ }
26787
+ function normalizeRemoveGuardError(error) {
26788
+ if (error instanceof errors_FsSafeError) {
26789
+ return error;
26790
+ }
26791
+ if (path_isNotFoundPathError(error)) {
26792
+ return fileNotFoundError(error instanceof Error ? error : undefined);
26793
+ }
26794
+ return normalizePinnedPathError(error);
26795
+ }
26796
+ function normalizeRemovePathError(error) {
26797
+ if (error instanceof errors_FsSafeError) {
26798
+ return error;
26799
+ }
26800
+ if (!isNodeError(error) || typeof error.code !== "string") {
26801
+ return normalizePinnedPathError(error);
26802
+ }
26803
+ const cause = error instanceof Error ? error : undefined;
26804
+ if (path_isNotFoundPathError(error)) {
26805
+ return fileNotFoundError(cause);
26806
+ }
26807
+ if (REMOVE_NOT_EMPTY_CODES.has(error.code)) {
26808
+ return new errors_FsSafeError("not-empty", "directory is not empty", { cause });
26809
+ }
26810
+ return new errors_FsSafeError("not-removable", "path could not be removed", { cause });
26811
+ }
26812
+
26738
26813
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/directory-guard.js
26739
26814
 
26740
26815
 
@@ -26742,17 +26817,18 @@ function resolveSafeRelativePath(rootDir, relativePath) {
26742
26817
 
26743
26818
 
26744
26819
 
26820
+
26745
26821
  async function directory_guard_createAsyncDirectoryGuard(dir) {
26746
26822
  const stat = await promises_.lstat(dir);
26747
26823
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
26748
- throw new errors_FsSafeError("not-file", "directory component must be a directory");
26824
+ throw root_errors_directoryComponentNotDirectoryError();
26749
26825
  }
26750
26826
  return { dir, realPath: await promises_.realpath(dir), stat };
26751
26827
  }
26752
26828
  async function assertAsyncDirectoryGuard(guard) {
26753
26829
  const stat = await promises_.lstat(guard.dir);
26754
26830
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
26755
- throw new errors_FsSafeError("not-file", "directory component must be a directory");
26831
+ throw root_errors_directoryComponentNotDirectoryError();
26756
26832
  }
26757
26833
  if (!file_identity_sameFileIdentity(stat, guard.stat) || (await promises_.realpath(guard.dir)) !== guard.realPath) {
26758
26834
  throw new errors_FsSafeError("path-mismatch", "directory changed during operation");
@@ -26761,14 +26837,14 @@ async function assertAsyncDirectoryGuard(guard) {
26761
26837
  function directory_guard_createSyncDirectoryGuard(dir) {
26762
26838
  const stat = fsSync.lstatSync(dir);
26763
26839
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
26764
- throw new FsSafeError("not-file", "directory component must be a directory");
26840
+ throw directoryComponentNotDirectoryError();
26765
26841
  }
26766
26842
  return { dir, realPath: fsSync.realpathSync(dir), stat };
26767
26843
  }
26768
26844
  function directory_guard_assertSyncDirectoryGuard(guard) {
26769
26845
  const stat = fsSync.lstatSync(guard.dir);
26770
26846
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
26771
- throw new FsSafeError("not-file", "directory component must be a directory");
26847
+ throw directoryComponentNotDirectoryError();
26772
26848
  }
26773
26849
  if (!sameFileIdentity(stat, guard.stat) || fsSync.realpathSync(guard.dir) !== guard.realPath) {
26774
26850
  throw new FsSafeError("path-mismatch", "directory changed during operation");
@@ -27114,6 +27190,7 @@ async function ensureDurableDirectory(options) {
27114
27190
 
27115
27191
 
27116
27192
 
27193
+
27117
27194
  function isSameOrChildPath(candidate, parent) {
27118
27195
  const parentPrefix = parent.endsWith(external_node_path_.sep) ? parent : `${parent}${external_node_path_.sep}`;
27119
27196
  return candidate === parent || candidate.startsWith(parentPrefix);
@@ -27126,9 +27203,7 @@ async function realpathOrThrowNotFile(target) {
27126
27203
  if (path_isNotFoundPathError(error)) {
27127
27204
  // A dangling symlink (or a component removed between lstat and
27128
27205
  // realpath) is not a usable directory component.
27129
- throw new errors_FsSafeError("not-file", "directory component must be a directory", {
27130
- cause: error instanceof Error ? error : undefined,
27131
- });
27206
+ throw root_errors_directoryComponentNotDirectoryError(error instanceof Error ? error : undefined);
27132
27207
  }
27133
27208
  throw error;
27134
27209
  }
@@ -27151,8 +27226,8 @@ async function mkdirPathComponentsWithGuards(params) {
27151
27226
  for (const part of relative.split(external_node_path_.sep).filter(Boolean)) {
27152
27227
  const next = external_node_path_.join(current, part);
27153
27228
  const parentGuard = await directory_guard_createAsyncDirectoryGuard(current);
27154
- await params.beforeComponent?.(next);
27155
27229
  await assertAsyncDirectoryGuard(parentGuard);
27230
+ await params.beforeComponent?.(next);
27156
27231
  try {
27157
27232
  await promises_.mkdir(next);
27158
27233
  }
@@ -27163,7 +27238,7 @@ async function mkdirPathComponentsWithGuards(params) {
27163
27238
  }
27164
27239
  const stat = await promises_.lstat(next);
27165
27240
  if (!stat.isSymbolicLink() && !stat.isDirectory()) {
27166
- throw new errors_FsSafeError("not-file", "directory component must be a directory");
27241
+ throw root_errors_directoryComponentNotDirectoryError();
27167
27242
  }
27168
27243
  // Node's recursive mkdir follows symlinks in missing components. Build one
27169
27244
  // segment at a time and realpath-check each segment before descending.
@@ -27183,7 +27258,7 @@ async function mkdirPathComponentsWithGuards(params) {
27183
27258
  // the returned resolved path, not their own lexical parent path.
27184
27259
  const targetStat = await promises_.stat(nextReal);
27185
27260
  if (!targetStat.isDirectory()) {
27186
- throw new errors_FsSafeError("not-file", "directory component must be a directory");
27261
+ throw root_errors_directoryComponentNotDirectoryError();
27187
27262
  }
27188
27263
  await directory_guard_createAsyncDirectoryGuard(nextReal);
27189
27264
  await assertAsyncDirectoryGuard(parentGuard);
@@ -27275,30 +27350,37 @@ function guardedRmSync(params) {
27275
27350
  }), { verifyAfter: params.verifyAfter });
27276
27351
  }
27277
27352
 
27278
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/deny-mutations.js
27353
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-existing.js
27279
27354
 
27280
27355
 
27281
27356
 
27282
27357
 
27283
- async function pathExists(filePath) {
27358
+ function isFilesystemRoot(candidate) {
27359
+ return external_node_path_.parse(candidate).root === candidate;
27360
+ }
27361
+ async function pathExists(targetPath) {
27284
27362
  try {
27285
- await promises_.lstat(filePath);
27363
+ await promises_.lstat(targetPath);
27286
27364
  return true;
27287
27365
  }
27288
- catch (err) {
27289
- if (!path_isNotFoundPathError(err)) {
27290
- throw err;
27366
+ catch (error) {
27367
+ if (path_isNotFoundPathError(error)) {
27368
+ return false;
27291
27369
  }
27292
- return false;
27370
+ throw error;
27293
27371
  }
27294
27372
  }
27295
27373
  async function resolvePathViaExistingAncestor(targetPath) {
27296
27374
  const normalized = external_node_path_.resolve(targetPath);
27297
27375
  let cursor = normalized;
27298
27376
  const missingSuffix = [];
27299
- while (external_node_path_.dirname(cursor) !== cursor && !(await pathExists(cursor))) {
27377
+ while (!isFilesystemRoot(cursor) && !(await pathExists(cursor))) {
27300
27378
  missingSuffix.unshift(external_node_path_.basename(cursor));
27301
- cursor = external_node_path_.dirname(cursor);
27379
+ const parent = external_node_path_.dirname(cursor);
27380
+ if (parent === cursor) {
27381
+ break;
27382
+ }
27383
+ cursor = parent;
27302
27384
  }
27303
27385
  if (!(await pathExists(cursor))) {
27304
27386
  return normalized;
@@ -27313,7 +27395,38 @@ async function resolvePathViaExistingAncestor(targetPath) {
27313
27395
  return normalized;
27314
27396
  }
27315
27397
  }
27316
- async function comparablePaths(rawPath) {
27398
+ function root_path_existing_resolvePathViaExistingAncestorSync(targetPath) {
27399
+ const normalized = path.resolve(targetPath);
27400
+ let cursor = normalized;
27401
+ const missingSuffix = [];
27402
+ while (!isFilesystemRoot(cursor) && !fs.existsSync(cursor)) {
27403
+ missingSuffix.unshift(path.basename(cursor));
27404
+ const parent = path.dirname(cursor);
27405
+ if (parent === cursor) {
27406
+ break;
27407
+ }
27408
+ cursor = parent;
27409
+ }
27410
+ if (!fs.existsSync(cursor)) {
27411
+ return normalized;
27412
+ }
27413
+ try {
27414
+ const resolvedAncestor = path.resolve(fs.realpathSync(cursor));
27415
+ return missingSuffix.length === 0
27416
+ ? resolvedAncestor
27417
+ : path.resolve(resolvedAncestor, ...missingSuffix);
27418
+ }
27419
+ catch {
27420
+ return normalized;
27421
+ }
27422
+ }
27423
+
27424
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/deny-mutations.js
27425
+
27426
+
27427
+
27428
+
27429
+ async function resolveMutationComparablePaths(rawPath) {
27317
27430
  path_assertNoNulPathInput(rawPath, "path contains a NUL byte");
27318
27431
  const resolved = external_node_path_.resolve(rawPath);
27319
27432
  return new Set([resolved, await resolvePathViaExistingAncestor(resolved)]);
@@ -27342,9 +27455,9 @@ async function assertMutationNotDenied(filePath, policy, options = {}) {
27342
27455
  if (!hasPolicyEntries(policy)) {
27343
27456
  return;
27344
27457
  }
27345
- const targetPaths = await comparablePaths(filePath);
27458
+ const targetPaths = await resolveMutationComparablePaths(filePath);
27346
27459
  for (const deniedPath of policyPathEntries(policy.paths)) {
27347
- const deniedPaths = await comparablePaths(deniedPath);
27460
+ const deniedPaths = await resolveMutationComparablePaths(deniedPath);
27348
27461
  for (const target of targetPaths) {
27349
27462
  for (const denied of deniedPaths) {
27350
27463
  if (isSamePath(denied, target) ||
@@ -27355,7 +27468,7 @@ async function assertMutationNotDenied(filePath, policy, options = {}) {
27355
27468
  }
27356
27469
  }
27357
27470
  for (const deniedPrefix of policyPathEntries(policy.prefixes)) {
27358
- const deniedPaths = await comparablePaths(deniedPrefix);
27471
+ const deniedPaths = await resolveMutationComparablePaths(deniedPrefix);
27359
27472
  for (const target of targetPaths) {
27360
27473
  for (const denied of deniedPaths) {
27361
27474
  if (path_isPathInside(denied, target) ||
@@ -27818,7 +27931,20 @@ async function createNativeExclusiveFile(targetPath, mode) {
27818
27931
  let fd;
27819
27932
  let created;
27820
27933
  try {
27821
- const opened = binding.openBeneath(parent.fd, basename, nativeOpenFlags(external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL));
27934
+ let opened;
27935
+ try {
27936
+ opened = binding.openBeneath(parent.fd, basename, nativeOpenFlags(external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL));
27937
+ }
27938
+ catch (error) {
27939
+ // The parent open above and every post-create operation remain untagged.
27940
+ // Only this exclusive-open failure has enough provenance for a caller to
27941
+ // classify a Windows lock-file denial without swallowing setup failures.
27942
+ const openError = error;
27943
+ if (process.platform === "win32" && openError.code === "EPERM") {
27944
+ openError.path = targetPath;
27945
+ }
27946
+ throw error;
27947
+ }
27822
27948
  fd = opened.fd;
27823
27949
  external_node_fs_.fchmodSync(fd, mode);
27824
27950
  created = external_node_fs_.fstatSync(fd);
@@ -27872,23 +27998,22 @@ function assertWithinMaxBytes(bytes, maxBytes) {
27872
27998
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
27873
27999
  }
27874
28000
  }
27875
- async function inputToBuffer(input, maxBytes) {
28001
+ async function writeNativeInput(fd, input, maxBytes) {
27876
28002
  if (input.kind === "buffer") {
27877
28003
  const data = typeof input.data === "string"
27878
28004
  ? Buffer.from(input.data, input.encoding ?? "utf8")
27879
28005
  : Buffer.from(input.data);
27880
28006
  assertWithinMaxBytes(data.byteLength, maxBytes);
27881
- return data;
28007
+ writeNativeFd(fd, data);
28008
+ return;
27882
28009
  }
27883
- const chunks = [];
27884
28010
  let bytes = 0;
27885
28011
  for await (const chunk of input.stream) {
27886
28012
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
27887
28013
  bytes += buffer.byteLength;
27888
28014
  assertWithinMaxBytes(bytes, maxBytes);
27889
- chunks.push(buffer);
28015
+ writeNativeFd(fd, buffer);
27890
28016
  }
27891
- return Buffer.concat(chunks, bytes);
27892
28017
  }
27893
28018
  function native_pinned_write_nativeOpenFlags(flags) {
27894
28019
  const closeOnExec = external_node_fs_.constants.O_CLOEXEC;
@@ -27900,7 +28025,6 @@ function sameNativeIdentity(left, right) {
27900
28025
  return left.dev === right.dev && left.ino === right.ino;
27901
28026
  }
27902
28027
  async function runPinnedWriteNative(binding, params) {
27903
- const data = await inputToBuffer(params.input, params.maxBytes);
27904
28028
  const root = await promises_.open(params.rootPath, external_node_fs_.constants.O_RDONLY |
27905
28029
  (typeof external_node_fs_.constants.O_DIRECTORY === "number" ? external_node_fs_.constants.O_DIRECTORY : 0));
27906
28030
  let parentFd;
@@ -27930,27 +28054,56 @@ async function runPinnedWriteNative(binding, params) {
27930
28054
  !sameNativeIdentity(parentPathStat, parentIdentity)) {
27931
28055
  throw new errors_FsSafeError("path-mismatch", "native write parent changed during resolution");
27932
28056
  }
27933
- try {
27934
- await promises_.lstat(external_node_path_.join(parentPath, params.basename));
27935
- throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
27936
- }
27937
- catch (error) {
27938
- if (error.code !== "ENOENT") {
27939
- throw error;
28057
+ if (params.overwrite === false) {
28058
+ try {
28059
+ await promises_.lstat(external_node_path_.join(parentPath, params.basename));
28060
+ throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
28061
+ }
28062
+ catch (error) {
28063
+ if (error.code !== "ENOENT") {
28064
+ throw error;
28065
+ }
27940
28066
  }
27941
28067
  }
27942
28068
  tempFd = binding.openBeneath(parentFd, tempName, native_pinned_write_nativeOpenFlags(external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL)).fd;
27943
- external_node_fs_.fchmodSync(tempFd, params.mode || 0o600);
27944
- writeNativeFd(tempFd, data);
27945
- syncNativeFileBestEffort(tempFd);
27946
28069
  tempIdentity = external_node_fs_.fstatSync(tempFd);
27947
- binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
28070
+ // Creation is requested at 0600 in the binding, but a restrictive umask
28071
+ // can remove owner access. Keep the unpublished inode private and
28072
+ // reopenable until the published name has been identity-fenced.
28073
+ external_node_fs_.fchmodSync(tempFd, 0o600);
28074
+ await writeNativeInput(tempFd, params.input, params.maxBytes);
28075
+ syncNativeFileBestEffort(tempFd);
28076
+ if (params.overwrite === false) {
28077
+ binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
28078
+ }
28079
+ else {
28080
+ binding.renameReplace(parentFd, tempName, parentFd, params.basename);
28081
+ }
27948
28082
  renamed = true;
27949
28083
  targetFd = binding.openBeneath(parentFd, params.basename, native_pinned_write_nativeOpenFlags(external_node_fs_.constants.O_RDONLY)).fd;
27950
28084
  const targetIdentity = binding.fstatIdentity(targetFd);
27951
28085
  if (!targetIdentity.isFile || !sameNativeIdentity(tempIdentity, targetIdentity)) {
27952
28086
  throw new errors_FsSafeError("path-mismatch", "native write target changed after rename");
27953
28087
  }
28088
+ // Native exclusive creation starts at 0600. Apply the requested mode only
28089
+ // after reopening and fencing the published name, both so mode 000 stays
28090
+ // verifiable and so broader modes are never exposed before that fence.
28091
+ try {
28092
+ external_node_fs_.fchmodSync(targetFd, params.mode);
28093
+ syncNativeFileBestEffort(targetFd);
28094
+ }
28095
+ catch (error) {
28096
+ external_node_fs_.closeSync(targetFd);
28097
+ targetFd = undefined;
28098
+ removeNativeCreatedFileIfStillPinned({
28099
+ binding,
28100
+ parentPath,
28101
+ parentFd,
28102
+ basename: params.basename,
28103
+ created: tempIdentity,
28104
+ });
28105
+ throw error;
28106
+ }
27954
28107
  syncNativeFileBestEffort(parentFd);
27955
28108
  return { dev: targetIdentity.dev, ino: targetIdentity.ino };
27956
28109
  }
@@ -28087,7 +28240,7 @@ async function readFileDescriptorBounded(fd, maxBytes) {
28087
28240
  });
28088
28241
  }
28089
28242
  /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
28090
- function readFileDescriptorBoundedSync(fd, maxBytes) {
28243
+ function bounded_read_readFileDescriptorBoundedSync(fd, maxBytes) {
28091
28244
  assertMaxBytes(maxBytes);
28092
28245
  const chunks = [];
28093
28246
  const scratch = createScratchBuffer(maxBytes);
@@ -28102,6 +28255,21 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
28102
28255
  }
28103
28256
  }
28104
28257
 
28258
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
28259
+ let test_hooks_fsSafeTestHooks;
28260
+ function allowFsSafeTestHooks() {
28261
+ return false || process.env.VITEST === "true";
28262
+ }
28263
+ function getFsSafeTestHooks() {
28264
+ return test_hooks_fsSafeTestHooks;
28265
+ }
28266
+ function __setFsSafeTestHooksForTest(hooks) {
28267
+ if (hooks && !allowFsSafeTestHooks()) {
28268
+ throw new Error("__setFsSafeTestHooksForTest is only available in tests");
28269
+ }
28270
+ test_hooks_fsSafeTestHooks = hooks;
28271
+ }
28272
+
28105
28273
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
28106
28274
 
28107
28275
 
@@ -28110,6 +28278,7 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
28110
28278
 
28111
28279
 
28112
28280
 
28281
+
28113
28282
  const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
28114
28283
  const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
28115
28284
  const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
@@ -28160,6 +28329,7 @@ function parseSidecarLockPayload(raw, parser) {
28160
28329
  }
28161
28330
  }
28162
28331
  async function readSidecarLockSnapshot(lockPath, options = {}) {
28332
+ let handle;
28163
28333
  try {
28164
28334
  if (options.lockRoot) {
28165
28335
  const opened = await options.lockRoot.open(relativeSidecarLockPath(options.lockRoot, lockPath));
@@ -28175,9 +28345,44 @@ async function readSidecarLockSnapshot(lockPath, options = {}) {
28175
28345
  await opened.handle.close().catch(() => undefined);
28176
28346
  }
28177
28347
  }
28178
- const stat = await promises_.lstat(lockPath);
28179
- const raw = await promises_.readFile(lockPath, "utf8");
28180
- return { raw, payload: parseSidecarLockPayload(raw, options.parsePayload), stat };
28348
+ const before = await promises_.lstat(lockPath);
28349
+ if (!before.isFile() || before.isSymbolicLink()) {
28350
+ if (options.rejectNonFile) {
28351
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
28352
+ }
28353
+ return null;
28354
+ }
28355
+ await getFsSafeTestHooks()?.beforeSidecarLockSnapshotOpen?.(lockPath);
28356
+ const noFollow = process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
28357
+ ? external_node_fs_.constants.O_NOFOLLOW
28358
+ : 0;
28359
+ try {
28360
+ handle = await promises_.open(lockPath, external_node_fs_.constants.O_RDONLY |
28361
+ noFollow |
28362
+ (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0));
28363
+ }
28364
+ catch (error) {
28365
+ if (options.rejectNonFile && error.code === "ELOOP") {
28366
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`, {
28367
+ cause: error,
28368
+ });
28369
+ }
28370
+ throw error;
28371
+ }
28372
+ const opened = await handle.stat();
28373
+ if (!opened.isFile()) {
28374
+ if (options.rejectNonFile) {
28375
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
28376
+ }
28377
+ return null;
28378
+ }
28379
+ if (!options.allowDescriptorIdentityDrift && !file_identity_sameFileIdentity(before, opened))
28380
+ return null;
28381
+ const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
28382
+ const after = await promises_.lstat(lockPath);
28383
+ if (!after.isFile() || !file_identity_sameFileIdentity(before, after))
28384
+ return null;
28385
+ return { raw, payload: parseSidecarLockPayload(raw, options.parsePayload), stat: after };
28181
28386
  }
28182
28387
  catch (err) {
28183
28388
  if (err.code === "ENOENT" ||
@@ -28186,19 +28391,26 @@ async function readSidecarLockSnapshot(lockPath, options = {}) {
28186
28391
  }
28187
28392
  throw err;
28188
28393
  }
28394
+ finally {
28395
+ await handle?.close().catch(() => undefined);
28396
+ }
28189
28397
  }
28190
- function readSidecarLockSnapshotSync(lockPath, parsePayload) {
28398
+ function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}) {
28191
28399
  let fd;
28192
28400
  try {
28193
28401
  const before = fsSync.lstatSync(lockPath);
28194
- if (!before.isFile() || before.isSymbolicLink())
28402
+ if (!before.isFile() || before.isSymbolicLink()) {
28403
+ if (options.rejectNonFile) {
28404
+ throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
28405
+ }
28195
28406
  return null;
28407
+ }
28196
28408
  const noFollow = process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
28197
28409
  ? fsSync.constants.O_NOFOLLOW
28198
28410
  : 0;
28199
28411
  fd = fsSync.openSync(lockPath, fsSync.constants.O_RDONLY | noFollow);
28200
28412
  const opened = fsSync.fstatSync(fd);
28201
- const raw = fsSync.readFileSync(fd, "utf8");
28413
+ const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
28202
28414
  const after = fsSync.lstatSync(lockPath);
28203
28415
  if (!sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
28204
28416
  return null;
@@ -28244,7 +28456,10 @@ function sidecarLockSnapshotMatches(current, observed) {
28244
28456
  return observed.stat !== undefined && current.stat !== undefined;
28245
28457
  }
28246
28458
  async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
28247
- const current = await readSidecarLockSnapshot(lockPath, options);
28459
+ const current = await readSidecarLockSnapshot(lockPath, {
28460
+ ...options,
28461
+ allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
28462
+ });
28248
28463
  if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
28249
28464
  return false;
28250
28465
  }
@@ -28257,7 +28472,10 @@ async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
28257
28472
  return true;
28258
28473
  }
28259
28474
  async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
28260
- const current = await readSidecarLockSnapshot(lockPath, options);
28475
+ const current = await readSidecarLockSnapshot(lockPath, {
28476
+ ...options,
28477
+ allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
28478
+ });
28261
28479
  return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
28262
28480
  }
28263
28481
  async function sidecarReclaimGuardExists(pathname) {
@@ -28325,36 +28543,6 @@ async function removeStaleSidecarLockIfAllowed(params) {
28325
28543
  }
28326
28544
  }
28327
28545
 
28328
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
28329
-
28330
- function createSidecarLockHandle(params) {
28331
- let released = false;
28332
- const release = async () => {
28333
- if (released)
28334
- return;
28335
- released = true;
28336
- await params.release();
28337
- };
28338
- return {
28339
- lockPath: params.lockPath,
28340
- normalizedTargetPath: params.normalizedTargetPath,
28341
- verifyStillHeld: params.verifyStillHeld,
28342
- release,
28343
- [Symbol.asyncDispose]: release,
28344
- };
28345
- }
28346
- function createHeldSidecarLockHandle(params) {
28347
- return createSidecarLockHandle({
28348
- lockPath: params.held.lockPath,
28349
- normalizedTargetPath: params.normalizedTargetPath,
28350
- verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
28351
- lockRoot: params.held.lockRoot,
28352
- parsePayload: params.held.parsePayload,
28353
- }),
28354
- release: params.release,
28355
- });
28356
- }
28357
-
28358
28546
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
28359
28547
 
28360
28548
  function computeSidecarLockDelayMs(retry, attempt) {
@@ -28365,7 +28553,23 @@ function computeSidecarLockDelayMs(retry, attempt) {
28365
28553
  const jitter = retry.randomize ? 1 + Math.random() : 1;
28366
28554
  return Math.min(maxTimeout, Math.round(base * jitter));
28367
28555
  }
28556
+ // Windows denies access to a lock file while a just-unlinked directory entry
28557
+ // is still being torn down, so a contended acquire sees EPERM on a name that is
28558
+ // already gone -- both when creating it exclusively and when reading the
28559
+ // holder's snapshot. The next attempt succeeds, so this is contention rather
28560
+ // than a permission failure. The error must name the lock file itself: the
28561
+ // exclusive-create helper opens the parent directory first, and a denial from
28562
+ // that setup step carries no teardown evidence and has to reach the caller.
28563
+ const maxTransientLockDenials = 8;
28564
+ function isTransientLockFileDenial(error, lockPath) {
28565
+ const denial = error;
28566
+ return process.platform === "win32" && denial?.code === "EPERM" && denial.path === lockPath;
28567
+ }
28368
28568
  function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
28569
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
28570
+ return createdAtMs !== null && nowMs - createdAtMs > staleMs;
28571
+ }
28572
+ function sidecarLockPayloadCreatedAtMs(payload) {
28369
28573
  const createdAt = payload &&
28370
28574
  typeof payload === "object" &&
28371
28575
  "createdAt" in payload &&
@@ -28373,11 +28577,12 @@ function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
28373
28577
  ? payload.createdAt
28374
28578
  : "";
28375
28579
  const createdAtMs = Date.parse(createdAt);
28376
- return Number.isFinite(createdAtMs) && nowMs - createdAtMs > staleMs;
28580
+ return Number.isFinite(createdAtMs) ? createdAtMs : null;
28377
28581
  }
28378
28582
  async function defaultSidecarLockShouldReclaim(params) {
28379
- if (sidecarLockPayloadIsStale(params.payload, params.staleMs, params.nowMs))
28380
- return true;
28583
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(params.payload);
28584
+ if (createdAtMs !== null)
28585
+ return params.nowMs - createdAtMs > params.staleMs;
28381
28586
  try {
28382
28587
  return params.nowMs - (await promises_.stat(params.lockPath)).mtimeMs > params.staleMs;
28383
28588
  }
@@ -28386,17 +28591,307 @@ async function defaultSidecarLockShouldReclaim(params) {
28386
28591
  }
28387
28592
  }
28388
28593
 
28389
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
28594
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js
28595
+
28596
+
28597
+
28598
+
28390
28599
 
28391
28600
 
28601
+ async function resolveNormalizedTargetPath(targetPath) {
28602
+ const resolved = external_node_path_.resolve(targetPath);
28603
+ const dir = external_node_path_.dirname(resolved);
28604
+ await promises_.mkdir(dir, { recursive: true });
28605
+ try {
28606
+ return external_node_path_.join(await promises_.realpath(dir), external_node_path_.basename(resolved));
28607
+ }
28608
+ catch {
28609
+ return resolved;
28610
+ }
28611
+ }
28612
+ async function acquireSidecarLock(options, context) {
28613
+ context.ensureExitCleanupRegistered();
28614
+ const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
28615
+ const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
28616
+ const held = context.held.get(normalizedTargetPath);
28617
+ if (held &&
28618
+ options.reentrantOwner !== undefined &&
28619
+ held.reentrantOwner !== undefined &&
28620
+ options.reentrantOwner === held.reentrantOwner) {
28621
+ held.refCount += 1;
28622
+ return context.handleForHeldLock(normalizedTargetPath, held);
28623
+ }
28624
+ const startedAt = Date.now();
28625
+ const retry = options.retry ?? {};
28626
+ const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
28627
+ const reclaimGuardPath = `${lockPath}.reclaim`;
28628
+ let ownsReclaimGuard = false;
28629
+ let attempt = 0;
28630
+ // Bounded so a genuine denial still surfaces as EPERM, not a lock timeout.
28631
+ let transientDenials = 0;
28632
+ const withinDenialBudget = () => ++transientDenials <= (/* inlined export .maxTransientLockDenials */8);
28633
+ const waitForRetry = async () => {
28634
+ const elapsed = Date.now() - startedAt;
28635
+ if ((options.timeoutMs !== undefined &&
28636
+ options.timeoutMs !== Number.POSITIVE_INFINITY &&
28637
+ elapsed >= options.timeoutMs) ||
28638
+ (maxRetries !== undefined && attempt >= maxRetries)) {
28639
+ throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
28640
+ code: "file_lock_timeout",
28641
+ lockPath,
28642
+ normalizedTargetPath,
28643
+ });
28644
+ }
28645
+ const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
28646
+ ? Number.POSITIVE_INFINITY
28647
+ : Math.max(0, options.timeoutMs - elapsed);
28648
+ const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
28649
+ attempt += 1;
28650
+ await new Promise((resolve) => setTimeout(resolve, delay));
28651
+ };
28652
+ // Waiting can fail on the caller's own retry or deadline limits. Classifying
28653
+ // a denial as contention must not cost them the original diagnosis, so hand
28654
+ // the denial back when no further attempt can be scheduled.
28655
+ const retryOrRethrowDenial = async (denial) => {
28656
+ try {
28657
+ await waitForRetry();
28658
+ }
28659
+ catch (waitError) {
28660
+ if (waitError.code === "file_lock_timeout")
28661
+ throw denial;
28662
+ throw waitError;
28663
+ }
28664
+ };
28665
+ try {
28666
+ while (true) {
28667
+ if (!ownsReclaimGuard && (await sidecarReclaimGuardExists(reclaimGuardPath))) {
28668
+ await waitForRetry();
28669
+ continue;
28670
+ }
28671
+ let handle = null;
28672
+ let createdSnapshot = null;
28673
+ let lockFileCreateDenied = false;
28674
+ try {
28675
+ const payload = await options.payload();
28676
+ const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
28677
+ if (options.lockRoot) {
28678
+ const relativeLockPath = relativeSidecarLockPath(options.lockRoot, lockPath);
28679
+ try {
28680
+ await options.lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
28681
+ }
28682
+ catch (error) {
28683
+ if (error instanceof errors_FsSafeError && error.code === "already-exists") {
28684
+ throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
28685
+ }
28686
+ throw error;
28687
+ }
28688
+ createdSnapshot = { raw, payload, ownershipToken };
28689
+ handle = (await options.lockRoot.open(relativeLockPath)).handle;
28690
+ }
28691
+ else {
28692
+ try {
28693
+ handle =
28694
+ (await createNativeExclusiveFile(lockPath, 0o600)) ??
28695
+ (await promises_.open(lockPath, "wx"));
28696
+ }
28697
+ catch (createError) {
28698
+ lockFileCreateDenied = isTransientLockFileDenial(createError, lockPath);
28699
+ throw createError;
28700
+ }
28701
+ await handle.writeFile(raw, "utf8");
28702
+ }
28703
+ const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
28704
+ const createdHeld = {
28705
+ refCount: 1,
28706
+ reentrantOwner: options.reentrantOwner,
28707
+ handle,
28708
+ lockPath,
28709
+ snapshot,
28710
+ acquiredAt: Date.now(),
28711
+ metadata: options.metadata ?? {},
28712
+ lockRoot: options.lockRoot,
28713
+ parsePayload: options.parsePayload,
28714
+ };
28715
+ context.held.set(normalizedTargetPath, createdHeld);
28716
+ if (ownsReclaimGuard) {
28717
+ try {
28718
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
28719
+ ownsReclaimGuard = false;
28720
+ }
28721
+ catch (err) {
28722
+ await context.releaseHeldLock(normalizedTargetPath, createdHeld, { force: true });
28723
+ throw err;
28724
+ }
28725
+ }
28726
+ const returnedHandle = context.handleForHeldLock(normalizedTargetPath, createdHeld);
28727
+ const interval = options.compromiseCheckIntervalMs;
28728
+ if (options.onCompromised && interval !== undefined && interval > 0) {
28729
+ createdHeld.compromiseTimer = setInterval(() => {
28730
+ void returnedHandle.verifyStillHeld().then((stillHeld) => {
28731
+ if (!stillHeld && createdHeld.compromiseTimer) {
28732
+ clearInterval(createdHeld.compromiseTimer);
28733
+ createdHeld.compromiseTimer = undefined;
28734
+ options.onCompromised?.({ lockPath, normalizedTargetPath });
28735
+ }
28736
+ });
28737
+ }, interval);
28738
+ createdHeld.compromiseTimer.unref();
28739
+ }
28740
+ return returnedHandle;
28741
+ }
28742
+ catch (err) {
28743
+ if (handle) {
28744
+ const failedSnapshot = { payload: null };
28745
+ try {
28746
+ failedSnapshot.stat = await handle.stat();
28747
+ }
28748
+ catch {
28749
+ // Best-effort cleanup of a failed exclusive create.
28750
+ }
28751
+ const current = context.held.get(normalizedTargetPath);
28752
+ if (current?.handle === handle) {
28753
+ context.held.delete(normalizedTargetPath);
28754
+ }
28755
+ await handle.close().catch(() => undefined);
28756
+ // The file may be empty or partial JSON, so remove by the identity
28757
+ // captured from our exclusive handle rather than by pathname alone.
28758
+ await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
28759
+ lockRoot: options.lockRoot,
28760
+ parsePayload: options.parsePayload,
28761
+ });
28762
+ }
28763
+ else if (createdSnapshot) {
28764
+ await removeSidecarLockIfUnchanged(lockPath, createdSnapshot, {
28765
+ lockRoot: options.lockRoot,
28766
+ parsePayload: options.parsePayload,
28767
+ });
28768
+ }
28769
+ if (lockFileCreateDenied && withinDenialBudget()) {
28770
+ await retryOrRethrowDenial(err);
28771
+ continue;
28772
+ }
28773
+ if (err.code !== "EEXIST") {
28774
+ throw err;
28775
+ }
28776
+ if (ownsReclaimGuard) {
28777
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
28778
+ ownsReclaimGuard = false;
28779
+ continue;
28780
+ }
28781
+ const nowMs = Date.now();
28782
+ let snapshot;
28783
+ try {
28784
+ snapshot = await readSidecarLockSnapshot(lockPath, {
28785
+ lockRoot: options.lockRoot,
28786
+ parsePayload: options.parsePayload,
28787
+ rejectNonFile: true,
28788
+ });
28789
+ }
28790
+ catch (readErr) {
28791
+ if (!isTransientLockFileDenial(readErr, lockPath) || !withinDenialBudget())
28792
+ throw readErr;
28793
+ await retryOrRethrowDenial(readErr);
28794
+ continue;
28795
+ }
28796
+ if (!snapshot) {
28797
+ continue;
28798
+ }
28799
+ if (context.held.has(normalizedTargetPath)) {
28800
+ await waitForRetry();
28801
+ continue;
28802
+ }
28803
+ const shouldReclaim = options.shouldReclaim ?? defaultSidecarLockShouldReclaim;
28804
+ if (await shouldReclaim({
28805
+ lockPath,
28806
+ normalizedTargetPath,
28807
+ payload: snapshot?.payload ?? null,
28808
+ staleMs: options.staleMs,
28809
+ nowMs,
28810
+ heldByThisProcess: context.held.has(normalizedTargetPath),
28811
+ })) {
28812
+ if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
28813
+ lockRoot: options.lockRoot,
28814
+ parsePayload: options.parsePayload,
28815
+ }))) {
28816
+ continue;
28817
+ }
28818
+ const staleRecovery = options.staleRecovery ?? "fail-closed";
28819
+ if (staleRecovery === "remove-if-unchanged") {
28820
+ if (!(await tryAcquireSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath))) {
28821
+ await waitForRetry();
28822
+ continue;
28823
+ }
28824
+ ownsReclaimGuard = true;
28825
+ const removal = await removeStaleSidecarLockIfAllowed({
28826
+ lockPath,
28827
+ normalizedTargetPath,
28828
+ snapshot,
28829
+ shouldRemoveStaleLock: options.shouldRemoveStaleLock,
28830
+ lockRoot: options.lockRoot,
28831
+ parsePayload: options.parsePayload,
28832
+ });
28833
+ if (removal === "removed" || removal === "changed") {
28834
+ continue;
28835
+ }
28836
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
28837
+ ownsReclaimGuard = false;
28838
+ }
28839
+ throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
28840
+ code: "file_lock_stale",
28841
+ lockPath,
28842
+ normalizedTargetPath,
28843
+ });
28844
+ }
28845
+ await waitForRetry();
28846
+ }
28847
+ }
28848
+ }
28849
+ finally {
28850
+ if (ownsReclaimGuard) {
28851
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
28852
+ }
28853
+ }
28854
+ }
28855
+
28856
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
28392
28857
 
28858
+ function createSidecarLockHandle(params) {
28859
+ let released = false;
28860
+ const release = async () => {
28861
+ if (released)
28862
+ return;
28863
+ released = true;
28864
+ await params.release();
28865
+ };
28866
+ return {
28867
+ lockPath: params.lockPath,
28868
+ normalizedTargetPath: params.normalizedTargetPath,
28869
+ verifyStillHeld: params.verifyStillHeld,
28870
+ release,
28871
+ [Symbol.asyncDispose]: release,
28872
+ };
28873
+ }
28874
+ function createHeldSidecarLockHandle(params) {
28875
+ return createSidecarLockHandle({
28876
+ lockPath: params.held.lockPath,
28877
+ normalizedTargetPath: params.normalizedTargetPath,
28878
+ verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
28879
+ lockRoot: params.held.lockRoot,
28880
+ parsePayload: params.held.parsePayload,
28881
+ }),
28882
+ release: params.release,
28883
+ });
28884
+ }
28393
28885
 
28886
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
28394
28887
 
28395
28888
 
28396
28889
 
28397
28890
 
28398
28891
 
28399
28892
  const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
28893
+ const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
28894
+ const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
28400
28895
  function getGlobalManagers() {
28401
28896
  const globalWithState = globalThis;
28402
28897
  if (!globalWithState[GLOBAL_STATE_KEY]) {
@@ -28468,17 +28963,6 @@ function snapshotMatchesSync(lockPath, observed) {
28468
28963
  }
28469
28964
  }
28470
28965
  }
28471
- async function resolveNormalizedTargetPath(targetPath) {
28472
- const resolved = external_node_path_.resolve(targetPath);
28473
- const dir = external_node_path_.dirname(resolved);
28474
- await promises_.mkdir(dir, { recursive: true });
28475
- try {
28476
- return external_node_path_.join(await promises_.realpath(dir), external_node_path_.basename(resolved));
28477
- }
28478
- catch {
28479
- return resolved;
28480
- }
28481
- }
28482
28966
  function releaseAllReclaimGuardsSync(state) {
28483
28967
  for (const reclaimGuardPath of state.reclaimGuards) {
28484
28968
  try {
@@ -28505,6 +28989,19 @@ function releaseAllLocksSync(state) {
28505
28989
  }
28506
28990
  releaseAllReclaimGuardsSync(state);
28507
28991
  }
28992
+ function ensureGlobalExitCleanupRegistered() {
28993
+ const globalWithCleanup = globalThis;
28994
+ if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
28995
+ return;
28996
+ globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
28997
+ const cleanup = () => {
28998
+ for (const state of getGlobalManagers().values()) {
28999
+ releaseAllLocksSync(state);
29000
+ }
29001
+ };
29002
+ globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
29003
+ process.on("exit", cleanup);
29004
+ }
28508
29005
  async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
28509
29006
  const current = state.held.get(normalizedTargetPath);
28510
29007
  if (current !== held) {
@@ -28553,217 +29050,18 @@ function handleForHeldLock(state, normalizedTargetPath, held) {
28553
29050
  function createSidecarLockManager(key) {
28554
29051
  const state = resolveManagerState(key);
28555
29052
  function ensureExitCleanupRegistered() {
28556
- if (!state.cleanupRegistered) {
28557
- state.cleanupRegistered = true;
28558
- state.reclaimCleanupRegistered = true;
28559
- process.on("exit", () => releaseAllLocksSync(state));
28560
- return;
28561
- }
28562
- if (!state.reclaimCleanupRegistered) {
28563
- state.reclaimCleanupRegistered = true;
28564
- process.on("exit", () => releaseAllReclaimGuardsSync(state));
28565
- }
29053
+ state.cleanupRegistered = true;
29054
+ state.reclaimCleanupRegistered = true;
29055
+ ensureGlobalExitCleanupRegistered();
28566
29056
  }
28567
29057
  async function acquire(options) {
28568
- ensureExitCleanupRegistered();
28569
- const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
28570
- const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
28571
- const held = state.held.get(normalizedTargetPath);
28572
- if (held &&
28573
- options.reentrantOwner !== undefined &&
28574
- held.reentrantOwner !== undefined &&
28575
- options.reentrantOwner === held.reentrantOwner) {
28576
- held.refCount += 1;
28577
- return handleForHeldLock(state, normalizedTargetPath, held);
28578
- }
28579
- const startedAt = Date.now();
28580
- const retry = options.retry ?? {};
28581
- const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
28582
- const reclaimGuardPath = `${lockPath}.reclaim`;
28583
- let ownsReclaimGuard = false;
28584
- let attempt = 0;
28585
- const waitForRetry = async () => {
28586
- const elapsed = Date.now() - startedAt;
28587
- if ((options.timeoutMs !== undefined &&
28588
- options.timeoutMs !== Number.POSITIVE_INFINITY &&
28589
- elapsed >= options.timeoutMs) ||
28590
- (maxRetries !== undefined && attempt >= maxRetries)) {
28591
- throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
28592
- code: "file_lock_timeout",
28593
- lockPath,
28594
- normalizedTargetPath,
28595
- });
28596
- }
28597
- const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
28598
- ? Number.POSITIVE_INFINITY
28599
- : Math.max(0, options.timeoutMs - elapsed);
28600
- const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
28601
- attempt += 1;
28602
- await new Promise((resolve) => setTimeout(resolve, delay));
28603
- };
28604
- try {
28605
- while (true) {
28606
- if (!ownsReclaimGuard && (await sidecarReclaimGuardExists(reclaimGuardPath))) {
28607
- await waitForRetry();
28608
- continue;
28609
- }
28610
- let handle = null;
28611
- try {
28612
- const payload = await options.payload();
28613
- const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
28614
- if (options.lockRoot) {
28615
- const relativeLockPath = relativeSidecarLockPath(options.lockRoot, lockPath);
28616
- try {
28617
- await options.lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
28618
- }
28619
- catch (error) {
28620
- if (error instanceof errors_FsSafeError && error.code === "already-exists") {
28621
- throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
28622
- }
28623
- throw error;
28624
- }
28625
- handle = (await options.lockRoot.open(relativeLockPath)).handle;
28626
- }
28627
- else {
28628
- handle = (await createNativeExclusiveFile(lockPath, 0o600)) ?? await promises_.open(lockPath, "wx");
28629
- await handle.writeFile(raw, "utf8");
28630
- }
28631
- const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
28632
- const createdHeld = {
28633
- refCount: 1,
28634
- reentrantOwner: options.reentrantOwner,
28635
- handle,
28636
- lockPath,
28637
- snapshot,
28638
- acquiredAt: Date.now(),
28639
- metadata: options.metadata ?? {},
28640
- lockRoot: options.lockRoot,
28641
- parsePayload: options.parsePayload,
28642
- };
28643
- state.held.set(normalizedTargetPath, createdHeld);
28644
- if (ownsReclaimGuard) {
28645
- try {
28646
- await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
28647
- ownsReclaimGuard = false;
28648
- }
28649
- catch (err) {
28650
- await releaseHeldLock(state, normalizedTargetPath, createdHeld, { force: true });
28651
- throw err;
28652
- }
28653
- }
28654
- const returnedHandle = handleForHeldLock(state, normalizedTargetPath, createdHeld);
28655
- const interval = options.compromiseCheckIntervalMs;
28656
- if (options.onCompromised && interval !== undefined && interval > 0) {
28657
- createdHeld.compromiseTimer = setInterval(() => {
28658
- void returnedHandle.verifyStillHeld().then((stillHeld) => {
28659
- if (!stillHeld && createdHeld.compromiseTimer) {
28660
- clearInterval(createdHeld.compromiseTimer);
28661
- createdHeld.compromiseTimer = undefined;
28662
- options.onCompromised?.({ lockPath, normalizedTargetPath });
28663
- }
28664
- });
28665
- }, interval);
28666
- createdHeld.compromiseTimer.unref();
28667
- }
28668
- return returnedHandle;
28669
- }
28670
- catch (err) {
28671
- if (handle) {
28672
- const failedSnapshot = { payload: null };
28673
- try {
28674
- failedSnapshot.stat = await handle.stat();
28675
- }
28676
- catch {
28677
- // Best-effort cleanup of a failed exclusive create.
28678
- }
28679
- const current = state.held.get(normalizedTargetPath);
28680
- if (current?.handle === handle) {
28681
- state.held.delete(normalizedTargetPath);
28682
- }
28683
- // If payload serialization/write fails, the file may be empty or
28684
- // partial JSON, so remove while our exclusive handle is still open.
28685
- if (!options.lockRoot) {
28686
- await promises_.rm(lockPath, { force: true }).catch(() => undefined);
28687
- }
28688
- await handle.close().catch(() => undefined);
28689
- // Windows can refuse removing an open file; retry after close but
28690
- // only if the path still points at the file identity we created.
28691
- await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
28692
- lockRoot: options.lockRoot,
28693
- parsePayload: options.parsePayload,
28694
- });
28695
- }
28696
- if (err.code !== "EEXIST") {
28697
- throw err;
28698
- }
28699
- if (ownsReclaimGuard) {
28700
- await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
28701
- ownsReclaimGuard = false;
28702
- continue;
28703
- }
28704
- const nowMs = Date.now();
28705
- const snapshot = await readSidecarLockSnapshot(lockPath, {
28706
- lockRoot: options.lockRoot,
28707
- parsePayload: options.parsePayload,
28708
- });
28709
- if (!snapshot) {
28710
- continue;
28711
- }
28712
- if (state.held.has(normalizedTargetPath)) {
28713
- await waitForRetry();
28714
- continue;
28715
- }
28716
- const shouldReclaim = options.shouldReclaim ?? defaultSidecarLockShouldReclaim;
28717
- if (await shouldReclaim({
28718
- lockPath,
28719
- normalizedTargetPath,
28720
- payload: snapshot?.payload ?? null,
28721
- staleMs: options.staleMs,
28722
- nowMs,
28723
- heldByThisProcess: state.held.has(normalizedTargetPath),
28724
- })) {
28725
- if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
28726
- lockRoot: options.lockRoot,
28727
- parsePayload: options.parsePayload,
28728
- }))) {
28729
- continue;
28730
- }
28731
- const staleRecovery = options.staleRecovery ?? "fail-closed";
28732
- if (staleRecovery === "remove-if-unchanged") {
28733
- if (!(await tryAcquireSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath))) {
28734
- await waitForRetry();
28735
- continue;
28736
- }
28737
- ownsReclaimGuard = true;
28738
- const removal = await removeStaleSidecarLockIfAllowed({
28739
- lockPath,
28740
- normalizedTargetPath,
28741
- snapshot,
28742
- shouldRemoveStaleLock: options.shouldRemoveStaleLock,
28743
- lockRoot: options.lockRoot,
28744
- parsePayload: options.parsePayload,
28745
- });
28746
- if (removal === "removed" || removal === "changed") {
28747
- continue;
28748
- }
28749
- await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
28750
- ownsReclaimGuard = false;
28751
- }
28752
- throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
28753
- code: "file_lock_stale",
28754
- lockPath,
28755
- normalizedTargetPath,
28756
- });
28757
- }
28758
- await waitForRetry();
28759
- }
28760
- }
28761
- }
28762
- finally {
28763
- if (ownsReclaimGuard) {
28764
- await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath).catch(() => undefined);
28765
- }
28766
- }
29058
+ return await acquireSidecarLock(options, {
29059
+ held: state.held,
29060
+ reclaimGuards: state.reclaimGuards,
29061
+ ensureExitCleanupRegistered,
29062
+ handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
29063
+ releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
29064
+ });
28767
29065
  }
28768
29066
  async function withLock(options, fn) {
28769
29067
  const lock = await acquire(options);
@@ -28799,21 +29097,6 @@ async function withSidecarLock(targetPath, options, fn) {
28799
29097
  return await manager.withLock({ ...acquireOptions, targetPath }, fn);
28800
29098
  }
28801
29099
 
28802
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
28803
- let test_hooks_fsSafeTestHooks;
28804
- function allowFsSafeTestHooks() {
28805
- return false || process.env.VITEST === "true";
28806
- }
28807
- function getFsSafeTestHooks() {
28808
- return test_hooks_fsSafeTestHooks;
28809
- }
28810
- function __setFsSafeTestHooksForTest(hooks) {
28811
- if (hooks && !allowFsSafeTestHooks()) {
28812
- throw new Error("__setFsSafeTestHooksForTest is only available in tests");
28813
- }
28814
- test_hooks_fsSafeTestHooks = hooks;
28815
- }
28816
-
28817
29100
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
28818
29101
 
28819
29102
 
@@ -28886,7 +29169,7 @@ async function runPinnedWriteHelper(params) {
28886
29169
  return await runPinnedWriteFallback(params);
28887
29170
  }
28888
29171
  const native = getNativeBinding();
28889
- if (native && params.overwrite === false) {
29172
+ if (native) {
28890
29173
  return await runPinnedWriteNative(native, params);
28891
29174
  }
28892
29175
  return await runPinnedWriteFallback(params);
@@ -28924,6 +29207,7 @@ async function runPinnedWriteFallback(params) {
28924
29207
  parentPath = await mkdirPathComponentsWithGuards({
28925
29208
  rootReal: params.rootPath,
28926
29209
  targetPath: parentPath,
29210
+ beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
28927
29211
  });
28928
29212
  }
28929
29213
  const parentGuard = params.mkdir
@@ -29063,77 +29347,135 @@ async function runPinnedWriteFallback(params) {
29063
29347
  return { dev: targetStat.dev, ino: targetStat.ino };
29064
29348
  }
29065
29349
 
29066
- // EXTERNAL MODULE: external "node:os"
29067
- var external_node_os_ = __webpack_require__(8161);
29068
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-existing.js
29350
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/error-detail.js
29351
+ const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
29352
+ function formatErrorDetail(value) {
29353
+ return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
29354
+ }
29069
29355
 
29356
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-symlink.js
29070
29357
 
29071
29358
 
29072
29359
 
29073
- function isFilesystemRoot(candidate) {
29074
- return external_node_path_.parse(candidate).root === candidate;
29360
+
29361
+
29362
+
29363
+ function normalizeSymlinkResolutionError(error) {
29364
+ if (isSymlinkOpenError(error)) {
29365
+ throw new errors_FsSafeError("symlink", "symlink path could not be resolved", {
29366
+ cause: error instanceof Error ? error : undefined,
29367
+ });
29368
+ }
29369
+ if (!path_isNotFoundPathError(error))
29370
+ throw error;
29075
29371
  }
29076
- async function root_path_existing_pathExists(targetPath) {
29372
+ async function resolveSymlinkHopPath(symlinkPath) {
29077
29373
  try {
29078
- await promises_.lstat(targetPath);
29079
- return true;
29374
+ return external_node_path_.resolve(await promises_.realpath(symlinkPath));
29080
29375
  }
29081
29376
  catch (error) {
29082
- if (path_isNotFoundPathError(error)) {
29083
- return false;
29084
- }
29085
- throw error;
29377
+ normalizeSymlinkResolutionError(error);
29378
+ const linkTarget = await promises_.readlink(symlinkPath);
29379
+ return resolvePathViaExistingAncestor(external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget));
29086
29380
  }
29087
29381
  }
29088
- async function root_path_existing_resolvePathViaExistingAncestor(targetPath) {
29089
- const normalized = external_node_path_.resolve(targetPath);
29090
- let cursor = normalized;
29091
- const missingSuffix = [];
29092
- while (!isFilesystemRoot(cursor) && !(await root_path_existing_pathExists(cursor))) {
29093
- missingSuffix.unshift(external_node_path_.basename(cursor));
29094
- const parent = external_node_path_.dirname(cursor);
29095
- if (parent === cursor) {
29096
- break;
29097
- }
29098
- cursor = parent;
29099
- }
29100
- if (!(await root_path_existing_pathExists(cursor))) {
29101
- return normalized;
29102
- }
29382
+ function root_path_symlink_resolveSymlinkHopPathSync(symlinkPath) {
29103
29383
  try {
29104
- const resolvedAncestor = external_node_path_.resolve(await promises_.realpath(cursor));
29105
- return missingSuffix.length === 0
29106
- ? resolvedAncestor
29107
- : external_node_path_.resolve(resolvedAncestor, ...missingSuffix);
29384
+ return path.resolve(fs.realpathSync(symlinkPath));
29108
29385
  }
29109
- catch {
29110
- return normalized;
29386
+ catch (error) {
29387
+ normalizeSymlinkResolutionError(error);
29388
+ const linkTarget = fs.readlinkSync(symlinkPath);
29389
+ return resolvePathViaExistingAncestorSync(path.resolve(path.dirname(symlinkPath), linkTarget));
29111
29390
  }
29112
29391
  }
29113
- function root_path_existing_resolvePathViaExistingAncestorSync(targetPath) {
29114
- const normalized = path.resolve(targetPath);
29115
- let cursor = normalized;
29116
- const missingSuffix = [];
29117
- while (!isFilesystemRoot(cursor) && !fs.existsSync(cursor)) {
29118
- missingSuffix.unshift(path.basename(cursor));
29119
- const parent = path.dirname(cursor);
29120
- if (parent === cursor) {
29121
- break;
29122
- }
29123
- cursor = parent;
29392
+
29393
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/safe-path-segment.js
29394
+
29395
+ const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
29396
+ const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
29397
+ // Windows treats "C:name" as relative to the drive's current directory even
29398
+ // though path.win32.isAbsolute() reports false.
29399
+ const DRIVE_RELATIVE_PREFIX = /^[A-Za-z]:(?![\\/])/;
29400
+ const HYPHEN_CHAR_CODE = 0x2d;
29401
+ function safe_path_segment_isDriveRelativePath(value) {
29402
+ return DRIVE_RELATIVE_PREFIX.test(value);
29403
+ }
29404
+ function assertNoDriveRelativePathSegments(value, label) {
29405
+ if (value.split("/").some(safe_path_segment_isDriveRelativePath)) {
29406
+ throw new errors_FsSafeError("invalid-path", `${label} must not contain a drive letter`);
29124
29407
  }
29125
- if (!fs.existsSync(cursor)) {
29126
- return normalized;
29408
+ return value;
29409
+ }
29410
+ function trimHyphenEdges(value) {
29411
+ let start = 0;
29412
+ let end = value.length;
29413
+ while (start < end && value.charCodeAt(start) === HYPHEN_CHAR_CODE) {
29414
+ start += 1;
29127
29415
  }
29128
- try {
29129
- const resolvedAncestor = path.resolve(fs.realpathSync(cursor));
29130
- return missingSuffix.length === 0
29131
- ? resolvedAncestor
29132
- : path.resolve(resolvedAncestor, ...missingSuffix);
29416
+ while (end > start && value.charCodeAt(end - 1) === HYPHEN_CHAR_CODE) {
29417
+ end -= 1;
29133
29418
  }
29134
- catch {
29135
- return normalized;
29419
+ return start === 0 && end === value.length ? value : value.slice(start, end);
29420
+ }
29421
+ function isSafePathSegment(segment, options = {}) {
29422
+ return (segment !== "" &&
29423
+ segment !== "." &&
29424
+ segment !== ".." &&
29425
+ !segment.includes("/") &&
29426
+ !segment.includes("\\") &&
29427
+ !segment.includes("\0") &&
29428
+ (options.allowDotPrefix === true || !segment.startsWith(".")) &&
29429
+ (options.allowDotPrefix === true
29430
+ ? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
29431
+ : SAFE_PATH_SEGMENT_PATTERN.test(segment)));
29432
+ }
29433
+ function assertSafePathSegment(segment, options = {}) {
29434
+ // Validate the exact value callers will later join into paths; trimming here
29435
+ // would let whitespace-padded ids pass and then be used verbatim.
29436
+ if (!isSafePathSegment(segment, options)) {
29437
+ throw new FsSafeError("invalid-path", `${options.label ?? "path segment"} must be a safe path segment`);
29438
+ }
29439
+ return segment;
29440
+ }
29441
+ function sanitizeSafePathSegment(value, fallback, options = {}) {
29442
+ const sanitized = value
29443
+ .trim()
29444
+ .replace(/[\\/]+/g, "-")
29445
+ .replace(/\0/g, "")
29446
+ .replace(/[^A-Za-z0-9._-]+/g, "-");
29447
+ const trimmed = trimHyphenEdges(sanitized);
29448
+ if (isSafePathSegment(trimmed, options)) {
29449
+ return trimmed;
29450
+ }
29451
+ return assertSafePathSegment(fallback, { ...options, label: "fallback path segment" });
29452
+ }
29453
+ function assertSafePathPrefix(prefix, options = {}) {
29454
+ // Prefixes are often derived from safe filenames. Normalize harmless
29455
+ // filename characters first, but still reject real path-control bytes.
29456
+ if (prefix.includes("/") || prefix.includes("\\") || prefix.includes("\0")) {
29457
+ return assertSafePathSegment(prefix, {
29458
+ allowDotPrefix: true,
29459
+ ...options,
29460
+ label: options.label ?? "path prefix",
29461
+ });
29136
29462
  }
29463
+ return assertSafePathSegment(prefix.replace(/[^A-Za-z0-9._-]+/g, "-"), {
29464
+ allowDotPrefix: true,
29465
+ ...options,
29466
+ label: options.label ?? "path prefix",
29467
+ });
29468
+ }
29469
+
29470
+ // EXTERNAL MODULE: external "node:os"
29471
+ var external_node_os_ = __webpack_require__(8161);
29472
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/short-path.js
29473
+
29474
+
29475
+ function shortPath(value) {
29476
+ const home = external_node_os_.homedir();
29477
+ const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
29478
+ return formatErrorDetail(shortened);
29137
29479
  }
29138
29480
 
29139
29481
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
@@ -29144,6 +29486,10 @@ function root_path_existing_resolvePathViaExistingAncestorSync(targetPath) {
29144
29486
 
29145
29487
 
29146
29488
 
29489
+
29490
+
29491
+
29492
+
29147
29493
  const ROOT_PATH_ALIAS_POLICIES = {
29148
29494
  strict: Object.freeze({
29149
29495
  allowFinalSymlinkForUnlink: false,
@@ -29155,11 +29501,20 @@ const ROOT_PATH_ALIAS_POLICIES = {
29155
29501
  }),
29156
29502
  };
29157
29503
  async function resolveRootPath(params) {
29504
+ try {
29505
+ return await resolveRootPathInternal(params);
29506
+ }
29507
+ catch (error) {
29508
+ throw sanitizeRootPathError(error);
29509
+ }
29510
+ }
29511
+ async function resolveRootPathInternal(params) {
29512
+ assertValidRootPathInputs(params);
29158
29513
  const rootPath = external_node_path_.resolve(params.rootPath);
29159
29514
  const absolutePath = external_node_path_.resolve(params.absolutePath);
29160
29515
  const rootCanonicalPath = params.rootCanonicalPath
29161
29516
  ? external_node_path_.resolve(params.rootCanonicalPath)
29162
- : await root_path_existing_resolvePathViaExistingAncestor(rootPath);
29517
+ : await resolvePathViaExistingAncestor(rootPath);
29163
29518
  const context = createBoundaryResolutionContext({
29164
29519
  resolveParams: params,
29165
29520
  rootPath,
@@ -29185,6 +29540,15 @@ async function resolveRootPath(params) {
29185
29540
  });
29186
29541
  }
29187
29542
  function resolveRootPathSync(params) {
29543
+ try {
29544
+ return resolveRootPathSyncInternal(params);
29545
+ }
29546
+ catch (error) {
29547
+ throw sanitizeRootPathError(error);
29548
+ }
29549
+ }
29550
+ function resolveRootPathSyncInternal(params) {
29551
+ assertValidRootPathInputs(params);
29188
29552
  const rootPath = path.resolve(params.rootPath);
29189
29553
  const absolutePath = path.resolve(params.absolutePath);
29190
29554
  const rootCanonicalPath = params.rootCanonicalPath
@@ -29214,6 +29578,29 @@ function resolveRootPathSync(params) {
29214
29578
  rootCanonicalPath: context.rootCanonicalPath,
29215
29579
  });
29216
29580
  }
29581
+ function sanitizeRootPathError(error) {
29582
+ if (error instanceof Error) {
29583
+ error.message = formatErrorDetail(error.message);
29584
+ }
29585
+ return error;
29586
+ }
29587
+ function assertValidRootPathInputs(params) {
29588
+ path_assertNoNulPathInput(params.rootPath, "root path contains a NUL byte");
29589
+ path_assertNoNulPathInput(params.absolutePath, "absolute path contains a NUL byte");
29590
+ assertNoEmbeddedDriveRelativeSegment(params.rootPath, "root path");
29591
+ assertNoEmbeddedDriveRelativeSegment(params.absolutePath, "absolute path");
29592
+ if (params.rootCanonicalPath !== undefined) {
29593
+ path_assertNoNulPathInput(params.rootCanonicalPath, "canonical root path contains a NUL byte");
29594
+ assertNoEmbeddedDriveRelativeSegment(params.rootCanonicalPath, "canonical root path");
29595
+ }
29596
+ }
29597
+ function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
29598
+ if (process.platform !== "win32") {
29599
+ return;
29600
+ }
29601
+ const root = external_node_path_.parse(filePath).root;
29602
+ assertNoDriveRelativePathSegments(filePath.slice(root.length).replaceAll("\\", "/"), label);
29603
+ }
29217
29604
  function isPromiseLike(value) {
29218
29605
  return Boolean(value &&
29219
29606
  (typeof value === "object" || typeof value === "function") &&
@@ -29232,6 +29619,15 @@ function createLexicalTraversalState(params) {
29232
29619
  preserveFinalSymlink: false,
29233
29620
  };
29234
29621
  }
29622
+ function createLexicalTraversalContext(params) {
29623
+ return {
29624
+ state: createLexicalTraversalState(params),
29625
+ resolveParams: params.params,
29626
+ rootPath: params.rootPath,
29627
+ rootCanonicalPath: params.rootCanonicalPath,
29628
+ absolutePath: params.absolutePath,
29629
+ };
29630
+ }
29235
29631
  function splitTraversalSegments(value) {
29236
29632
  return value
29237
29633
  .split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
@@ -29255,149 +29651,97 @@ function rawPathRelativeToRoot(rootPath, candidatePath) {
29255
29651
  : candidatePrefix === rootWithSep;
29256
29652
  return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
29257
29653
  }
29258
- function assertLexicalCursorInsideBoundary(params) {
29654
+ function assertLexicalCursorInsideBoundary(context, candidatePath) {
29259
29655
  assertInsideBoundary({
29260
- boundaryLabel: params.params.boundaryLabel,
29261
- rootCanonicalPath: params.rootCanonicalPath,
29262
- candidatePath: params.candidatePath,
29263
- absolutePath: params.absolutePath,
29656
+ boundaryLabel: context.resolveParams.boundaryLabel,
29657
+ rootCanonicalPath: context.rootCanonicalPath,
29658
+ candidatePath,
29659
+ absolutePath: context.absolutePath,
29264
29660
  });
29265
29661
  }
29266
- function applyMissingSuffixToCanonicalCursor(params) {
29267
- const missingSuffix = params.state.segments.slice(params.missingFromIndex);
29662
+ function applyMissingSuffixToCanonicalCursor(context, missingFromIndex) {
29663
+ const missingSuffix = context.state.segments.slice(missingFromIndex);
29268
29664
  for (const segment of missingSuffix) {
29269
- advanceCanonicalCursorForSegment({
29270
- state: params.state,
29271
- segment,
29272
- rootCanonicalPath: params.rootCanonicalPath,
29273
- params: params.params,
29274
- absolutePath: params.absolutePath,
29275
- });
29665
+ advanceCanonicalCursorForSegment(context, segment);
29276
29666
  }
29277
29667
  }
29278
- function advanceCanonicalCursorForSegment(params) {
29279
- params.state.canonicalCursor = external_node_path_.resolve(params.state.canonicalCursor, params.segment);
29280
- assertLexicalCursorInsideBoundary({
29281
- params: params.params,
29282
- rootCanonicalPath: params.rootCanonicalPath,
29283
- candidatePath: params.state.canonicalCursor,
29284
- absolutePath: params.absolutePath,
29285
- });
29668
+ function advanceCanonicalCursorForSegment(context, segment) {
29669
+ context.state.canonicalCursor = external_node_path_.resolve(context.state.canonicalCursor, segment);
29670
+ assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
29286
29671
  }
29287
- function finalizeLexicalResolution(params) {
29288
- assertLexicalCursorInsideBoundary({
29289
- params: params.params,
29290
- rootCanonicalPath: params.rootCanonicalPath,
29291
- candidatePath: params.state.canonicalCursor,
29292
- absolutePath: params.absolutePath,
29293
- });
29672
+ function finalizeLexicalResolution(context, kind) {
29673
+ assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
29294
29674
  return buildResolvedRootPath({
29295
- absolutePath: params.absolutePath,
29296
- canonicalPath: params.state.canonicalCursor,
29297
- rootPath: params.rootPath,
29298
- rootCanonicalPath: params.rootCanonicalPath,
29299
- kind: params.kind,
29675
+ absolutePath: context.absolutePath,
29676
+ canonicalPath: context.state.canonicalCursor,
29677
+ rootPath: context.rootPath,
29678
+ rootCanonicalPath: context.rootCanonicalPath,
29679
+ kind,
29300
29680
  });
29301
29681
  }
29302
- function handleLexicalLstatFailure(params) {
29303
- if (!path_isNotFoundPathError(params.error)) {
29682
+ function handleLexicalLstatFailure(context, error, missingFromIndex) {
29683
+ if (!path_isNotFoundPathError(error)) {
29304
29684
  return false;
29305
29685
  }
29306
- applyMissingSuffixToCanonicalCursor({
29307
- state: params.state,
29308
- missingFromIndex: params.missingFromIndex,
29309
- rootCanonicalPath: params.rootCanonicalPath,
29310
- params: params.resolveParams,
29311
- absolutePath: params.absolutePath,
29312
- });
29686
+ applyMissingSuffixToCanonicalCursor(context, missingFromIndex);
29313
29687
  return true;
29314
29688
  }
29315
- function handleLexicalStatReadFailure(params) {
29316
- if (handleLexicalLstatFailure({
29317
- error: params.error,
29318
- state: params.state,
29319
- missingFromIndex: params.missingFromIndex,
29320
- rootCanonicalPath: params.rootCanonicalPath,
29321
- resolveParams: params.resolveParams,
29322
- absolutePath: params.absolutePath,
29323
- })) {
29689
+ function handleLexicalStatReadFailure(context, error, missingFromIndex) {
29690
+ if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
29324
29691
  return null;
29325
29692
  }
29326
- throw params.error;
29693
+ throw error;
29327
29694
  }
29328
- function handleLexicalStatDisposition(params) {
29695
+ function handleLexicalStatDisposition(context, params) {
29329
29696
  if (!params.isSymbolicLink) {
29330
- advanceCanonicalCursorForSegment({
29331
- state: params.state,
29332
- segment: params.segment,
29333
- rootCanonicalPath: params.rootCanonicalPath,
29334
- params: params.resolveParams,
29335
- absolutePath: params.absolutePath,
29336
- });
29697
+ advanceCanonicalCursorForSegment(context, params.segment);
29337
29698
  return "continue";
29338
29699
  }
29339
- if (params.state.allowFinalSymlink && params.isLast) {
29340
- params.state.preserveFinalSymlink = true;
29341
- advanceCanonicalCursorForSegment({
29342
- state: params.state,
29343
- segment: params.segment,
29344
- rootCanonicalPath: params.rootCanonicalPath,
29345
- params: params.resolveParams,
29346
- absolutePath: params.absolutePath,
29347
- });
29700
+ if (context.resolveParams.rejectSymlinks === true && params.isLast) {
29701
+ throw new errors_FsSafeError("symlink", "symlink path component not allowed");
29702
+ }
29703
+ if (context.state.allowFinalSymlink && params.isLast) {
29704
+ context.state.preserveFinalSymlink = true;
29705
+ advanceCanonicalCursorForSegment(context, params.segment);
29348
29706
  return "break";
29349
29707
  }
29350
29708
  return "resolve-link";
29351
29709
  }
29352
- function applyResolvedSymlinkHop(params) {
29353
- if (!path_isPathInside(params.rootCanonicalPath, params.linkCanonical)) {
29710
+ function applyResolvedSymlinkHop(context, linkCanonical) {
29711
+ if (!path_isPathInside(context.rootCanonicalPath, linkCanonical)) {
29354
29712
  throw symlinkEscapeError({
29355
- boundaryLabel: params.boundaryLabel,
29356
- rootCanonicalPath: params.rootCanonicalPath,
29357
- symlinkPath: params.state.lexicalCursor,
29713
+ boundaryLabel: context.resolveParams.boundaryLabel,
29714
+ rootCanonicalPath: context.rootCanonicalPath,
29715
+ symlinkPath: context.state.lexicalCursor,
29358
29716
  });
29359
29717
  }
29360
- params.state.canonicalCursor = params.linkCanonical;
29361
- params.state.lexicalCursor = params.linkCanonical;
29718
+ context.state.canonicalCursor = linkCanonical;
29719
+ context.state.lexicalCursor = linkCanonical;
29362
29720
  }
29363
- function readLexicalStat(params) {
29721
+ function readLexicalStat(context, params) {
29364
29722
  try {
29365
- const stat = params.read(params.state.lexicalCursor);
29723
+ const stat = params.read(context.state.lexicalCursor);
29366
29724
  if (isPromiseLike(stat)) {
29367
- return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure({ ...params, error }));
29725
+ return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
29368
29726
  }
29369
29727
  return stat;
29370
29728
  }
29371
29729
  catch (error) {
29372
- return handleLexicalStatReadFailure({ ...params, error });
29730
+ return handleLexicalStatReadFailure(context, error, params.missingFromIndex);
29373
29731
  }
29374
29732
  }
29375
- function resolveAndApplySymlinkHop(params) {
29376
- const linkCanonical = params.resolveLinkCanonical(params.state.lexicalCursor);
29733
+ function resolveAndApplySymlinkHop(context, params) {
29734
+ const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
29377
29735
  if (isPromiseLike(linkCanonical)) {
29378
- return Promise.resolve(linkCanonical).then((value) => applyResolvedSymlinkHop({
29379
- state: params.state,
29380
- linkCanonical: value,
29381
- rootCanonicalPath: params.rootCanonicalPath,
29382
- boundaryLabel: params.boundaryLabel,
29383
- }));
29736
+ return Promise.resolve(linkCanonical).then((value) => {
29737
+ applyResolvedSymlinkHop(context, value);
29738
+ });
29384
29739
  }
29385
- applyResolvedSymlinkHop({
29386
- state: params.state,
29387
- linkCanonical,
29388
- rootCanonicalPath: params.rootCanonicalPath,
29389
- boundaryLabel: params.boundaryLabel,
29390
- });
29740
+ applyResolvedSymlinkHop(context, linkCanonical);
29391
29741
  }
29392
- function applyParentTraversalStep(params) {
29393
- params.state.lexicalCursor = external_node_path_.resolve(params.state.lexicalCursor, "..");
29394
- advanceCanonicalCursorForSegment({
29395
- state: params.state,
29396
- segment: "..",
29397
- rootCanonicalPath: params.rootCanonicalPath,
29398
- params: params.resolveParams,
29399
- absolutePath: params.absolutePath,
29400
- });
29742
+ function applyParentTraversalStep(context) {
29743
+ context.state.lexicalCursor = external_node_path_.resolve(context.state.lexicalCursor, "..");
29744
+ advanceCanonicalCursorForSegment(context, "..");
29401
29745
  }
29402
29746
  function* iterateLexicalTraversal(state) {
29403
29747
  for (let idx = 0; idx < state.segments.length; idx += 1) {
@@ -29407,32 +29751,22 @@ function* iterateLexicalTraversal(state) {
29407
29751
  }
29408
29752
  }
29409
29753
  async function resolveRootPathLexicalAsync(params) {
29410
- const state = createLexicalTraversalState(params);
29411
- const sharedStepParams = {
29412
- state,
29413
- rootCanonicalPath: params.rootCanonicalPath,
29414
- resolveParams: params.params,
29415
- absolutePath: params.absolutePath,
29416
- };
29754
+ const context = createLexicalTraversalContext(params);
29755
+ const { state } = context;
29417
29756
  for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
29418
29757
  if (segment === "..") {
29419
- applyParentTraversalStep({
29420
- ...sharedStepParams,
29421
- resolveParams: params.params,
29422
- });
29758
+ applyParentTraversalStep(context);
29423
29759
  continue;
29424
29760
  }
29425
29761
  state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
29426
- const stat = await readLexicalStat({
29427
- ...sharedStepParams,
29762
+ const stat = await readLexicalStat(context, {
29428
29763
  missingFromIndex: idx,
29429
29764
  read: (cursor) => promises_.lstat(cursor),
29430
29765
  });
29431
29766
  if (!stat) {
29432
29767
  break;
29433
29768
  }
29434
- const disposition = handleLexicalStatDisposition({
29435
- ...sharedStepParams,
29769
+ const disposition = handleLexicalStatDisposition(context, {
29436
29770
  isSymbolicLink: stat.isSymbolicLink(),
29437
29771
  segment,
29438
29772
  isLast,
@@ -29443,41 +29777,29 @@ async function resolveRootPathLexicalAsync(params) {
29443
29777
  if (disposition === "break") {
29444
29778
  break;
29445
29779
  }
29446
- await resolveAndApplySymlinkHop({
29447
- state,
29448
- rootCanonicalPath: params.rootCanonicalPath,
29449
- boundaryLabel: params.params.boundaryLabel,
29780
+ await resolveAndApplySymlinkHop(context, {
29450
29781
  resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
29451
29782
  });
29783
+ if (context.resolveParams.rejectSymlinks === true) {
29784
+ throw new errors_FsSafeError("symlink", "symlink path component not allowed");
29785
+ }
29452
29786
  }
29453
29787
  const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
29454
- return finalizeLexicalResolution({
29455
- ...params,
29456
- state,
29457
- kind,
29458
- });
29788
+ return finalizeLexicalResolution(context, kind);
29459
29789
  }
29460
29790
  function resolveRootPathLexicalSync(params) {
29461
- const state = createLexicalTraversalState(params);
29791
+ const context = createLexicalTraversalContext(params);
29792
+ const { state } = context;
29462
29793
  for (let idx = 0; idx < state.segments.length; idx += 1) {
29463
29794
  const segment = state.segments[idx] ?? "";
29464
29795
  const isLast = idx === state.segments.length - 1;
29465
29796
  if (segment === "..") {
29466
- applyParentTraversalStep({
29467
- state,
29468
- rootCanonicalPath: params.rootCanonicalPath,
29469
- resolveParams: params.params,
29470
- absolutePath: params.absolutePath,
29471
- });
29797
+ applyParentTraversalStep(context);
29472
29798
  continue;
29473
29799
  }
29474
29800
  state.lexicalCursor = path.join(state.lexicalCursor, segment);
29475
- const maybeStat = readLexicalStat({
29476
- state,
29801
+ const maybeStat = readLexicalStat(context, {
29477
29802
  missingFromIndex: idx,
29478
- rootCanonicalPath: params.rootCanonicalPath,
29479
- resolveParams: params.params,
29480
- absolutePath: params.absolutePath,
29481
29803
  read: (cursor) => fs.lstatSync(cursor),
29482
29804
  });
29483
29805
  if (isPromiseLike(maybeStat)) {
@@ -29487,14 +29809,10 @@ function resolveRootPathLexicalSync(params) {
29487
29809
  if (!stat) {
29488
29810
  break;
29489
29811
  }
29490
- const disposition = handleLexicalStatDisposition({
29491
- state,
29812
+ const disposition = handleLexicalStatDisposition(context, {
29492
29813
  isSymbolicLink: stat.isSymbolicLink(),
29493
29814
  segment,
29494
29815
  isLast,
29495
- rootCanonicalPath: params.rootCanonicalPath,
29496
- resolveParams: params.params,
29497
- absolutePath: params.absolutePath,
29498
29816
  });
29499
29817
  if (disposition === "continue") {
29500
29818
  continue;
@@ -29502,22 +29820,18 @@ function resolveRootPathLexicalSync(params) {
29502
29820
  if (disposition === "break") {
29503
29821
  break;
29504
29822
  }
29505
- const maybeApplied = resolveAndApplySymlinkHop({
29506
- state,
29507
- rootCanonicalPath: params.rootCanonicalPath,
29508
- boundaryLabel: params.params.boundaryLabel,
29823
+ const maybeApplied = resolveAndApplySymlinkHop(context, {
29509
29824
  resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
29510
29825
  });
29511
29826
  if (isPromiseLike(maybeApplied)) {
29512
29827
  throw new Error("Unexpected async symlink resolution");
29513
29828
  }
29829
+ if (context.resolveParams.rejectSymlinks === true) {
29830
+ throw new FsSafeError("symlink", "symlink path component not allowed");
29831
+ }
29514
29832
  }
29515
29833
  const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
29516
- return finalizeLexicalResolution({
29517
- ...params,
29518
- state,
29519
- kind,
29520
- });
29834
+ return finalizeLexicalResolution(context, kind);
29521
29835
  }
29522
29836
  function resolveCanonicalOutsideLexicalPath(params) {
29523
29837
  return params.outsideLexicalCanonicalPath ?? params.absolutePath;
@@ -29581,7 +29895,7 @@ async function resolveOutsideLexicalCanonicalPathAsync(params) {
29581
29895
  if (path_isPathInside(params.rootPath, params.absolutePath)) {
29582
29896
  return undefined;
29583
29897
  }
29584
- return await root_path_existing_resolvePathViaExistingAncestor(params.absolutePath);
29898
+ return await resolvePathViaExistingAncestor(params.absolutePath);
29585
29899
  }
29586
29900
  function resolveOutsideLexicalCanonicalPathSync(params) {
29587
29901
  if (isPathInside(params.rootPath, params.absolutePath)) {
@@ -29688,39 +30002,6 @@ function pathEscapeError(params) {
29688
30002
  function symlinkEscapeError(params) {
29689
30003
  return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
29690
30004
  }
29691
- function shortPath(value) {
29692
- const home = external_node_os_.homedir();
29693
- if (value.startsWith(home)) {
29694
- return `~${value.slice(home.length)}`;
29695
- }
29696
- return value;
29697
- }
29698
- async function resolveSymlinkHopPath(symlinkPath) {
29699
- try {
29700
- return external_node_path_.resolve(await promises_.realpath(symlinkPath));
29701
- }
29702
- catch (error) {
29703
- if (!path_isNotFoundPathError(error)) {
29704
- throw error;
29705
- }
29706
- const linkTarget = await promises_.readlink(symlinkPath);
29707
- const linkAbsolute = external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget);
29708
- return root_path_existing_resolvePathViaExistingAncestor(linkAbsolute);
29709
- }
29710
- }
29711
- function resolveSymlinkHopPathSync(symlinkPath) {
29712
- try {
29713
- return path.resolve(fs.realpathSync(symlinkPath));
29714
- }
29715
- catch (error) {
29716
- if (!isNotFoundPathError(error)) {
29717
- throw error;
29718
- }
29719
- const linkTarget = fs.readlinkSync(symlinkPath);
29720
- const linkAbsolute = path.resolve(path.dirname(symlinkPath), linkTarget);
29721
- return resolvePathViaExistingAncestorSync(linkAbsolute);
29722
- }
29723
- }
29724
30005
 
29725
30006
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path-policy.js
29726
30007
 
@@ -29764,14 +30045,8 @@ async function assertNoHardlinkedFinalPath(params) {
29764
30045
  return;
29765
30046
  }
29766
30047
  if (stat.nlink > 1) {
29767
- throw new Error(`Hardlinked path is not allowed under ${params.boundaryLabel} (${path_policy_shortPath(params.root)}): ${path_policy_shortPath(params.filePath)}`);
29768
- }
29769
- }
29770
- function path_policy_shortPath(value) {
29771
- if (value.startsWith(external_node_os_.homedir())) {
29772
- return `~${value.slice(external_node_os_.homedir().length)}`;
30048
+ throw new Error(`Hardlinked path is not allowed under ${params.boundaryLabel} (${shortPath(params.root)}): ${shortPath(params.filePath)}`);
29773
30049
  }
29774
- return value;
29775
30050
  }
29776
30051
 
29777
30052
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/string-coerce.js
@@ -29853,6 +30128,10 @@ function hasNonEmptyString(value) {
29853
30128
 
29854
30129
 
29855
30130
  const ENCODED_FILE_URL_SEPARATOR_RE = /%(?:2f|5c)/i;
30131
+ const FILE_URL_PREFIX_RE = /^file:\/\//i;
30132
+ function isFileUrl(input) {
30133
+ return FILE_URL_PREFIX_RE.test(input);
30134
+ }
29856
30135
  function isLocalFileUrlHost(hostname) {
29857
30136
  const normalized = normalizeLowercaseStringOrEmpty(hostname);
29858
30137
  return normalized === "" || normalized === "localhost";
@@ -29883,7 +30162,7 @@ function assertNoWindowsNetworkPath(filePath, label = "Path") {
29883
30162
  throw new Error(`${label} cannot use Windows network paths: ${filePath}`);
29884
30163
  }
29885
30164
  }
29886
- function safeFileURLToPath(fileUrl) {
30165
+ function safeFileURLToPath(fileUrl, platform = process.platform) {
29887
30166
  let parsed;
29888
30167
  try {
29889
30168
  parsed = new external_node_url_.URL(fileUrl);
@@ -29900,13 +30179,15 @@ function safeFileURLToPath(fileUrl) {
29900
30179
  if (hasEncodedFileUrlSeparator(parsed.pathname)) {
29901
30180
  throw new Error(`file:// URLs cannot encode path separators: ${fileUrl}`);
29902
30181
  }
29903
- const filePath = (0,external_node_url_.fileURLToPath)(parsed);
29904
- assertNoWindowsNetworkPath(filePath, "Local file URL");
30182
+ const filePath = (0,external_node_url_.fileURLToPath)(parsed, { windows: platform === "win32" });
30183
+ if (isWindowsNetworkPath(filePath, platform)) {
30184
+ throw new Error(`Local file URL cannot use Windows network paths: ${filePath}`);
30185
+ }
29905
30186
  return filePath;
29906
30187
  }
29907
- function trySafeFileURLToPath(fileUrl) {
30188
+ function trySafeFileURLToPath(fileUrl, platform = process.platform) {
29908
30189
  try {
29909
- return safeFileURLToPath(fileUrl);
30190
+ return safeFileURLToPath(fileUrl, platform);
29910
30191
  }
29911
30192
  catch {
29912
30193
  return undefined;
@@ -29916,7 +30197,7 @@ function basenameFromMediaSource(source) {
29916
30197
  if (!source) {
29917
30198
  return undefined;
29918
30199
  }
29919
- if (source.startsWith("file://")) {
30200
+ if (isFileUrl(source)) {
29920
30201
  const filePath = trySafeFileURLToPath(source);
29921
30202
  return filePath ? path.basename(filePath) || undefined : undefined;
29922
30203
  }
@@ -30001,11 +30282,11 @@ function trimTrailingWindowsIgnoredChars(value) {
30001
30282
  }
30002
30283
  return end === value.length ? value : value.slice(0, end);
30003
30284
  }
30004
- function candidateReadPaths(filePath) {
30005
- if (!filePath.startsWith("file://")) {
30285
+ function candidateReadPaths(filePath, platform) {
30286
+ if (!isFileUrl(filePath)) {
30006
30287
  return [filePath];
30007
30288
  }
30008
- const parsed = trySafeFileURLToPath(filePath);
30289
+ const parsed = trySafeFileURLToPath(filePath, platform);
30009
30290
  return parsed === undefined ? [filePath] : [filePath, parsed];
30010
30291
  }
30011
30292
  function normalizePosixPath(filePath, cwd) {
@@ -30048,7 +30329,7 @@ function matchWindowsDeviceReadPath(filePath) {
30048
30329
  }
30049
30330
  function matchUnsafeDeviceReadPath(filePath, options = {}) {
30050
30331
  const platform = options.platform ?? process.platform;
30051
- for (const candidate of candidateReadPaths(filePath)) {
30332
+ for (const candidate of candidateReadPaths(filePath, platform)) {
30052
30333
  const match = platform === "win32"
30053
30334
  ? matchWindowsDeviceReadPath(candidate)
30054
30335
  : matchPosixDeviceReadPath(candidate, options.cwd);
@@ -30085,23 +30366,6 @@ async function read_opened_file_readOpenedFileSafely(params) {
30085
30366
  };
30086
30367
  }
30087
30368
 
30088
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path-stat.js
30089
- function pathStatFromStats(stat) {
30090
- return {
30091
- dev: Number(stat.dev),
30092
- gid: Number(stat.gid),
30093
- ino: Number(stat.ino),
30094
- isDirectory: stat.isDirectory(),
30095
- isFile: stat.isFile(),
30096
- isSymbolicLink: stat.isSymbolicLink(),
30097
- mode: stat.mode,
30098
- mtimeMs: stat.mtimeMs,
30099
- nlink: stat.nlink,
30100
- size: stat.size,
30101
- uid: stat.uid,
30102
- };
30103
- }
30104
-
30105
30369
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/home-dir.js
30106
30370
 
30107
30371
 
@@ -30224,10 +30488,19 @@ function resolveOsHomeRelativePath(input, opts) {
30224
30488
 
30225
30489
 
30226
30490
 
30491
+
30492
+
30493
+
30227
30494
  const ensureTrailingSep = (value) => value.endsWith(external_node_path_.sep) ? value : value + external_node_path_.sep;
30228
30495
  function assertValidRootRelativePath(relativePath) {
30229
30496
  path_assertNoNulPathInput(relativePath, "relative path contains a NUL byte");
30230
30497
  }
30498
+ function assertValidRootDestinationPath(relativePath) {
30499
+ assertValidRootRelativePath(relativePath);
30500
+ if (safe_path_segment_isDriveRelativePath(relativePath)) {
30501
+ throw new errors_FsSafeError("invalid-path", "relative path must not start with a drive letter");
30502
+ }
30503
+ }
30231
30504
  let cachedHomePath;
30232
30505
  async function expandRelativePathWithHome(relativePath) {
30233
30506
  const rawHome = process.env.HOME || process.env.USERPROFILE || external_node_os_.homedir();
@@ -30246,12 +30519,14 @@ async function expandRelativePathWithHome(relativePath) {
30246
30519
  async function resolveRootContext(rootDir) {
30247
30520
  path_assertNoNulPathInput(rootDir, "root dir contains a NUL byte");
30248
30521
  let rootReal;
30522
+ let rootIdentity;
30249
30523
  try {
30250
30524
  rootReal = await promises_.realpath(rootDir);
30251
30525
  const rootStat = await promises_.stat(rootReal);
30252
30526
  if (!rootStat.isDirectory()) {
30253
30527
  throw new errors_FsSafeError("invalid-path", "root dir is not a directory");
30254
30528
  }
30529
+ rootIdentity = { dev: rootStat.dev, ino: rootStat.ino };
30255
30530
  }
30256
30531
  catch (err) {
30257
30532
  if (err instanceof errors_FsSafeError) {
@@ -30264,16 +30539,37 @@ async function resolveRootContext(rootDir) {
30264
30539
  }
30265
30540
  return {
30266
30541
  rootDir: external_node_path_.resolve(rootDir),
30542
+ rootIdentity,
30267
30543
  rootReal,
30268
30544
  rootWithSep: ensureTrailingSep(rootReal),
30269
30545
  };
30270
30546
  }
30547
+ async function assertRootIdentityCurrent(root) {
30548
+ let current;
30549
+ try {
30550
+ current = await promises_.lstat(root.rootReal);
30551
+ }
30552
+ catch (error) {
30553
+ throw new errors_FsSafeError("path-mismatch", "root path changed during operation", {
30554
+ cause: error instanceof Error ? error : undefined,
30555
+ });
30556
+ }
30557
+ if (current.isSymbolicLink() ||
30558
+ !current.isDirectory() ||
30559
+ !file_identity_sameFileIdentity(current, root.rootIdentity)) {
30560
+ throw new errors_FsSafeError("path-mismatch", "root path changed during operation");
30561
+ }
30562
+ }
30271
30563
  async function resolvePathInRoot(root, relativePath, options) {
30272
30564
  assertValidRootRelativePath(relativePath);
30565
+ await assertRootIdentityCurrent(root);
30273
30566
  const expanded = await expandRelativePathWithHome(relativePath);
30274
30567
  const resolved = external_node_path_.resolve(root.rootWithSep, expanded);
30275
30568
  if (!path_isPathInside(root.rootWithSep, resolved)) {
30276
- throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
30569
+ throw outsideWorkspaceError();
30570
+ }
30571
+ if (options?.rejectUnsafeDeviceReads === true) {
30572
+ assertNoUnsafeDeviceReadPath(resolved);
30277
30573
  }
30278
30574
  const rawAbsolutePath = external_node_path_.isAbsolute(expanded)
30279
30575
  ? expanded
@@ -30285,9 +30581,18 @@ async function resolvePathInRoot(root, relativePath, options) {
30285
30581
  rootCanonicalPath: root.rootReal,
30286
30582
  boundaryLabel: "root",
30287
30583
  policy: options?.allowFinalSymlink ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
30584
+ rejectSymlinks: options?.rejectSymlinks,
30288
30585
  });
30289
30586
  }
30290
30587
  catch (error) {
30588
+ if (error instanceof errors_FsSafeError && error.code === "symlink") {
30589
+ throw error;
30590
+ }
30591
+ if (hasNodeErrorCode(error, "ENAMETOOLONG")) {
30592
+ throw new errors_FsSafeError("invalid-path", "relative path is too long", {
30593
+ cause: error instanceof Error ? error : undefined,
30594
+ });
30595
+ }
30291
30596
  const code = options?.aliasErrorCode ?? "outside-workspace";
30292
30597
  throw new errors_FsSafeError(code, code === "path-alias" ? "path alias escape blocked" : "file is outside workspace root", {
30293
30598
  cause: error instanceof Error ? error : undefined,
@@ -30299,29 +30604,6 @@ async function resolvePathWithinRoot(params) {
30299
30604
  return await resolvePathInRoot(await resolveRootContext(params.rootDir), params.relativePath);
30300
30605
  }
30301
30606
 
30302
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-errors.js
30303
-
30304
-
30305
- function isAlreadyExistsError(error) {
30306
- return hasNodeErrorCode(error, "EEXIST") || /File exists|EEXIST/i.test(String(error));
30307
- }
30308
- function normalizePinnedWriteError(error) {
30309
- if (error instanceof errors_FsSafeError) {
30310
- return error;
30311
- }
30312
- return new errors_FsSafeError("invalid-path", "path is not a regular file under root", {
30313
- cause: error instanceof Error ? error : undefined,
30314
- });
30315
- }
30316
- function normalizePinnedPathError(error) {
30317
- if (error instanceof errors_FsSafeError) {
30318
- return error;
30319
- }
30320
- return new errors_FsSafeError("path-alias", "path is not under root", {
30321
- cause: error instanceof Error ? error : undefined,
30322
- });
30323
- }
30324
-
30325
30607
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/json-stringify.js
30326
30608
  function stringifyJsonDocument(value, replacer, space) {
30327
30609
  const text = JSON.stringify(value, replacer, space);
@@ -30356,6 +30638,17 @@ function limitEntry(relativePath) {
30356
30638
  return { relativePath, kind: "truncated", size: 0 };
30357
30639
  }
30358
30640
  async function* walkRoot(root, relativePath, options) {
30641
+ if (!["skip", "follow-within-root"].includes(options.symlinkPolicy)) {
30642
+ throw new TypeError(`invalid root walk symlink policy: ${String(options.symlinkPolicy)}`);
30643
+ }
30644
+ if (options.limitBehavior !== undefined &&
30645
+ !["truncate", "throw"].includes(options.limitBehavior)) {
30646
+ throw new TypeError(`invalid root walk limit behavior: ${String(options.limitBehavior)}`);
30647
+ }
30648
+ if (options.onDirectoryError !== undefined &&
30649
+ !["throw", "skip-and-report"].includes(options.onDirectoryError)) {
30650
+ throw new TypeError(`invalid root walk directory error behavior: ${String(options.onDirectoryError)}`);
30651
+ }
30359
30652
  const maxDepth = validateBudget("maxDepth", options.maxDepth);
30360
30653
  const maxEntries = validateBudget("maxEntries", options.maxEntries);
30361
30654
  const visitedDirectories = new Set();
@@ -30383,17 +30676,21 @@ async function* walkRoot(root, relativePath, options) {
30383
30676
  return;
30384
30677
  }
30385
30678
  visitedDirectories.add(resolvedDirectory.canonicalPath);
30679
+ options.signal?.throwIfAborted();
30386
30680
  const listingDirectory = external_node_path_.relative(root.rootReal, resolvedDirectory.canonicalPath)
30387
30681
  .split(external_node_path_.sep)
30388
30682
  .join(external_node_path_.posix.sep);
30389
30683
  entries = await root.list(listingDirectory, { withFileTypes: true });
30390
30684
  }
30391
30685
  catch (error) {
30686
+ // Cancellation is never a recoverable directory read failure.
30687
+ options.signal?.throwIfAborted();
30392
30688
  if ((options.onDirectoryError ?? "throw") === "throw")
30393
30689
  throw error;
30394
30690
  yield { relativePath: directory, kind: "directory-error", size: 0, error };
30395
30691
  return;
30396
30692
  }
30693
+ options.signal?.throwIfAborted();
30397
30694
  for (const entry of entries) {
30398
30695
  options.signal?.throwIfAborted();
30399
30696
  const child = directory
@@ -30574,11 +30871,11 @@ function logWarn(message) {
30574
30871
  }
30575
30872
  }
30576
30873
  const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_.constants;
30577
- const NONBLOCK_OPEN_FLAG = "O_NONBLOCK" in external_node_fs_.constants ? external_node_fs_.constants.O_NONBLOCK : 0;
30578
- const OPEN_READ_FLAGS = external_node_fs_.constants.O_RDONLY | (SUPPORTS_NOFOLLOW ? external_node_fs_.constants.O_NOFOLLOW : 0);
30579
- const OPEN_READ_NONBLOCK_FLAGS = OPEN_READ_FLAGS | NONBLOCK_OPEN_FLAG;
30580
- const OPEN_READ_FOLLOW_FLAGS = external_node_fs_.constants.O_RDONLY;
30581
- const OPEN_READ_FOLLOW_NONBLOCK_FLAGS = OPEN_READ_FOLLOW_FLAGS | NONBLOCK_OPEN_FLAG;
30874
+ const NONBLOCK_OPEN_FLAG = process.platform !== "win32" && "O_NONBLOCK" in external_node_fs_.constants ? external_node_fs_.constants.O_NONBLOCK : 0;
30875
+ const OPEN_READ_FLAGS = external_node_fs_.constants.O_RDONLY |
30876
+ (SUPPORTS_NOFOLLOW ? external_node_fs_.constants.O_NOFOLLOW : 0) |
30877
+ NONBLOCK_OPEN_FLAG;
30878
+ const OPEN_READ_FOLLOW_FLAGS = external_node_fs_.constants.O_RDONLY | NONBLOCK_OPEN_FLAG;
30582
30879
  const OPEN_WRITE_EXISTING_FLAGS = external_node_fs_.constants.O_WRONLY | (SUPPORTS_NOFOLLOW ? external_node_fs_.constants.O_NOFOLLOW : 0);
30583
30880
  const OPEN_WRITE_CREATE_FLAGS = external_node_fs_.constants.O_WRONLY |
30584
30881
  external_node_fs_.constants.O_CREAT |
@@ -30591,6 +30888,21 @@ const OPEN_APPEND_CREATE_FLAGS = external_node_fs_.constants.O_RDWR |
30591
30888
  external_node_fs_.constants.O_EXCL |
30592
30889
  (SUPPORTS_NOFOLLOW ? external_node_fs_.constants.O_NOFOLLOW : 0);
30593
30890
  const DEFAULT_ROOT_MAX_BYTES = 16 * 1024 * 1024;
30891
+ function pathStatFromStats(stat) {
30892
+ return {
30893
+ dev: Number(stat.dev),
30894
+ gid: Number(stat.gid),
30895
+ ino: Number(stat.ino),
30896
+ isDirectory: stat.isDirectory(),
30897
+ isFile: stat.isFile(),
30898
+ isSymbolicLink: stat.isSymbolicLink(),
30899
+ mode: stat.mode,
30900
+ mtimeMs: stat.mtimeMs,
30901
+ nlink: stat.nlink,
30902
+ size: stat.size,
30903
+ uid: stat.uid,
30904
+ };
30905
+ }
30594
30906
  function openResult(params) {
30595
30907
  return {
30596
30908
  handle: params.handle,
@@ -30603,11 +30915,15 @@ function openResult(params) {
30603
30915
  async function openVerifiedLocalFile(filePath, options) {
30604
30916
  assertNoUnsafeDeviceReadPath(filePath);
30605
30917
  const fsSafeTestHooks = getFsSafeTestHooks();
30918
+ let preOpenStat;
30606
30919
  // Reject directories before opening so we never surface EISDIR to callers (e.g. tool
30607
30920
  // results that get sent to messaging channels). See openclaw/openclaw#31186.
30608
30921
  try {
30609
- const preStat = await promises_.lstat(filePath);
30610
- if (preStat.isDirectory()) {
30922
+ preOpenStat = await promises_.lstat(filePath);
30923
+ if (preOpenStat.isSymbolicLink() && options?.symlinks !== "follow-within-root") {
30924
+ throw new errors_FsSafeError("symlink", "symlink not allowed");
30925
+ }
30926
+ if (!preOpenStat.isFile() && !preOpenStat.isSymbolicLink()) {
30611
30927
  throw new errors_FsSafeError("not-file", "not a file");
30612
30928
  }
30613
30929
  await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
@@ -30621,12 +30937,8 @@ async function openVerifiedLocalFile(filePath, options) {
30621
30937
  let handle;
30622
30938
  try {
30623
30939
  const openFlags = options?.symlinks === "follow-within-root"
30624
- ? options?.nonBlockingRead
30625
- ? OPEN_READ_FOLLOW_NONBLOCK_FLAGS
30626
- : OPEN_READ_FOLLOW_FLAGS
30627
- : options?.nonBlockingRead
30628
- ? OPEN_READ_NONBLOCK_FLAGS
30629
- : OPEN_READ_FLAGS;
30940
+ ? OPEN_READ_FOLLOW_FLAGS
30941
+ : OPEN_READ_FLAGS;
30630
30942
  await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
30631
30943
  handle = await promises_.open(filePath, openFlags);
30632
30944
  try {
@@ -30639,7 +30951,7 @@ async function openVerifiedLocalFile(filePath, options) {
30639
30951
  }
30640
30952
  catch (err) {
30641
30953
  if (path_isNotFoundPathError(err)) {
30642
- throw new errors_FsSafeError("not-found", "file not found");
30954
+ throw fileNotFoundError();
30643
30955
  }
30644
30956
  if (isSymlinkOpenError(err)) {
30645
30957
  throw new errors_FsSafeError("symlink", "symlink open blocked", { cause: err });
@@ -30655,8 +30967,13 @@ async function openVerifiedLocalFile(filePath, options) {
30655
30967
  if (!stat.isFile()) {
30656
30968
  throw new errors_FsSafeError("not-file", "not a file");
30657
30969
  }
30970
+ if (preOpenStat &&
30971
+ !preOpenStat.isSymbolicLink() &&
30972
+ !file_identity_sameFileIdentity(stat, preOpenStat)) {
30973
+ throw new errors_FsSafeError("path-mismatch", "path changed before open");
30974
+ }
30658
30975
  if (options?.hardlinks === "reject" && stat.nlink > 1) {
30659
- throw new errors_FsSafeError("hardlink", "hardlinked path not allowed");
30976
+ throw hardlinkedPathNotAllowedError();
30660
30977
  }
30661
30978
  if (options?.symlinks === "follow-within-root") {
30662
30979
  const pathStat = await promises_.stat(filePath);
@@ -30676,7 +30993,7 @@ async function openVerifiedLocalFile(filePath, options) {
30676
30993
  const realPath = await resolveOpenedFileRealPathForHandle(handle, filePath);
30677
30994
  const realStat = await promises_.stat(realPath);
30678
30995
  if (options?.hardlinks === "reject" && realStat.nlink > 1) {
30679
- throw new errors_FsSafeError("hardlink", "hardlinked path not allowed");
30996
+ throw hardlinkedPathNotAllowedError();
30680
30997
  }
30681
30998
  if (!file_identity_sameFileIdentity(stat, realStat)) {
30682
30999
  throw new errors_FsSafeError("path-mismatch", "path mismatch");
@@ -30689,17 +31006,19 @@ async function openVerifiedLocalFile(filePath, options) {
30689
31006
  throw err;
30690
31007
  }
30691
31008
  if (path_isNotFoundPathError(err)) {
30692
- throw new errors_FsSafeError("not-found", "file not found");
31009
+ throw fileNotFoundError();
30693
31010
  }
30694
31011
  throw err;
30695
31012
  }
30696
31013
  }
30697
31014
  class RootHandle {
31015
+ rootIdentity;
30698
31016
  rootDir;
30699
31017
  rootReal;
30700
31018
  rootWithSep;
30701
31019
  defaults;
30702
31020
  constructor(context, defaults = {}) {
31021
+ this.rootIdentity = context.rootIdentity;
30703
31022
  this.rootDir = context.rootDir;
30704
31023
  this.rootReal = context.rootReal;
30705
31024
  this.rootWithSep = context.rootWithSep;
@@ -30708,11 +31027,19 @@ class RootHandle {
30708
31027
  get context() {
30709
31028
  return {
30710
31029
  rootDir: this.rootDir,
31030
+ rootIdentity: this.rootIdentity,
30711
31031
  rootReal: this.rootReal,
30712
31032
  rootWithSep: this.rootWithSep,
30713
31033
  };
30714
31034
  }
31035
+ mutationOptions(options) {
31036
+ return {
31037
+ ...options,
31038
+ denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
31039
+ };
31040
+ }
30715
31041
  async resolve(relativePath) {
31042
+ assertValidRootDestinationPath(relativePath);
30716
31043
  return (await resolvePathInRoot(this.context, relativePath, { allowFinalSymlink: true })).resolved;
30717
31044
  }
30718
31045
  async open(relativePath, options = {}) {
@@ -30752,67 +31079,67 @@ class RootHandle {
30752
31079
  };
30753
31080
  }
30754
31081
  async openWritable(relativePath, options = {}) {
31082
+ assertValidRootDestinationPath(relativePath);
30755
31083
  const writeMode = options.writeMode ?? "replace";
30756
31084
  return await openWritableFileInRoot(this.context, {
30757
31085
  relativePath,
30758
31086
  mkdir: this.defaults.mkdir,
30759
31087
  mode: this.defaults.mode,
30760
- ...options,
30761
- denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
31088
+ ...this.mutationOptions(options),
30762
31089
  append: writeMode === "append",
30763
31090
  truncateExisting: writeMode === "replace",
30764
31091
  });
30765
31092
  }
30766
31093
  async append(relativePath, data, options = {}) {
31094
+ assertValidRootDestinationPath(relativePath);
30767
31095
  await appendFileInRoot(this.context, {
30768
31096
  relativePath,
30769
31097
  data,
30770
31098
  mkdir: this.defaults.mkdir,
30771
31099
  mode: this.defaults.mode,
30772
- ...options,
30773
- denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
31100
+ ...this.mutationOptions(options),
30774
31101
  });
30775
31102
  }
30776
31103
  async remove(relativePath, options = {}) {
30777
31104
  assertValidRootRelativePath(relativePath);
30778
31105
  await removePathInRoot(this.context, {
30779
31106
  relativePath,
30780
- denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
31107
+ ...this.mutationOptions(options),
30781
31108
  });
30782
31109
  }
30783
31110
  async mkdir(relativePath, options = {}) {
30784
- assertValidRootRelativePath(relativePath);
31111
+ assertValidRootDestinationPath(relativePath);
30785
31112
  await mkdirPathInRoot(this.context, {
30786
31113
  relativePath,
30787
- denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
31114
+ ...this.mutationOptions(options),
30788
31115
  });
30789
31116
  }
30790
31117
  async ensureRoot(options = {}) {
30791
31118
  await mkdirPathInRoot(this.context, {
30792
31119
  relativePath: "",
30793
31120
  allowRoot: true,
30794
- denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
31121
+ ...this.mutationOptions(options),
30795
31122
  });
30796
31123
  }
30797
31124
  async write(relativePath, data, options = {}) {
31125
+ assertValidRootDestinationPath(relativePath);
30798
31126
  await writeFileInRoot(this.context, {
30799
31127
  relativePath,
30800
31128
  data,
30801
31129
  mkdir: this.defaults.mkdir,
30802
31130
  mode: this.defaults.mode,
30803
31131
  renameIdentity: this.defaults.renameIdentity,
30804
- ...options,
30805
- denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
31132
+ ...this.mutationOptions(options),
30806
31133
  });
30807
31134
  }
30808
31135
  async create(relativePath, data, options = {}) {
31136
+ assertValidRootDestinationPath(relativePath);
30809
31137
  await writeFileInRoot(this.context, {
30810
31138
  relativePath,
30811
31139
  data,
30812
31140
  mkdir: this.defaults.mkdir,
30813
31141
  mode: this.defaults.mode,
30814
- ...options,
30815
- denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
31142
+ ...this.mutationOptions(options),
30816
31143
  overwrite: false,
30817
31144
  });
30818
31145
  }
@@ -30827,15 +31154,14 @@ class RootHandle {
30827
31154
  await this.create(relativePath, trailingNewline ? `${json}\n` : json, writeOptions);
30828
31155
  }
30829
31156
  async copyIn(relativePath, sourcePath, options = {}) {
30830
- assertValidRootRelativePath(relativePath);
31157
+ assertValidRootDestinationPath(relativePath);
30831
31158
  await copyFileInRoot(this.context, {
30832
31159
  sourcePath,
30833
31160
  relativePath,
30834
31161
  maxBytes: this.defaults.maxBytes,
30835
31162
  mkdir: this.defaults.mkdir,
30836
31163
  mode: this.defaults.mode,
30837
- ...options,
30838
- denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
31164
+ ...this.mutationOptions(options),
30839
31165
  });
30840
31166
  }
30841
31167
  async exists(relativePath) {
@@ -30862,9 +31188,9 @@ class RootHandle {
30862
31188
  }
30863
31189
  async move(fromRelative, toRelative, options = {}) {
30864
31190
  assertValidRootRelativePath(fromRelative);
30865
- assertValidRootRelativePath(toRelative);
31191
+ assertValidRootDestinationPath(toRelative);
30866
31192
  validatePinnedOperationPayload({ from: fromRelative, to: toRelative });
30867
- const denyMutations = mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations);
31193
+ const { denyMutations } = this.mutationOptions(options);
30868
31194
  await assertMoveMutationAllowed(this.context, {
30869
31195
  fromRelative,
30870
31196
  toRelative,
@@ -30896,6 +31222,8 @@ async function root_impl_root(rootDir, defaults = {}) {
30896
31222
  async function openFileInRoot(root, params) {
30897
31223
  const { rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath, {
30898
31224
  allowFinalSymlink: true,
31225
+ rejectUnsafeDeviceReads: true,
31226
+ rejectSymlinks: params.symlinks !== "follow-within-root",
30899
31227
  });
30900
31228
  let opened;
30901
31229
  try {
@@ -30912,11 +31240,11 @@ async function openFileInRoot(root, params) {
30912
31240
  }
30913
31241
  if (params.hardlinks !== "allow" && opened.stat.nlink > 1) {
30914
31242
  await opened.handle.close().catch(() => { });
30915
- throw new errors_FsSafeError("hardlink", "hardlinked path not allowed");
31243
+ throw hardlinkedPathNotAllowedError();
30916
31244
  }
30917
31245
  if (!path_isPathInside(rootWithSep, opened.realPath)) {
30918
31246
  await opened.handle.close().catch(() => { });
30919
- throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
31247
+ throw outsideWorkspaceError();
30920
31248
  }
30921
31249
  return opened;
30922
31250
  }
@@ -30998,33 +31326,47 @@ async function verifyAtomicWriteResult(params) {
30998
31326
  throw new errors_FsSafeError("path-mismatch", "path changed during write");
30999
31327
  }
31000
31328
  if (!path_isPathInside(params.root.rootWithSep, opened.realPath)) {
31001
- throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
31329
+ throw outsideWorkspaceError();
31002
31330
  }
31003
31331
  }
31004
31332
  finally {
31005
31333
  await opened.handle.close().catch(() => { });
31006
31334
  }
31007
31335
  }
31008
- async function openWritableFileInRoot(root, params) {
31009
- const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath, { aliasErrorCode: "path-alias" });
31010
- await assertMutationNotDenied(resolved, params.denyMutations);
31011
- try {
31012
- await assertNoPathAliasEscape({
31013
- absolutePath: resolved,
31014
- rootPath: rootReal,
31015
- boundaryLabel: "root",
31016
- });
31017
- }
31018
- catch (err) {
31019
- throw new errors_FsSafeError("path-alias", "path alias escape blocked", { cause: err });
31336
+ async function resolveGuardedWritePathInRoot(root, params) {
31337
+ const resolvedPath = await resolvePathInRoot(root, params.relativePath, {
31338
+ aliasErrorCode: "path-alias",
31339
+ allowFinalSymlink: params.allowFinalSymlink,
31340
+ });
31341
+ await assertMutationNotDenied(resolvedPath.resolved, params.denyMutations, params.protectDeniedAncestors ? { protectAncestors: true } : undefined);
31342
+ if (await (params.shouldAssertNoPathAlias?.(resolvedPath) ?? true)) {
31343
+ try {
31344
+ await assertNoPathAliasEscape({
31345
+ absolutePath: resolvedPath.resolved,
31346
+ rootPath: resolvedPath.rootReal,
31347
+ boundaryLabel: "root",
31348
+ });
31349
+ }
31350
+ catch (error) {
31351
+ throw new errors_FsSafeError("path-alias", "path alias escape blocked", {
31352
+ cause: error instanceof Error ? error : undefined,
31353
+ });
31354
+ }
31020
31355
  }
31356
+ return resolvedPath;
31357
+ }
31358
+ async function openWritableFileInRoot(root, params) {
31359
+ const { rootReal, rootWithSep, resolved } = await resolveGuardedWritePathInRoot(root, {
31360
+ relativePath: params.relativePath,
31361
+ denyMutations: params.denyMutations,
31362
+ });
31021
31363
  let ioPath = params.mkdir === false
31022
31364
  ? resolved
31023
31365
  : await prepareRootWriteTarget(rootReal, resolved);
31024
31366
  try {
31025
31367
  const resolvedRealPath = await promises_.realpath(ioPath);
31026
31368
  if (!path_isPathInside(rootWithSep, resolvedRealPath)) {
31027
- throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
31369
+ throw outsideWorkspaceError();
31028
31370
  }
31029
31371
  ioPath = resolvedRealPath;
31030
31372
  }
@@ -31055,7 +31397,7 @@ async function openWritableFileInRoot(root, params) {
31055
31397
  }
31056
31398
  catch (err) {
31057
31399
  if (path_isNotFoundPathError(err)) {
31058
- throw new errors_FsSafeError("not-found", "file not found");
31400
+ throw fileNotFoundError();
31059
31401
  }
31060
31402
  if (isSymlinkOpenError(err)) {
31061
31403
  throw new errors_FsSafeError("symlink", "symlink open blocked", { cause: err });
@@ -31066,13 +31408,17 @@ async function openWritableFileInRoot(root, params) {
31066
31408
  throw err;
31067
31409
  }
31068
31410
  let realPathForCleanup = null;
31411
+ let createdIdentity = null;
31069
31412
  try {
31070
31413
  const stat = await handle.stat();
31414
+ if (createdForWrite) {
31415
+ createdIdentity = stat;
31416
+ }
31071
31417
  if (!stat.isFile()) {
31072
- throw new errors_FsSafeError("invalid-path", "path is not a regular file under root");
31418
+ throw new errors_FsSafeError("not-file", "path is not a regular file under root");
31073
31419
  }
31074
31420
  if (stat.nlink > 1) {
31075
- throw new errors_FsSafeError("hardlink", "hardlinked path not allowed");
31421
+ throw hardlinkedPathNotAllowedError();
31076
31422
  }
31077
31423
  try {
31078
31424
  const lstat = await promises_.lstat(ioPath);
@@ -31095,10 +31441,10 @@ async function openWritableFileInRoot(root, params) {
31095
31441
  throw new errors_FsSafeError("path-mismatch", "path mismatch");
31096
31442
  }
31097
31443
  if (realStat.nlink > 1) {
31098
- throw new errors_FsSafeError("hardlink", "hardlinked path not allowed");
31444
+ throw hardlinkedPathNotAllowedError();
31099
31445
  }
31100
31446
  if (!path_isPathInside(rootWithSep, realPath)) {
31101
- throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
31447
+ throw outsideWorkspaceError();
31102
31448
  }
31103
31449
  // Truncate only after boundary and identity checks complete. This avoids
31104
31450
  // irreversible side effects if a symlink target changes before validation.
@@ -31118,8 +31464,8 @@ async function openWritableFileInRoot(root, params) {
31118
31464
  const cleanupCreatedPath = createdForWrite && err instanceof errors_FsSafeError;
31119
31465
  const cleanupPath = realPathForCleanup ?? ioPath;
31120
31466
  await handle.close().catch(() => { });
31121
- if (cleanupCreatedPath) {
31122
- await promises_.rm(cleanupPath, { force: true }).catch(() => { });
31467
+ if (cleanupCreatedPath && createdIdentity) {
31468
+ await removePathIfIdentityUnchanged(cleanupPath, createdIdentity).catch(() => { });
31123
31469
  }
31124
31470
  throw err;
31125
31471
  }
@@ -31164,7 +31510,11 @@ async function appendFileInRoot(root, params) {
31164
31510
  }
31165
31511
  async function removePathInRoot(root, params) {
31166
31512
  validatePinnedOperationPayload({ relativePath: params.relativePath });
31167
- const resolved = await resolvePinnedRemovePathInRoot(root, params.relativePath, params.denyMutations);
31513
+ const resolved = await resolvePinnedPathInRoot(root, {
31514
+ relativePath: params.relativePath,
31515
+ denyMutations: params.denyMutations,
31516
+ remove: true,
31517
+ });
31168
31518
  try {
31169
31519
  await removePathFallback(resolved);
31170
31520
  }
@@ -31183,15 +31533,16 @@ async function mkdirPathInRoot(root, params) {
31183
31533
  }
31184
31534
  }
31185
31535
  async function writeFileInRoot(root, params) {
31186
- if (process.platform === "win32") {
31187
- await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
31536
+ await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
31537
+ if (process.platform === "win32" &&
31538
+ (params.renameIdentity === "verify-content-with-lock" || !getNativeBinding())) {
31188
31539
  await writeFileFallback(root, params);
31540
+ return;
31541
+ }
31542
+ const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
31543
+ await serializePathWrite(pinned.targetPath, async () => {
31544
+ await commitPinnedWriteInRoot(root, pinned, params);
31189
31545
  });
31190
- return;
31191
- }
31192
- const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
31193
- await serializePathWrite(pinned.targetPath, async () => {
31194
- await commitPinnedWriteInRoot(root, pinned, params);
31195
31546
  });
31196
31547
  }
31197
31548
  async function commitPinnedWriteInRoot(root, pinned, params) {
@@ -31207,6 +31558,7 @@ async function commitPinnedWriteInRoot(root, pinned, params) {
31207
31558
  mode: params.mode ?? pinned.mode,
31208
31559
  overwrite: params.overwrite,
31209
31560
  input: { kind: "buffer", data: params.data, encoding: params.encoding },
31561
+ rootIdentity: root.rootIdentity,
31210
31562
  });
31211
31563
  }
31212
31564
  catch (error) {
@@ -31244,26 +31596,35 @@ async function copyFileInRoot(root, params) {
31244
31596
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${source.stat.size})`);
31245
31597
  }
31246
31598
  try {
31247
- const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
31248
- await serializePathWrite(pinned.targetPath, async () => {
31249
- await assertCopySourceCurrent(source);
31250
- const identity = await runPinnedWriteHelper({
31251
- rootPath: pinned.rootReal,
31252
- relativeParentPath: pinned.relativeParentPath,
31253
- basename: pinned.basename,
31254
- mkdir: params.mkdir !== false,
31255
- mode: pinned.mode,
31256
- overwrite: true,
31257
- maxBytes: params.maxBytes,
31258
- input: { kind: "stream", stream: source.handle.createReadStream() },
31599
+ await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
31600
+ const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
31601
+ await serializePathWrite(pinned.targetPath, async () => {
31602
+ await assertCopySourceCurrent(source);
31603
+ let identity;
31604
+ try {
31605
+ identity = await runPinnedWriteHelper({
31606
+ rootPath: pinned.rootReal,
31607
+ relativeParentPath: pinned.relativeParentPath,
31608
+ basename: pinned.basename,
31609
+ mkdir: params.mkdir !== false,
31610
+ mode: pinned.mode,
31611
+ overwrite: true,
31612
+ maxBytes: params.maxBytes,
31613
+ input: { kind: "stream", stream: source.handle.createReadStream() },
31614
+ rootIdentity: root.rootIdentity,
31615
+ });
31616
+ }
31617
+ catch (error) {
31618
+ throw normalizePinnedWriteError(error);
31619
+ }
31620
+ try {
31621
+ await assertCopySourcePathCurrent(source);
31622
+ }
31623
+ catch (error) {
31624
+ await removePathIfIdentityUnchanged(pinned.targetPath, identity).catch(() => undefined);
31625
+ throw error;
31626
+ }
31259
31627
  });
31260
- try {
31261
- await assertCopySourcePathCurrent(source);
31262
- }
31263
- catch (error) {
31264
- await removeCopyTargetIfUnchanged(pinned.targetPath, identity).catch(() => undefined);
31265
- throw error;
31266
- }
31267
31628
  });
31268
31629
  }
31269
31630
  finally {
@@ -31283,7 +31644,7 @@ async function assertCopySourcePathCurrent(source) {
31283
31644
  throw new errors_FsSafeError("path-mismatch", "copy source path changed");
31284
31645
  }
31285
31646
  }
31286
- async function removeCopyTargetIfUnchanged(targetPath, identity) {
31647
+ async function removePathIfIdentityUnchanged(targetPath, identity) {
31287
31648
  const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(targetPath));
31288
31649
  const current = await promises_.lstat(targetPath);
31289
31650
  if (current.isSymbolicLink() || !file_identity_sameFileIdentity(current, identity)) {
@@ -31294,25 +31655,15 @@ async function removeCopyTargetIfUnchanged(targetPath, identity) {
31294
31655
  });
31295
31656
  }
31296
31657
  async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
31297
- const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, relativePath, {
31298
- aliasErrorCode: "path-alias",
31658
+ const { rootReal, rootWithSep, resolved } = await resolveGuardedWritePathInRoot(root, {
31659
+ relativePath,
31660
+ denyMutations,
31299
31661
  });
31300
- await assertMutationNotDenied(resolved, denyMutations);
31301
- try {
31302
- await assertNoPathAliasEscape({
31303
- absolutePath: resolved,
31304
- rootPath: rootReal,
31305
- boundaryLabel: "root",
31306
- });
31307
- }
31308
- catch (err) {
31309
- throw new errors_FsSafeError("path-alias", "path alias escape blocked", { cause: err });
31310
- }
31311
31662
  // resolvePathInRoot already enforces isPathInside, so any actual escape
31312
31663
  // is rejected upstream.
31313
31664
  const relativeResolved = external_node_path_.relative(rootReal, resolved);
31314
31665
  if (external_node_path_.isAbsolute(relativeResolved)) {
31315
- throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
31666
+ throw outsideWorkspaceError();
31316
31667
  }
31317
31668
  const relativePosix = relativeResolved
31318
31669
  ? relativeResolved.split(external_node_path_.sep).join(external_node_path_.posix.sep)
@@ -31327,11 +31678,12 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode,
31327
31678
  relativePath,
31328
31679
  hardlinks: "reject",
31329
31680
  nonBlockingRead: true,
31681
+ symlinks: "follow-within-root",
31330
31682
  });
31331
31683
  try {
31332
31684
  mode = requestedMode ?? (opened.stat.mode & 0o777);
31333
31685
  if (!path_isPathInside(rootWithSep, opened.realPath)) {
31334
- throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
31686
+ throw outsideWorkspaceError();
31335
31687
  }
31336
31688
  }
31337
31689
  finally {
@@ -31355,17 +31707,9 @@ async function resolvePinnedPathInRoot(root, params) {
31355
31707
  return await resolvePinnedOperationPathInRoot(root, {
31356
31708
  allowRoot: params.allowRoot,
31357
31709
  denyMutations: params.denyMutations,
31358
- protectDenyMutationAncestors: false,
31710
+ protectDenyMutationAncestors: params.remove === true,
31359
31711
  relativePath: params.relativePath,
31360
- policy: PATH_ALIAS_POLICIES.strict,
31361
- });
31362
- }
31363
- async function resolvePinnedRemovePathInRoot(root, relativePath, denyMutations) {
31364
- return await resolvePinnedOperationPathInRoot(root, {
31365
- denyMutations,
31366
- protectDenyMutationAncestors: true,
31367
- relativePath,
31368
- policy: PATH_ALIAS_POLICIES.unlinkTarget,
31712
+ policy: params.remove ? PATH_ALIAS_POLICIES.unlinkTarget : PATH_ALIAS_POLICIES.strict,
31369
31713
  });
31370
31714
  }
31371
31715
  async function resolvePinnedOperationPathInRoot(root, params) {
@@ -31383,11 +31727,11 @@ async function resolvePinnedOperationPathInRoot(root, params) {
31383
31727
  relativeResolved === "." ||
31384
31728
  firstSegment === ".." ||
31385
31729
  external_node_path_.isAbsolute(relativeResolved)) {
31386
- throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
31730
+ throw outsideWorkspaceError();
31387
31731
  }
31388
31732
  const relativePosix = relativeResolved.split(external_node_path_.sep).join(external_node_path_.posix.sep);
31389
31733
  if (!path_isPathInside(resolved.rootWithSep, resolved.canonicalPath)) {
31390
- throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
31734
+ throw outsideWorkspaceError();
31391
31735
  }
31392
31736
  await assertMutationNotDenied(resolved.canonicalPath, params.denyMutations, {
31393
31737
  protectAncestors: params.protectDenyMutationAncestors,
@@ -31395,6 +31739,7 @@ async function resolvePinnedOperationPathInRoot(root, params) {
31395
31739
  return { rootReal: resolved.rootReal, resolved: resolved.canonicalPath, relativePosix };
31396
31740
  }
31397
31741
  async function resolvePinnedRootPathInRoot(root, params) {
31742
+ await assertRootIdentityCurrent(root);
31398
31743
  const rootReal = root.rootReal;
31399
31744
  let resolved;
31400
31745
  try {
@@ -31419,11 +31764,25 @@ async function resolvePinnedRootPathInRoot(root, params) {
31419
31764
  canonicalPath: resolved.canonicalPath,
31420
31765
  };
31421
31766
  }
31767
+ async function prepareRemoveGuard(targetPath) {
31768
+ try {
31769
+ const guard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(targetPath));
31770
+ await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("remove", targetPath);
31771
+ await assertAsyncDirectoryGuard(guard);
31772
+ return guard;
31773
+ }
31774
+ catch (error) {
31775
+ throw normalizeRemoveGuardError(error);
31776
+ }
31777
+ }
31422
31778
  async function removePathFallback(resolved) {
31423
- const guard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(resolved.resolved));
31424
- await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("remove", resolved.resolved);
31425
- await assertAsyncDirectoryGuard(guard);
31426
- await ((await promises_.lstat(resolved.resolved)).isDirectory() ? promises_.rmdir(resolved.resolved) : promises_.rm(resolved.resolved));
31779
+ const guard = await prepareRemoveGuard(resolved.resolved);
31780
+ try {
31781
+ await ((await promises_.lstat(resolved.resolved)).isDirectory() ? promises_.rmdir(resolved.resolved) : promises_.rm(resolved.resolved));
31782
+ }
31783
+ catch (error) {
31784
+ throw normalizeRemovePathError(error);
31785
+ }
31427
31786
  await assertAsyncDirectoryGuard(guard).catch(() => undefined);
31428
31787
  }
31429
31788
  async function mkdirPathFallback(resolved) {
@@ -31435,13 +31794,13 @@ async function mkdirPathFallback(resolved) {
31435
31794
  async function statPathFallback(root, relativePath) {
31436
31795
  const resolved = await resolvePinnedPathInRoot(root, { relativePath, allowRoot: true });
31437
31796
  try {
31438
- return pathStatFromStats(await promises_.lstat(resolved.resolved));
31797
+ const stat = pathStatFromStats(await promises_.lstat(resolved.resolved));
31798
+ await assertRootIdentityCurrent(root);
31799
+ return stat;
31439
31800
  }
31440
31801
  catch (error) {
31441
31802
  if (path_isNotFoundPathError(error)) {
31442
- throw new errors_FsSafeError("not-found", "file not found", {
31443
- cause: error instanceof Error ? error : undefined,
31444
- });
31803
+ throw fileNotFoundError(error instanceof Error ? error : undefined);
31445
31804
  }
31446
31805
  throw error;
31447
31806
  }
@@ -31452,6 +31811,7 @@ async function listPathFallback(root, relativePath, withFileTypes) {
31452
31811
  const names = await promises_.readdir(resolved.resolved);
31453
31812
  const sortedNames = names.toSorted();
31454
31813
  if (!withFileTypes) {
31814
+ await assertRootIdentityCurrent(root);
31455
31815
  return sortedNames;
31456
31816
  }
31457
31817
  const entries = [];
@@ -31461,6 +31821,7 @@ async function listPathFallback(root, relativePath, withFileTypes) {
31461
31821
  ...pathStatFromStats(await promises_.lstat(external_node_path_.join(resolved.resolved, name))),
31462
31822
  });
31463
31823
  }
31824
+ await assertRootIdentityCurrent(root);
31464
31825
  return entries;
31465
31826
  }
31466
31827
  catch (error) {
@@ -31473,6 +31834,8 @@ async function listPathFallback(root, relativePath, withFileTypes) {
31473
31834
  }
31474
31835
  }
31475
31836
  async function assertMoveMutationAllowed(root, params) {
31837
+ // Keep this preflight separate from the pinned resolutions in movePathFallback:
31838
+ // mutation denials must take precedence over source alias or identity failures.
31476
31839
  const source = await resolvePathInRoot(root, params.fromRelative, {
31477
31840
  aliasErrorCode: "path-alias",
31478
31841
  allowFinalSymlink: true,
@@ -31494,40 +31857,29 @@ async function movePathFallback(root, params) {
31494
31857
  relativePath: params.fromRelative,
31495
31858
  policy: PATH_ALIAS_POLICIES.strict,
31496
31859
  });
31497
- const target = await resolvePathInRoot(root, params.toRelative, {
31498
- aliasErrorCode: "path-alias",
31499
- allowFinalSymlink: true,
31500
- });
31501
- await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
31502
- await resolvePinnedRootPathInRoot(root, {
31860
+ const target = await resolveGuardedWritePathInRoot(root, {
31503
31861
  relativePath: params.toRelative,
31504
- policy: PATH_ALIAS_POLICIES.unlinkTarget,
31505
- });
31506
- const targetStat = await promises_.lstat(target.resolved).catch(() => undefined);
31507
- const replacesFinalSymlink = process.platform !== "win32" && params.overwrite && targetStat?.isSymbolicLink() === true;
31508
- if (!replacesFinalSymlink) {
31509
- try {
31510
- await assertNoPathAliasEscape({
31511
- absolutePath: target.resolved,
31512
- rootPath: target.rootReal,
31513
- boundaryLabel: "root",
31514
- });
31515
- }
31516
- catch (error) {
31517
- throw new errors_FsSafeError("path-alias", "path alias escape blocked", {
31518
- cause: error instanceof Error ? error : undefined,
31862
+ denyMutations: params.denyMutations,
31863
+ allowFinalSymlink: true,
31864
+ protectDeniedAncestors: true,
31865
+ shouldAssertNoPathAlias: async (resolvedTarget) => {
31866
+ await resolvePinnedRootPathInRoot(root, {
31867
+ relativePath: params.toRelative,
31868
+ policy: PATH_ALIAS_POLICIES.unlinkTarget,
31519
31869
  });
31520
- }
31521
- }
31870
+ const targetStat = await promises_.lstat(resolvedTarget.resolved).catch(() => undefined);
31871
+ return !(process.platform !== "win32" &&
31872
+ params.overwrite &&
31873
+ targetStat?.isSymbolicLink() === true);
31874
+ },
31875
+ });
31522
31876
  let sourceStat;
31523
31877
  try {
31524
31878
  sourceStat = await promises_.lstat(source.resolved);
31525
31879
  }
31526
31880
  catch (error) {
31527
31881
  if (path_isNotFoundPathError(error)) {
31528
- throw new errors_FsSafeError("not-found", "file not found", {
31529
- cause: error instanceof Error ? error : undefined,
31530
- });
31882
+ throw fileNotFoundError(error instanceof Error ? error : undefined);
31531
31883
  }
31532
31884
  throw error;
31533
31885
  }
@@ -31535,7 +31887,7 @@ async function movePathFallback(root, params) {
31535
31887
  throw new errors_FsSafeError("symlink", "symlink not allowed");
31536
31888
  }
31537
31889
  if (sourceStat.isFile() && sourceStat.nlink > 1) {
31538
- throw new errors_FsSafeError("hardlink", "hardlinked path not allowed");
31890
+ throw hardlinkedPathNotAllowedError();
31539
31891
  }
31540
31892
  if (!params.overwrite && sourceStat.isDirectory()) {
31541
31893
  throw new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
@@ -31564,9 +31916,7 @@ async function movePathFallback(root, params) {
31564
31916
  }
31565
31917
  catch (error) {
31566
31918
  if (path_isNotFoundPathError(error)) {
31567
- throw new errors_FsSafeError("not-found", "file not found", {
31568
- cause: error instanceof Error ? error : undefined,
31569
- });
31919
+ throw fileNotFoundError(error instanceof Error ? error : undefined);
31570
31920
  }
31571
31921
  if (hasNodeErrorCode(error, "EEXIST")) {
31572
31922
  throw new errors_FsSafeError("already-exists", "destination exists", {
@@ -31632,30 +31982,22 @@ async function writeFileFallback(root, params) {
31632
31982
  }
31633
31983
  }
31634
31984
  async function writeMissingFileFallback(root, params) {
31635
- const { rootReal, resolved } = await resolvePathInRoot(root, params.relativePath, {
31636
- aliasErrorCode: "path-alias",
31985
+ const { rootReal, resolved } = await resolveGuardedWritePathInRoot(root, {
31986
+ relativePath: params.relativePath,
31987
+ denyMutations: params.denyMutations,
31637
31988
  });
31638
- await assertMutationNotDenied(resolved, params.denyMutations);
31639
- try {
31640
- await assertNoPathAliasEscape({
31641
- absolutePath: resolved,
31642
- rootPath: rootReal,
31643
- boundaryLabel: "root",
31644
- });
31645
- }
31646
- catch (err) {
31647
- throw new errors_FsSafeError("path-alias", "path alias escape blocked", { cause: err });
31648
- }
31649
31989
  const targetPath = params.mkdir === false
31650
31990
  ? resolved
31651
31991
  : await prepareRootWriteTarget(rootReal, resolved);
31652
31992
  const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(targetPath));
31653
31993
  let created = false;
31994
+ let createdIdentity;
31654
31995
  try {
31655
31996
  const { handle, writtenStat } = await withAsyncDirectoryGuards([parentGuard], async () => {
31656
31997
  const handle = await promises_.open(targetPath, OPEN_WRITE_CREATE_FLAGS, params.mode ?? 0o600);
31657
31998
  created = true;
31658
31999
  try {
32000
+ createdIdentity = await handle.stat();
31659
32001
  if (typeof params.data === "string") {
31660
32002
  await handle.writeFile(params.data, params.encoding ?? "utf8");
31661
32003
  }
@@ -31691,8 +32033,8 @@ async function writeMissingFileFallback(root, params) {
31691
32033
  throw err;
31692
32034
  }
31693
32035
  finally {
31694
- if (created) {
31695
- await promises_.rm(targetPath, { force: true }).catch(() => undefined);
32036
+ if (created && createdIdentity) {
32037
+ await removePathIfIdentityUnchanged(targetPath, createdIdentity).catch(() => undefined);
31696
32038
  }
31697
32039
  }
31698
32040
  }
@@ -32428,7 +32770,7 @@ function getExpectedArgumentLength(message) {
32428
32770
  while (regex.exec(message) !== null) expectedLength++;
32429
32771
  return expectedLength;
32430
32772
  }
32431
- function createError(sym, value, constructor) {
32773
+ function dist_createError(sym, value, constructor) {
32432
32774
  dist_messages.set(sym, value);
32433
32775
  return makeNodeErrorWithCode(constructor, sym);
32434
32776
  }
@@ -32546,7 +32888,7 @@ function determineSpecificType(value) {
32546
32888
  }
32547
32889
  }
32548
32890
  }
32549
- createError("ERR_INVALID_ARG_TYPE", (name, expected, actual) => {
32891
+ dist_createError("ERR_INVALID_ARG_TYPE", (name, expected, actual) => {
32550
32892
  external_node_assert_.ok(typeof name === "string", "'name' must be a string");
32551
32893
  if (!Array.isArray(expected)) expected = [expected];
32552
32894
  let message = "The ";
@@ -32590,13 +32932,13 @@ createError("ERR_INVALID_ARG_TYPE", (name, expected, actual) => {
32590
32932
  message += `. Received ${determineSpecificType(actual)}`;
32591
32933
  return message;
32592
32934
  }, TypeError);
32593
- const ERR_INVALID_MODULE_SPECIFIER = createError("ERR_INVALID_MODULE_SPECIFIER", (request, reason, base) => {
32935
+ const ERR_INVALID_MODULE_SPECIFIER = dist_createError("ERR_INVALID_MODULE_SPECIFIER", (request, reason, base) => {
32594
32936
  return `Invalid module "${request}" ${reason}${base ? ` imported from ${base}` : ""}`;
32595
32937
  }, TypeError);
32596
- const ERR_INVALID_PACKAGE_CONFIG = createError("ERR_INVALID_PACKAGE_CONFIG", (path, base, message) => {
32938
+ const ERR_INVALID_PACKAGE_CONFIG = dist_createError("ERR_INVALID_PACKAGE_CONFIG", (path, base, message) => {
32597
32939
  return `Invalid package config ${path}${base ? ` while importing ${base}` : ""}${message ? `. ${message}` : ""}`;
32598
32940
  }, Error);
32599
- const ERR_INVALID_PACKAGE_TARGET = createError("ERR_INVALID_PACKAGE_TARGET", (packagePath, key, target, isImport = false, base) => {
32941
+ const ERR_INVALID_PACKAGE_TARGET = dist_createError("ERR_INVALID_PACKAGE_TARGET", (packagePath, key, target, isImport = false, base) => {
32600
32942
  const relatedError = typeof target === "string" && !isImport && target.length > 0 && !target.startsWith("./");
32601
32943
  if (key === ".") {
32602
32944
  external_node_assert_.ok(isImport === false);
@@ -32604,26 +32946,26 @@ const ERR_INVALID_PACKAGE_TARGET = createError("ERR_INVALID_PACKAGE_TARGET", (pa
32604
32946
  }
32605
32947
  return `Invalid "${isImport ? "imports" : "exports"}" target ${JSON.stringify(target)} defined for '${key}' in the package config ${packagePath}package.json${base ? ` imported from ${base}` : ""}${relatedError ? "; targets must start with \"./\"" : ""}`;
32606
32948
  }, Error);
32607
- const ERR_MODULE_NOT_FOUND = createError("ERR_MODULE_NOT_FOUND", function(path, base, exactUrl = false) {
32949
+ const ERR_MODULE_NOT_FOUND = dist_createError("ERR_MODULE_NOT_FOUND", function(path, base, exactUrl = false) {
32608
32950
  if (exactUrl && typeof exactUrl === "string") this.url = `${exactUrl}`;
32609
32951
  return `Cannot find ${exactUrl ? "module" : "package"} '${path}' imported from ${base}`;
32610
32952
  }, Error);
32611
- const ERR_PACKAGE_IMPORT_NOT_DEFINED = createError("ERR_PACKAGE_IMPORT_NOT_DEFINED", (specifier, packagePath, base) => {
32953
+ const ERR_PACKAGE_IMPORT_NOT_DEFINED = dist_createError("ERR_PACKAGE_IMPORT_NOT_DEFINED", (specifier, packagePath, base) => {
32612
32954
  return `Package import specifier "${specifier}" is not defined${packagePath ? ` in package ${packagePath || ""}package.json` : ""} imported from ${base}`;
32613
32955
  }, TypeError);
32614
- const ERR_PACKAGE_PATH_NOT_EXPORTED = createError("ERR_PACKAGE_PATH_NOT_EXPORTED", (packagePath, subpath, base) => {
32956
+ const ERR_PACKAGE_PATH_NOT_EXPORTED = dist_createError("ERR_PACKAGE_PATH_NOT_EXPORTED", (packagePath, subpath, base) => {
32615
32957
  if (subpath === ".") return `No "exports" main defined in ${packagePath}package.json${base ? ` imported from ${base}` : ""}`;
32616
32958
  return `Package subpath '${subpath}' is not defined by "exports" in ${packagePath}package.json${base ? ` imported from ${base}` : ""}`;
32617
32959
  }, Error);
32618
- const ERR_UNSUPPORTED_DIR_IMPORT = createError("ERR_UNSUPPORTED_DIR_IMPORT", function(path, base, exactUrl = void 0) {
32960
+ const ERR_UNSUPPORTED_DIR_IMPORT = dist_createError("ERR_UNSUPPORTED_DIR_IMPORT", function(path, base, exactUrl = void 0) {
32619
32961
  this.url = exactUrl;
32620
32962
  return `Directory import '${path}' is not supported resolving ES modules imported from ${base}`;
32621
32963
  }, Error);
32622
- const ERR_UNSUPPORTED_RESOLVE_REQUEST = createError("ERR_UNSUPPORTED_RESOLVE_REQUEST", "Failed to resolve module specifier \"%s\" from \"%s\": Invalid relative URL or base scheme is not hierarchical.", TypeError);
32623
- const ERR_UNKNOWN_FILE_EXTENSION = createError("ERR_UNKNOWN_FILE_EXTENSION", (extension, path) => {
32964
+ const ERR_UNSUPPORTED_RESOLVE_REQUEST = dist_createError("ERR_UNSUPPORTED_RESOLVE_REQUEST", "Failed to resolve module specifier \"%s\" from \"%s\": Invalid relative URL or base scheme is not hierarchical.", TypeError);
32965
+ const ERR_UNKNOWN_FILE_EXTENSION = dist_createError("ERR_UNKNOWN_FILE_EXTENSION", (extension, path) => {
32624
32966
  return `Unknown file extension "${extension}" for ${path}`;
32625
32967
  }, TypeError);
32626
- createError("ERR_INVALID_ARG_VALUE", (name, value, reason = "is invalid") => {
32968
+ dist_createError("ERR_INVALID_ARG_VALUE", (name, value, reason = "is invalid") => {
32627
32969
  let inspected = (0,external_node_util_.inspect)(value);
32628
32970
  if (inspected.length > 128) inspected = `${inspected.slice(0, 128)}...`;
32629
32971
  return `The ${name.includes(".") ? "property" : "argument"} '${name}' ${reason}. Received ${inspected}`;
@@ -70046,4 +70388,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
70046
70388
  // module factories are used so entry inlining is disabled
70047
70389
  // startup
70048
70390
  // Load entry module and return exports
70049
- var __webpack_exports__ = __webpack_require__(7489);
70391
+ var __webpack_exports__ = __webpack_require__(4583);