@lousy-agents/mcp 5.21.4 → 5.21.5

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 +1810 -1581
  2. package/package.json +1 -1
@@ -6559,7 +6559,7 @@ function escapeJsonPtr(str) {
6559
6559
 
6560
6560
 
6561
6561
  },
6562
- 836(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6562
+ 3829(__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);
@@ -37098,6 +37098,85 @@ function sameFileIdentityForCleanup(left, right, platform = process.platform) {
37098
37098
  return sameStatValue(left.dev, right.dev) && sameStatValue(left.ino, right.ino);
37099
37099
  }
37100
37100
 
37101
+ ;// CONCATENATED MODULE: external "node:async_hooks"
37102
+ const external_node_async_hooks_namespaceObject = __rspack_createRequire_require("node:async_hooks");
37103
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/file-observation.js
37104
+
37105
+ const active = new external_node_async_hooks_namespaceObject.AsyncLocalStorage();
37106
+ function recordFileObservationFailure(error, kind) {
37107
+ const failures = active.getStore();
37108
+ if (!failures)
37109
+ return;
37110
+ let kinds = failures.get(error);
37111
+ if (!kinds)
37112
+ failures.set(error, kinds = new Set());
37113
+ kinds.add(kind);
37114
+ }
37115
+ function isFileObservationFailure(error, kind) {
37116
+ return active.getStore()?.get(error)?.has(kind) === true;
37117
+ }
37118
+ // Each Root observation owns its receipts, including nested and concurrent reads.
37119
+ function fileObservation() {
37120
+ const failures = new Map();
37121
+ return {
37122
+ run(operation) { return active.run(failures, operation); },
37123
+ has(error, kind) { return failures.get(error)?.has(kind) === true; },
37124
+ };
37125
+ }
37126
+
37127
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/strict-file-identity.js
37128
+
37129
+
37130
+ function identityMismatch() {
37131
+ const error = new errors_FsSafeError("path-mismatch", "file identity changed or could not be verified");
37132
+ recordFileObservationFailure(error, "identity");
37133
+ return error;
37134
+ }
37135
+ function identityCheck(expected, platform) {
37136
+ const known = {};
37137
+ const check = (stat) => {
37138
+ let complete = true;
37139
+ for (const field of ["dev", "ino"]) {
37140
+ const value = stat[field];
37141
+ // Numeric receipts cannot recover identity bits already lost to rounding.
37142
+ if (typeof value !== "bigint")
37143
+ throw identityMismatch();
37144
+ if (platform === "win32" && value === 0n) {
37145
+ complete = false;
37146
+ }
37147
+ else {
37148
+ if (known[field] !== undefined && known[field] !== value)
37149
+ throw identityMismatch();
37150
+ known[field] = value;
37151
+ }
37152
+ }
37153
+ return complete;
37154
+ };
37155
+ if (expected && !check(expected))
37156
+ throw identityMismatch();
37157
+ return check;
37158
+ }
37159
+ // Retry only unknown Windows identities, retaining every known component so a
37160
+ // later observation cannot erase a definite mismatch. Never reopen the file.
37161
+ async function inspectFileIdentity(inspect, expected, platform = process.platform) {
37162
+ const check = identityCheck(expected, platform);
37163
+ for (let attempt = 0; attempt < 2; attempt++) {
37164
+ const stat = await inspect();
37165
+ if (check(stat))
37166
+ return stat;
37167
+ }
37168
+ throw identityMismatch();
37169
+ }
37170
+ function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
37171
+ const check = identityCheck(expected, platform);
37172
+ for (let attempt = 0; attempt < 2; attempt++) {
37173
+ const stat = inspect();
37174
+ if (check(stat))
37175
+ return stat;
37176
+ }
37177
+ throw identityMismatch();
37178
+ }
37179
+
37101
37180
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path.js
37102
37181
 
37103
37182
 
@@ -37312,6 +37391,7 @@ function normalizeRemovePathError(error) {
37312
37391
 
37313
37392
 
37314
37393
 
37394
+
37315
37395
  async function directory_guard_createAsyncDirectoryGuard(dir) {
37316
37396
  const stat = await promises_.lstat(dir);
37317
37397
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
@@ -37376,6 +37456,15 @@ function directory_guard_createNearestExistingSyncDirectoryGuard(rootReal, targe
37376
37456
  }
37377
37457
  return directory_guard_createSyncDirectoryGuard(root);
37378
37458
  }
37459
+ // Recovery receipts must retain every identity bit, including on Windows.
37460
+ async function inspectDirectoryIdentity(dir, expected) {
37461
+ return await inspectFileIdentity(async () => {
37462
+ const stat = await promises_.lstat(dir, { bigint: true });
37463
+ if (stat.isSymbolicLink() || !stat.isDirectory())
37464
+ throw root_errors_directoryComponentNotDirectoryError();
37465
+ return stat;
37466
+ }, expected);
37467
+ }
37379
37468
 
37380
37469
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/directory-durability.js
37381
37470
 
@@ -37817,6 +37906,8 @@ async function guardedRename(params) {
37817
37906
  ? await createNearestExistingDirectoryGuard(params.targetRoot, path.dirname(params.to))
37818
37907
  : await createAsyncDirectoryGuard(path.dirname(params.to));
37819
37908
  await withAsyncDirectoryGuards([sourceGuard, targetGuard], async () => {
37909
+ // Authority must survive all awaited guards; do not yield before rename dispatch.
37910
+ params.assertBeforeRename?.();
37820
37911
  await fs.rename(params.from, params.to);
37821
37912
  }, { verifyAfter: params.verifyAfter });
37822
37913
  }
@@ -37986,6 +38077,49 @@ function mergeDenyMutationPolicies(defaultPolicy, callPolicy) {
37986
38077
  };
37987
38078
  }
37988
38079
 
38080
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/opened-file-failure.js
38081
+
38082
+
38083
+
38084
+ function recordFileOpenFailure(error, filePath) {
38085
+ recordFileObservationFailure(error, `open:${filePath}`);
38086
+ throw error;
38087
+ }
38088
+ function openedPathResolutionError(error = new errors_FsSafeError("path-mismatch", "unable to resolve opened file path")) {
38089
+ recordFileObservationFailure(error, "resolution");
38090
+ return error;
38091
+ }
38092
+ async function recordPreOpenFileChange(error, handle, filePath, before, opened) {
38093
+ if (!isFileObservationFailure(error, "identity") || !before?.isFile() || !opened?.isFile() ||
38094
+ ![0n, 1n].includes(before.nlink) || ![0n, 1n].includes(opened.nlink))
38095
+ return;
38096
+ try {
38097
+ await inspectFileIdentity(async () => before);
38098
+ const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), opened);
38099
+ if (current.isFile() && [0n, 1n].includes(current.nlink) &&
38100
+ (before.dev !== current.dev || before.ino !== current.ino)) {
38101
+ // A stale pathname sample is not proof that its former inode was unlinked.
38102
+ recordFileObservationFailure(error, `changed:${filePath}`);
38103
+ }
38104
+ }
38105
+ catch {
38106
+ // Unknown, changed, or closed descriptor evidence never permits a discard.
38107
+ }
38108
+ }
38109
+ async function recordOpenedFileFailure(error, handle, filePath, identity) {
38110
+ if ((!isFileObservationFailure(error, "resolution") && !isFileObservationFailure(error, "identity")) ||
38111
+ !identity.isFile() || identity.nlink > 1n)
38112
+ return;
38113
+ try {
38114
+ const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), identity);
38115
+ if (current.isFile() && current.nlink === 0n)
38116
+ recordFileObservationFailure(error, `unlinked:${filePath}`);
38117
+ }
38118
+ catch {
38119
+ // A closed, changed, or unknown descriptor provides no retry evidence.
38120
+ }
38121
+ }
38122
+
37989
38123
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/opened-realpath.js
37990
38124
 
37991
38125
 
@@ -38025,6 +38159,12 @@ async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
38025
38159
  }
38026
38160
  catch (err) {
38027
38161
  if (!path_isNotFoundPathError(err)) {
38162
+ // Windows can fail here on a deleted-but-open file. Brand only this
38163
+ // resolver operation; the caller must still prove unlink on the same fd.
38164
+ if (process.platform === "win32" && err instanceof Error &&
38165
+ ["EPERM", "EBADF"].includes(err.code ?? "")) {
38166
+ throw openedPathResolutionError(err);
38167
+ }
38028
38168
  throw err;
38029
38169
  }
38030
38170
  }
@@ -38032,7 +38172,7 @@ async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
38032
38172
  if (parentResolved) {
38033
38173
  return parentResolved;
38034
38174
  }
38035
- throw new errors_FsSafeError("path-mismatch", "unable to resolve opened file path");
38175
+ throw openedPathResolutionError();
38036
38176
  }
38037
38177
  async function resolveOpenedFileRealPathFromParent(handleStat, ioPath, statOptions) {
38038
38178
  let parentReal;
@@ -38526,7 +38666,7 @@ async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuar
38526
38666
  let renamed = false;
38527
38667
  let completed = false;
38528
38668
  try {
38529
- tempName = `.${params.basename}.${(0,external_node_crypto_.randomUUID)()}.native.tmp`;
38669
+ tempName = `.fs-safe-${(0,external_node_crypto_.randomUUID)()}.tmp`;
38530
38670
  tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL)).fd;
38531
38671
  tempIdentity = external_node_fs_.fstatSync(tempFd);
38532
38672
  const verificationIdentity = external_node_fs_.fstatSync(tempFd, { bigint: true });
@@ -38736,7 +38876,7 @@ const UNCOMMITTED_RENAME_ERRORS = new Set([
38736
38876
  "ENAMETOOLONG", "ENOENT", "ENOSPC", "ENOSYS", "ENOTDIR", "ENOTEMPTY", "ENOTSUP",
38737
38877
  "EPERM", "EROFS", "ETXTBSY", "EXDEV",
38738
38878
  ]);
38739
- function failure(error, details) {
38879
+ function native_staged_file_failure(error, details) {
38740
38880
  const code = error instanceof errors_FsSafeError
38741
38881
  ? error.code
38742
38882
  : error?.code === "EEXIST" ? "already-exists" : "helper-failed";
@@ -38868,10 +39008,10 @@ class NativeStagedFile {
38868
39008
  catch (error) {
38869
39009
  const { receipt: cleanup, error: cleanupError } = this.#finalize();
38870
39010
  if (cleanupError) {
38871
- throw failure(new AggregateError([error, cleanupError], "preparation and cleanup failed"), { phase: "prepare", publication: cleanup.publication, cleanup });
39011
+ throw native_staged_file_failure(new AggregateError([error, cleanupError], "preparation and cleanup failed"), { phase: "prepare", publication: cleanup.publication, cleanup });
38872
39012
  }
38873
39013
  if (cleanup.status === "preserved") {
38874
- throw failure(error, { phase: "prepare", publication: cleanup.publication, cleanup });
39014
+ throw native_staged_file_failure(error, { phase: "prepare", publication: cleanup.publication, cleanup });
38875
39015
  }
38876
39016
  throw error;
38877
39017
  }
@@ -38930,7 +39070,7 @@ class NativeStagedFile {
38930
39070
  const publication = this.#state.status === "closed"
38931
39071
  ? this.#state.receipt.publication
38932
39072
  : this.#state.publication;
38933
- throw failure(error, { phase: "publish", publication });
39073
+ throw native_staged_file_failure(error, { phase: "publish", publication });
38934
39074
  }
38935
39075
  }
38936
39076
  #finalize() {
@@ -38971,7 +39111,7 @@ class NativeStagedFile {
38971
39111
  status: outcome,
38972
39112
  resources,
38973
39113
  });
38974
- const error = errors.length ? failure(errors.length === 1 ? errors[0] : new AggregateError(errors, "staged cleanup failed"), { phase: "cleanup", publication: state.publication, cleanup: receipt }) : undefined;
39114
+ const error = errors.length ? native_staged_file_failure(errors.length === 1 ? errors[0] : new AggregateError(errors, "staged cleanup failed"), { phase: "cleanup", publication: state.publication, cleanup: receipt }) : undefined;
38975
39115
  this.#state = { status: "closed", receipt, error };
38976
39116
  return this.#state;
38977
39117
  }
@@ -39280,438 +39420,1184 @@ function bounded_read_readFileDescriptorBoundedSync(fd, maxBytes) {
39280
39420
  }
39281
39421
  }
39282
39422
 
39283
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
39284
- let test_hooks_fsSafeTestHooks;
39285
- function allowFsSafeTestHooks() {
39286
- return false || process.env.VITEST === "true";
39287
- }
39288
- function getFsSafeTestHooks() {
39289
- return test_hooks_fsSafeTestHooks;
39290
- }
39291
- function __setFsSafeTestHooksForTest(hooks) {
39292
- if (hooks && !allowFsSafeTestHooks()) {
39293
- throw new Error("__setFsSafeTestHooksForTest is only available in tests");
39294
- }
39295
- test_hooks_fsSafeTestHooks = hooks;
39423
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/error-detail.js
39424
+ const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
39425
+ function formatErrorDetail(value) {
39426
+ return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
39296
39427
  }
39297
39428
 
39298
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
39299
-
39300
-
39301
-
39429
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-symlink.js
39302
39430
 
39303
39431
 
39304
39432
 
39305
39433
 
39306
39434
 
39307
39435
 
39308
- const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
39309
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
39310
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
39311
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
39312
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN = new RegExp(`\\n(${SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX}[ \\t]{${SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS}})\\n$`);
39313
- function createSidecarLockOwnershipToken() {
39314
- let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
39315
- for (const byte of (0,external_node_crypto_.randomBytes)(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
39316
- for (let bit = 7; bit >= 0; bit -= 1) {
39317
- token += byte & (1 << bit) ? "\t" : " ";
39318
- }
39319
- }
39320
- return token;
39321
- }
39322
- function readSidecarLockOwnershipToken(raw) {
39323
- return SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN.exec(raw)?.[1];
39324
- }
39325
- function serializeSidecarLockPayload(payload) {
39326
- const ownershipToken = createSidecarLockOwnershipToken();
39327
- return {
39328
- raw: `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`,
39329
- ownershipToken,
39330
- };
39331
- }
39332
- function relativeSidecarLockPath(lockRoot, lockPath) {
39333
- const resolved = external_node_path_.resolve(lockPath);
39334
- const lexicalRelative = external_node_path_.relative(lockRoot.rootDir, resolved);
39335
- const relative = lexicalRelative !== ".." &&
39336
- !lexicalRelative.startsWith(`..${external_node_path_.sep}`) &&
39337
- !external_node_path_.isAbsolute(lexicalRelative)
39338
- ? lexicalRelative
39339
- : external_node_path_.relative(lockRoot.rootReal, resolved);
39340
- if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relative)) {
39341
- throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
39436
+ function normalizeSymlinkResolutionError(error) {
39437
+ if (isSymlinkOpenError(error)) {
39438
+ throw new errors_FsSafeError("symlink", "symlink path could not be resolved", {
39439
+ cause: error instanceof Error ? error : undefined,
39440
+ });
39342
39441
  }
39343
- return relative.split(external_node_path_.sep).join(external_node_path_.posix.sep);
39442
+ if (!path_isNotFoundPathError(error))
39443
+ throw error;
39344
39444
  }
39345
- function parseSidecarLockPayload(raw, parser) {
39346
- if (parser) {
39347
- return parser(raw);
39348
- }
39445
+ async function resolveSymlinkHopPath(symlinkPath) {
39349
39446
  try {
39350
- const parsed = JSON.parse(raw);
39351
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
39447
+ return external_node_path_.resolve(await promises_.realpath(symlinkPath));
39352
39448
  }
39353
- catch {
39354
- return null;
39449
+ catch (error) {
39450
+ normalizeSymlinkResolutionError(error);
39451
+ const linkTarget = await promises_.readlink(symlinkPath);
39452
+ return resolvePathViaExistingAncestor(external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget));
39355
39453
  }
39356
39454
  }
39357
- async function readSidecarLockSnapshot(lockPath, options = {}) {
39358
- let handle;
39455
+ function root_path_symlink_resolveSymlinkHopPathSync(symlinkPath) {
39359
39456
  try {
39360
- if (options.lockRoot) {
39361
- const lockRoot = options.lockRoot;
39362
- const relative = relativeSidecarLockPath(lockRoot, lockPath);
39363
- const opened = await lockRoot.open(relative).catch(async (error) => {
39364
- if (error instanceof errors_FsSafeError && error.code === "path-mismatch") {
39365
- // An owner can unlink after open. Prove absence through the same root;
39366
- // acquisition retries create-only, so a later replacement stays guarded.
39367
- try {
39368
- await lockRoot.stat(relative);
39369
- }
39370
- catch (probeError) {
39371
- if (probeError instanceof errors_FsSafeError && probeError.code === "not-found") {
39372
- return null;
39373
- }
39374
- }
39375
- }
39376
- throw error;
39377
- });
39378
- if (!opened)
39379
- return null;
39380
- try {
39381
- const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
39382
- return {
39383
- raw,
39384
- payload: parseSidecarLockPayload(raw, options.parsePayload),
39385
- stat: opened.stat,
39386
- };
39387
- }
39388
- finally {
39389
- await opened.handle.close().catch(() => undefined);
39390
- }
39391
- }
39392
- const before = await promises_.lstat(lockPath);
39393
- if (!before.isFile() || before.isSymbolicLink()) {
39394
- if (options.rejectNonFile) {
39395
- throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
39396
- }
39397
- return null;
39398
- }
39399
- await getFsSafeTestHooks()?.beforeSidecarLockSnapshotOpen?.(lockPath);
39400
- const noFollow = process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
39401
- ? external_node_fs_.constants.O_NOFOLLOW
39402
- : 0;
39403
- try {
39404
- handle = await promises_.open(lockPath, external_node_fs_.constants.O_RDONLY |
39405
- noFollow |
39406
- (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0));
39407
- }
39408
- catch (error) {
39409
- if (options.rejectNonFile && error.code === "ELOOP") {
39410
- throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`, {
39411
- cause: error,
39412
- });
39413
- }
39414
- throw error;
39415
- }
39416
- const opened = await handle.stat();
39417
- if (!opened.isFile()) {
39418
- if (options.rejectNonFile) {
39419
- throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
39420
- }
39421
- return null;
39422
- }
39423
- if (!options.allowDescriptorIdentityDrift && !file_identity_sameFileIdentity(before, opened))
39424
- return null;
39425
- const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
39426
- const after = await promises_.lstat(lockPath);
39427
- if (!after.isFile() || !file_identity_sameFileIdentity(before, after))
39428
- return null;
39429
- return { raw, payload: parseSidecarLockPayload(raw, options.parsePayload), stat: after };
39430
- }
39431
- catch (err) {
39432
- if (err.code === "ENOENT" ||
39433
- (err instanceof errors_FsSafeError && err.code === "not-found")) {
39434
- return null;
39435
- }
39436
- throw err;
39457
+ return path.resolve(fs.realpathSync(symlinkPath));
39437
39458
  }
39438
- finally {
39439
- await handle?.close().catch(() => undefined);
39459
+ catch (error) {
39460
+ normalizeSymlinkResolutionError(error);
39461
+ const linkTarget = fs.readlinkSync(symlinkPath);
39462
+ return resolvePathViaExistingAncestorSync(path.resolve(path.dirname(symlinkPath), linkTarget));
39440
39463
  }
39441
39464
  }
39442
- function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}) {
39443
- let fd;
39444
- try {
39445
- const before = fsSync.lstatSync(lockPath);
39446
- if (!before.isFile() || before.isSymbolicLink()) {
39447
- if (options.rejectNonFile) {
39448
- throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
39449
- }
39450
- return null;
39451
- }
39452
- fd = fsSync.openSync(lockPath, resolveReadOpenFlags());
39453
- const opened = fsSync.fstatSync(fd);
39454
- if (!opened.isFile()) {
39455
- if (options.rejectNonFile) {
39456
- throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
39457
- }
39458
- return null;
39459
- }
39460
- const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
39461
- const after = fsSync.lstatSync(lockPath);
39462
- if (!sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
39463
- return null;
39464
- return {
39465
- raw,
39466
- payload: parseSidecarLockPayload(raw, parsePayload),
39467
- stat: after,
39468
- ownershipToken: readSidecarLockOwnershipToken(raw),
39469
- };
39465
+
39466
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/safe-path-segment.js
39467
+
39468
+ const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
39469
+ const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
39470
+ // Windows treats "C:name" as relative to the drive's current directory even
39471
+ // though path.win32.isAbsolute() reports false.
39472
+ const DRIVE_RELATIVE_PREFIX = /^[A-Za-z]:(?![\\/])/;
39473
+ const HYPHEN_CHAR_CODE = 0x2d;
39474
+ function safe_path_segment_isDriveRelativePath(value) {
39475
+ return DRIVE_RELATIVE_PREFIX.test(value);
39476
+ }
39477
+ function assertNoDriveRelativePathSegments(value, label) {
39478
+ if (value.split("/").some(safe_path_segment_isDriveRelativePath)) {
39479
+ throw new errors_FsSafeError("invalid-path", `${label} must not contain a drive letter`);
39470
39480
  }
39471
- catch (error) {
39472
- if (error.code === "ENOENT")
39473
- return null;
39474
- throw error;
39481
+ return value;
39482
+ }
39483
+ function trimHyphenEdges(value) {
39484
+ let start = 0;
39485
+ let end = value.length;
39486
+ while (start < end && value.charCodeAt(start) === HYPHEN_CHAR_CODE) {
39487
+ start += 1;
39475
39488
  }
39476
- finally {
39477
- if (fd !== undefined)
39478
- fsSync.closeSync(fd);
39489
+ while (end > start && value.charCodeAt(end - 1) === HYPHEN_CHAR_CODE) {
39490
+ end -= 1;
39479
39491
  }
39492
+ return start === 0 && end === value.length ? value : value.slice(start, end);
39480
39493
  }
39481
- function removeSidecarLockIfUnchangedSync(lockPath, observed) {
39482
- const current = readSidecarLockSnapshotSync(lockPath);
39483
- if (!current || !sidecarLockSnapshotMatches(current, observed))
39484
- return false;
39485
- fsSync.rmSync(lockPath);
39486
- return true;
39494
+ function isSafePathSegment(segment, options = {}) {
39495
+ return (segment !== "" &&
39496
+ segment !== "." &&
39497
+ segment !== ".." &&
39498
+ !segment.includes("/") &&
39499
+ !segment.includes("\\") &&
39500
+ !segment.includes("\0") &&
39501
+ (options.allowDotPrefix === true || !segment.startsWith(".")) &&
39502
+ (options.allowDotPrefix === true
39503
+ ? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
39504
+ : SAFE_PATH_SEGMENT_PATTERN.test(segment)));
39487
39505
  }
39488
- function sidecarLockSnapshotMatches(current, observed) {
39489
- if (observed.ownershipToken !== undefined) {
39490
- return (current.stat?.isFile() === true &&
39491
- current.raw !== undefined &&
39492
- observed.raw !== undefined &&
39493
- readSidecarLockOwnershipToken(current.raw) === observed.ownershipToken &&
39494
- readSidecarLockOwnershipToken(observed.raw) === observed.ownershipToken &&
39495
- current.raw === observed.raw);
39496
- }
39497
- if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
39498
- return false;
39506
+ function assertSafePathSegment(segment, options = {}) {
39507
+ // Validate the exact value callers will later join into paths; trimming here
39508
+ // would let whitespace-padded ids pass and then be used verbatim.
39509
+ if (!isSafePathSegment(segment, options)) {
39510
+ throw new FsSafeError("invalid-path", `${options.label ?? "path segment"} must be a safe path segment`);
39499
39511
  }
39500
- if (observed.raw !== undefined) {
39501
- return current.raw === observed.raw;
39512
+ return segment;
39513
+ }
39514
+ function sanitizeSafePathSegment(value, fallback, options = {}) {
39515
+ const sanitized = value
39516
+ .trim()
39517
+ .replace(/[\\/]+/g, "-")
39518
+ .replace(/\0/g, "")
39519
+ .replace(/[^A-Za-z0-9._-]+/g, "-");
39520
+ const trimmed = trimHyphenEdges(sanitized);
39521
+ if (isSafePathSegment(trimmed, options)) {
39522
+ return trimmed;
39502
39523
  }
39503
- return observed.stat !== undefined && current.stat !== undefined;
39524
+ return assertSafePathSegment(fallback, { ...options, label: "fallback path segment" });
39504
39525
  }
39505
- async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
39506
- const current = await readSidecarLockSnapshot(lockPath, {
39526
+ function assertSafePathPrefix(prefix, options = {}) {
39527
+ // Prefixes are often derived from safe filenames. Normalize harmless
39528
+ // filename characters first, but still reject real path-control bytes.
39529
+ if (prefix.includes("/") || prefix.includes("\\") || prefix.includes("\0")) {
39530
+ return assertSafePathSegment(prefix, {
39531
+ allowDotPrefix: true,
39532
+ ...options,
39533
+ label: options.label ?? "path prefix",
39534
+ });
39535
+ }
39536
+ return assertSafePathSegment(prefix.replace(/[^A-Za-z0-9._-]+/g, "-"), {
39537
+ allowDotPrefix: true,
39507
39538
  ...options,
39508
- allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
39539
+ label: options.label ?? "path prefix",
39509
39540
  });
39510
- if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
39511
- return false;
39512
- }
39513
- if (options.lockRoot) {
39514
- await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath));
39541
+ }
39542
+
39543
+ // EXTERNAL MODULE: external "node:os"
39544
+ var external_node_os_ = __webpack_require__(8161);
39545
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/short-path.js
39546
+
39547
+
39548
+ function shortPath(value) {
39549
+ const home = external_node_os_.homedir();
39550
+ const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
39551
+ return formatErrorDetail(shortened);
39552
+ }
39553
+
39554
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
39555
+
39556
+
39557
+
39558
+
39559
+
39560
+
39561
+
39562
+
39563
+
39564
+
39565
+
39566
+ const ROOT_PATH_ALIAS_POLICIES = {
39567
+ strict: Object.freeze({
39568
+ allowFinalSymlinkForUnlink: false,
39569
+ allowFinalHardlinkForUnlink: false,
39570
+ }),
39571
+ unlinkTarget: Object.freeze({
39572
+ allowFinalSymlinkForUnlink: true,
39573
+ allowFinalHardlinkForUnlink: true,
39574
+ }),
39575
+ };
39576
+ async function resolveRootPath(params) {
39577
+ try {
39578
+ return await resolveRootPathInternal(params);
39515
39579
  }
39516
- else {
39517
- await promises_.rm(lockPath, { force: true });
39580
+ catch (error) {
39581
+ throw sanitizeRootPathError(error);
39518
39582
  }
39519
- return true;
39520
39583
  }
39521
- async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
39522
- const current = await readSidecarLockSnapshot(lockPath, {
39523
- ...options,
39524
- allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
39584
+ async function resolveRootPathInternal(params) {
39585
+ assertValidRootPathInputs(params);
39586
+ const rootPath = external_node_path_.resolve(params.rootPath);
39587
+ const absolutePath = external_node_path_.resolve(params.absolutePath);
39588
+ const rootCanonicalPath = params.rootCanonicalPath
39589
+ ? external_node_path_.resolve(params.rootCanonicalPath)
39590
+ : await resolvePathViaExistingAncestor(rootPath);
39591
+ const context = createBoundaryResolutionContext({
39592
+ resolveParams: params,
39593
+ rootPath,
39594
+ absolutePath,
39595
+ rootCanonicalPath,
39596
+ outsideLexicalCanonicalPath: await resolveOutsideLexicalCanonicalPathAsync({
39597
+ rootPath,
39598
+ absolutePath,
39599
+ }),
39600
+ });
39601
+ const outsideResult = await resolveOutsideRootPathAsync({
39602
+ boundaryLabel: params.boundaryLabel,
39603
+ context,
39604
+ });
39605
+ if (outsideResult) {
39606
+ return outsideResult;
39607
+ }
39608
+ return resolveRootPathLexicalAsync({
39609
+ params,
39610
+ absolutePath: context.absolutePath,
39611
+ rootPath: context.rootPath,
39612
+ rootCanonicalPath: context.rootCanonicalPath,
39525
39613
  });
39526
- return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
39527
39614
  }
39528
- async function sidecarReclaimGuardExists(pathname) {
39615
+ function resolveRootPathSync(params) {
39529
39616
  try {
39530
- await promises_.lstat(pathname);
39531
- return true;
39617
+ return resolveRootPathSyncInternal(params);
39532
39618
  }
39533
- catch (err) {
39534
- if (err.code === "ENOENT") {
39535
- return false;
39536
- }
39537
- throw err;
39619
+ catch (error) {
39620
+ throw sanitizeRootPathError(error);
39538
39621
  }
39539
39622
  }
39540
- async function tryAcquireSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
39541
- try {
39542
- await promises_.mkdir(reclaimGuardPath);
39543
- reclaimGuards.add(reclaimGuardPath);
39544
- return true;
39623
+ function resolveRootPathSyncInternal(params) {
39624
+ assertValidRootPathInputs(params);
39625
+ const rootPath = path.resolve(params.rootPath);
39626
+ const absolutePath = path.resolve(params.absolutePath);
39627
+ const rootCanonicalPath = params.rootCanonicalPath
39628
+ ? path.resolve(params.rootCanonicalPath)
39629
+ : resolvePathViaExistingAncestorSync(rootPath);
39630
+ const context = createBoundaryResolutionContext({
39631
+ resolveParams: params,
39632
+ rootPath,
39633
+ absolutePath,
39634
+ rootCanonicalPath,
39635
+ outsideLexicalCanonicalPath: resolveOutsideLexicalCanonicalPathSync({
39636
+ rootPath,
39637
+ absolutePath,
39638
+ }),
39639
+ });
39640
+ const outsideResult = resolveOutsideRootPathSync({
39641
+ boundaryLabel: params.boundaryLabel,
39642
+ context,
39643
+ });
39644
+ if (outsideResult) {
39645
+ return outsideResult;
39545
39646
  }
39546
- catch (err) {
39547
- if (err.code === "EEXIST") {
39548
- return false;
39549
- }
39550
- throw err;
39647
+ return resolveRootPathLexicalSync({
39648
+ params,
39649
+ absolutePath: context.absolutePath,
39650
+ rootPath: context.rootPath,
39651
+ rootCanonicalPath: context.rootCanonicalPath,
39652
+ });
39653
+ }
39654
+ function sanitizeRootPathError(error) {
39655
+ if (error instanceof Error) {
39656
+ error.message = formatErrorDetail(error.message);
39551
39657
  }
39658
+ return error;
39552
39659
  }
39553
- async function releaseSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
39554
- await promises_.rmdir(reclaimGuardPath);
39555
- reclaimGuards.delete(reclaimGuardPath);
39660
+ function assertValidRootPathInputs(params) {
39661
+ path_assertNoNulPathInput(params.rootPath, "root path contains a NUL byte");
39662
+ path_assertNoNulPathInput(params.absolutePath, "absolute path contains a NUL byte");
39663
+ assertNoEmbeddedDriveRelativeSegment(params.rootPath, "root path");
39664
+ assertNoEmbeddedDriveRelativeSegment(params.absolutePath, "absolute path");
39665
+ if (params.rootCanonicalPath !== undefined) {
39666
+ path_assertNoNulPathInput(params.rootCanonicalPath, "canonical root path contains a NUL byte");
39667
+ assertNoEmbeddedDriveRelativeSegment(params.rootCanonicalPath, "canonical root path");
39668
+ }
39556
39669
  }
39557
- async function removeStaleSidecarLockIfAllowed(params) {
39558
- if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
39559
- return "not-approved";
39670
+ function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
39671
+ if (process.platform !== "win32") {
39672
+ return;
39560
39673
  }
39561
- const ioOptions = { lockRoot: params.lockRoot, parsePayload: params.parsePayload };
39562
- if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
39563
- return "changed";
39674
+ const root = external_node_path_.parse(filePath).root;
39675
+ assertNoDriveRelativePathSegments(filePath.slice(root.length).replaceAll("\\", "/"), label);
39676
+ }
39677
+ function isPromiseLike(value) {
39678
+ return Boolean(value &&
39679
+ (typeof value === "object" || typeof value === "function") &&
39680
+ "then" in value &&
39681
+ typeof value.then === "function");
39682
+ }
39683
+ function createLexicalTraversalState(params) {
39684
+ const rawAbsolutePath = params.params.absolutePath;
39685
+ const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
39686
+ const relative = rawRelativePath ?? external_node_path_.relative(params.rootPath, params.absolutePath);
39687
+ return {
39688
+ segments: splitTraversalSegments(relative),
39689
+ allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
39690
+ canonicalCursor: params.rootCanonicalPath,
39691
+ lexicalCursor: params.rootPath,
39692
+ preserveFinalSymlink: false,
39693
+ };
39694
+ }
39695
+ function createLexicalTraversalContext(params) {
39696
+ return {
39697
+ state: createLexicalTraversalState(params),
39698
+ resolveParams: params.params,
39699
+ rootPath: params.rootPath,
39700
+ rootCanonicalPath: params.rootCanonicalPath,
39701
+ absolutePath: params.absolutePath,
39702
+ };
39703
+ }
39704
+ function splitTraversalSegments(value) {
39705
+ return value
39706
+ .split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
39707
+ .filter((segment) => Boolean(segment) && segment !== ".");
39708
+ }
39709
+ function rawPathRelativeToRoot(rootPath, candidatePath) {
39710
+ if (!external_node_path_.isAbsolute(candidatePath)) {
39711
+ return undefined;
39564
39712
  }
39565
- if (!(await params.shouldRemoveStaleLock({
39566
- lockPath: params.lockPath,
39567
- normalizedTargetPath: params.normalizedTargetPath,
39568
- raw: params.snapshot.raw,
39569
- payload: params.snapshot.payload,
39570
- }))) {
39571
- return "not-approved";
39713
+ const root = external_node_path_.resolve(rootPath);
39714
+ const candidate = process.platform === "win32"
39715
+ ? candidatePath.replaceAll("/", external_node_path_.sep)
39716
+ : candidatePath;
39717
+ if (candidate === root) {
39718
+ return "";
39572
39719
  }
39573
- if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
39574
- return "changed";
39720
+ const rootWithSep = root.endsWith(external_node_path_.sep) ? root : `${root}${external_node_path_.sep}`;
39721
+ const candidatePrefix = candidate.slice(0, rootWithSep.length);
39722
+ const prefixMatches = process.platform === "win32"
39723
+ ? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
39724
+ : candidatePrefix === rootWithSep;
39725
+ return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
39726
+ }
39727
+ function assertLexicalCursorInsideBoundary(context, candidatePath) {
39728
+ assertInsideBoundary({
39729
+ boundaryLabel: context.resolveParams.boundaryLabel,
39730
+ rootCanonicalPath: context.rootCanonicalPath,
39731
+ candidatePath,
39732
+ absolutePath: context.absolutePath,
39733
+ });
39734
+ }
39735
+ function applyMissingSuffixToCanonicalCursor(context, missingFromIndex) {
39736
+ const missingSuffix = context.state.segments.slice(missingFromIndex);
39737
+ for (const segment of missingSuffix) {
39738
+ advanceCanonicalCursorForSegment(context, segment);
39739
+ }
39740
+ }
39741
+ function advanceCanonicalCursorForSegment(context, segment) {
39742
+ context.state.canonicalCursor = external_node_path_.resolve(context.state.canonicalCursor, segment);
39743
+ assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
39744
+ }
39745
+ function finalizeLexicalResolution(context, kind) {
39746
+ assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
39747
+ return buildResolvedRootPath({
39748
+ absolutePath: context.absolutePath,
39749
+ canonicalPath: context.state.canonicalCursor,
39750
+ rootPath: context.rootPath,
39751
+ rootCanonicalPath: context.rootCanonicalPath,
39752
+ kind,
39753
+ });
39754
+ }
39755
+ function handleLexicalLstatFailure(context, error, missingFromIndex) {
39756
+ if (!path_isNotFoundPathError(error)) {
39757
+ return false;
39758
+ }
39759
+ applyMissingSuffixToCanonicalCursor(context, missingFromIndex);
39760
+ return true;
39761
+ }
39762
+ function handleLexicalStatReadFailure(context, error, missingFromIndex) {
39763
+ if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
39764
+ return null;
39765
+ }
39766
+ throw error;
39767
+ }
39768
+ function handleLexicalStatDisposition(context, params) {
39769
+ if (!params.isSymbolicLink) {
39770
+ advanceCanonicalCursorForSegment(context, params.segment);
39771
+ return "continue";
39772
+ }
39773
+ if (context.resolveParams.rejectSymlinks === true && params.isLast) {
39774
+ throw new errors_FsSafeError("symlink", "symlink path component not allowed");
39775
+ }
39776
+ if (context.state.allowFinalSymlink && params.isLast) {
39777
+ context.state.preserveFinalSymlink = true;
39778
+ advanceCanonicalCursorForSegment(context, params.segment);
39779
+ return "break";
39780
+ }
39781
+ return "resolve-link";
39782
+ }
39783
+ function applyResolvedSymlinkHop(context, linkCanonical) {
39784
+ if (!path_isPathInside(context.rootCanonicalPath, linkCanonical)) {
39785
+ throw symlinkEscapeError({
39786
+ boundaryLabel: context.resolveParams.boundaryLabel,
39787
+ rootCanonicalPath: context.rootCanonicalPath,
39788
+ symlinkPath: context.state.lexicalCursor,
39789
+ });
39575
39790
  }
39791
+ context.state.canonicalCursor = linkCanonical;
39792
+ context.state.lexicalCursor = linkCanonical;
39793
+ }
39794
+ function readLexicalStat(context, params) {
39576
39795
  try {
39577
- if (params.lockRoot) {
39578
- await params.lockRoot.remove(relativeSidecarLockPath(params.lockRoot, params.lockPath));
39579
- }
39580
- else {
39581
- await promises_.rm(params.lockPath);
39796
+ const stat = params.read(context.state.lexicalCursor);
39797
+ if (isPromiseLike(stat)) {
39798
+ return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
39582
39799
  }
39583
- return "removed";
39800
+ return stat;
39584
39801
  }
39585
- catch (err) {
39586
- if (err.code === "ENOENT") {
39587
- return "changed";
39802
+ catch (error) {
39803
+ return handleLexicalStatReadFailure(context, error, params.missingFromIndex);
39804
+ }
39805
+ }
39806
+ function resolveAndApplySymlinkHop(context, params) {
39807
+ const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
39808
+ if (isPromiseLike(linkCanonical)) {
39809
+ return Promise.resolve(linkCanonical).then((value) => {
39810
+ applyResolvedSymlinkHop(context, value);
39811
+ });
39812
+ }
39813
+ applyResolvedSymlinkHop(context, linkCanonical);
39814
+ }
39815
+ function applyParentTraversalStep(context) {
39816
+ context.state.lexicalCursor = external_node_path_.resolve(context.state.lexicalCursor, "..");
39817
+ advanceCanonicalCursorForSegment(context, "..");
39818
+ }
39819
+ function* iterateLexicalTraversal(state) {
39820
+ for (let idx = 0; idx < state.segments.length; idx += 1) {
39821
+ const segment = state.segments[idx] ?? "";
39822
+ const isLast = idx === state.segments.length - 1;
39823
+ yield { idx, segment, isLast };
39824
+ }
39825
+ }
39826
+ async function resolveRootPathLexicalAsync(params) {
39827
+ const context = createLexicalTraversalContext(params);
39828
+ const { state } = context;
39829
+ for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
39830
+ if (segment === "..") {
39831
+ applyParentTraversalStep(context);
39832
+ continue;
39833
+ }
39834
+ state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
39835
+ const stat = await readLexicalStat(context, {
39836
+ missingFromIndex: idx,
39837
+ read: (cursor) => promises_.lstat(cursor),
39838
+ });
39839
+ if (!stat) {
39840
+ break;
39841
+ }
39842
+ const disposition = handleLexicalStatDisposition(context, {
39843
+ isSymbolicLink: stat.isSymbolicLink(),
39844
+ segment,
39845
+ isLast,
39846
+ });
39847
+ if (disposition === "continue") {
39848
+ continue;
39849
+ }
39850
+ if (disposition === "break") {
39851
+ break;
39852
+ }
39853
+ await resolveAndApplySymlinkHop(context, {
39854
+ resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
39855
+ });
39856
+ if (context.resolveParams.rejectSymlinks === true) {
39857
+ throw new errors_FsSafeError("symlink", "symlink path component not allowed");
39588
39858
  }
39589
- throw err;
39590
39859
  }
39860
+ const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
39861
+ return finalizeLexicalResolution(context, kind);
39591
39862
  }
39592
-
39593
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
39594
-
39595
- function assertFiniteNonNegative(value, label) {
39596
- if (!Number.isFinite(value) || value < 0) {
39597
- throw new RangeError(`${label} must be a finite non-negative number`);
39863
+ function resolveRootPathLexicalSync(params) {
39864
+ const context = createLexicalTraversalContext(params);
39865
+ const { state } = context;
39866
+ for (let idx = 0; idx < state.segments.length; idx += 1) {
39867
+ const segment = state.segments[idx] ?? "";
39868
+ const isLast = idx === state.segments.length - 1;
39869
+ if (segment === "..") {
39870
+ applyParentTraversalStep(context);
39871
+ continue;
39872
+ }
39873
+ state.lexicalCursor = path.join(state.lexicalCursor, segment);
39874
+ const maybeStat = readLexicalStat(context, {
39875
+ missingFromIndex: idx,
39876
+ read: (cursor) => fs.lstatSync(cursor),
39877
+ });
39878
+ if (isPromiseLike(maybeStat)) {
39879
+ throw new Error("Unexpected async lexical stat");
39880
+ }
39881
+ const stat = maybeStat;
39882
+ if (!stat) {
39883
+ break;
39884
+ }
39885
+ const disposition = handleLexicalStatDisposition(context, {
39886
+ isSymbolicLink: stat.isSymbolicLink(),
39887
+ segment,
39888
+ isLast,
39889
+ });
39890
+ if (disposition === "continue") {
39891
+ continue;
39892
+ }
39893
+ if (disposition === "break") {
39894
+ break;
39895
+ }
39896
+ const maybeApplied = resolveAndApplySymlinkHop(context, {
39897
+ resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
39898
+ });
39899
+ if (isPromiseLike(maybeApplied)) {
39900
+ throw new Error("Unexpected async symlink resolution");
39901
+ }
39902
+ if (context.resolveParams.rejectSymlinks === true) {
39903
+ throw new FsSafeError("symlink", "symlink path component not allowed");
39904
+ }
39598
39905
  }
39906
+ const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
39907
+ return finalizeLexicalResolution(context, kind);
39599
39908
  }
39600
- function validateSidecarLockRetryOptions(retry) {
39601
- if (retry.retries !== undefined && (!Number.isSafeInteger(retry.retries) || retry.retries < 0)) {
39602
- throw new RangeError("lock retry.retries must be a non-negative safe integer");
39909
+ function resolveCanonicalOutsideLexicalPath(params) {
39910
+ return params.outsideLexicalCanonicalPath ?? params.absolutePath;
39911
+ }
39912
+ function createBoundaryResolutionContext(params) {
39913
+ const lexicalInside = path_isPathInside(params.rootPath, params.absolutePath);
39914
+ const canonicalOutsideLexicalPath = resolveCanonicalOutsideLexicalPath({
39915
+ absolutePath: params.absolutePath,
39916
+ outsideLexicalCanonicalPath: params.outsideLexicalCanonicalPath,
39917
+ });
39918
+ assertLexicalBoundaryOrCanonicalAlias({
39919
+ skipLexicalRootCheck: params.resolveParams.skipLexicalRootCheck,
39920
+ lexicalInside,
39921
+ canonicalOutsideLexicalPath,
39922
+ rootCanonicalPath: params.rootCanonicalPath,
39923
+ boundaryLabel: params.resolveParams.boundaryLabel,
39924
+ rootPath: params.rootPath,
39925
+ absolutePath: params.absolutePath,
39926
+ });
39927
+ return {
39928
+ rootPath: params.rootPath,
39929
+ absolutePath: params.absolutePath,
39930
+ rootCanonicalPath: params.rootCanonicalPath,
39931
+ lexicalInside,
39932
+ canonicalOutsideLexicalPath,
39933
+ };
39934
+ }
39935
+ async function resolveOutsideRootPathAsync(params) {
39936
+ if (params.context.lexicalInside) {
39937
+ return null;
39603
39938
  }
39604
- if (retry.factor !== undefined)
39605
- assertFiniteNonNegative(retry.factor, "lock retry.factor");
39606
- if (retry.minTimeout !== undefined) {
39607
- assertFiniteNonNegative(retry.minTimeout, "lock retry.minTimeout");
39939
+ const kind = await getPathKind(params.context.absolutePath, false);
39940
+ return buildOutsideRootPathFromContext({
39941
+ boundaryLabel: params.boundaryLabel,
39942
+ context: params.context,
39943
+ kind,
39944
+ });
39945
+ }
39946
+ function resolveOutsideRootPathSync(params) {
39947
+ if (params.context.lexicalInside) {
39948
+ return null;
39608
39949
  }
39609
- if (retry.maxTimeout !== undefined) {
39610
- assertFiniteNonNegative(retry.maxTimeout, "lock retry.maxTimeout");
39950
+ const kind = getPathKindSync(params.context.absolutePath, false);
39951
+ return buildOutsideRootPathFromContext({
39952
+ boundaryLabel: params.boundaryLabel,
39953
+ context: params.context,
39954
+ kind,
39955
+ });
39956
+ }
39957
+ function buildOutsideRootPathFromContext(params) {
39958
+ return buildOutsideLexicalRootPath({
39959
+ boundaryLabel: params.boundaryLabel,
39960
+ rootCanonicalPath: params.context.rootCanonicalPath,
39961
+ absolutePath: params.context.absolutePath,
39962
+ canonicalOutsideLexicalPath: params.context.canonicalOutsideLexicalPath,
39963
+ rootPath: params.context.rootPath,
39964
+ kind: params.kind,
39965
+ });
39966
+ }
39967
+ async function resolveOutsideLexicalCanonicalPathAsync(params) {
39968
+ if (path_isPathInside(params.rootPath, params.absolutePath)) {
39969
+ return undefined;
39611
39970
  }
39612
- if (retry.minTimeout !== undefined &&
39613
- retry.maxTimeout !== undefined &&
39614
- retry.minTimeout > retry.maxTimeout) {
39615
- throw new RangeError("lock retry.minTimeout must not exceed retry.maxTimeout");
39971
+ return await resolvePathViaExistingAncestor(params.absolutePath);
39972
+ }
39973
+ function resolveOutsideLexicalCanonicalPathSync(params) {
39974
+ if (isPathInside(params.rootPath, params.absolutePath)) {
39975
+ return undefined;
39616
39976
  }
39977
+ return resolvePathViaExistingAncestorSync(params.absolutePath);
39617
39978
  }
39618
- function validateSidecarLockTimeoutMs(timeoutMs) {
39619
- if (timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY)
39979
+ function buildOutsideLexicalRootPath(params) {
39980
+ assertInsideBoundary({
39981
+ boundaryLabel: params.boundaryLabel,
39982
+ rootCanonicalPath: params.rootCanonicalPath,
39983
+ candidatePath: params.canonicalOutsideLexicalPath,
39984
+ absolutePath: params.absolutePath,
39985
+ });
39986
+ return buildResolvedRootPath({
39987
+ absolutePath: params.absolutePath,
39988
+ canonicalPath: params.canonicalOutsideLexicalPath,
39989
+ rootPath: params.rootPath,
39990
+ rootCanonicalPath: params.rootCanonicalPath,
39991
+ kind: params.kind,
39992
+ });
39993
+ }
39994
+ function assertLexicalBoundaryOrCanonicalAlias(params) {
39995
+ if (params.skipLexicalRootCheck || params.lexicalInside) {
39620
39996
  return;
39621
- assertFiniteNonNegative(timeoutMs, "lock timeoutMs");
39997
+ }
39998
+ if (path_isPathInside(params.rootCanonicalPath, params.canonicalOutsideLexicalPath)) {
39999
+ return;
40000
+ }
40001
+ throw pathEscapeError({
40002
+ boundaryLabel: params.boundaryLabel,
40003
+ rootPath: params.rootPath,
40004
+ absolutePath: params.absolutePath,
40005
+ });
39622
40006
  }
39623
- function computeSidecarLockDelayMs(retry, attempt) {
39624
- validateSidecarLockRetryOptions(retry);
39625
- const minTimeout = retry.minTimeout ?? 50;
39626
- const maxTimeout = retry.maxTimeout ?? 1000;
39627
- const factor = retry.factor ?? 1;
39628
- const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
39629
- const jitter = retry.randomize ? 1 + Math.random() : 1;
39630
- return Math.min(maxTimeout, Math.round(base * jitter));
40007
+ function buildResolvedRootPath(params) {
40008
+ return {
40009
+ absolutePath: params.absolutePath,
40010
+ canonicalPath: params.canonicalPath,
40011
+ rootPath: params.rootPath,
40012
+ rootCanonicalPath: params.rootCanonicalPath,
40013
+ relativePath: relativeInsideRoot(params.rootCanonicalPath, params.canonicalPath),
40014
+ exists: params.kind.exists,
40015
+ kind: params.kind.kind,
40016
+ };
39631
40017
  }
39632
- // Windows denies access to a lock file while a just-unlinked directory entry
39633
- // is still being torn down, so a contended acquire sees EPERM on a name that is
39634
- // already gone -- both when creating it exclusively and when reading the
39635
- // holder's snapshot. The next attempt succeeds, so this is contention rather
39636
- // than a permission failure. The error must name the lock file itself: the
39637
- // exclusive-create helper opens the parent directory first, and a denial from
39638
- // that setup step carries no teardown evidence and has to reach the caller.
39639
- const maxTransientLockDenials = 8;
39640
- function isTransientLockFileDenial(error, lockPath) {
39641
- const denial = error;
39642
- return process.platform === "win32" && denial?.code === "EPERM" && denial.path === lockPath;
40018
+ async function getPathKind(absolutePath, preserveFinalSymlink) {
40019
+ try {
40020
+ const stat = preserveFinalSymlink
40021
+ ? await promises_.lstat(absolutePath)
40022
+ : await promises_.stat(absolutePath);
40023
+ return { exists: true, kind: toResolvedKind(stat) };
40024
+ }
40025
+ catch (error) {
40026
+ if (path_isNotFoundPathError(error)) {
40027
+ return { exists: false, kind: "missing" };
40028
+ }
40029
+ throw error;
40030
+ }
39643
40031
  }
39644
- function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
39645
- const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
39646
- return createdAtMs !== null && nowMs - createdAtMs > staleMs;
40032
+ function getPathKindSync(absolutePath, preserveFinalSymlink) {
40033
+ try {
40034
+ const stat = preserveFinalSymlink ? fs.lstatSync(absolutePath) : fs.statSync(absolutePath);
40035
+ return { exists: true, kind: toResolvedKind(stat) };
40036
+ }
40037
+ catch (error) {
40038
+ if (isNotFoundPathError(error)) {
40039
+ return { exists: false, kind: "missing" };
40040
+ }
40041
+ throw error;
40042
+ }
39647
40043
  }
39648
- function sidecarLockPayloadCreatedAtMs(payload) {
39649
- const createdAt = payload &&
39650
- typeof payload === "object" &&
39651
- "createdAt" in payload &&
39652
- typeof payload.createdAt === "string"
39653
- ? payload.createdAt
39654
- : "";
39655
- const createdAtMs = Date.parse(createdAt);
39656
- return Number.isFinite(createdAtMs) ? createdAtMs : null;
40044
+ function toResolvedKind(stat) {
40045
+ if (stat.isFile()) {
40046
+ return "file";
40047
+ }
40048
+ if (stat.isDirectory()) {
40049
+ return "directory";
40050
+ }
40051
+ if (stat.isSymbolicLink()) {
40052
+ return "symlink";
40053
+ }
40054
+ return "other";
39657
40055
  }
39658
- async function defaultSidecarLockShouldReclaim(params) {
39659
- const createdAtMs = sidecarLockPayloadCreatedAtMs(params.payload);
39660
- if (createdAtMs !== null)
39661
- return params.nowMs - createdAtMs > params.staleMs;
39662
- try {
39663
- return params.nowMs - (await promises_.stat(params.lockPath)).mtimeMs > params.staleMs;
40056
+ function relativeInsideRoot(rootPath, targetPath) {
40057
+ const relative = external_node_path_.relative(external_node_path_.resolve(rootPath), external_node_path_.resolve(targetPath));
40058
+ if (!relative || relative === ".") {
40059
+ return "";
39664
40060
  }
39665
- catch {
39666
- return true;
40061
+ if (isPathRelativeEscape(relative)) {
40062
+ return "";
39667
40063
  }
40064
+ return relative;
39668
40065
  }
39669
-
39670
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/suppressed-error.js
39671
- function createSuppressedError(error, suppressed, message) {
39672
- const constructor = globalThis.SuppressedError;
39673
- if (constructor) {
39674
- return new constructor(error, suppressed, message);
40066
+ function assertInsideBoundary(params) {
40067
+ if (path_isPathInside(params.rootCanonicalPath, params.candidatePath)) {
40068
+ return;
39675
40069
  }
39676
- const combined = new Error(message, {
39677
- cause: error instanceof Error ? error : undefined,
39678
- });
39679
- combined.name = "SuppressedError";
39680
- Object.defineProperties(combined, {
39681
- error: { configurable: true, value: error },
39682
- suppressed: { configurable: true, value: suppressed },
39683
- });
39684
- return combined;
40070
+ throw new Error(`Path resolves outside ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.absolutePath)}`);
40071
+ }
40072
+ function pathEscapeError(params) {
40073
+ return new Error(`Path escapes ${params.boundaryLabel} (${shortPath(params.rootPath)}): ${shortPath(params.absolutePath)}`);
40074
+ }
40075
+ function symlinkEscapeError(params) {
40076
+ return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
39685
40077
  }
39686
40078
 
39687
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js
39688
-
40079
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-root.js
39689
40080
 
39690
40081
 
39691
40082
 
39692
40083
 
39693
40084
 
39694
40085
 
39695
- async function resolveNormalizedTargetPath(targetPath) {
39696
- const resolved = external_node_path_.resolve(targetPath);
39697
- const dir = external_node_path_.dirname(resolved);
39698
- await promises_.mkdir(dir, { recursive: true });
40086
+ async function openSidecarRoot(lockRoot, relative, discardObservation, onOpenFailure) {
40087
+ const resolved = await lockRoot.resolve(relative);
40088
+ const canonicalPath = async () => (await resolveRootPath({
40089
+ absolutePath: resolved,
40090
+ rootPath: lockRoot.rootReal,
40091
+ rootCanonicalPath: lockRoot.rootReal,
40092
+ boundaryLabel: "sidecar lock root",
40093
+ })).canonicalPath;
40094
+ const expectedRealPath = await canonicalPath();
40095
+ if (expectedRealPath === lockRoot.rootReal)
40096
+ throw new errors_FsSafeError("not-file", "sidecar lock is a directory");
40097
+ const parents = [];
40098
+ let completeParents = true;
40099
+ if (discardObservation) {
40100
+ // Follow permitted in-root aliases first; receipts cover canonical ancestry.
40101
+ for (let dir = external_node_path_.dirname(expectedRealPath);; dir = external_node_path_.dirname(dir)) {
40102
+ try {
40103
+ parents.push({ dir, stat: await inspectDirectoryIdentity(dir) });
40104
+ }
40105
+ catch (error) {
40106
+ if (!path_isNotFoundPathError(error))
40107
+ throw error;
40108
+ completeParents = false;
40109
+ }
40110
+ if (dir === lockRoot.rootReal)
40111
+ break;
40112
+ }
40113
+ }
40114
+ const observation = fileObservation();
39699
40115
  try {
39700
- return external_node_path_.join(await promises_.realpath(dir), external_node_path_.basename(resolved));
40116
+ const opened = await observation.run(() => lockRoot.open(relative));
40117
+ if (opened.realPath !== expectedRealPath) {
40118
+ await opened.handle.close().catch(() => undefined);
40119
+ throw new errors_FsSafeError("path-mismatch", "sidecar lock path changed during open");
40120
+ }
40121
+ return opened;
39701
40122
  }
39702
- catch {
39703
- return resolved;
40123
+ catch (error) {
40124
+ const openFailed = observation.has(error, `open:${resolved}`);
40125
+ if (openFailed)
40126
+ onOpenFailure?.(error);
40127
+ const missing = openFailed && error instanceof errors_FsSafeError && error.code === "not-found";
40128
+ if (missing && !discardObservation)
40129
+ return null;
40130
+ const discardable = observation.has(error, `unlinked:${resolved}`) ||
40131
+ (discardObservation === "changed" && observation.has(error, `changed:${resolved}`));
40132
+ if (!discardObservation || (!missing && (!completeParents || !discardable)))
40133
+ throw error;
40134
+ try {
40135
+ try {
40136
+ const current = await lockRoot.stat(relative);
40137
+ if (!current.isFile || current.isSymbolicLink || current.nlink !== 1)
40138
+ throw error;
40139
+ }
40140
+ catch (probeError) {
40141
+ if (!(probeError instanceof errors_FsSafeError && probeError.code === "not-found"))
40142
+ throw probeError;
40143
+ }
40144
+ for (const { dir, stat } of parents)
40145
+ await inspectDirectoryIdentity(dir, stat);
40146
+ await lockRoot.resolve(relative);
40147
+ if (await canonicalPath() === expectedRealPath)
40148
+ return null;
40149
+ }
40150
+ catch {
40151
+ // Keep the original failure when directory or confinement proof fails.
40152
+ }
40153
+ throw error;
39704
40154
  }
39705
40155
  }
39706
- async function acquireSidecarLock(options, context) {
39707
- const retry = options.retry ?? {};
39708
- validateSidecarLockRetryOptions(retry);
39709
- validateSidecarLockTimeoutMs(options.timeoutMs);
39710
- context.ensureExitCleanupRegistered();
39711
- const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
39712
- const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
39713
- let held = context.held.get(normalizedTargetPath);
39714
- if (held &&
40156
+
40157
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
40158
+ let test_hooks_fsSafeTestHooks;
40159
+ function allowFsSafeTestHooks() {
40160
+ return false || process.env.VITEST === "true";
40161
+ }
40162
+ function getFsSafeTestHooks() {
40163
+ return test_hooks_fsSafeTestHooks;
40164
+ }
40165
+ function __setFsSafeTestHooksForTest(hooks) {
40166
+ if (hooks && !allowFsSafeTestHooks()) {
40167
+ throw new Error("__setFsSafeTestHooksForTest is only available in tests");
40168
+ }
40169
+ test_hooks_fsSafeTestHooks = hooks;
40170
+ }
40171
+
40172
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
40173
+
40174
+
40175
+
40176
+
40177
+
40178
+
40179
+
40180
+
40181
+
40182
+
40183
+ const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
40184
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
40185
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
40186
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
40187
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN = new RegExp(`\\n(${SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX}[ \\t]{${SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS}})\\n$`);
40188
+ function parseSidecarLockSnapshot(snapshot, parser) {
40189
+ return snapshot && { ...snapshot, payload: parseSidecarLockPayload(snapshot.raw, parser) };
40190
+ }
40191
+ function createSidecarLockOwnershipToken() {
40192
+ let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
40193
+ for (const byte of (0,external_node_crypto_.randomBytes)(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
40194
+ for (let bit = 7; bit >= 0; bit -= 1) {
40195
+ token += byte & (1 << bit) ? "\t" : " ";
40196
+ }
40197
+ }
40198
+ return token;
40199
+ }
40200
+ function readSidecarLockOwnershipToken(raw) {
40201
+ return SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN.exec(raw)?.[1];
40202
+ }
40203
+ function serializeSidecarLockPayload(payload) {
40204
+ const ownershipToken = createSidecarLockOwnershipToken();
40205
+ return {
40206
+ raw: `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`,
40207
+ ownershipToken,
40208
+ };
40209
+ }
40210
+ function relativeSidecarLockPath(lockRoot, lockPath) {
40211
+ const resolved = external_node_path_.resolve(lockPath);
40212
+ const lexicalRelative = external_node_path_.relative(lockRoot.rootDir, resolved);
40213
+ const relative = lexicalRelative !== ".." &&
40214
+ !lexicalRelative.startsWith(`..${external_node_path_.sep}`) &&
40215
+ !external_node_path_.isAbsolute(lexicalRelative)
40216
+ ? lexicalRelative
40217
+ : external_node_path_.relative(lockRoot.rootReal, resolved);
40218
+ if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relative)) {
40219
+ throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
40220
+ }
40221
+ return relative.split(external_node_path_.sep).join(external_node_path_.posix.sep);
40222
+ }
40223
+ function parseSidecarLockPayload(raw, parser) {
40224
+ if (parser) {
40225
+ return parser(raw);
40226
+ }
40227
+ try {
40228
+ const parsed = JSON.parse(raw);
40229
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
40230
+ }
40231
+ catch {
40232
+ return null;
40233
+ }
40234
+ }
40235
+ function missingSnapshotPath(error) {
40236
+ if (error.code === "ENOENT")
40237
+ return null;
40238
+ throw error;
40239
+ }
40240
+ async function readSidecarLockSnapshot(lockPath, options = {}) {
40241
+ return parseSidecarLockSnapshot(await readSidecarLockRawSnapshot(lockPath, options), options.parsePayload);
40242
+ }
40243
+ async function readSidecarLockRawSnapshot(lockPath, options = {}) {
40244
+ let handle;
40245
+ try {
40246
+ if (options.lockRoot) {
40247
+ const lockRoot = options.lockRoot;
40248
+ const relative = relativeSidecarLockPath(lockRoot, lockPath);
40249
+ const opened = await openSidecarRoot(lockRoot, relative, options.discardObservation, options.onOpenFailure);
40250
+ if (!opened)
40251
+ return null;
40252
+ try {
40253
+ const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
40254
+ return {
40255
+ raw,
40256
+ stat: opened.stat,
40257
+ };
40258
+ }
40259
+ finally {
40260
+ await opened.handle.close().catch(() => undefined);
40261
+ }
40262
+ }
40263
+ const before = await promises_.lstat(lockPath).catch(missingSnapshotPath);
40264
+ if (!before)
40265
+ return null;
40266
+ if (!before.isFile() || before.isSymbolicLink()) {
40267
+ if (options.rejectNonFile) {
40268
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
40269
+ }
40270
+ return null;
40271
+ }
40272
+ await getFsSafeTestHooks()?.beforeSidecarLockSnapshotOpen?.(lockPath);
40273
+ const noFollow = process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
40274
+ ? external_node_fs_.constants.O_NOFOLLOW
40275
+ : 0;
40276
+ try {
40277
+ handle = await promises_.open(lockPath, external_node_fs_.constants.O_RDONLY |
40278
+ noFollow |
40279
+ (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0));
40280
+ }
40281
+ catch (error) {
40282
+ if (error.code === "ENOENT")
40283
+ return null;
40284
+ if (options.rejectNonFile && error.code === "ELOOP") {
40285
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`, {
40286
+ cause: error,
40287
+ });
40288
+ }
40289
+ options.onOpenFailure?.(error);
40290
+ throw error;
40291
+ }
40292
+ const opened = await handle.stat();
40293
+ if (!opened.isFile()) {
40294
+ if (options.rejectNonFile) {
40295
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
40296
+ }
40297
+ return null;
40298
+ }
40299
+ if (!options.allowDescriptorIdentityDrift && !file_identity_sameFileIdentity(before, opened))
40300
+ return null;
40301
+ const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
40302
+ const after = await promises_.lstat(lockPath).catch(missingSnapshotPath);
40303
+ if (!after || !after.isFile() || !file_identity_sameFileIdentity(before, after))
40304
+ return null;
40305
+ return { raw, stat: after };
40306
+ }
40307
+ finally {
40308
+ await handle?.close().catch(() => undefined);
40309
+ }
40310
+ }
40311
+ function lstatSidecarLockSync(lockPath) {
40312
+ try {
40313
+ return fsSync.lstatSync(lockPath);
40314
+ }
40315
+ catch (error) {
40316
+ return missingSnapshotPath(error);
40317
+ }
40318
+ }
40319
+ function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}) {
40320
+ return parseSidecarLockSnapshot(readSidecarLockRawSnapshotSync(lockPath, options), parsePayload);
40321
+ }
40322
+ function readSidecarLockRawSnapshotSync(lockPath, options = {}) {
40323
+ let fd;
40324
+ try {
40325
+ const before = lstatSidecarLockSync(lockPath);
40326
+ if (!before)
40327
+ return null;
40328
+ if (!before.isFile() || before.isSymbolicLink()) {
40329
+ if (options.rejectNonFile) {
40330
+ throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
40331
+ }
40332
+ return null;
40333
+ }
40334
+ try {
40335
+ fd = fsSync.openSync(lockPath, resolveReadOpenFlags());
40336
+ }
40337
+ catch (error) {
40338
+ if (error.code === "ENOENT")
40339
+ return null;
40340
+ options.onOpenFailure?.(error);
40341
+ throw error;
40342
+ }
40343
+ const opened = fsSync.fstatSync(fd);
40344
+ if (!opened.isFile()) {
40345
+ if (options.rejectNonFile) {
40346
+ throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
40347
+ }
40348
+ return null;
40349
+ }
40350
+ const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
40351
+ const after = lstatSidecarLockSync(lockPath);
40352
+ if (!after || !sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
40353
+ return null;
40354
+ return {
40355
+ raw,
40356
+ stat: after,
40357
+ ownershipToken: readSidecarLockOwnershipToken(raw),
40358
+ };
40359
+ }
40360
+ finally {
40361
+ if (fd !== undefined)
40362
+ fsSync.closeSync(fd);
40363
+ }
40364
+ }
40365
+ function removeSidecarLockIfUnchangedSync(lockPath, observed) {
40366
+ const current = readSidecarLockSnapshotSync(lockPath);
40367
+ if (!current || !sidecarLockSnapshotMatches(current, observed))
40368
+ return false;
40369
+ fsSync.rmSync(lockPath);
40370
+ return true;
40371
+ }
40372
+ function sidecarLockSnapshotMatches(current, observed) {
40373
+ if (observed.ownershipToken !== undefined) {
40374
+ return (current.stat?.isFile() === true &&
40375
+ current.raw !== undefined &&
40376
+ observed.raw !== undefined &&
40377
+ readSidecarLockOwnershipToken(current.raw) === observed.ownershipToken &&
40378
+ readSidecarLockOwnershipToken(observed.raw) === observed.ownershipToken &&
40379
+ current.raw === observed.raw);
40380
+ }
40381
+ if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
40382
+ return false;
40383
+ }
40384
+ if (observed.raw !== undefined) {
40385
+ return current.raw === observed.raw;
40386
+ }
40387
+ return observed.stat !== undefined && current.stat !== undefined;
40388
+ }
40389
+ async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
40390
+ const current = await readSidecarLockSnapshot(lockPath, {
40391
+ ...options,
40392
+ allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
40393
+ });
40394
+ if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
40395
+ return false;
40396
+ }
40397
+ if (options.lockRoot) {
40398
+ await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath));
40399
+ }
40400
+ else {
40401
+ await promises_.rm(lockPath, { force: true });
40402
+ }
40403
+ return true;
40404
+ }
40405
+ async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
40406
+ const current = await readSidecarLockSnapshot(lockPath, {
40407
+ ...options,
40408
+ allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
40409
+ });
40410
+ return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
40411
+ }
40412
+ async function sidecarReclaimGuardExists(pathname) {
40413
+ try {
40414
+ await promises_.lstat(pathname);
40415
+ return true;
40416
+ }
40417
+ catch (err) {
40418
+ if (err.code === "ENOENT") {
40419
+ return false;
40420
+ }
40421
+ throw err;
40422
+ }
40423
+ }
40424
+ async function tryAcquireSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
40425
+ try {
40426
+ await promises_.mkdir(reclaimGuardPath);
40427
+ reclaimGuards.add(reclaimGuardPath);
40428
+ return true;
40429
+ }
40430
+ catch (err) {
40431
+ if (err.code === "EEXIST") {
40432
+ return false;
40433
+ }
40434
+ throw err;
40435
+ }
40436
+ }
40437
+ async function releaseSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
40438
+ await promises_.rmdir(reclaimGuardPath);
40439
+ reclaimGuards.delete(reclaimGuardPath);
40440
+ }
40441
+ async function removeStaleSidecarLockIfAllowed(params) {
40442
+ if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
40443
+ return "not-approved";
40444
+ }
40445
+ const ioOptions = { lockRoot: params.lockRoot, parsePayload: params.parsePayload };
40446
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
40447
+ return "changed";
40448
+ }
40449
+ if (!(await params.shouldRemoveStaleLock({
40450
+ lockPath: params.lockPath,
40451
+ normalizedTargetPath: params.normalizedTargetPath,
40452
+ raw: params.snapshot.raw,
40453
+ payload: params.snapshot.payload,
40454
+ }))) {
40455
+ return "not-approved";
40456
+ }
40457
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
40458
+ return "changed";
40459
+ }
40460
+ try {
40461
+ if (params.lockRoot) {
40462
+ await params.lockRoot.remove(relativeSidecarLockPath(params.lockRoot, params.lockPath));
40463
+ }
40464
+ else {
40465
+ await promises_.rm(params.lockPath);
40466
+ }
40467
+ return "removed";
40468
+ }
40469
+ catch (err) {
40470
+ if (err.code === "ENOENT") {
40471
+ return "changed";
40472
+ }
40473
+ throw err;
40474
+ }
40475
+ }
40476
+
40477
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
40478
+
40479
+ function assertFiniteNonNegative(value, label) {
40480
+ if (!Number.isFinite(value) || value < 0) {
40481
+ throw new RangeError(`${label} must be a finite non-negative number`);
40482
+ }
40483
+ }
40484
+ function validateSidecarLockRetryOptions(retry) {
40485
+ if (retry.retries !== undefined && (!Number.isSafeInteger(retry.retries) || retry.retries < 0)) {
40486
+ throw new RangeError("lock retry.retries must be a non-negative safe integer");
40487
+ }
40488
+ if (retry.factor !== undefined)
40489
+ assertFiniteNonNegative(retry.factor, "lock retry.factor");
40490
+ if (retry.minTimeout !== undefined) {
40491
+ assertFiniteNonNegative(retry.minTimeout, "lock retry.minTimeout");
40492
+ }
40493
+ if (retry.maxTimeout !== undefined) {
40494
+ assertFiniteNonNegative(retry.maxTimeout, "lock retry.maxTimeout");
40495
+ }
40496
+ if (retry.minTimeout !== undefined &&
40497
+ retry.maxTimeout !== undefined &&
40498
+ retry.minTimeout > retry.maxTimeout) {
40499
+ throw new RangeError("lock retry.minTimeout must not exceed retry.maxTimeout");
40500
+ }
40501
+ }
40502
+ function validateSidecarLockTimeoutMs(timeoutMs) {
40503
+ if (timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY)
40504
+ return;
40505
+ assertFiniteNonNegative(timeoutMs, "lock timeoutMs");
40506
+ }
40507
+ function computeSidecarLockDelayMs(retry, attempt) {
40508
+ validateSidecarLockRetryOptions(retry);
40509
+ const minTimeout = retry.minTimeout ?? 50;
40510
+ const maxTimeout = retry.maxTimeout ?? 1000;
40511
+ const factor = retry.factor ?? 1;
40512
+ const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
40513
+ const jitter = retry.randomize ? 1 + Math.random() : 1;
40514
+ return Math.min(maxTimeout, Math.round(base * jitter));
40515
+ }
40516
+ // Windows denies access to a lock file while a just-unlinked directory entry
40517
+ // is still being torn down, so a contended acquire sees EPERM on a name that is
40518
+ // already gone -- both when creating it exclusively and when reading the
40519
+ // holder's snapshot. The next attempt succeeds, so this is contention rather
40520
+ // than a permission failure. The error must name the lock file itself: the
40521
+ // exclusive-create helper opens the parent directory first, and a denial from
40522
+ // that setup step carries no teardown evidence and has to reach the caller.
40523
+ const maxTransientLockDenials = 8;
40524
+ function isTransientLockFileDenial(error, lockPath) {
40525
+ const denial = error;
40526
+ return process.platform === "win32" && denial?.code === "EPERM" && denial.path === lockPath;
40527
+ }
40528
+ function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
40529
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
40530
+ return createdAtMs !== null && nowMs - createdAtMs > staleMs;
40531
+ }
40532
+ function sidecarLockPayloadCreatedAtMs(payload) {
40533
+ const createdAt = payload &&
40534
+ typeof payload === "object" &&
40535
+ "createdAt" in payload &&
40536
+ typeof payload.createdAt === "string"
40537
+ ? payload.createdAt
40538
+ : "";
40539
+ const createdAtMs = Date.parse(createdAt);
40540
+ return Number.isFinite(createdAtMs) ? createdAtMs : null;
40541
+ }
40542
+ async function defaultSidecarLockShouldReclaim(params) {
40543
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(params.payload);
40544
+ if (createdAtMs !== null)
40545
+ return params.nowMs - createdAtMs > params.staleMs;
40546
+ try {
40547
+ return params.nowMs - (await promises_.stat(params.lockPath)).mtimeMs > params.staleMs;
40548
+ }
40549
+ catch {
40550
+ return true;
40551
+ }
40552
+ }
40553
+
40554
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/suppressed-error.js
40555
+ function createSuppressedError(error, suppressed, message) {
40556
+ const constructor = globalThis.SuppressedError;
40557
+ if (constructor) {
40558
+ return new constructor(error, suppressed, message);
40559
+ }
40560
+ const combined = new Error(message, {
40561
+ cause: error instanceof Error ? error : undefined,
40562
+ });
40563
+ combined.name = "SuppressedError";
40564
+ Object.defineProperties(combined, {
40565
+ error: { configurable: true, value: error },
40566
+ suppressed: { configurable: true, value: suppressed },
40567
+ });
40568
+ return combined;
40569
+ }
40570
+
40571
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js
40572
+
40573
+
40574
+
40575
+
40576
+
40577
+
40578
+
40579
+
40580
+
40581
+ async function resolveNormalizedTargetPath(targetPath) {
40582
+ const resolved = external_node_path_.resolve(targetPath);
40583
+ const dir = external_node_path_.dirname(resolved);
40584
+ await promises_.mkdir(dir, { recursive: true });
40585
+ try {
40586
+ return external_node_path_.join(await promises_.realpath(dir), external_node_path_.basename(resolved));
40587
+ }
40588
+ catch {
40589
+ return resolved;
40590
+ }
40591
+ }
40592
+ async function acquireSidecarLock(options, context) {
40593
+ const retry = options.retry ?? {};
40594
+ validateSidecarLockRetryOptions(retry);
40595
+ validateSidecarLockTimeoutMs(options.timeoutMs);
40596
+ context.ensureExitCleanupRegistered();
40597
+ const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
40598
+ const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
40599
+ let held = context.held.get(normalizedTargetPath);
40600
+ if (held &&
39715
40601
  options.reentrantOwner !== undefined &&
39716
40602
  held.reentrantOwner !== undefined &&
39717
40603
  options.reentrantOwner === held.reentrantOwner) {
@@ -39780,13 +40666,14 @@ async function acquireSidecarLock(options, context) {
39780
40666
  let handle = null;
39781
40667
  let createdSnapshot = null;
39782
40668
  let lockFileCreateDenied = false;
40669
+ const payload = await options.payload();
40670
+ const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
39783
40671
  try {
39784
- const payload = await options.payload();
39785
- const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
39786
40672
  if (options.lockRoot) {
39787
- const relativeLockPath = relativeSidecarLockPath(options.lockRoot, lockPath);
40673
+ const lockRoot = options.lockRoot;
40674
+ const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
39788
40675
  try {
39789
- await options.lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
40676
+ await lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
39790
40677
  }
39791
40678
  catch (error) {
39792
40679
  if (error instanceof errors_FsSafeError && error.code === "already-exists") {
@@ -39795,7 +40682,23 @@ async function acquireSidecarLock(options, context) {
39795
40682
  throw error;
39796
40683
  }
39797
40684
  createdSnapshot = { raw, payload, ownershipToken };
39798
- handle = (await options.lockRoot.open(relativeLockPath)).handle;
40685
+ const opened = await openSidecarRoot(lockRoot, relativeLockPath, "unlinked");
40686
+ if (!opened) {
40687
+ await waitForRetry();
40688
+ continue;
40689
+ }
40690
+ try {
40691
+ // Root.open admits the current file, not necessarily the one we created.
40692
+ const currentRaw = await readFileHandleBounded(opened.handle, Buffer.byteLength(raw));
40693
+ if (!currentRaw.equals(Buffer.from(raw))) {
40694
+ throw new errors_FsSafeError("path-mismatch", "created sidecar lock changed before admission");
40695
+ }
40696
+ }
40697
+ catch (error) {
40698
+ await opened.handle.close().catch(() => undefined);
40699
+ throw error;
40700
+ }
40701
+ handle = opened.handle;
39799
40702
  }
39800
40703
  else {
39801
40704
  try {
@@ -39810,6 +40713,12 @@ async function acquireSidecarLock(options, context) {
39810
40713
  await handle.writeFile(raw, "utf8");
39811
40714
  }
39812
40715
  const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
40716
+ if (snapshot.stat.nlink === 0) {
40717
+ await handle.close();
40718
+ handle = null;
40719
+ await waitForRetry();
40720
+ continue;
40721
+ }
39813
40722
  const createdHeld = {
39814
40723
  refCount: 1,
39815
40724
  reentrantOwner: options.reentrantOwner,
@@ -39854,7 +40763,7 @@ async function acquireSidecarLock(options, context) {
39854
40763
  catch (err) {
39855
40764
  try {
39856
40765
  if (handle) {
39857
- const failedSnapshot = { payload: null };
40766
+ const failedSnapshot = createdSnapshot ?? { payload: null };
39858
40767
  try {
39859
40768
  failedSnapshot.stat = await handle.stat();
39860
40769
  }
@@ -39866,8 +40775,8 @@ async function acquireSidecarLock(options, context) {
39866
40775
  context.held.delete(normalizedTargetPath);
39867
40776
  }
39868
40777
  await handle.close().catch(() => undefined);
39869
- // The file may be empty or partial JSON, so remove by the identity
39870
- // captured from our exclusive handle rather than by pathname alone.
40778
+ // Root-created records retain the creator's byte/token receipt;
40779
+ // partial direct writes use the exclusive descriptor's identity.
39871
40780
  await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
39872
40781
  lockRoot: options.lockRoot,
39873
40782
  parsePayload: options.parsePayload,
@@ -39893,24 +40802,29 @@ async function acquireSidecarLock(options, context) {
39893
40802
  if (ownsReclaimGuard) {
39894
40803
  await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
39895
40804
  ownsReclaimGuard = false;
40805
+ await waitForRetry();
39896
40806
  continue;
39897
40807
  }
39898
40808
  const nowMs = Date.now();
39899
- let snapshot;
40809
+ let rawSnapshot;
40810
+ let lockFileOpenDenied = false;
39900
40811
  try {
39901
- snapshot = await readSidecarLockSnapshot(lockPath, {
40812
+ rawSnapshot = await readSidecarLockRawSnapshot(lockPath, {
39902
40813
  lockRoot: options.lockRoot,
39903
- parsePayload: options.parsePayload,
39904
40814
  rejectNonFile: true,
40815
+ discardObservation: "changed",
40816
+ onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
39905
40817
  });
39906
40818
  }
39907
40819
  catch (readErr) {
39908
- if (!isTransientLockFileDenial(readErr, lockPath) || !withinDenialBudget())
40820
+ if (!lockFileOpenDenied || !withinDenialBudget())
39909
40821
  throw readErr;
39910
40822
  await retryOrRethrowDenial(readErr);
39911
40823
  continue;
39912
40824
  }
40825
+ const snapshot = parseSidecarLockSnapshot(rawSnapshot, options.parsePayload);
39913
40826
  if (!snapshot) {
40827
+ await waitForRetry();
39914
40828
  continue;
39915
40829
  }
39916
40830
  if (context.held.has(normalizedTargetPath)) {
@@ -39930,6 +40844,7 @@ async function acquireSidecarLock(options, context) {
39930
40844
  lockRoot: options.lockRoot,
39931
40845
  parsePayload: options.parsePayload,
39932
40846
  }))) {
40847
+ await waitForRetry();
39933
40848
  continue;
39934
40849
  }
39935
40850
  const staleRecovery = options.staleRecovery ?? "fail-closed";
@@ -39948,6 +40863,8 @@ async function acquireSidecarLock(options, context) {
39948
40863
  parsePayload: options.parsePayload,
39949
40864
  });
39950
40865
  if (removal === "removed" || removal === "changed") {
40866
+ if (removal === "changed")
40867
+ await waitForRetry();
39951
40868
  continue;
39952
40869
  }
39953
40870
  await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
@@ -39963,1176 +40880,521 @@ async function acquireSidecarLock(options, context) {
39963
40880
  }
39964
40881
  }
39965
40882
  }
39966
- finally {
39967
- if (ownsReclaimGuard) {
39968
- await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
39969
- }
39970
- }
39971
- }
39972
-
39973
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
39974
-
39975
- function createSidecarLockHandle(params) {
39976
- let released = false;
39977
- let releasePromise;
39978
- let releaseStarted = false;
39979
- const release = async () => {
39980
- if (released)
39981
- return;
39982
- if (!releasePromise) {
39983
- const retry = releaseStarted;
39984
- releaseStarted = true;
39985
- releasePromise = (async () => {
39986
- await params.release({ retry });
39987
- released = true;
39988
- })();
39989
- }
39990
- try {
39991
- await releasePromise;
39992
- }
39993
- finally {
39994
- releasePromise = undefined;
39995
- }
39996
- };
39997
- return {
39998
- lockPath: params.lockPath,
39999
- normalizedTargetPath: params.normalizedTargetPath,
40000
- verifyStillHeld: params.verifyStillHeld,
40001
- release,
40002
- [Symbol.asyncDispose]: release,
40003
- };
40004
- }
40005
- function createHeldSidecarLockHandle(params) {
40006
- return createSidecarLockHandle({
40007
- lockPath: params.held.lockPath,
40008
- normalizedTargetPath: params.normalizedTargetPath,
40009
- verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
40010
- lockRoot: params.held.lockRoot,
40011
- parsePayload: params.held.parsePayload,
40012
- }),
40013
- release: (options) => params.release(options),
40014
- });
40015
- }
40016
-
40017
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
40018
-
40019
-
40020
-
40021
-
40022
-
40023
-
40024
- const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
40025
- const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
40026
- const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
40027
- function getGlobalManagers() {
40028
- const globalWithState = globalThis;
40029
- if (!globalWithState[GLOBAL_STATE_KEY]) {
40030
- globalWithState[GLOBAL_STATE_KEY] = new Map();
40031
- }
40032
- return globalWithState[GLOBAL_STATE_KEY];
40033
- }
40034
- function resolveManagerState(key) {
40035
- const managers = getGlobalManagers();
40036
- let state = managers.get(key);
40037
- if (!state) {
40038
- state = {
40039
- cleanupRegistered: false,
40040
- held: new Map(),
40041
- reclaimCleanupRegistered: false,
40042
- reclaimGuards: new Set(),
40043
- };
40044
- managers.set(key, state);
40045
- }
40046
- else {
40047
- // The global manager symbol is shared across package copies and hot reloads.
40048
- // Backfill state created by fs-safe versions that predate reclaim guards.
40049
- state.reclaimCleanupRegistered ??= false;
40050
- state.reclaimGuards ??= new Set();
40051
- for (const held of state.held.values()) {
40052
- held.refCount ??= 1;
40053
- }
40054
- }
40055
- return state;
40056
- }
40057
- function snapshotMatchesSync(lockPath, observed) {
40058
- let fd;
40059
- try {
40060
- const beforeStat = external_node_fs_.lstatSync(lockPath);
40061
- if (!beforeStat.isFile()) {
40062
- return false;
40063
- }
40064
- const openFlags = external_node_fs_.constants.O_RDONLY |
40065
- (process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
40066
- ? external_node_fs_.constants.O_NOFOLLOW
40067
- : 0) |
40068
- (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0);
40069
- fd = external_node_fs_.openSync(lockPath, openFlags);
40070
- const openedStat = external_node_fs_.fstatSync(fd);
40071
- if (!openedStat.isFile()) {
40072
- return false;
40073
- }
40074
- if (observed.raw !== undefined && openedStat.size !== Buffer.byteLength(observed.raw)) {
40075
- return false;
40076
- }
40077
- const raw = external_node_fs_.readFileSync(fd, "utf8");
40078
- const afterStat = external_node_fs_.lstatSync(lockPath);
40079
- if (!afterStat.isFile() || !file_identity_sameFileIdentity(beforeStat, afterStat)) {
40080
- return false;
40081
- }
40082
- return sidecarLockSnapshotMatches({ raw, payload: null, stat: afterStat }, observed);
40083
- }
40084
- catch {
40085
- return false;
40086
- }
40087
- finally {
40088
- if (fd !== undefined) {
40089
- try {
40090
- external_node_fs_.closeSync(fd);
40091
- }
40092
- catch {
40093
- // Best-effort process-exit cleanup.
40094
- }
40095
- }
40096
- }
40097
- }
40098
- function releaseAllReclaimGuardsSync(state) {
40099
- for (const reclaimGuardPath of state.reclaimGuards) {
40100
- try {
40101
- external_node_fs_.rmdirSync(reclaimGuardPath);
40102
- state.reclaimGuards.delete(reclaimGuardPath);
40103
- }
40104
- catch {
40105
- // Best-effort process-exit cleanup. A surviving guard fails closed.
40106
- }
40107
- }
40108
- }
40109
- function releaseAllLocksSync(state) {
40110
- for (const [normalizedTargetPath, held] of state.held) {
40111
- void held.handle.close().catch(() => undefined);
40112
- try {
40113
- if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
40114
- external_node_fs_.rmSync(held.lockPath, { force: true });
40115
- }
40116
- }
40117
- catch {
40118
- // Best-effort process-exit cleanup.
40119
- }
40120
- state.held.delete(normalizedTargetPath);
40121
- }
40122
- releaseAllReclaimGuardsSync(state);
40123
- }
40124
- function ensureGlobalExitCleanupRegistered() {
40125
- const globalWithCleanup = globalThis;
40126
- if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
40127
- return;
40128
- globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
40129
- const cleanup = () => {
40130
- for (const state of getGlobalManagers().values()) {
40131
- releaseAllLocksSync(state);
40132
- }
40133
- };
40134
- globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
40135
- process.on("exit", cleanup);
40136
- }
40137
- async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
40138
- const current = state.held.get(normalizedTargetPath);
40139
- if (current !== held) {
40140
- return false;
40141
- }
40142
- if (held.releasePromise) {
40143
- await held.releasePromise;
40144
- return true;
40145
- }
40146
- if (options.force) {
40147
- held.refCount = 0;
40148
- }
40149
- else if (!options.retry && held.refCount > 0) {
40150
- held.refCount -= 1;
40151
- }
40152
- if (held.refCount > 0) {
40153
- return false;
40154
- }
40155
- held.releasePromise = (async () => {
40156
- await held.handle.close().catch(() => undefined);
40157
- await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
40158
- lockRoot: held.lockRoot,
40159
- parsePayload: held.parsePayload,
40160
- });
40161
- if (state.held.get(normalizedTargetPath) === held) {
40162
- state.held.delete(normalizedTargetPath);
40163
- }
40164
- if (held.compromiseTimer) {
40165
- clearInterval(held.compromiseTimer);
40166
- held.compromiseTimer = undefined;
40167
- }
40168
- })();
40169
- try {
40170
- await held.releasePromise;
40171
- return true;
40172
- }
40173
- finally {
40174
- held.releasePromise = undefined;
40175
- }
40176
- }
40177
- function handleForHeldLock(state, normalizedTargetPath, held) {
40178
- return createHeldSidecarLockHandle({
40179
- normalizedTargetPath,
40180
- held,
40181
- release: async (options) => await releaseHeldLock(state, normalizedTargetPath, held, { retry: options?.retry }),
40182
- });
40183
- }
40184
- function createSidecarLockManager(key) {
40185
- const state = resolveManagerState(key);
40186
- function ensureExitCleanupRegistered() {
40187
- state.cleanupRegistered = true;
40188
- state.reclaimCleanupRegistered = true;
40189
- ensureGlobalExitCleanupRegistered();
40190
- }
40191
- async function acquire(options) {
40192
- return await acquireSidecarLock(options, {
40193
- held: state.held,
40194
- reclaimGuards: state.reclaimGuards,
40195
- ensureExitCleanupRegistered,
40196
- handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
40197
- releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
40198
- });
40199
- }
40200
- async function withLock(options, fn) {
40201
- const lock = await acquire(options);
40202
- let result;
40203
- try {
40204
- result = await fn();
40205
- }
40206
- catch (bodyError) {
40207
- try {
40208
- await lock.release();
40209
- }
40210
- catch (releaseError) {
40211
- throw createSuppressedError(releaseError, bodyError, "file lock callback and release both failed");
40212
- }
40213
- throw bodyError;
40214
- }
40215
- await lock.release();
40216
- return result;
40217
- }
40218
- async function drain() {
40219
- for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
40220
- await releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
40221
- }
40222
- }
40223
- function reset() {
40224
- releaseAllLocksSync(state);
40225
- }
40226
- function heldEntries() {
40227
- return Array.from(state.held.entries()).map(([normalizedTargetPath, held]) => ({
40228
- normalizedTargetPath,
40229
- lockPath: held.lockPath,
40230
- acquiredAt: held.acquiredAt,
40231
- metadata: held.metadata,
40232
- forceRelease: () => releaseHeldLock(state, normalizedTargetPath, held, { force: true }),
40233
- }));
40234
- }
40235
- return { acquire, withLock, drain, reset, heldEntries };
40236
- }
40237
- async function withSidecarLock(targetPath, options, fn) {
40238
- const manager = createSidecarLockManager(options.managerKey ?? `fs-safe.sidecar-lock:${targetPath}`);
40239
- const { managerKey: _managerKey, ...acquireOptions } = options;
40240
- return await manager.withLock({ ...acquireOptions, targetPath }, fn);
40241
- }
40242
-
40243
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
40244
-
40245
-
40246
-
40247
-
40248
-
40249
-
40250
-
40251
-
40252
-
40253
-
40254
-
40255
-
40256
-
40257
-
40258
-
40259
-
40260
-
40261
- function pinned_write_byteLength(input, encoding) {
40262
- return typeof input === "string"
40263
- ? Buffer.byteLength(input, encoding ?? "utf8")
40264
- : input.byteLength;
40265
- }
40266
- function assertSafeBasename(basename) {
40267
- if (!basename ||
40268
- basename === "." ||
40269
- basename === ".." ||
40270
- basename.includes("/") ||
40271
- basename.includes("\0")) {
40272
- throw new errors_FsSafeError("invalid-path", "invalid target path");
40273
- }
40274
- }
40275
- function assertWithinMaxBytes(bytes, maxBytes) {
40276
- if (maxBytes !== undefined && bytes > maxBytes) {
40277
- throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
40278
- }
40279
- }
40280
- async function syncFileBestEffort(handle) {
40281
- try {
40282
- await handle.sync();
40283
- }
40284
- catch (error) {
40285
- if (error?.code !== "EPERM") {
40286
- throw error;
40287
- }
40288
- }
40289
- }
40290
- async function writeStreamToHandle(stream, handle, maxBytes) {
40291
- let bytes = 0;
40292
- for await (const chunk of stream) {
40293
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
40294
- bytes += buffer.byteLength;
40295
- assertWithinMaxBytes(bytes, maxBytes);
40296
- let offset = 0;
40297
- while (offset < buffer.byteLength) {
40298
- const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
40299
- if (bytesWritten <= 0) {
40300
- throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
40301
- }
40302
- offset += bytesWritten;
40303
- }
40304
- }
40305
- }
40306
- async function runPinnedWriteHelper(params) {
40307
- const normalizedParams = { ...params, maxBytes: byte_budget_normalizeMaxBytes(params.maxBytes) };
40308
- assertSafeBasename(params.basename);
40309
- validatePinnedOperationPayload({
40310
- relativeParentPath: params.relativeParentPath,
40311
- });
40312
- // The explicit compatibility policy uses the guarded Node fallback, where
40313
- // content verification can replace the strict post-rename inode check.
40314
- if (normalizedParams.onRenameIdentityMismatch === "verify-content") {
40315
- return await runPinnedWriteFallback(normalizedParams);
40316
- }
40317
- const native = getNativeBinding();
40318
- if (native) {
40319
- return await runPinnedWriteNative(native, normalizedParams);
40320
- }
40321
- return await runPinnedWriteFallback(normalizedParams);
40322
- }
40323
- async function runPinnedWriteWithRenamePolicy(params) {
40324
- const { targetPath, renameIdentity, ...writeParams } = params;
40325
- if (renameIdentity !== "verify-content-with-lock") {
40326
- return await runPinnedWriteHelper(writeParams);
40327
- }
40328
- const relativeTargetPath = writeParams.relativeParentPath
40329
- ? `${writeParams.relativeParentPath}/${writeParams.basename}`
40330
- : writeParams.basename;
40331
- const lockPath = external_node_path_.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
40332
- return await withSidecarLock(writeParams.rootPath, {
40333
- managerKey: `fs-safe.write:${targetPath}`,
40334
- lockPath,
40335
- staleMs: 30_000,
40336
- timeoutMs: 5_000,
40337
- payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
40338
- retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
40339
- }, async () => await runPinnedWriteHelper({
40340
- ...writeParams,
40341
- onRenameIdentityMismatch: "verify-content",
40342
- }));
40343
- }
40344
- async function runPinnedWriteFallback(params) {
40345
- let parentPath = params.relativeParentPath
40346
- ? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
40347
- : params.rootPath;
40348
- if (params.mkdir) {
40349
- // mkdirPathComponentsWithGuards may resolve the final component through
40350
- // an in-root symlink (e.g. a skill-bank layout). Use its returned real
40351
- // path for the subsequent guard and target path so we don't re-check the
40352
- // original, possibly-symlinked, lexical path and reject it outright.
40353
- parentPath = await mkdirPathComponentsWithGuards({
40354
- rootReal: params.rootPath,
40355
- targetPath: parentPath,
40356
- beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
40357
- });
40358
- }
40359
- const parentGuard = params.mkdir
40360
- ? await directory_guard_createAsyncDirectoryGuard(parentPath)
40361
- : await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
40362
- const targetPath = external_node_path_.join(parentPath, params.basename);
40363
- if (params.overwrite === false) {
40364
- const handle = await withAsyncDirectoryGuards([parentGuard], async () => await promises_.open(targetPath, external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL, params.mode), {
40365
- onPostGuardFailure: async (openedHandle) => {
40366
- // The parent failed verification, so targetPath may now resolve
40367
- // somewhere else. Close the fd, but do not clean up by path.
40368
- await openedHandle.close().catch(() => undefined);
40369
- },
40370
- });
40371
- let created = true;
40372
- try {
40373
- const verificationIdentity = await handle.stat({ bigint: true });
40374
- await handle.chmod(params.mode);
40375
- if (params.input.kind === "buffer") {
40376
- assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
40377
- if (typeof params.input.data === "string") {
40378
- await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
40379
- }
40380
- else {
40381
- await handle.writeFile(params.input.data);
40382
- }
40383
- }
40384
- else {
40385
- await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
40386
- }
40387
- await syncFileBestEffort(handle);
40388
- const stat = await handle.stat();
40389
- await syncDirectoryBestEffort(parentPath);
40390
- // Publication is complete. A failed outer check must not remove its target.
40391
- created = false;
40392
- await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
40393
- return { dev: stat.dev, ino: stat.ino };
40394
- }
40395
- finally {
40396
- await handle.close().catch(() => undefined);
40397
- if (created) {
40398
- await promises_.rm(targetPath, { force: true }).catch(() => undefined);
40399
- }
40400
- }
40401
- }
40402
- const tempPath = external_node_path_.join(parentPath, `.${params.basename}.${(0,external_node_crypto_.randomUUID)()}.fallback.tmp`);
40403
- const tempFlags = external_node_fs_.constants.O_WRONLY |
40404
- external_node_fs_.constants.O_CREAT |
40405
- external_node_fs_.constants.O_EXCL |
40406
- (process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_.constants
40407
- ? external_node_fs_.constants.O_NOFOLLOW
40408
- : 0);
40409
- let handle;
40410
- let tempStat;
40411
- let readHandle;
40412
- let renamed = false;
40413
- try {
40414
- handle = await promises_.open(tempPath, tempFlags, params.mode);
40415
- let verificationIdentity = await handle.stat({ bigint: true });
40416
- await handle.chmod(params.mode);
40417
- if (params.input.kind === "buffer") {
40418
- assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
40419
- if (typeof params.input.data === "string") {
40420
- await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
40421
- }
40422
- else {
40423
- await handle.writeFile(params.input.data);
40424
- }
40425
- }
40426
- else {
40427
- await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
40428
- }
40429
- tempStat = await handle.stat();
40430
- const tempPathStat = await promises_.lstat(tempPath);
40431
- if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
40432
- throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
40433
- }
40434
- const expectedTempStat = tempStat;
40435
- await syncFileBestEffort(handle);
40436
- let verifiedIdentity = expectedTempStat;
40437
- await withAsyncDirectoryGuards([parentGuard], async () => {
40438
- await promises_.rename(tempPath, targetPath);
40439
- renamed = true;
40440
- await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
40441
- await syncDirectoryBestEffort(parentPath);
40442
- const targetStat = await promises_.lstat(targetPath);
40443
- if (targetStat.isSymbolicLink()) {
40444
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
40445
- }
40446
- if (!file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
40447
- // On filesystems like rclone FUSE, rename(2) can give the destination a
40448
- // different inode from the source temp fd even with zero concurrency. The
40449
- // caller must ensure mutual exclusion before passing "verify-content";
40450
- // fall back to a content hash for this rename-boundary check only.
40451
- if (params.onRenameIdentityMismatch !== "verify-content") {
40452
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
40453
- }
40454
- if (params.input.kind !== "buffer") {
40455
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
40456
- }
40457
- const expectedHash = sha256Hex(params.input.data, params.input.encoding);
40458
- readHandle = await promises_.open(targetPath, read_open_flags_resolveReadOpenFlags());
40459
- const readHandleStat = await readHandle.stat({ bigint: true });
40460
- const actualHash = sha256Hex(await readHandle.readFile());
40461
- if (actualHash !== expectedHash) {
40462
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
40463
- }
40464
- // The content-verified destination, not the old temp inode, is now pinned.
40465
- verificationIdentity = readHandleStat;
40466
- // Preserve the helper's legacy numeric return facts, not the private proof.
40467
- verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
40468
- }
40469
- });
40470
- await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
40471
- return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
40472
- }
40473
- finally {
40474
- await readHandle?.close().catch(() => undefined);
40475
- await handle?.close().catch(() => undefined);
40476
- if (!renamed) {
40477
- await promises_.rm(tempPath, { force: true }).catch(() => undefined);
40478
- }
40479
- }
40480
- }
40481
-
40482
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/error-detail.js
40483
- const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
40484
- function formatErrorDetail(value) {
40485
- return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
40486
- }
40487
-
40488
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-symlink.js
40489
-
40490
-
40491
-
40492
-
40493
-
40494
-
40495
- function normalizeSymlinkResolutionError(error) {
40496
- if (isSymlinkOpenError(error)) {
40497
- throw new errors_FsSafeError("symlink", "symlink path could not be resolved", {
40498
- cause: error instanceof Error ? error : undefined,
40499
- });
40500
- }
40501
- if (!path_isNotFoundPathError(error))
40502
- throw error;
40503
- }
40504
- async function resolveSymlinkHopPath(symlinkPath) {
40505
- try {
40506
- return external_node_path_.resolve(await promises_.realpath(symlinkPath));
40507
- }
40508
- catch (error) {
40509
- normalizeSymlinkResolutionError(error);
40510
- const linkTarget = await promises_.readlink(symlinkPath);
40511
- return resolvePathViaExistingAncestor(external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget));
40512
- }
40513
- }
40514
- function root_path_symlink_resolveSymlinkHopPathSync(symlinkPath) {
40515
- try {
40516
- return path.resolve(fs.realpathSync(symlinkPath));
40517
- }
40518
- catch (error) {
40519
- normalizeSymlinkResolutionError(error);
40520
- const linkTarget = fs.readlinkSync(symlinkPath);
40521
- return resolvePathViaExistingAncestorSync(path.resolve(path.dirname(symlinkPath), linkTarget));
40522
- }
40523
- }
40524
-
40525
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/safe-path-segment.js
40526
-
40527
- const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
40528
- const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
40529
- // Windows treats "C:name" as relative to the drive's current directory even
40530
- // though path.win32.isAbsolute() reports false.
40531
- const DRIVE_RELATIVE_PREFIX = /^[A-Za-z]:(?![\\/])/;
40532
- const HYPHEN_CHAR_CODE = 0x2d;
40533
- function safe_path_segment_isDriveRelativePath(value) {
40534
- return DRIVE_RELATIVE_PREFIX.test(value);
40535
- }
40536
- function assertNoDriveRelativePathSegments(value, label) {
40537
- if (value.split("/").some(safe_path_segment_isDriveRelativePath)) {
40538
- throw new errors_FsSafeError("invalid-path", `${label} must not contain a drive letter`);
40539
- }
40540
- return value;
40541
- }
40542
- function trimHyphenEdges(value) {
40543
- let start = 0;
40544
- let end = value.length;
40545
- while (start < end && value.charCodeAt(start) === HYPHEN_CHAR_CODE) {
40546
- start += 1;
40547
- }
40548
- while (end > start && value.charCodeAt(end - 1) === HYPHEN_CHAR_CODE) {
40549
- end -= 1;
40550
- }
40551
- return start === 0 && end === value.length ? value : value.slice(start, end);
40552
- }
40553
- function isSafePathSegment(segment, options = {}) {
40554
- return (segment !== "" &&
40555
- segment !== "." &&
40556
- segment !== ".." &&
40557
- !segment.includes("/") &&
40558
- !segment.includes("\\") &&
40559
- !segment.includes("\0") &&
40560
- (options.allowDotPrefix === true || !segment.startsWith(".")) &&
40561
- (options.allowDotPrefix === true
40562
- ? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
40563
- : SAFE_PATH_SEGMENT_PATTERN.test(segment)));
40564
- }
40565
- function assertSafePathSegment(segment, options = {}) {
40566
- // Validate the exact value callers will later join into paths; trimming here
40567
- // would let whitespace-padded ids pass and then be used verbatim.
40568
- if (!isSafePathSegment(segment, options)) {
40569
- throw new FsSafeError("invalid-path", `${options.label ?? "path segment"} must be a safe path segment`);
40570
- }
40571
- return segment;
40572
- }
40573
- function sanitizeSafePathSegment(value, fallback, options = {}) {
40574
- const sanitized = value
40575
- .trim()
40576
- .replace(/[\\/]+/g, "-")
40577
- .replace(/\0/g, "")
40578
- .replace(/[^A-Za-z0-9._-]+/g, "-");
40579
- const trimmed = trimHyphenEdges(sanitized);
40580
- if (isSafePathSegment(trimmed, options)) {
40581
- return trimmed;
40582
- }
40583
- return assertSafePathSegment(fallback, { ...options, label: "fallback path segment" });
40584
- }
40585
- function assertSafePathPrefix(prefix, options = {}) {
40586
- // Prefixes are often derived from safe filenames. Normalize harmless
40587
- // filename characters first, but still reject real path-control bytes.
40588
- if (prefix.includes("/") || prefix.includes("\\") || prefix.includes("\0")) {
40589
- return assertSafePathSegment(prefix, {
40590
- allowDotPrefix: true,
40591
- ...options,
40592
- label: options.label ?? "path prefix",
40593
- });
40883
+ finally {
40884
+ if (ownsReclaimGuard) {
40885
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
40886
+ }
40594
40887
  }
40595
- return assertSafePathSegment(prefix.replace(/[^A-Za-z0-9._-]+/g, "-"), {
40596
- allowDotPrefix: true,
40597
- ...options,
40598
- label: options.label ?? "path prefix",
40599
- });
40600
40888
  }
40601
40889
 
40602
- // EXTERNAL MODULE: external "node:os"
40603
- var external_node_os_ = __webpack_require__(8161);
40604
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/short-path.js
40605
-
40890
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
40606
40891
 
40607
- function shortPath(value) {
40608
- const home = external_node_os_.homedir();
40609
- const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
40610
- return formatErrorDetail(shortened);
40892
+ function createSidecarLockHandle(params) {
40893
+ let released = false;
40894
+ let releasePromise;
40895
+ let releaseStarted = false;
40896
+ const release = async () => {
40897
+ if (released)
40898
+ return;
40899
+ if (!releasePromise) {
40900
+ const retry = releaseStarted;
40901
+ releaseStarted = true;
40902
+ releasePromise = (async () => {
40903
+ await params.release({ retry });
40904
+ released = true;
40905
+ })();
40906
+ }
40907
+ try {
40908
+ await releasePromise;
40909
+ }
40910
+ finally {
40911
+ releasePromise = undefined;
40912
+ }
40913
+ };
40914
+ return {
40915
+ lockPath: params.lockPath,
40916
+ normalizedTargetPath: params.normalizedTargetPath,
40917
+ verifyStillHeld: params.verifyStillHeld,
40918
+ release,
40919
+ [Symbol.asyncDispose]: release,
40920
+ };
40921
+ }
40922
+ function createHeldSidecarLockHandle(params) {
40923
+ return createSidecarLockHandle({
40924
+ lockPath: params.held.lockPath,
40925
+ normalizedTargetPath: params.normalizedTargetPath,
40926
+ verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
40927
+ lockRoot: params.held.lockRoot,
40928
+ parsePayload: params.held.parsePayload,
40929
+ }),
40930
+ release: (options) => params.release(options),
40931
+ });
40611
40932
  }
40612
40933
 
40613
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
40614
-
40615
-
40616
-
40617
-
40618
-
40934
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
40619
40935
 
40620
40936
 
40621
40937
 
40622
40938
 
40623
40939
 
40624
40940
 
40625
- const ROOT_PATH_ALIAS_POLICIES = {
40626
- strict: Object.freeze({
40627
- allowFinalSymlinkForUnlink: false,
40628
- allowFinalHardlinkForUnlink: false,
40629
- }),
40630
- unlinkTarget: Object.freeze({
40631
- allowFinalSymlinkForUnlink: true,
40632
- allowFinalHardlinkForUnlink: true,
40633
- }),
40634
- };
40635
- async function resolveRootPath(params) {
40636
- try {
40637
- return await resolveRootPathInternal(params);
40638
- }
40639
- catch (error) {
40640
- throw sanitizeRootPathError(error);
40941
+ const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
40942
+ const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
40943
+ const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
40944
+ function getGlobalManagers() {
40945
+ const globalWithState = globalThis;
40946
+ if (!globalWithState[GLOBAL_STATE_KEY]) {
40947
+ globalWithState[GLOBAL_STATE_KEY] = new Map();
40641
40948
  }
40949
+ return globalWithState[GLOBAL_STATE_KEY];
40642
40950
  }
40643
- async function resolveRootPathInternal(params) {
40644
- assertValidRootPathInputs(params);
40645
- const rootPath = external_node_path_.resolve(params.rootPath);
40646
- const absolutePath = external_node_path_.resolve(params.absolutePath);
40647
- const rootCanonicalPath = params.rootCanonicalPath
40648
- ? external_node_path_.resolve(params.rootCanonicalPath)
40649
- : await resolvePathViaExistingAncestor(rootPath);
40650
- const context = createBoundaryResolutionContext({
40651
- resolveParams: params,
40652
- rootPath,
40653
- absolutePath,
40654
- rootCanonicalPath,
40655
- outsideLexicalCanonicalPath: await resolveOutsideLexicalCanonicalPathAsync({
40656
- rootPath,
40657
- absolutePath,
40658
- }),
40659
- });
40660
- const outsideResult = await resolveOutsideRootPathAsync({
40661
- boundaryLabel: params.boundaryLabel,
40662
- context,
40663
- });
40664
- if (outsideResult) {
40665
- return outsideResult;
40951
+ function resolveManagerState(key) {
40952
+ const managers = getGlobalManagers();
40953
+ let state = managers.get(key);
40954
+ if (!state) {
40955
+ state = {
40956
+ cleanupRegistered: false,
40957
+ held: new Map(),
40958
+ reclaimCleanupRegistered: false,
40959
+ reclaimGuards: new Set(),
40960
+ };
40961
+ managers.set(key, state);
40666
40962
  }
40667
- return resolveRootPathLexicalAsync({
40668
- params,
40669
- absolutePath: context.absolutePath,
40670
- rootPath: context.rootPath,
40671
- rootCanonicalPath: context.rootCanonicalPath,
40672
- });
40963
+ else {
40964
+ // The global manager symbol is shared across package copies and hot reloads.
40965
+ // Backfill state created by fs-safe versions that predate reclaim guards.
40966
+ state.reclaimCleanupRegistered ??= false;
40967
+ state.reclaimGuards ??= new Set();
40968
+ for (const held of state.held.values()) {
40969
+ held.refCount ??= 1;
40970
+ }
40971
+ }
40972
+ return state;
40673
40973
  }
40674
- function resolveRootPathSync(params) {
40974
+ function snapshotMatchesSync(lockPath, observed) {
40975
+ let fd;
40675
40976
  try {
40676
- return resolveRootPathSyncInternal(params);
40977
+ const beforeStat = external_node_fs_.lstatSync(lockPath);
40978
+ if (!beforeStat.isFile()) {
40979
+ return false;
40980
+ }
40981
+ const openFlags = external_node_fs_.constants.O_RDONLY |
40982
+ (process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
40983
+ ? external_node_fs_.constants.O_NOFOLLOW
40984
+ : 0) |
40985
+ (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0);
40986
+ fd = external_node_fs_.openSync(lockPath, openFlags);
40987
+ const openedStat = external_node_fs_.fstatSync(fd);
40988
+ if (!openedStat.isFile()) {
40989
+ return false;
40990
+ }
40991
+ if (observed.raw !== undefined && openedStat.size !== Buffer.byteLength(observed.raw)) {
40992
+ return false;
40993
+ }
40994
+ const raw = external_node_fs_.readFileSync(fd, "utf8");
40995
+ const afterStat = external_node_fs_.lstatSync(lockPath);
40996
+ if (!afterStat.isFile() || !file_identity_sameFileIdentity(beforeStat, afterStat)) {
40997
+ return false;
40998
+ }
40999
+ return sidecarLockSnapshotMatches({ raw, payload: null, stat: afterStat }, observed);
40677
41000
  }
40678
- catch (error) {
40679
- throw sanitizeRootPathError(error);
41001
+ catch {
41002
+ return false;
40680
41003
  }
40681
- }
40682
- function resolveRootPathSyncInternal(params) {
40683
- assertValidRootPathInputs(params);
40684
- const rootPath = path.resolve(params.rootPath);
40685
- const absolutePath = path.resolve(params.absolutePath);
40686
- const rootCanonicalPath = params.rootCanonicalPath
40687
- ? path.resolve(params.rootCanonicalPath)
40688
- : resolvePathViaExistingAncestorSync(rootPath);
40689
- const context = createBoundaryResolutionContext({
40690
- resolveParams: params,
40691
- rootPath,
40692
- absolutePath,
40693
- rootCanonicalPath,
40694
- outsideLexicalCanonicalPath: resolveOutsideLexicalCanonicalPathSync({
40695
- rootPath,
40696
- absolutePath,
40697
- }),
40698
- });
40699
- const outsideResult = resolveOutsideRootPathSync({
40700
- boundaryLabel: params.boundaryLabel,
40701
- context,
40702
- });
40703
- if (outsideResult) {
40704
- return outsideResult;
41004
+ finally {
41005
+ if (fd !== undefined) {
41006
+ try {
41007
+ external_node_fs_.closeSync(fd);
41008
+ }
41009
+ catch {
41010
+ // Best-effort process-exit cleanup.
41011
+ }
41012
+ }
40705
41013
  }
40706
- return resolveRootPathLexicalSync({
40707
- params,
40708
- absolutePath: context.absolutePath,
40709
- rootPath: context.rootPath,
40710
- rootCanonicalPath: context.rootCanonicalPath,
40711
- });
40712
41014
  }
40713
- function sanitizeRootPathError(error) {
40714
- if (error instanceof Error) {
40715
- error.message = formatErrorDetail(error.message);
41015
+ function releaseAllReclaimGuardsSync(state) {
41016
+ for (const reclaimGuardPath of state.reclaimGuards) {
41017
+ try {
41018
+ external_node_fs_.rmdirSync(reclaimGuardPath);
41019
+ state.reclaimGuards.delete(reclaimGuardPath);
41020
+ }
41021
+ catch {
41022
+ // Best-effort process-exit cleanup. A surviving guard fails closed.
41023
+ }
40716
41024
  }
40717
- return error;
40718
41025
  }
40719
- function assertValidRootPathInputs(params) {
40720
- path_assertNoNulPathInput(params.rootPath, "root path contains a NUL byte");
40721
- path_assertNoNulPathInput(params.absolutePath, "absolute path contains a NUL byte");
40722
- assertNoEmbeddedDriveRelativeSegment(params.rootPath, "root path");
40723
- assertNoEmbeddedDriveRelativeSegment(params.absolutePath, "absolute path");
40724
- if (params.rootCanonicalPath !== undefined) {
40725
- path_assertNoNulPathInput(params.rootCanonicalPath, "canonical root path contains a NUL byte");
40726
- assertNoEmbeddedDriveRelativeSegment(params.rootCanonicalPath, "canonical root path");
41026
+ function releaseAllLocksSync(state) {
41027
+ for (const [normalizedTargetPath, held] of state.held) {
41028
+ void held.handle.close().catch(() => undefined);
41029
+ try {
41030
+ if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
41031
+ external_node_fs_.rmSync(held.lockPath, { force: true });
41032
+ }
41033
+ }
41034
+ catch {
41035
+ // Best-effort process-exit cleanup.
41036
+ }
41037
+ state.held.delete(normalizedTargetPath);
40727
41038
  }
41039
+ releaseAllReclaimGuardsSync(state);
40728
41040
  }
40729
- function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
40730
- if (process.platform !== "win32") {
41041
+ function ensureGlobalExitCleanupRegistered() {
41042
+ const globalWithCleanup = globalThis;
41043
+ if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
40731
41044
  return;
40732
- }
40733
- const root = external_node_path_.parse(filePath).root;
40734
- assertNoDriveRelativePathSegments(filePath.slice(root.length).replaceAll("\\", "/"), label);
40735
- }
40736
- function isPromiseLike(value) {
40737
- return Boolean(value &&
40738
- (typeof value === "object" || typeof value === "function") &&
40739
- "then" in value &&
40740
- typeof value.then === "function");
40741
- }
40742
- function createLexicalTraversalState(params) {
40743
- const rawAbsolutePath = params.params.absolutePath;
40744
- const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
40745
- const relative = rawRelativePath ?? external_node_path_.relative(params.rootPath, params.absolutePath);
40746
- return {
40747
- segments: splitTraversalSegments(relative),
40748
- allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
40749
- canonicalCursor: params.rootCanonicalPath,
40750
- lexicalCursor: params.rootPath,
40751
- preserveFinalSymlink: false,
40752
- };
40753
- }
40754
- function createLexicalTraversalContext(params) {
40755
- return {
40756
- state: createLexicalTraversalState(params),
40757
- resolveParams: params.params,
40758
- rootPath: params.rootPath,
40759
- rootCanonicalPath: params.rootCanonicalPath,
40760
- absolutePath: params.absolutePath,
41045
+ globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
41046
+ const cleanup = () => {
41047
+ for (const state of getGlobalManagers().values()) {
41048
+ releaseAllLocksSync(state);
41049
+ }
40761
41050
  };
41051
+ globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
41052
+ process.on("exit", cleanup);
40762
41053
  }
40763
- function splitTraversalSegments(value) {
40764
- return value
40765
- .split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
40766
- .filter((segment) => Boolean(segment) && segment !== ".");
40767
- }
40768
- function rawPathRelativeToRoot(rootPath, candidatePath) {
40769
- if (!external_node_path_.isAbsolute(candidatePath)) {
40770
- return undefined;
40771
- }
40772
- const root = external_node_path_.resolve(rootPath);
40773
- const candidate = process.platform === "win32"
40774
- ? candidatePath.replaceAll("/", external_node_path_.sep)
40775
- : candidatePath;
40776
- if (candidate === root) {
40777
- return "";
40778
- }
40779
- const rootWithSep = root.endsWith(external_node_path_.sep) ? root : `${root}${external_node_path_.sep}`;
40780
- const candidatePrefix = candidate.slice(0, rootWithSep.length);
40781
- const prefixMatches = process.platform === "win32"
40782
- ? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
40783
- : candidatePrefix === rootWithSep;
40784
- return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
40785
- }
40786
- function assertLexicalCursorInsideBoundary(context, candidatePath) {
40787
- assertInsideBoundary({
40788
- boundaryLabel: context.resolveParams.boundaryLabel,
40789
- rootCanonicalPath: context.rootCanonicalPath,
40790
- candidatePath,
40791
- absolutePath: context.absolutePath,
40792
- });
40793
- }
40794
- function applyMissingSuffixToCanonicalCursor(context, missingFromIndex) {
40795
- const missingSuffix = context.state.segments.slice(missingFromIndex);
40796
- for (const segment of missingSuffix) {
40797
- advanceCanonicalCursorForSegment(context, segment);
40798
- }
40799
- }
40800
- function advanceCanonicalCursorForSegment(context, segment) {
40801
- context.state.canonicalCursor = external_node_path_.resolve(context.state.canonicalCursor, segment);
40802
- assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
40803
- }
40804
- function finalizeLexicalResolution(context, kind) {
40805
- assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
40806
- return buildResolvedRootPath({
40807
- absolutePath: context.absolutePath,
40808
- canonicalPath: context.state.canonicalCursor,
40809
- rootPath: context.rootPath,
40810
- rootCanonicalPath: context.rootCanonicalPath,
40811
- kind,
40812
- });
40813
- }
40814
- function handleLexicalLstatFailure(context, error, missingFromIndex) {
40815
- if (!path_isNotFoundPathError(error)) {
41054
+ async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
41055
+ const current = state.held.get(normalizedTargetPath);
41056
+ if (current !== held) {
40816
41057
  return false;
40817
41058
  }
40818
- applyMissingSuffixToCanonicalCursor(context, missingFromIndex);
40819
- return true;
40820
- }
40821
- function handleLexicalStatReadFailure(context, error, missingFromIndex) {
40822
- if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
40823
- return null;
41059
+ if (held.releasePromise) {
41060
+ await held.releasePromise;
41061
+ return true;
40824
41062
  }
40825
- throw error;
40826
- }
40827
- function handleLexicalStatDisposition(context, params) {
40828
- if (!params.isSymbolicLink) {
40829
- advanceCanonicalCursorForSegment(context, params.segment);
40830
- return "continue";
41063
+ if (options.force) {
41064
+ held.refCount = 0;
40831
41065
  }
40832
- if (context.resolveParams.rejectSymlinks === true && params.isLast) {
40833
- throw new errors_FsSafeError("symlink", "symlink path component not allowed");
41066
+ else if (!options.retry && held.refCount > 0) {
41067
+ held.refCount -= 1;
40834
41068
  }
40835
- if (context.state.allowFinalSymlink && params.isLast) {
40836
- context.state.preserveFinalSymlink = true;
40837
- advanceCanonicalCursorForSegment(context, params.segment);
40838
- return "break";
41069
+ if (held.refCount > 0) {
41070
+ return false;
40839
41071
  }
40840
- return "resolve-link";
40841
- }
40842
- function applyResolvedSymlinkHop(context, linkCanonical) {
40843
- if (!path_isPathInside(context.rootCanonicalPath, linkCanonical)) {
40844
- throw symlinkEscapeError({
40845
- boundaryLabel: context.resolveParams.boundaryLabel,
40846
- rootCanonicalPath: context.rootCanonicalPath,
40847
- symlinkPath: context.state.lexicalCursor,
41072
+ held.releasePromise = (async () => {
41073
+ await held.handle.close().catch(() => undefined);
41074
+ await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
41075
+ lockRoot: held.lockRoot,
41076
+ parsePayload: held.parsePayload,
40848
41077
  });
40849
- }
40850
- context.state.canonicalCursor = linkCanonical;
40851
- context.state.lexicalCursor = linkCanonical;
40852
- }
40853
- function readLexicalStat(context, params) {
40854
- try {
40855
- const stat = params.read(context.state.lexicalCursor);
40856
- if (isPromiseLike(stat)) {
40857
- return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
41078
+ if (state.held.get(normalizedTargetPath) === held) {
41079
+ state.held.delete(normalizedTargetPath);
40858
41080
  }
40859
- return stat;
40860
- }
40861
- catch (error) {
40862
- return handleLexicalStatReadFailure(context, error, params.missingFromIndex);
41081
+ if (held.compromiseTimer) {
41082
+ clearInterval(held.compromiseTimer);
41083
+ held.compromiseTimer = undefined;
41084
+ }
41085
+ })();
41086
+ try {
41087
+ await held.releasePromise;
41088
+ return true;
40863
41089
  }
40864
- }
40865
- function resolveAndApplySymlinkHop(context, params) {
40866
- const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
40867
- if (isPromiseLike(linkCanonical)) {
40868
- return Promise.resolve(linkCanonical).then((value) => {
40869
- applyResolvedSymlinkHop(context, value);
40870
- });
41090
+ finally {
41091
+ held.releasePromise = undefined;
40871
41092
  }
40872
- applyResolvedSymlinkHop(context, linkCanonical);
40873
- }
40874
- function applyParentTraversalStep(context) {
40875
- context.state.lexicalCursor = external_node_path_.resolve(context.state.lexicalCursor, "..");
40876
- advanceCanonicalCursorForSegment(context, "..");
40877
41093
  }
40878
- function* iterateLexicalTraversal(state) {
40879
- for (let idx = 0; idx < state.segments.length; idx += 1) {
40880
- const segment = state.segments[idx] ?? "";
40881
- const isLast = idx === state.segments.length - 1;
40882
- yield { idx, segment, isLast };
40883
- }
41094
+ function handleForHeldLock(state, normalizedTargetPath, held) {
41095
+ return createHeldSidecarLockHandle({
41096
+ normalizedTargetPath,
41097
+ held,
41098
+ release: async (options) => await releaseHeldLock(state, normalizedTargetPath, held, { retry: options?.retry }),
41099
+ });
40884
41100
  }
40885
- async function resolveRootPathLexicalAsync(params) {
40886
- const context = createLexicalTraversalContext(params);
40887
- const { state } = context;
40888
- for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
40889
- if (segment === "..") {
40890
- applyParentTraversalStep(context);
40891
- continue;
40892
- }
40893
- state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
40894
- const stat = await readLexicalStat(context, {
40895
- missingFromIndex: idx,
40896
- read: (cursor) => promises_.lstat(cursor),
40897
- });
40898
- if (!stat) {
40899
- break;
40900
- }
40901
- const disposition = handleLexicalStatDisposition(context, {
40902
- isSymbolicLink: stat.isSymbolicLink(),
40903
- segment,
40904
- isLast,
40905
- });
40906
- if (disposition === "continue") {
40907
- continue;
40908
- }
40909
- if (disposition === "break") {
40910
- break;
40911
- }
40912
- await resolveAndApplySymlinkHop(context, {
40913
- resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
40914
- });
40915
- if (context.resolveParams.rejectSymlinks === true) {
40916
- throw new errors_FsSafeError("symlink", "symlink path component not allowed");
40917
- }
41101
+ function createSidecarLockManager(key) {
41102
+ const state = resolveManagerState(key);
41103
+ function ensureExitCleanupRegistered() {
41104
+ state.cleanupRegistered = true;
41105
+ state.reclaimCleanupRegistered = true;
41106
+ ensureGlobalExitCleanupRegistered();
40918
41107
  }
40919
- const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
40920
- return finalizeLexicalResolution(context, kind);
40921
- }
40922
- function resolveRootPathLexicalSync(params) {
40923
- const context = createLexicalTraversalContext(params);
40924
- const { state } = context;
40925
- for (let idx = 0; idx < state.segments.length; idx += 1) {
40926
- const segment = state.segments[idx] ?? "";
40927
- const isLast = idx === state.segments.length - 1;
40928
- if (segment === "..") {
40929
- applyParentTraversalStep(context);
40930
- continue;
40931
- }
40932
- state.lexicalCursor = path.join(state.lexicalCursor, segment);
40933
- const maybeStat = readLexicalStat(context, {
40934
- missingFromIndex: idx,
40935
- read: (cursor) => fs.lstatSync(cursor),
40936
- });
40937
- if (isPromiseLike(maybeStat)) {
40938
- throw new Error("Unexpected async lexical stat");
40939
- }
40940
- const stat = maybeStat;
40941
- if (!stat) {
40942
- break;
40943
- }
40944
- const disposition = handleLexicalStatDisposition(context, {
40945
- isSymbolicLink: stat.isSymbolicLink(),
40946
- segment,
40947
- isLast,
40948
- });
40949
- if (disposition === "continue") {
40950
- continue;
40951
- }
40952
- if (disposition === "break") {
40953
- break;
40954
- }
40955
- const maybeApplied = resolveAndApplySymlinkHop(context, {
40956
- resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
41108
+ async function acquire(options) {
41109
+ return await acquireSidecarLock(options, {
41110
+ held: state.held,
41111
+ reclaimGuards: state.reclaimGuards,
41112
+ ensureExitCleanupRegistered,
41113
+ handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
41114
+ releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
40957
41115
  });
40958
- if (isPromiseLike(maybeApplied)) {
40959
- throw new Error("Unexpected async symlink resolution");
41116
+ }
41117
+ async function withLock(options, fn) {
41118
+ const lock = await acquire(options);
41119
+ let result;
41120
+ try {
41121
+ result = await fn();
40960
41122
  }
40961
- if (context.resolveParams.rejectSymlinks === true) {
40962
- throw new FsSafeError("symlink", "symlink path component not allowed");
41123
+ catch (bodyError) {
41124
+ try {
41125
+ await lock.release();
41126
+ }
41127
+ catch (releaseError) {
41128
+ throw createSuppressedError(releaseError, bodyError, "file lock callback and release both failed");
41129
+ }
41130
+ throw bodyError;
40963
41131
  }
41132
+ await lock.release();
41133
+ return result;
40964
41134
  }
40965
- const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
40966
- return finalizeLexicalResolution(context, kind);
40967
- }
40968
- function resolveCanonicalOutsideLexicalPath(params) {
40969
- return params.outsideLexicalCanonicalPath ?? params.absolutePath;
40970
- }
40971
- function createBoundaryResolutionContext(params) {
40972
- const lexicalInside = path_isPathInside(params.rootPath, params.absolutePath);
40973
- const canonicalOutsideLexicalPath = resolveCanonicalOutsideLexicalPath({
40974
- absolutePath: params.absolutePath,
40975
- outsideLexicalCanonicalPath: params.outsideLexicalCanonicalPath,
40976
- });
40977
- assertLexicalBoundaryOrCanonicalAlias({
40978
- skipLexicalRootCheck: params.resolveParams.skipLexicalRootCheck,
40979
- lexicalInside,
40980
- canonicalOutsideLexicalPath,
40981
- rootCanonicalPath: params.rootCanonicalPath,
40982
- boundaryLabel: params.resolveParams.boundaryLabel,
40983
- rootPath: params.rootPath,
40984
- absolutePath: params.absolutePath,
40985
- });
40986
- return {
40987
- rootPath: params.rootPath,
40988
- absolutePath: params.absolutePath,
40989
- rootCanonicalPath: params.rootCanonicalPath,
40990
- lexicalInside,
40991
- canonicalOutsideLexicalPath,
40992
- };
40993
- }
40994
- async function resolveOutsideRootPathAsync(params) {
40995
- if (params.context.lexicalInside) {
40996
- return null;
41135
+ async function drain() {
41136
+ for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
41137
+ await releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
41138
+ }
40997
41139
  }
40998
- const kind = await getPathKind(params.context.absolutePath, false);
40999
- return buildOutsideRootPathFromContext({
41000
- boundaryLabel: params.boundaryLabel,
41001
- context: params.context,
41002
- kind,
41003
- });
41004
- }
41005
- function resolveOutsideRootPathSync(params) {
41006
- if (params.context.lexicalInside) {
41007
- return null;
41140
+ function reset() {
41141
+ releaseAllLocksSync(state);
41008
41142
  }
41009
- const kind = getPathKindSync(params.context.absolutePath, false);
41010
- return buildOutsideRootPathFromContext({
41011
- boundaryLabel: params.boundaryLabel,
41012
- context: params.context,
41013
- kind,
41014
- });
41015
- }
41016
- function buildOutsideRootPathFromContext(params) {
41017
- return buildOutsideLexicalRootPath({
41018
- boundaryLabel: params.boundaryLabel,
41019
- rootCanonicalPath: params.context.rootCanonicalPath,
41020
- absolutePath: params.context.absolutePath,
41021
- canonicalOutsideLexicalPath: params.context.canonicalOutsideLexicalPath,
41022
- rootPath: params.context.rootPath,
41023
- kind: params.kind,
41024
- });
41025
- }
41026
- async function resolveOutsideLexicalCanonicalPathAsync(params) {
41027
- if (path_isPathInside(params.rootPath, params.absolutePath)) {
41028
- return undefined;
41143
+ function heldEntries() {
41144
+ return Array.from(state.held.entries()).map(([normalizedTargetPath, held]) => ({
41145
+ normalizedTargetPath,
41146
+ lockPath: held.lockPath,
41147
+ acquiredAt: held.acquiredAt,
41148
+ metadata: held.metadata,
41149
+ forceRelease: () => releaseHeldLock(state, normalizedTargetPath, held, { force: true }),
41150
+ }));
41029
41151
  }
41030
- return await resolvePathViaExistingAncestor(params.absolutePath);
41152
+ return { acquire, withLock, drain, reset, heldEntries };
41031
41153
  }
41032
- function resolveOutsideLexicalCanonicalPathSync(params) {
41033
- if (isPathInside(params.rootPath, params.absolutePath)) {
41034
- return undefined;
41035
- }
41036
- return resolvePathViaExistingAncestorSync(params.absolutePath);
41154
+ async function withSidecarLock(targetPath, options, fn) {
41155
+ const manager = createSidecarLockManager(options.managerKey ?? `fs-safe.sidecar-lock:${targetPath}`);
41156
+ const { managerKey: _managerKey, ...acquireOptions } = options;
41157
+ return await manager.withLock({ ...acquireOptions, targetPath }, fn);
41037
41158
  }
41038
- function buildOutsideLexicalRootPath(params) {
41039
- assertInsideBoundary({
41040
- boundaryLabel: params.boundaryLabel,
41041
- rootCanonicalPath: params.rootCanonicalPath,
41042
- candidatePath: params.canonicalOutsideLexicalPath,
41043
- absolutePath: params.absolutePath,
41044
- });
41045
- return buildResolvedRootPath({
41046
- absolutePath: params.absolutePath,
41047
- canonicalPath: params.canonicalOutsideLexicalPath,
41048
- rootPath: params.rootPath,
41049
- rootCanonicalPath: params.rootCanonicalPath,
41050
- kind: params.kind,
41051
- });
41159
+
41160
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
41161
+
41162
+
41163
+
41164
+
41165
+
41166
+
41167
+
41168
+
41169
+
41170
+
41171
+
41172
+
41173
+
41174
+
41175
+
41176
+
41177
+
41178
+ function pinned_write_byteLength(input, encoding) {
41179
+ return typeof input === "string"
41180
+ ? Buffer.byteLength(input, encoding ?? "utf8")
41181
+ : input.byteLength;
41052
41182
  }
41053
- function assertLexicalBoundaryOrCanonicalAlias(params) {
41054
- if (params.skipLexicalRootCheck || params.lexicalInside) {
41055
- return;
41056
- }
41057
- if (path_isPathInside(params.rootCanonicalPath, params.canonicalOutsideLexicalPath)) {
41058
- return;
41183
+ function assertSafeBasename(basename) {
41184
+ if (!basename ||
41185
+ basename === "." ||
41186
+ basename === ".." ||
41187
+ basename.includes("/") ||
41188
+ basename.includes("\0")) {
41189
+ throw new errors_FsSafeError("invalid-path", "invalid target path");
41059
41190
  }
41060
- throw pathEscapeError({
41061
- boundaryLabel: params.boundaryLabel,
41062
- rootPath: params.rootPath,
41063
- absolutePath: params.absolutePath,
41064
- });
41065
41191
  }
41066
- function buildResolvedRootPath(params) {
41067
- return {
41068
- absolutePath: params.absolutePath,
41069
- canonicalPath: params.canonicalPath,
41070
- rootPath: params.rootPath,
41071
- rootCanonicalPath: params.rootCanonicalPath,
41072
- relativePath: relativeInsideRoot(params.rootCanonicalPath, params.canonicalPath),
41073
- exists: params.kind.exists,
41074
- kind: params.kind.kind,
41075
- };
41192
+ function assertWithinMaxBytes(bytes, maxBytes) {
41193
+ if (maxBytes !== undefined && bytes > maxBytes) {
41194
+ throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
41195
+ }
41076
41196
  }
41077
- async function getPathKind(absolutePath, preserveFinalSymlink) {
41197
+ async function syncFileBestEffort(handle) {
41078
41198
  try {
41079
- const stat = preserveFinalSymlink
41080
- ? await promises_.lstat(absolutePath)
41081
- : await promises_.stat(absolutePath);
41082
- return { exists: true, kind: toResolvedKind(stat) };
41199
+ await handle.sync();
41083
41200
  }
41084
41201
  catch (error) {
41085
- if (path_isNotFoundPathError(error)) {
41086
- return { exists: false, kind: "missing" };
41202
+ if (error?.code !== "EPERM") {
41203
+ throw error;
41087
41204
  }
41088
- throw error;
41089
41205
  }
41090
41206
  }
41091
- function getPathKindSync(absolutePath, preserveFinalSymlink) {
41092
- try {
41093
- const stat = preserveFinalSymlink ? fs.lstatSync(absolutePath) : fs.statSync(absolutePath);
41094
- return { exists: true, kind: toResolvedKind(stat) };
41095
- }
41096
- catch (error) {
41097
- if (isNotFoundPathError(error)) {
41098
- return { exists: false, kind: "missing" };
41207
+ async function writeStreamToHandle(stream, handle, maxBytes) {
41208
+ let bytes = 0;
41209
+ for await (const chunk of stream) {
41210
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
41211
+ bytes += buffer.byteLength;
41212
+ assertWithinMaxBytes(bytes, maxBytes);
41213
+ let offset = 0;
41214
+ while (offset < buffer.byteLength) {
41215
+ const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
41216
+ if (bytesWritten <= 0) {
41217
+ throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
41218
+ }
41219
+ offset += bytesWritten;
41099
41220
  }
41100
- throw error;
41101
41221
  }
41102
41222
  }
41103
- function toResolvedKind(stat) {
41104
- if (stat.isFile()) {
41105
- return "file";
41223
+ async function runPinnedWriteHelper(params) {
41224
+ const normalizedParams = { ...params, maxBytes: byte_budget_normalizeMaxBytes(params.maxBytes) };
41225
+ assertSafeBasename(params.basename);
41226
+ validatePinnedOperationPayload({
41227
+ relativeParentPath: params.relativeParentPath,
41228
+ });
41229
+ // The explicit compatibility policy uses the guarded Node fallback, where
41230
+ // content verification can replace the strict post-rename inode check.
41231
+ if (normalizedParams.onRenameIdentityMismatch === "verify-content") {
41232
+ return await runPinnedWriteFallback(normalizedParams);
41106
41233
  }
41107
- if (stat.isDirectory()) {
41108
- return "directory";
41234
+ const native = getNativeBinding();
41235
+ if (native) {
41236
+ return await runPinnedWriteNative(native, normalizedParams);
41109
41237
  }
41110
- if (stat.isSymbolicLink()) {
41111
- return "symlink";
41238
+ return await runPinnedWriteFallback(normalizedParams);
41239
+ }
41240
+ async function runPinnedWriteWithRenamePolicy(params) {
41241
+ const { targetPath, renameIdentity, ...writeParams } = params;
41242
+ if (renameIdentity !== "verify-content-with-lock") {
41243
+ return await runPinnedWriteHelper(writeParams);
41112
41244
  }
41113
- return "other";
41245
+ const relativeTargetPath = writeParams.relativeParentPath
41246
+ ? `${writeParams.relativeParentPath}/${writeParams.basename}`
41247
+ : writeParams.basename;
41248
+ const lockPath = external_node_path_.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
41249
+ return await withSidecarLock(writeParams.rootPath, {
41250
+ managerKey: `fs-safe.write:${targetPath}`,
41251
+ lockPath,
41252
+ staleMs: 30_000,
41253
+ timeoutMs: 5_000,
41254
+ payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
41255
+ retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
41256
+ }, async () => await runPinnedWriteHelper({
41257
+ ...writeParams,
41258
+ onRenameIdentityMismatch: "verify-content",
41259
+ }));
41114
41260
  }
41115
- function relativeInsideRoot(rootPath, targetPath) {
41116
- const relative = external_node_path_.relative(external_node_path_.resolve(rootPath), external_node_path_.resolve(targetPath));
41117
- if (!relative || relative === ".") {
41118
- return "";
41261
+ async function runPinnedWriteFallback(params) {
41262
+ let parentPath = params.relativeParentPath
41263
+ ? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
41264
+ : params.rootPath;
41265
+ if (params.mkdir) {
41266
+ // mkdirPathComponentsWithGuards may resolve the final component through
41267
+ // an in-root symlink (e.g. a skill-bank layout). Use its returned real
41268
+ // path for the subsequent guard and target path so we don't re-check the
41269
+ // original, possibly-symlinked, lexical path and reject it outright.
41270
+ parentPath = await mkdirPathComponentsWithGuards({
41271
+ rootReal: params.rootPath,
41272
+ targetPath: parentPath,
41273
+ beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
41274
+ });
41119
41275
  }
41120
- if (isPathRelativeEscape(relative)) {
41121
- return "";
41276
+ const parentGuard = params.mkdir
41277
+ ? await directory_guard_createAsyncDirectoryGuard(parentPath)
41278
+ : await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
41279
+ const targetPath = external_node_path_.join(parentPath, params.basename);
41280
+ if (params.overwrite === false) {
41281
+ const handle = await withAsyncDirectoryGuards([parentGuard], async () => await promises_.open(targetPath, external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL, params.mode), {
41282
+ onPostGuardFailure: async (openedHandle) => {
41283
+ // The parent failed verification, so targetPath may now resolve
41284
+ // somewhere else. Close the fd, but do not clean up by path.
41285
+ await openedHandle.close().catch(() => undefined);
41286
+ },
41287
+ });
41288
+ let created = true;
41289
+ try {
41290
+ const verificationIdentity = await handle.stat({ bigint: true });
41291
+ await handle.chmod(params.mode);
41292
+ if (params.input.kind === "buffer") {
41293
+ assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
41294
+ if (typeof params.input.data === "string") {
41295
+ await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
41296
+ }
41297
+ else {
41298
+ await handle.writeFile(params.input.data);
41299
+ }
41300
+ }
41301
+ else {
41302
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
41303
+ }
41304
+ await syncFileBestEffort(handle);
41305
+ const stat = await handle.stat();
41306
+ await syncDirectoryBestEffort(parentPath);
41307
+ // Publication is complete. A failed outer check must not remove its target.
41308
+ created = false;
41309
+ await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
41310
+ return { dev: stat.dev, ino: stat.ino };
41311
+ }
41312
+ finally {
41313
+ await handle.close().catch(() => undefined);
41314
+ if (created) {
41315
+ await promises_.rm(targetPath, { force: true }).catch(() => undefined);
41316
+ }
41317
+ }
41122
41318
  }
41123
- return relative;
41124
- }
41125
- function assertInsideBoundary(params) {
41126
- if (path_isPathInside(params.rootCanonicalPath, params.candidatePath)) {
41127
- return;
41319
+ // Private staging must not consume the destination basename's filename budget.
41320
+ const tempPath = external_node_path_.join(parentPath, `.fs-safe-${(0,external_node_crypto_.randomUUID)()}.tmp`);
41321
+ const tempFlags = external_node_fs_.constants.O_WRONLY |
41322
+ external_node_fs_.constants.O_CREAT |
41323
+ external_node_fs_.constants.O_EXCL |
41324
+ (process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_.constants
41325
+ ? external_node_fs_.constants.O_NOFOLLOW
41326
+ : 0);
41327
+ let handle;
41328
+ let tempStat;
41329
+ let readHandle;
41330
+ let renamed = false;
41331
+ try {
41332
+ handle = await promises_.open(tempPath, tempFlags, params.mode);
41333
+ let verificationIdentity = await handle.stat({ bigint: true });
41334
+ await handle.chmod(params.mode);
41335
+ if (params.input.kind === "buffer") {
41336
+ assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
41337
+ if (typeof params.input.data === "string") {
41338
+ await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
41339
+ }
41340
+ else {
41341
+ await handle.writeFile(params.input.data);
41342
+ }
41343
+ }
41344
+ else {
41345
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
41346
+ }
41347
+ tempStat = await handle.stat();
41348
+ const tempPathStat = await promises_.lstat(tempPath);
41349
+ if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
41350
+ throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
41351
+ }
41352
+ const expectedTempStat = tempStat;
41353
+ await syncFileBestEffort(handle);
41354
+ let verifiedIdentity = expectedTempStat;
41355
+ await withAsyncDirectoryGuards([parentGuard], async () => {
41356
+ await promises_.rename(tempPath, targetPath);
41357
+ renamed = true;
41358
+ await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
41359
+ await syncDirectoryBestEffort(parentPath);
41360
+ const targetStat = await promises_.lstat(targetPath);
41361
+ if (targetStat.isSymbolicLink()) {
41362
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
41363
+ }
41364
+ if (!file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
41365
+ // On filesystems like rclone FUSE, rename(2) can give the destination a
41366
+ // different inode from the source temp fd even with zero concurrency. The
41367
+ // caller must ensure mutual exclusion before passing "verify-content";
41368
+ // fall back to a content hash for this rename-boundary check only.
41369
+ if (params.onRenameIdentityMismatch !== "verify-content") {
41370
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
41371
+ }
41372
+ if (params.input.kind !== "buffer") {
41373
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
41374
+ }
41375
+ const expectedHash = sha256Hex(params.input.data, params.input.encoding);
41376
+ readHandle = await promises_.open(targetPath, read_open_flags_resolveReadOpenFlags());
41377
+ const readHandleStat = await readHandle.stat({ bigint: true });
41378
+ const actualHash = sha256Hex(await readHandle.readFile());
41379
+ if (actualHash !== expectedHash) {
41380
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
41381
+ }
41382
+ // The content-verified destination, not the old temp inode, is now pinned.
41383
+ verificationIdentity = readHandleStat;
41384
+ // Preserve the helper's legacy numeric return facts, not the private proof.
41385
+ verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
41386
+ }
41387
+ });
41388
+ await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
41389
+ return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
41390
+ }
41391
+ finally {
41392
+ await readHandle?.close().catch(() => undefined);
41393
+ await handle?.close().catch(() => undefined);
41394
+ if (!renamed) {
41395
+ await promises_.rm(tempPath, { force: true }).catch(() => undefined);
41396
+ }
41128
41397
  }
41129
- throw new Error(`Path resolves outside ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.absolutePath)}`);
41130
- }
41131
- function pathEscapeError(params) {
41132
- return new Error(`Path escapes ${params.boundaryLabel} (${shortPath(params.rootPath)}): ${shortPath(params.absolutePath)}`);
41133
- }
41134
- function symlinkEscapeError(params) {
41135
- return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
41136
41398
  }
41137
41399
 
41138
41400
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path-policy.js
@@ -42081,56 +42343,6 @@ async function verifyAtomicWriteResult(params) {
42081
42343
  }
42082
42344
  }
42083
42345
 
42084
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/strict-file-identity.js
42085
-
42086
- function identityMismatch() {
42087
- return new errors_FsSafeError("path-mismatch", "file identity changed or could not be verified");
42088
- }
42089
- function identityCheck(expected, platform) {
42090
- const known = {};
42091
- const check = (stat) => {
42092
- let complete = true;
42093
- for (const field of ["dev", "ino"]) {
42094
- const value = stat[field];
42095
- // Numeric receipts cannot recover identity bits already lost to rounding.
42096
- if (typeof value !== "bigint")
42097
- throw identityMismatch();
42098
- if (platform === "win32" && value === 0n) {
42099
- complete = false;
42100
- }
42101
- else {
42102
- if (known[field] !== undefined && known[field] !== value)
42103
- throw identityMismatch();
42104
- known[field] = value;
42105
- }
42106
- }
42107
- return complete;
42108
- };
42109
- if (expected && !check(expected))
42110
- throw identityMismatch();
42111
- return check;
42112
- }
42113
- // Retry only unknown Windows identities, retaining every known component so a
42114
- // later observation cannot erase a definite mismatch. Never reopen the file.
42115
- async function inspectFileIdentity(inspect, expected, platform = process.platform) {
42116
- const check = identityCheck(expected, platform);
42117
- for (let attempt = 0; attempt < 2; attempt++) {
42118
- const stat = await inspect();
42119
- if (check(stat))
42120
- return stat;
42121
- }
42122
- throw identityMismatch();
42123
- }
42124
- function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
42125
- const check = identityCheck(expected, platform);
42126
- for (let attempt = 0; attempt < 2; attempt++) {
42127
- const stat = inspect();
42128
- if (check(stat))
42129
- return stat;
42130
- }
42131
- throw identityMismatch();
42132
- }
42133
-
42134
42346
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-impl.js
42135
42347
 
42136
42348
 
@@ -42161,6 +42373,7 @@ function inspectFileIdentitySync(inspect, expected, platform = process.platform)
42161
42373
 
42162
42374
 
42163
42375
 
42376
+
42164
42377
 
42165
42378
 
42166
42379
  function logWarn(message) {
@@ -42236,25 +42449,15 @@ async function openVerifiedLocalFile(filePath, options) {
42236
42449
  if (preOpenStat) {
42237
42450
  await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
42238
42451
  }
42452
+ const openFlags = options?.symlinks === "follow-within-root"
42453
+ ? OPEN_READ_FOLLOW_FLAGS
42454
+ : OPEN_READ_FLAGS;
42455
+ await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
42239
42456
  let handle;
42240
42457
  try {
42241
- const openFlags = options?.symlinks === "follow-within-root"
42242
- ? OPEN_READ_FOLLOW_FLAGS
42243
- : OPEN_READ_FLAGS;
42244
- await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
42245
- handle = await promises_.open(filePath, openFlags);
42246
- try {
42247
- await fsSafeTestHooks?.afterOpen?.(filePath, handle);
42248
- }
42249
- catch (err) {
42250
- await handle.close().catch(() => { });
42251
- throw err;
42252
- }
42458
+ handle = await promises_.open(filePath, openFlags).catch((error) => recordFileOpenFailure(path_isNotFoundPathError(error) ? fileNotFoundError() : error, filePath));
42253
42459
  }
42254
42460
  catch (err) {
42255
- if (path_isNotFoundPathError(err)) {
42256
- throw fileNotFoundError();
42257
- }
42258
42461
  if (isSymlinkOpenError(err)) {
42259
42462
  throw new errors_FsSafeError("symlink", "symlink open blocked", { cause: err });
42260
42463
  }
@@ -42265,16 +42468,35 @@ async function openVerifiedLocalFile(filePath, options) {
42265
42468
  throw err;
42266
42469
  }
42267
42470
  try {
42471
+ await fsSafeTestHooks?.afterOpen?.(filePath, handle);
42268
42472
  const stat = await handle.stat();
42269
42473
  if (!stat.isFile()) {
42270
42474
  throw new errors_FsSafeError("not-file", "not a file");
42271
42475
  }
42272
42476
  // Keep numeric Stats for the public receipt, never for identity verification.
42273
- const identity = await inspectFileIdentity(() => handle.stat({ bigint: true }), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined);
42477
+ let openedIdentity;
42478
+ const identity = await inspectFileIdentity(async () => (openedIdentity = await handle.stat({ bigint: true })), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined).catch(async (error) => {
42479
+ if ([0, 1].includes(stat.nlink)) {
42480
+ await recordPreOpenFileChange(error, handle, filePath, preOpenStat, openedIdentity);
42481
+ }
42482
+ throw error;
42483
+ });
42274
42484
  if (options?.hardlinks === "reject" && stat.nlink > 1) {
42275
42485
  throw hardlinkedPathNotAllowedError();
42276
42486
  }
42277
- await inspectFileIdentity(async () => {
42487
+ const inspectPathIdentity = async (inspect) => {
42488
+ try {
42489
+ return await inspectFileIdentity(inspect, identity);
42490
+ }
42491
+ catch (error) {
42492
+ const failure = path_isNotFoundPathError(error) ? openedPathResolutionError(fileNotFoundError()) : error;
42493
+ if (stat.nlink <= 1 && (!preOpenStat || preOpenStat.nlink <= 1n)) {
42494
+ await recordOpenedFileFailure(failure, handle, filePath, identity);
42495
+ }
42496
+ throw failure;
42497
+ }
42498
+ };
42499
+ await inspectPathIdentity(async () => {
42278
42500
  const pathStat = options?.symlinks === "follow-within-root"
42279
42501
  ? await promises_.stat(filePath, { bigint: true })
42280
42502
  : await promises_.lstat(filePath, { bigint: true });
@@ -42282,26 +42504,26 @@ async function openVerifiedLocalFile(filePath, options) {
42282
42504
  throw new errors_FsSafeError("symlink", "symlink not allowed");
42283
42505
  }
42284
42506
  return pathStat;
42285
- }, identity);
42507
+ });
42286
42508
  await fsSafeTestHooks?.afterOpenedPathIdentityCheck?.(filePath, handle);
42287
- const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath);
42288
- await inspectFileIdentity(async () => {
42509
+ const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath)
42510
+ .catch(async (error) => {
42511
+ if (stat.nlink <= 1 && (!preOpenStat || preOpenStat.nlink <= 1n)) {
42512
+ await recordOpenedFileFailure(error, handle, filePath, identity);
42513
+ }
42514
+ throw error;
42515
+ });
42516
+ await inspectPathIdentity(async () => {
42289
42517
  const realStat = await promises_.stat(realPath, { bigint: true });
42290
42518
  if (options?.hardlinks === "reject" && realStat.nlink > 1n) {
42291
42519
  throw hardlinkedPathNotAllowedError();
42292
42520
  }
42293
42521
  return realStat;
42294
- }, identity);
42522
+ });
42295
42523
  return { opened: openResult({ handle, realPath, stat }), identity };
42296
42524
  }
42297
42525
  catch (err) {
42298
42526
  await handle.close().catch(() => { });
42299
- if (err instanceof errors_FsSafeError) {
42300
- throw err;
42301
- }
42302
- if (path_isNotFoundPathError(err)) {
42303
- throw fileNotFoundError();
42304
- }
42305
42527
  throw err;
42306
42528
  }
42307
42529
  }
@@ -42530,19 +42752,10 @@ async function openFileInRoot(root, params) {
42530
42752
  rejectUnsafeDeviceReads: true,
42531
42753
  rejectSymlinks: params.symlinks !== "follow-within-root",
42532
42754
  });
42533
- let opened;
42534
- try {
42535
- ({ opened } = await openVerifiedLocalFile(resolved, {
42536
- nonBlockingRead: params.nonBlockingRead,
42537
- symlinks: params.symlinks,
42538
- }));
42539
- }
42540
- catch (err) {
42541
- if (err instanceof errors_FsSafeError) {
42542
- throw err;
42543
- }
42544
- throw err;
42545
- }
42755
+ const { opened } = await openVerifiedLocalFile(resolved, {
42756
+ nonBlockingRead: params.nonBlockingRead,
42757
+ symlinks: params.symlinks,
42758
+ });
42546
42759
  if (params.hardlinks !== "allow" && opened.stat.nlink > 1) {
42547
42760
  await opened.handle.close().catch(() => { });
42548
42761
  throw hardlinkedPathNotAllowedError();
@@ -42594,9 +42807,7 @@ function emitWriteBoundaryWarning(reason) {
42594
42807
  logWarn(`security: fs-safe write boundary warning (${reason})`);
42595
42808
  }
42596
42809
  function buildAtomicWriteTempPath(targetPath) {
42597
- const dir = external_node_path_.dirname(targetPath);
42598
- const base = external_node_path_.basename(targetPath);
42599
- return external_node_path_.join(dir, `.${base}.${process.pid}.${(0,external_node_crypto_.randomUUID)()}.tmp`);
42810
+ return external_node_path_.join(external_node_path_.dirname(targetPath), `.fs-safe-${(0,external_node_crypto_.randomUUID)()}.tmp`);
42600
42811
  }
42601
42812
  function rootWriteQueueKey(root, relativePath) {
42602
42813
  return `${root.rootReal}\0${relativePath}`;
@@ -42835,7 +43046,7 @@ async function writeFileInRoot(root, params) {
42835
43046
  await writeFileFallback(root, params);
42836
43047
  return;
42837
43048
  }
42838
- const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
43049
+ const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations, params.overwrite);
42839
43050
  await serializePathWrite(pinned.targetPath, async () => {
42840
43051
  await commitPinnedWriteInRoot(root, pinned, params);
42841
43052
  });
@@ -42957,7 +43168,7 @@ async function removePathIfIdentityUnchanged(targetPath, identity) {
42957
43168
  await promises_.rm(targetPath);
42958
43169
  });
42959
43170
  }
42960
- async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
43171
+ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations, overwrite = true) {
42961
43172
  const { rootReal, rootWithSep, resolved } = await resolveGuardedWritePathInRoot(root, {
42962
43173
  relativePath,
42963
43174
  denyMutations,
@@ -42975,22 +43186,40 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode,
42975
43186
  if (!basename || basename === "." || basename === "/") {
42976
43187
  throw new errors_FsSafeError("invalid-path", "invalid target path");
42977
43188
  }
43189
+ // Exclusive publication cannot inherit an existing file's mode. Keep type
43190
+ // and alias checks, but never open a competing owner's record just to reject it.
43191
+ if (!overwrite) {
43192
+ try {
43193
+ const existing = await promises_.stat(resolved);
43194
+ if (!existing.isFile())
43195
+ throw new errors_FsSafeError("not-file", "not a file");
43196
+ if (existing.nlink > 1)
43197
+ throw hardlinkedPathNotAllowedError();
43198
+ throw new errors_FsSafeError("already-exists", "file already exists");
43199
+ }
43200
+ catch (error) {
43201
+ if (!path_isNotFoundPathError(error))
43202
+ throw error;
43203
+ }
43204
+ }
42978
43205
  let mode = requestedMode ?? 0o600;
42979
43206
  try {
42980
- const opened = await openFileInRoot(root, {
42981
- relativePath,
42982
- hardlinks: "reject",
42983
- nonBlockingRead: true,
42984
- symlinks: "follow-within-root",
42985
- });
42986
- try {
42987
- mode = requestedMode ?? (opened.stat.mode & 0o777);
42988
- if (!path_isPathInside(rootWithSep, opened.realPath)) {
42989
- throw outsideWorkspaceError();
43207
+ if (overwrite) {
43208
+ const opened = await openFileInRoot(root, {
43209
+ relativePath,
43210
+ hardlinks: "reject",
43211
+ nonBlockingRead: true,
43212
+ symlinks: "follow-within-root",
43213
+ });
43214
+ try {
43215
+ mode = requestedMode ?? (opened.stat.mode & 0o777);
43216
+ if (!path_isPathInside(rootWithSep, opened.realPath)) {
43217
+ throw outsideWorkspaceError();
43218
+ }
43219
+ }
43220
+ finally {
43221
+ await opened.handle.close().catch(() => { });
42990
43222
  }
42991
- }
42992
- finally {
42993
- await opened.handle.close().catch(() => { });
42994
43223
  }
42995
43224
  }
42996
43225
  catch (err) {
@@ -91840,4 +92069,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
91840
92069
  // module factories are used so entry inlining is disabled
91841
92070
  // startup
91842
92071
  // Load entry module and return exports
91843
- var __webpack_exports__ = __webpack_require__(836);
92072
+ var __webpack_exports__ = __webpack_require__(3829);