@lousy-agents/cli 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.
package/dist/index.js CHANGED
@@ -3053,7 +3053,7 @@ exports.basename = (path, { windows } = {}) => {
3053
3053
 
3054
3054
 
3055
3055
  },
3056
- 8441(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
3056
+ 8853(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
3057
3057
  // NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
3058
3058
  var constructs_namespaceObject = {};
3059
3059
  __webpack_require__.r(constructs_namespaceObject);
@@ -3152,6 +3152,85 @@ function sameFileIdentityForCleanup(left, right, platform = process.platform) {
3152
3152
  return sameStatValue(left.dev, right.dev) && sameStatValue(left.ino, right.ino);
3153
3153
  }
3154
3154
 
3155
+ ;// CONCATENATED MODULE: external "node:async_hooks"
3156
+ const external_node_async_hooks_namespaceObject = __rspack_createRequire_require("node:async_hooks");
3157
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/file-observation.js
3158
+
3159
+ const active = new external_node_async_hooks_namespaceObject.AsyncLocalStorage();
3160
+ function recordFileObservationFailure(error, kind) {
3161
+ const failures = active.getStore();
3162
+ if (!failures)
3163
+ return;
3164
+ let kinds = failures.get(error);
3165
+ if (!kinds)
3166
+ failures.set(error, kinds = new Set());
3167
+ kinds.add(kind);
3168
+ }
3169
+ function isFileObservationFailure(error, kind) {
3170
+ return active.getStore()?.get(error)?.has(kind) === true;
3171
+ }
3172
+ // Each Root observation owns its receipts, including nested and concurrent reads.
3173
+ function fileObservation() {
3174
+ const failures = new Map();
3175
+ return {
3176
+ run(operation) { return active.run(failures, operation); },
3177
+ has(error, kind) { return failures.get(error)?.has(kind) === true; },
3178
+ };
3179
+ }
3180
+
3181
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/strict-file-identity.js
3182
+
3183
+
3184
+ function identityMismatch() {
3185
+ const error = new errors_FsSafeError("path-mismatch", "file identity changed or could not be verified");
3186
+ recordFileObservationFailure(error, "identity");
3187
+ return error;
3188
+ }
3189
+ function identityCheck(expected, platform) {
3190
+ const known = {};
3191
+ const check = (stat) => {
3192
+ let complete = true;
3193
+ for (const field of ["dev", "ino"]) {
3194
+ const value = stat[field];
3195
+ // Numeric receipts cannot recover identity bits already lost to rounding.
3196
+ if (typeof value !== "bigint")
3197
+ throw identityMismatch();
3198
+ if (platform === "win32" && value === 0n) {
3199
+ complete = false;
3200
+ }
3201
+ else {
3202
+ if (known[field] !== undefined && known[field] !== value)
3203
+ throw identityMismatch();
3204
+ known[field] = value;
3205
+ }
3206
+ }
3207
+ return complete;
3208
+ };
3209
+ if (expected && !check(expected))
3210
+ throw identityMismatch();
3211
+ return check;
3212
+ }
3213
+ // Retry only unknown Windows identities, retaining every known component so a
3214
+ // later observation cannot erase a definite mismatch. Never reopen the file.
3215
+ async function inspectFileIdentity(inspect, expected, platform = process.platform) {
3216
+ const check = identityCheck(expected, platform);
3217
+ for (let attempt = 0; attempt < 2; attempt++) {
3218
+ const stat = await inspect();
3219
+ if (check(stat))
3220
+ return stat;
3221
+ }
3222
+ throw identityMismatch();
3223
+ }
3224
+ function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
3225
+ const check = identityCheck(expected, platform);
3226
+ for (let attempt = 0; attempt < 2; attempt++) {
3227
+ const stat = inspect();
3228
+ if (check(stat))
3229
+ return stat;
3230
+ }
3231
+ throw identityMismatch();
3232
+ }
3233
+
3155
3234
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path.js
3156
3235
 
3157
3236
 
@@ -3366,6 +3445,7 @@ function normalizeRemovePathError(error) {
3366
3445
 
3367
3446
 
3368
3447
 
3448
+
3369
3449
  async function directory_guard_createAsyncDirectoryGuard(dir) {
3370
3450
  const stat = await promises_.lstat(dir);
3371
3451
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
@@ -3430,6 +3510,15 @@ function directory_guard_createNearestExistingSyncDirectoryGuard(rootReal, targe
3430
3510
  }
3431
3511
  return directory_guard_createSyncDirectoryGuard(root);
3432
3512
  }
3513
+ // Recovery receipts must retain every identity bit, including on Windows.
3514
+ async function inspectDirectoryIdentity(dir, expected) {
3515
+ return await inspectFileIdentity(async () => {
3516
+ const stat = await promises_.lstat(dir, { bigint: true });
3517
+ if (stat.isSymbolicLink() || !stat.isDirectory())
3518
+ throw root_errors_directoryComponentNotDirectoryError();
3519
+ return stat;
3520
+ }, expected);
3521
+ }
3433
3522
 
3434
3523
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/directory-durability.js
3435
3524
 
@@ -3871,6 +3960,8 @@ async function guardedRename(params) {
3871
3960
  ? await createNearestExistingDirectoryGuard(params.targetRoot, path.dirname(params.to))
3872
3961
  : await createAsyncDirectoryGuard(path.dirname(params.to));
3873
3962
  await withAsyncDirectoryGuards([sourceGuard, targetGuard], async () => {
3963
+ // Authority must survive all awaited guards; do not yield before rename dispatch.
3964
+ params.assertBeforeRename?.();
3874
3965
  await fs.rename(params.from, params.to);
3875
3966
  }, { verifyAfter: params.verifyAfter });
3876
3967
  }
@@ -4040,6 +4131,49 @@ function mergeDenyMutationPolicies(defaultPolicy, callPolicy) {
4040
4131
  };
4041
4132
  }
4042
4133
 
4134
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/opened-file-failure.js
4135
+
4136
+
4137
+
4138
+ function recordFileOpenFailure(error, filePath) {
4139
+ recordFileObservationFailure(error, `open:${filePath}`);
4140
+ throw error;
4141
+ }
4142
+ function openedPathResolutionError(error = new errors_FsSafeError("path-mismatch", "unable to resolve opened file path")) {
4143
+ recordFileObservationFailure(error, "resolution");
4144
+ return error;
4145
+ }
4146
+ async function recordPreOpenFileChange(error, handle, filePath, before, opened) {
4147
+ if (!isFileObservationFailure(error, "identity") || !before?.isFile() || !opened?.isFile() ||
4148
+ ![0n, 1n].includes(before.nlink) || ![0n, 1n].includes(opened.nlink))
4149
+ return;
4150
+ try {
4151
+ await inspectFileIdentity(async () => before);
4152
+ const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), opened);
4153
+ if (current.isFile() && [0n, 1n].includes(current.nlink) &&
4154
+ (before.dev !== current.dev || before.ino !== current.ino)) {
4155
+ // A stale pathname sample is not proof that its former inode was unlinked.
4156
+ recordFileObservationFailure(error, `changed:${filePath}`);
4157
+ }
4158
+ }
4159
+ catch {
4160
+ // Unknown, changed, or closed descriptor evidence never permits a discard.
4161
+ }
4162
+ }
4163
+ async function recordOpenedFileFailure(error, handle, filePath, identity) {
4164
+ if ((!isFileObservationFailure(error, "resolution") && !isFileObservationFailure(error, "identity")) ||
4165
+ !identity.isFile() || identity.nlink > 1n)
4166
+ return;
4167
+ try {
4168
+ const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), identity);
4169
+ if (current.isFile() && current.nlink === 0n)
4170
+ recordFileObservationFailure(error, `unlinked:${filePath}`);
4171
+ }
4172
+ catch {
4173
+ // A closed, changed, or unknown descriptor provides no retry evidence.
4174
+ }
4175
+ }
4176
+
4043
4177
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/opened-realpath.js
4044
4178
 
4045
4179
 
@@ -4079,6 +4213,12 @@ async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
4079
4213
  }
4080
4214
  catch (err) {
4081
4215
  if (!path_isNotFoundPathError(err)) {
4216
+ // Windows can fail here on a deleted-but-open file. Brand only this
4217
+ // resolver operation; the caller must still prove unlink on the same fd.
4218
+ if (process.platform === "win32" && err instanceof Error &&
4219
+ ["EPERM", "EBADF"].includes(err.code ?? "")) {
4220
+ throw openedPathResolutionError(err);
4221
+ }
4082
4222
  throw err;
4083
4223
  }
4084
4224
  }
@@ -4086,7 +4226,7 @@ async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
4086
4226
  if (parentResolved) {
4087
4227
  return parentResolved;
4088
4228
  }
4089
- throw new errors_FsSafeError("path-mismatch", "unable to resolve opened file path");
4229
+ throw openedPathResolutionError();
4090
4230
  }
4091
4231
  async function resolveOpenedFileRealPathFromParent(handleStat, ioPath, statOptions) {
4092
4232
  let parentReal;
@@ -4580,7 +4720,7 @@ async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuar
4580
4720
  let renamed = false;
4581
4721
  let completed = false;
4582
4722
  try {
4583
- tempName = `.${params.basename}.${(0,external_node_crypto_.randomUUID)()}.native.tmp`;
4723
+ tempName = `.fs-safe-${(0,external_node_crypto_.randomUUID)()}.tmp`;
4584
4724
  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;
4585
4725
  tempIdentity = external_node_fs_.fstatSync(tempFd);
4586
4726
  const verificationIdentity = external_node_fs_.fstatSync(tempFd, { bigint: true });
@@ -4790,7 +4930,7 @@ const UNCOMMITTED_RENAME_ERRORS = new Set([
4790
4930
  "ENAMETOOLONG", "ENOENT", "ENOSPC", "ENOSYS", "ENOTDIR", "ENOTEMPTY", "ENOTSUP",
4791
4931
  "EPERM", "EROFS", "ETXTBSY", "EXDEV",
4792
4932
  ]);
4793
- function failure(error, details) {
4933
+ function native_staged_file_failure(error, details) {
4794
4934
  const code = error instanceof errors_FsSafeError
4795
4935
  ? error.code
4796
4936
  : error?.code === "EEXIST" ? "already-exists" : "helper-failed";
@@ -4922,10 +5062,10 @@ class NativeStagedFile {
4922
5062
  catch (error) {
4923
5063
  const { receipt: cleanup, error: cleanupError } = this.#finalize();
4924
5064
  if (cleanupError) {
4925
- throw failure(new AggregateError([error, cleanupError], "preparation and cleanup failed"), { phase: "prepare", publication: cleanup.publication, cleanup });
5065
+ throw native_staged_file_failure(new AggregateError([error, cleanupError], "preparation and cleanup failed"), { phase: "prepare", publication: cleanup.publication, cleanup });
4926
5066
  }
4927
5067
  if (cleanup.status === "preserved") {
4928
- throw failure(error, { phase: "prepare", publication: cleanup.publication, cleanup });
5068
+ throw native_staged_file_failure(error, { phase: "prepare", publication: cleanup.publication, cleanup });
4929
5069
  }
4930
5070
  throw error;
4931
5071
  }
@@ -4984,7 +5124,7 @@ class NativeStagedFile {
4984
5124
  const publication = this.#state.status === "closed"
4985
5125
  ? this.#state.receipt.publication
4986
5126
  : this.#state.publication;
4987
- throw failure(error, { phase: "publish", publication });
5127
+ throw native_staged_file_failure(error, { phase: "publish", publication });
4988
5128
  }
4989
5129
  }
4990
5130
  #finalize() {
@@ -5025,7 +5165,7 @@ class NativeStagedFile {
5025
5165
  status: outcome,
5026
5166
  resources,
5027
5167
  });
5028
- const error = errors.length ? failure(errors.length === 1 ? errors[0] : new AggregateError(errors, "staged cleanup failed"), { phase: "cleanup", publication: state.publication, cleanup: receipt }) : undefined;
5168
+ 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;
5029
5169
  this.#state = { status: "closed", receipt, error };
5030
5170
  return this.#state;
5031
5171
  }
@@ -5334,438 +5474,1184 @@ function bounded_read_readFileDescriptorBoundedSync(fd, maxBytes) {
5334
5474
  }
5335
5475
  }
5336
5476
 
5337
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
5338
- let test_hooks_fsSafeTestHooks;
5339
- function allowFsSafeTestHooks() {
5340
- return false || process.env.VITEST === "true";
5341
- }
5342
- function getFsSafeTestHooks() {
5343
- return test_hooks_fsSafeTestHooks;
5344
- }
5345
- function __setFsSafeTestHooksForTest(hooks) {
5346
- if (hooks && !allowFsSafeTestHooks()) {
5347
- throw new Error("__setFsSafeTestHooksForTest is only available in tests");
5348
- }
5349
- test_hooks_fsSafeTestHooks = hooks;
5477
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/error-detail.js
5478
+ const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
5479
+ function formatErrorDetail(value) {
5480
+ return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
5350
5481
  }
5351
5482
 
5352
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
5353
-
5354
-
5355
-
5483
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-symlink.js
5356
5484
 
5357
5485
 
5358
5486
 
5359
5487
 
5360
5488
 
5361
5489
 
5362
- const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
5363
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
5364
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
5365
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
5366
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN = new RegExp(`\\n(${SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX}[ \\t]{${SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS}})\\n$`);
5367
- function createSidecarLockOwnershipToken() {
5368
- let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
5369
- for (const byte of (0,external_node_crypto_.randomBytes)(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
5370
- for (let bit = 7; bit >= 0; bit -= 1) {
5371
- token += byte & (1 << bit) ? "\t" : " ";
5372
- }
5373
- }
5374
- return token;
5375
- }
5376
- function readSidecarLockOwnershipToken(raw) {
5377
- return SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN.exec(raw)?.[1];
5378
- }
5379
- function serializeSidecarLockPayload(payload) {
5380
- const ownershipToken = createSidecarLockOwnershipToken();
5381
- return {
5382
- raw: `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`,
5383
- ownershipToken,
5384
- };
5385
- }
5386
- function relativeSidecarLockPath(lockRoot, lockPath) {
5387
- const resolved = external_node_path_.resolve(lockPath);
5388
- const lexicalRelative = external_node_path_.relative(lockRoot.rootDir, resolved);
5389
- const relative = lexicalRelative !== ".." &&
5390
- !lexicalRelative.startsWith(`..${external_node_path_.sep}`) &&
5391
- !external_node_path_.isAbsolute(lexicalRelative)
5392
- ? lexicalRelative
5393
- : external_node_path_.relative(lockRoot.rootReal, resolved);
5394
- if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relative)) {
5395
- throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
5490
+ function normalizeSymlinkResolutionError(error) {
5491
+ if (isSymlinkOpenError(error)) {
5492
+ throw new errors_FsSafeError("symlink", "symlink path could not be resolved", {
5493
+ cause: error instanceof Error ? error : undefined,
5494
+ });
5396
5495
  }
5397
- return relative.split(external_node_path_.sep).join(external_node_path_.posix.sep);
5496
+ if (!path_isNotFoundPathError(error))
5497
+ throw error;
5398
5498
  }
5399
- function parseSidecarLockPayload(raw, parser) {
5400
- if (parser) {
5401
- return parser(raw);
5402
- }
5499
+ async function resolveSymlinkHopPath(symlinkPath) {
5403
5500
  try {
5404
- const parsed = JSON.parse(raw);
5405
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
5501
+ return external_node_path_.resolve(await promises_.realpath(symlinkPath));
5406
5502
  }
5407
- catch {
5408
- return null;
5503
+ catch (error) {
5504
+ normalizeSymlinkResolutionError(error);
5505
+ const linkTarget = await promises_.readlink(symlinkPath);
5506
+ return resolvePathViaExistingAncestor(external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget));
5409
5507
  }
5410
5508
  }
5411
- async function readSidecarLockSnapshot(lockPath, options = {}) {
5412
- let handle;
5509
+ function root_path_symlink_resolveSymlinkHopPathSync(symlinkPath) {
5413
5510
  try {
5414
- if (options.lockRoot) {
5415
- const lockRoot = options.lockRoot;
5416
- const relative = relativeSidecarLockPath(lockRoot, lockPath);
5417
- const opened = await lockRoot.open(relative).catch(async (error) => {
5418
- if (error instanceof errors_FsSafeError && error.code === "path-mismatch") {
5419
- // An owner can unlink after open. Prove absence through the same root;
5420
- // acquisition retries create-only, so a later replacement stays guarded.
5421
- try {
5422
- await lockRoot.stat(relative);
5423
- }
5424
- catch (probeError) {
5425
- if (probeError instanceof errors_FsSafeError && probeError.code === "not-found") {
5426
- return null;
5427
- }
5428
- }
5429
- }
5430
- throw error;
5431
- });
5432
- if (!opened)
5433
- return null;
5434
- try {
5435
- const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
5436
- return {
5437
- raw,
5438
- payload: parseSidecarLockPayload(raw, options.parsePayload),
5439
- stat: opened.stat,
5440
- };
5441
- }
5442
- finally {
5443
- await opened.handle.close().catch(() => undefined);
5444
- }
5445
- }
5446
- const before = await promises_.lstat(lockPath);
5447
- if (!before.isFile() || before.isSymbolicLink()) {
5448
- if (options.rejectNonFile) {
5449
- throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
5450
- }
5451
- return null;
5452
- }
5453
- await getFsSafeTestHooks()?.beforeSidecarLockSnapshotOpen?.(lockPath);
5454
- const noFollow = process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
5455
- ? external_node_fs_.constants.O_NOFOLLOW
5456
- : 0;
5457
- try {
5458
- handle = await promises_.open(lockPath, external_node_fs_.constants.O_RDONLY |
5459
- noFollow |
5460
- (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0));
5461
- }
5462
- catch (error) {
5463
- if (options.rejectNonFile && error.code === "ELOOP") {
5464
- throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`, {
5465
- cause: error,
5466
- });
5467
- }
5468
- throw error;
5469
- }
5470
- const opened = await handle.stat();
5471
- if (!opened.isFile()) {
5472
- if (options.rejectNonFile) {
5473
- throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
5474
- }
5475
- return null;
5476
- }
5477
- if (!options.allowDescriptorIdentityDrift && !file_identity_sameFileIdentity(before, opened))
5478
- return null;
5479
- const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
5480
- const after = await promises_.lstat(lockPath);
5481
- if (!after.isFile() || !file_identity_sameFileIdentity(before, after))
5482
- return null;
5483
- return { raw, payload: parseSidecarLockPayload(raw, options.parsePayload), stat: after };
5484
- }
5485
- catch (err) {
5486
- if (err.code === "ENOENT" ||
5487
- (err instanceof errors_FsSafeError && err.code === "not-found")) {
5488
- return null;
5489
- }
5490
- throw err;
5511
+ return path.resolve(fs.realpathSync(symlinkPath));
5491
5512
  }
5492
- finally {
5493
- await handle?.close().catch(() => undefined);
5513
+ catch (error) {
5514
+ normalizeSymlinkResolutionError(error);
5515
+ const linkTarget = fs.readlinkSync(symlinkPath);
5516
+ return resolvePathViaExistingAncestorSync(path.resolve(path.dirname(symlinkPath), linkTarget));
5494
5517
  }
5495
5518
  }
5496
- function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}) {
5497
- let fd;
5498
- try {
5499
- const before = fsSync.lstatSync(lockPath);
5500
- if (!before.isFile() || before.isSymbolicLink()) {
5501
- if (options.rejectNonFile) {
5502
- throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
5503
- }
5504
- return null;
5505
- }
5506
- fd = fsSync.openSync(lockPath, resolveReadOpenFlags());
5507
- const opened = fsSync.fstatSync(fd);
5508
- if (!opened.isFile()) {
5509
- if (options.rejectNonFile) {
5510
- throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
5511
- }
5512
- return null;
5513
- }
5514
- const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
5515
- const after = fsSync.lstatSync(lockPath);
5516
- if (!sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
5517
- return null;
5518
- return {
5519
- raw,
5520
- payload: parseSidecarLockPayload(raw, parsePayload),
5521
- stat: after,
5522
- ownershipToken: readSidecarLockOwnershipToken(raw),
5523
- };
5519
+
5520
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/safe-path-segment.js
5521
+
5522
+ const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
5523
+ const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
5524
+ // Windows treats "C:name" as relative to the drive's current directory even
5525
+ // though path.win32.isAbsolute() reports false.
5526
+ const DRIVE_RELATIVE_PREFIX = /^[A-Za-z]:(?![\\/])/;
5527
+ const HYPHEN_CHAR_CODE = 0x2d;
5528
+ function safe_path_segment_isDriveRelativePath(value) {
5529
+ return DRIVE_RELATIVE_PREFIX.test(value);
5530
+ }
5531
+ function assertNoDriveRelativePathSegments(value, label) {
5532
+ if (value.split("/").some(safe_path_segment_isDriveRelativePath)) {
5533
+ throw new errors_FsSafeError("invalid-path", `${label} must not contain a drive letter`);
5524
5534
  }
5525
- catch (error) {
5526
- if (error.code === "ENOENT")
5527
- return null;
5528
- throw error;
5535
+ return value;
5536
+ }
5537
+ function trimHyphenEdges(value) {
5538
+ let start = 0;
5539
+ let end = value.length;
5540
+ while (start < end && value.charCodeAt(start) === HYPHEN_CHAR_CODE) {
5541
+ start += 1;
5529
5542
  }
5530
- finally {
5531
- if (fd !== undefined)
5532
- fsSync.closeSync(fd);
5543
+ while (end > start && value.charCodeAt(end - 1) === HYPHEN_CHAR_CODE) {
5544
+ end -= 1;
5533
5545
  }
5546
+ return start === 0 && end === value.length ? value : value.slice(start, end);
5534
5547
  }
5535
- function removeSidecarLockIfUnchangedSync(lockPath, observed) {
5536
- const current = readSidecarLockSnapshotSync(lockPath);
5537
- if (!current || !sidecarLockSnapshotMatches(current, observed))
5538
- return false;
5539
- fsSync.rmSync(lockPath);
5540
- return true;
5548
+ function isSafePathSegment(segment, options = {}) {
5549
+ return (segment !== "" &&
5550
+ segment !== "." &&
5551
+ segment !== ".." &&
5552
+ !segment.includes("/") &&
5553
+ !segment.includes("\\") &&
5554
+ !segment.includes("\0") &&
5555
+ (options.allowDotPrefix === true || !segment.startsWith(".")) &&
5556
+ (options.allowDotPrefix === true
5557
+ ? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
5558
+ : SAFE_PATH_SEGMENT_PATTERN.test(segment)));
5541
5559
  }
5542
- function sidecarLockSnapshotMatches(current, observed) {
5543
- if (observed.ownershipToken !== undefined) {
5544
- return (current.stat?.isFile() === true &&
5545
- current.raw !== undefined &&
5546
- observed.raw !== undefined &&
5547
- readSidecarLockOwnershipToken(current.raw) === observed.ownershipToken &&
5548
- readSidecarLockOwnershipToken(observed.raw) === observed.ownershipToken &&
5549
- current.raw === observed.raw);
5550
- }
5551
- if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
5552
- return false;
5560
+ function assertSafePathSegment(segment, options = {}) {
5561
+ // Validate the exact value callers will later join into paths; trimming here
5562
+ // would let whitespace-padded ids pass and then be used verbatim.
5563
+ if (!isSafePathSegment(segment, options)) {
5564
+ throw new FsSafeError("invalid-path", `${options.label ?? "path segment"} must be a safe path segment`);
5553
5565
  }
5554
- if (observed.raw !== undefined) {
5555
- return current.raw === observed.raw;
5566
+ return segment;
5567
+ }
5568
+ function sanitizeSafePathSegment(value, fallback, options = {}) {
5569
+ const sanitized = value
5570
+ .trim()
5571
+ .replace(/[\\/]+/g, "-")
5572
+ .replace(/\0/g, "")
5573
+ .replace(/[^A-Za-z0-9._-]+/g, "-");
5574
+ const trimmed = trimHyphenEdges(sanitized);
5575
+ if (isSafePathSegment(trimmed, options)) {
5576
+ return trimmed;
5556
5577
  }
5557
- return observed.stat !== undefined && current.stat !== undefined;
5578
+ return assertSafePathSegment(fallback, { ...options, label: "fallback path segment" });
5558
5579
  }
5559
- async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
5560
- const current = await readSidecarLockSnapshot(lockPath, {
5580
+ function assertSafePathPrefix(prefix, options = {}) {
5581
+ // Prefixes are often derived from safe filenames. Normalize harmless
5582
+ // filename characters first, but still reject real path-control bytes.
5583
+ if (prefix.includes("/") || prefix.includes("\\") || prefix.includes("\0")) {
5584
+ return assertSafePathSegment(prefix, {
5585
+ allowDotPrefix: true,
5586
+ ...options,
5587
+ label: options.label ?? "path prefix",
5588
+ });
5589
+ }
5590
+ return assertSafePathSegment(prefix.replace(/[^A-Za-z0-9._-]+/g, "-"), {
5591
+ allowDotPrefix: true,
5561
5592
  ...options,
5562
- allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
5593
+ label: options.label ?? "path prefix",
5563
5594
  });
5564
- if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
5565
- return false;
5566
- }
5567
- if (options.lockRoot) {
5568
- await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath));
5595
+ }
5596
+
5597
+ // EXTERNAL MODULE: external "node:os"
5598
+ var external_node_os_ = __webpack_require__(8161);
5599
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/short-path.js
5600
+
5601
+
5602
+ function shortPath(value) {
5603
+ const home = external_node_os_.homedir();
5604
+ const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
5605
+ return formatErrorDetail(shortened);
5606
+ }
5607
+
5608
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
5609
+
5610
+
5611
+
5612
+
5613
+
5614
+
5615
+
5616
+
5617
+
5618
+
5619
+
5620
+ const ROOT_PATH_ALIAS_POLICIES = {
5621
+ strict: Object.freeze({
5622
+ allowFinalSymlinkForUnlink: false,
5623
+ allowFinalHardlinkForUnlink: false,
5624
+ }),
5625
+ unlinkTarget: Object.freeze({
5626
+ allowFinalSymlinkForUnlink: true,
5627
+ allowFinalHardlinkForUnlink: true,
5628
+ }),
5629
+ };
5630
+ async function resolveRootPath(params) {
5631
+ try {
5632
+ return await resolveRootPathInternal(params);
5569
5633
  }
5570
- else {
5571
- await promises_.rm(lockPath, { force: true });
5634
+ catch (error) {
5635
+ throw sanitizeRootPathError(error);
5572
5636
  }
5573
- return true;
5574
5637
  }
5575
- async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
5576
- const current = await readSidecarLockSnapshot(lockPath, {
5577
- ...options,
5578
- allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
5638
+ async function resolveRootPathInternal(params) {
5639
+ assertValidRootPathInputs(params);
5640
+ const rootPath = external_node_path_.resolve(params.rootPath);
5641
+ const absolutePath = external_node_path_.resolve(params.absolutePath);
5642
+ const rootCanonicalPath = params.rootCanonicalPath
5643
+ ? external_node_path_.resolve(params.rootCanonicalPath)
5644
+ : await resolvePathViaExistingAncestor(rootPath);
5645
+ const context = createBoundaryResolutionContext({
5646
+ resolveParams: params,
5647
+ rootPath,
5648
+ absolutePath,
5649
+ rootCanonicalPath,
5650
+ outsideLexicalCanonicalPath: await resolveOutsideLexicalCanonicalPathAsync({
5651
+ rootPath,
5652
+ absolutePath,
5653
+ }),
5654
+ });
5655
+ const outsideResult = await resolveOutsideRootPathAsync({
5656
+ boundaryLabel: params.boundaryLabel,
5657
+ context,
5658
+ });
5659
+ if (outsideResult) {
5660
+ return outsideResult;
5661
+ }
5662
+ return resolveRootPathLexicalAsync({
5663
+ params,
5664
+ absolutePath: context.absolutePath,
5665
+ rootPath: context.rootPath,
5666
+ rootCanonicalPath: context.rootCanonicalPath,
5579
5667
  });
5580
- return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
5581
5668
  }
5582
- async function sidecarReclaimGuardExists(pathname) {
5669
+ function resolveRootPathSync(params) {
5583
5670
  try {
5584
- await promises_.lstat(pathname);
5585
- return true;
5671
+ return resolveRootPathSyncInternal(params);
5586
5672
  }
5587
- catch (err) {
5588
- if (err.code === "ENOENT") {
5589
- return false;
5590
- }
5591
- throw err;
5673
+ catch (error) {
5674
+ throw sanitizeRootPathError(error);
5592
5675
  }
5593
5676
  }
5594
- async function tryAcquireSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
5595
- try {
5596
- await promises_.mkdir(reclaimGuardPath);
5597
- reclaimGuards.add(reclaimGuardPath);
5598
- return true;
5677
+ function resolveRootPathSyncInternal(params) {
5678
+ assertValidRootPathInputs(params);
5679
+ const rootPath = path.resolve(params.rootPath);
5680
+ const absolutePath = path.resolve(params.absolutePath);
5681
+ const rootCanonicalPath = params.rootCanonicalPath
5682
+ ? path.resolve(params.rootCanonicalPath)
5683
+ : resolvePathViaExistingAncestorSync(rootPath);
5684
+ const context = createBoundaryResolutionContext({
5685
+ resolveParams: params,
5686
+ rootPath,
5687
+ absolutePath,
5688
+ rootCanonicalPath,
5689
+ outsideLexicalCanonicalPath: resolveOutsideLexicalCanonicalPathSync({
5690
+ rootPath,
5691
+ absolutePath,
5692
+ }),
5693
+ });
5694
+ const outsideResult = resolveOutsideRootPathSync({
5695
+ boundaryLabel: params.boundaryLabel,
5696
+ context,
5697
+ });
5698
+ if (outsideResult) {
5699
+ return outsideResult;
5599
5700
  }
5600
- catch (err) {
5601
- if (err.code === "EEXIST") {
5602
- return false;
5603
- }
5604
- throw err;
5701
+ return resolveRootPathLexicalSync({
5702
+ params,
5703
+ absolutePath: context.absolutePath,
5704
+ rootPath: context.rootPath,
5705
+ rootCanonicalPath: context.rootCanonicalPath,
5706
+ });
5707
+ }
5708
+ function sanitizeRootPathError(error) {
5709
+ if (error instanceof Error) {
5710
+ error.message = formatErrorDetail(error.message);
5605
5711
  }
5712
+ return error;
5606
5713
  }
5607
- async function releaseSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
5608
- await promises_.rmdir(reclaimGuardPath);
5609
- reclaimGuards.delete(reclaimGuardPath);
5714
+ function assertValidRootPathInputs(params) {
5715
+ path_assertNoNulPathInput(params.rootPath, "root path contains a NUL byte");
5716
+ path_assertNoNulPathInput(params.absolutePath, "absolute path contains a NUL byte");
5717
+ assertNoEmbeddedDriveRelativeSegment(params.rootPath, "root path");
5718
+ assertNoEmbeddedDriveRelativeSegment(params.absolutePath, "absolute path");
5719
+ if (params.rootCanonicalPath !== undefined) {
5720
+ path_assertNoNulPathInput(params.rootCanonicalPath, "canonical root path contains a NUL byte");
5721
+ assertNoEmbeddedDriveRelativeSegment(params.rootCanonicalPath, "canonical root path");
5722
+ }
5610
5723
  }
5611
- async function removeStaleSidecarLockIfAllowed(params) {
5612
- if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
5613
- return "not-approved";
5724
+ function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
5725
+ if (process.platform !== "win32") {
5726
+ return;
5614
5727
  }
5615
- const ioOptions = { lockRoot: params.lockRoot, parsePayload: params.parsePayload };
5616
- if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
5617
- return "changed";
5728
+ const root = external_node_path_.parse(filePath).root;
5729
+ assertNoDriveRelativePathSegments(filePath.slice(root.length).replaceAll("\\", "/"), label);
5730
+ }
5731
+ function isPromiseLike(value) {
5732
+ return Boolean(value &&
5733
+ (typeof value === "object" || typeof value === "function") &&
5734
+ "then" in value &&
5735
+ typeof value.then === "function");
5736
+ }
5737
+ function createLexicalTraversalState(params) {
5738
+ const rawAbsolutePath = params.params.absolutePath;
5739
+ const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
5740
+ const relative = rawRelativePath ?? external_node_path_.relative(params.rootPath, params.absolutePath);
5741
+ return {
5742
+ segments: splitTraversalSegments(relative),
5743
+ allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
5744
+ canonicalCursor: params.rootCanonicalPath,
5745
+ lexicalCursor: params.rootPath,
5746
+ preserveFinalSymlink: false,
5747
+ };
5748
+ }
5749
+ function createLexicalTraversalContext(params) {
5750
+ return {
5751
+ state: createLexicalTraversalState(params),
5752
+ resolveParams: params.params,
5753
+ rootPath: params.rootPath,
5754
+ rootCanonicalPath: params.rootCanonicalPath,
5755
+ absolutePath: params.absolutePath,
5756
+ };
5757
+ }
5758
+ function splitTraversalSegments(value) {
5759
+ return value
5760
+ .split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
5761
+ .filter((segment) => Boolean(segment) && segment !== ".");
5762
+ }
5763
+ function rawPathRelativeToRoot(rootPath, candidatePath) {
5764
+ if (!external_node_path_.isAbsolute(candidatePath)) {
5765
+ return undefined;
5618
5766
  }
5619
- if (!(await params.shouldRemoveStaleLock({
5620
- lockPath: params.lockPath,
5621
- normalizedTargetPath: params.normalizedTargetPath,
5622
- raw: params.snapshot.raw,
5623
- payload: params.snapshot.payload,
5624
- }))) {
5625
- return "not-approved";
5767
+ const root = external_node_path_.resolve(rootPath);
5768
+ const candidate = process.platform === "win32"
5769
+ ? candidatePath.replaceAll("/", external_node_path_.sep)
5770
+ : candidatePath;
5771
+ if (candidate === root) {
5772
+ return "";
5626
5773
  }
5627
- if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
5628
- return "changed";
5774
+ const rootWithSep = root.endsWith(external_node_path_.sep) ? root : `${root}${external_node_path_.sep}`;
5775
+ const candidatePrefix = candidate.slice(0, rootWithSep.length);
5776
+ const prefixMatches = process.platform === "win32"
5777
+ ? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
5778
+ : candidatePrefix === rootWithSep;
5779
+ return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
5780
+ }
5781
+ function assertLexicalCursorInsideBoundary(context, candidatePath) {
5782
+ assertInsideBoundary({
5783
+ boundaryLabel: context.resolveParams.boundaryLabel,
5784
+ rootCanonicalPath: context.rootCanonicalPath,
5785
+ candidatePath,
5786
+ absolutePath: context.absolutePath,
5787
+ });
5788
+ }
5789
+ function applyMissingSuffixToCanonicalCursor(context, missingFromIndex) {
5790
+ const missingSuffix = context.state.segments.slice(missingFromIndex);
5791
+ for (const segment of missingSuffix) {
5792
+ advanceCanonicalCursorForSegment(context, segment);
5793
+ }
5794
+ }
5795
+ function advanceCanonicalCursorForSegment(context, segment) {
5796
+ context.state.canonicalCursor = external_node_path_.resolve(context.state.canonicalCursor, segment);
5797
+ assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
5798
+ }
5799
+ function finalizeLexicalResolution(context, kind) {
5800
+ assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
5801
+ return buildResolvedRootPath({
5802
+ absolutePath: context.absolutePath,
5803
+ canonicalPath: context.state.canonicalCursor,
5804
+ rootPath: context.rootPath,
5805
+ rootCanonicalPath: context.rootCanonicalPath,
5806
+ kind,
5807
+ });
5808
+ }
5809
+ function handleLexicalLstatFailure(context, error, missingFromIndex) {
5810
+ if (!path_isNotFoundPathError(error)) {
5811
+ return false;
5812
+ }
5813
+ applyMissingSuffixToCanonicalCursor(context, missingFromIndex);
5814
+ return true;
5815
+ }
5816
+ function handleLexicalStatReadFailure(context, error, missingFromIndex) {
5817
+ if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
5818
+ return null;
5819
+ }
5820
+ throw error;
5821
+ }
5822
+ function handleLexicalStatDisposition(context, params) {
5823
+ if (!params.isSymbolicLink) {
5824
+ advanceCanonicalCursorForSegment(context, params.segment);
5825
+ return "continue";
5826
+ }
5827
+ if (context.resolveParams.rejectSymlinks === true && params.isLast) {
5828
+ throw new errors_FsSafeError("symlink", "symlink path component not allowed");
5829
+ }
5830
+ if (context.state.allowFinalSymlink && params.isLast) {
5831
+ context.state.preserveFinalSymlink = true;
5832
+ advanceCanonicalCursorForSegment(context, params.segment);
5833
+ return "break";
5834
+ }
5835
+ return "resolve-link";
5836
+ }
5837
+ function applyResolvedSymlinkHop(context, linkCanonical) {
5838
+ if (!path_isPathInside(context.rootCanonicalPath, linkCanonical)) {
5839
+ throw symlinkEscapeError({
5840
+ boundaryLabel: context.resolveParams.boundaryLabel,
5841
+ rootCanonicalPath: context.rootCanonicalPath,
5842
+ symlinkPath: context.state.lexicalCursor,
5843
+ });
5629
5844
  }
5845
+ context.state.canonicalCursor = linkCanonical;
5846
+ context.state.lexicalCursor = linkCanonical;
5847
+ }
5848
+ function readLexicalStat(context, params) {
5630
5849
  try {
5631
- if (params.lockRoot) {
5632
- await params.lockRoot.remove(relativeSidecarLockPath(params.lockRoot, params.lockPath));
5633
- }
5634
- else {
5635
- await promises_.rm(params.lockPath);
5850
+ const stat = params.read(context.state.lexicalCursor);
5851
+ if (isPromiseLike(stat)) {
5852
+ return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
5636
5853
  }
5637
- return "removed";
5854
+ return stat;
5638
5855
  }
5639
- catch (err) {
5640
- if (err.code === "ENOENT") {
5641
- return "changed";
5856
+ catch (error) {
5857
+ return handleLexicalStatReadFailure(context, error, params.missingFromIndex);
5858
+ }
5859
+ }
5860
+ function resolveAndApplySymlinkHop(context, params) {
5861
+ const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
5862
+ if (isPromiseLike(linkCanonical)) {
5863
+ return Promise.resolve(linkCanonical).then((value) => {
5864
+ applyResolvedSymlinkHop(context, value);
5865
+ });
5866
+ }
5867
+ applyResolvedSymlinkHop(context, linkCanonical);
5868
+ }
5869
+ function applyParentTraversalStep(context) {
5870
+ context.state.lexicalCursor = external_node_path_.resolve(context.state.lexicalCursor, "..");
5871
+ advanceCanonicalCursorForSegment(context, "..");
5872
+ }
5873
+ function* iterateLexicalTraversal(state) {
5874
+ for (let idx = 0; idx < state.segments.length; idx += 1) {
5875
+ const segment = state.segments[idx] ?? "";
5876
+ const isLast = idx === state.segments.length - 1;
5877
+ yield { idx, segment, isLast };
5878
+ }
5879
+ }
5880
+ async function resolveRootPathLexicalAsync(params) {
5881
+ const context = createLexicalTraversalContext(params);
5882
+ const { state } = context;
5883
+ for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
5884
+ if (segment === "..") {
5885
+ applyParentTraversalStep(context);
5886
+ continue;
5887
+ }
5888
+ state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
5889
+ const stat = await readLexicalStat(context, {
5890
+ missingFromIndex: idx,
5891
+ read: (cursor) => promises_.lstat(cursor),
5892
+ });
5893
+ if (!stat) {
5894
+ break;
5895
+ }
5896
+ const disposition = handleLexicalStatDisposition(context, {
5897
+ isSymbolicLink: stat.isSymbolicLink(),
5898
+ segment,
5899
+ isLast,
5900
+ });
5901
+ if (disposition === "continue") {
5902
+ continue;
5903
+ }
5904
+ if (disposition === "break") {
5905
+ break;
5906
+ }
5907
+ await resolveAndApplySymlinkHop(context, {
5908
+ resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
5909
+ });
5910
+ if (context.resolveParams.rejectSymlinks === true) {
5911
+ throw new errors_FsSafeError("symlink", "symlink path component not allowed");
5642
5912
  }
5643
- throw err;
5644
5913
  }
5914
+ const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
5915
+ return finalizeLexicalResolution(context, kind);
5645
5916
  }
5646
-
5647
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
5648
-
5649
- function assertFiniteNonNegative(value, label) {
5650
- if (!Number.isFinite(value) || value < 0) {
5651
- throw new RangeError(`${label} must be a finite non-negative number`);
5917
+ function resolveRootPathLexicalSync(params) {
5918
+ const context = createLexicalTraversalContext(params);
5919
+ const { state } = context;
5920
+ for (let idx = 0; idx < state.segments.length; idx += 1) {
5921
+ const segment = state.segments[idx] ?? "";
5922
+ const isLast = idx === state.segments.length - 1;
5923
+ if (segment === "..") {
5924
+ applyParentTraversalStep(context);
5925
+ continue;
5926
+ }
5927
+ state.lexicalCursor = path.join(state.lexicalCursor, segment);
5928
+ const maybeStat = readLexicalStat(context, {
5929
+ missingFromIndex: idx,
5930
+ read: (cursor) => fs.lstatSync(cursor),
5931
+ });
5932
+ if (isPromiseLike(maybeStat)) {
5933
+ throw new Error("Unexpected async lexical stat");
5934
+ }
5935
+ const stat = maybeStat;
5936
+ if (!stat) {
5937
+ break;
5938
+ }
5939
+ const disposition = handleLexicalStatDisposition(context, {
5940
+ isSymbolicLink: stat.isSymbolicLink(),
5941
+ segment,
5942
+ isLast,
5943
+ });
5944
+ if (disposition === "continue") {
5945
+ continue;
5946
+ }
5947
+ if (disposition === "break") {
5948
+ break;
5949
+ }
5950
+ const maybeApplied = resolveAndApplySymlinkHop(context, {
5951
+ resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
5952
+ });
5953
+ if (isPromiseLike(maybeApplied)) {
5954
+ throw new Error("Unexpected async symlink resolution");
5955
+ }
5956
+ if (context.resolveParams.rejectSymlinks === true) {
5957
+ throw new FsSafeError("symlink", "symlink path component not allowed");
5958
+ }
5652
5959
  }
5960
+ const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
5961
+ return finalizeLexicalResolution(context, kind);
5653
5962
  }
5654
- function validateSidecarLockRetryOptions(retry) {
5655
- if (retry.retries !== undefined && (!Number.isSafeInteger(retry.retries) || retry.retries < 0)) {
5656
- throw new RangeError("lock retry.retries must be a non-negative safe integer");
5963
+ function resolveCanonicalOutsideLexicalPath(params) {
5964
+ return params.outsideLexicalCanonicalPath ?? params.absolutePath;
5965
+ }
5966
+ function createBoundaryResolutionContext(params) {
5967
+ const lexicalInside = path_isPathInside(params.rootPath, params.absolutePath);
5968
+ const canonicalOutsideLexicalPath = resolveCanonicalOutsideLexicalPath({
5969
+ absolutePath: params.absolutePath,
5970
+ outsideLexicalCanonicalPath: params.outsideLexicalCanonicalPath,
5971
+ });
5972
+ assertLexicalBoundaryOrCanonicalAlias({
5973
+ skipLexicalRootCheck: params.resolveParams.skipLexicalRootCheck,
5974
+ lexicalInside,
5975
+ canonicalOutsideLexicalPath,
5976
+ rootCanonicalPath: params.rootCanonicalPath,
5977
+ boundaryLabel: params.resolveParams.boundaryLabel,
5978
+ rootPath: params.rootPath,
5979
+ absolutePath: params.absolutePath,
5980
+ });
5981
+ return {
5982
+ rootPath: params.rootPath,
5983
+ absolutePath: params.absolutePath,
5984
+ rootCanonicalPath: params.rootCanonicalPath,
5985
+ lexicalInside,
5986
+ canonicalOutsideLexicalPath,
5987
+ };
5988
+ }
5989
+ async function resolveOutsideRootPathAsync(params) {
5990
+ if (params.context.lexicalInside) {
5991
+ return null;
5657
5992
  }
5658
- if (retry.factor !== undefined)
5659
- assertFiniteNonNegative(retry.factor, "lock retry.factor");
5660
- if (retry.minTimeout !== undefined) {
5661
- assertFiniteNonNegative(retry.minTimeout, "lock retry.minTimeout");
5993
+ const kind = await getPathKind(params.context.absolutePath, false);
5994
+ return buildOutsideRootPathFromContext({
5995
+ boundaryLabel: params.boundaryLabel,
5996
+ context: params.context,
5997
+ kind,
5998
+ });
5999
+ }
6000
+ function resolveOutsideRootPathSync(params) {
6001
+ if (params.context.lexicalInside) {
6002
+ return null;
5662
6003
  }
5663
- if (retry.maxTimeout !== undefined) {
5664
- assertFiniteNonNegative(retry.maxTimeout, "lock retry.maxTimeout");
6004
+ const kind = getPathKindSync(params.context.absolutePath, false);
6005
+ return buildOutsideRootPathFromContext({
6006
+ boundaryLabel: params.boundaryLabel,
6007
+ context: params.context,
6008
+ kind,
6009
+ });
6010
+ }
6011
+ function buildOutsideRootPathFromContext(params) {
6012
+ return buildOutsideLexicalRootPath({
6013
+ boundaryLabel: params.boundaryLabel,
6014
+ rootCanonicalPath: params.context.rootCanonicalPath,
6015
+ absolutePath: params.context.absolutePath,
6016
+ canonicalOutsideLexicalPath: params.context.canonicalOutsideLexicalPath,
6017
+ rootPath: params.context.rootPath,
6018
+ kind: params.kind,
6019
+ });
6020
+ }
6021
+ async function resolveOutsideLexicalCanonicalPathAsync(params) {
6022
+ if (path_isPathInside(params.rootPath, params.absolutePath)) {
6023
+ return undefined;
5665
6024
  }
5666
- if (retry.minTimeout !== undefined &&
5667
- retry.maxTimeout !== undefined &&
5668
- retry.minTimeout > retry.maxTimeout) {
5669
- throw new RangeError("lock retry.minTimeout must not exceed retry.maxTimeout");
6025
+ return await resolvePathViaExistingAncestor(params.absolutePath);
6026
+ }
6027
+ function resolveOutsideLexicalCanonicalPathSync(params) {
6028
+ if (isPathInside(params.rootPath, params.absolutePath)) {
6029
+ return undefined;
5670
6030
  }
6031
+ return resolvePathViaExistingAncestorSync(params.absolutePath);
5671
6032
  }
5672
- function validateSidecarLockTimeoutMs(timeoutMs) {
5673
- if (timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY)
6033
+ function buildOutsideLexicalRootPath(params) {
6034
+ assertInsideBoundary({
6035
+ boundaryLabel: params.boundaryLabel,
6036
+ rootCanonicalPath: params.rootCanonicalPath,
6037
+ candidatePath: params.canonicalOutsideLexicalPath,
6038
+ absolutePath: params.absolutePath,
6039
+ });
6040
+ return buildResolvedRootPath({
6041
+ absolutePath: params.absolutePath,
6042
+ canonicalPath: params.canonicalOutsideLexicalPath,
6043
+ rootPath: params.rootPath,
6044
+ rootCanonicalPath: params.rootCanonicalPath,
6045
+ kind: params.kind,
6046
+ });
6047
+ }
6048
+ function assertLexicalBoundaryOrCanonicalAlias(params) {
6049
+ if (params.skipLexicalRootCheck || params.lexicalInside) {
5674
6050
  return;
5675
- assertFiniteNonNegative(timeoutMs, "lock timeoutMs");
6051
+ }
6052
+ if (path_isPathInside(params.rootCanonicalPath, params.canonicalOutsideLexicalPath)) {
6053
+ return;
6054
+ }
6055
+ throw pathEscapeError({
6056
+ boundaryLabel: params.boundaryLabel,
6057
+ rootPath: params.rootPath,
6058
+ absolutePath: params.absolutePath,
6059
+ });
5676
6060
  }
5677
- function computeSidecarLockDelayMs(retry, attempt) {
5678
- validateSidecarLockRetryOptions(retry);
5679
- const minTimeout = retry.minTimeout ?? 50;
5680
- const maxTimeout = retry.maxTimeout ?? 1000;
5681
- const factor = retry.factor ?? 1;
5682
- const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
5683
- const jitter = retry.randomize ? 1 + Math.random() : 1;
5684
- return Math.min(maxTimeout, Math.round(base * jitter));
6061
+ function buildResolvedRootPath(params) {
6062
+ return {
6063
+ absolutePath: params.absolutePath,
6064
+ canonicalPath: params.canonicalPath,
6065
+ rootPath: params.rootPath,
6066
+ rootCanonicalPath: params.rootCanonicalPath,
6067
+ relativePath: relativeInsideRoot(params.rootCanonicalPath, params.canonicalPath),
6068
+ exists: params.kind.exists,
6069
+ kind: params.kind.kind,
6070
+ };
5685
6071
  }
5686
- // Windows denies access to a lock file while a just-unlinked directory entry
5687
- // is still being torn down, so a contended acquire sees EPERM on a name that is
5688
- // already gone -- both when creating it exclusively and when reading the
5689
- // holder's snapshot. The next attempt succeeds, so this is contention rather
5690
- // than a permission failure. The error must name the lock file itself: the
5691
- // exclusive-create helper opens the parent directory first, and a denial from
5692
- // that setup step carries no teardown evidence and has to reach the caller.
5693
- const maxTransientLockDenials = 8;
5694
- function isTransientLockFileDenial(error, lockPath) {
5695
- const denial = error;
5696
- return process.platform === "win32" && denial?.code === "EPERM" && denial.path === lockPath;
6072
+ async function getPathKind(absolutePath, preserveFinalSymlink) {
6073
+ try {
6074
+ const stat = preserveFinalSymlink
6075
+ ? await promises_.lstat(absolutePath)
6076
+ : await promises_.stat(absolutePath);
6077
+ return { exists: true, kind: toResolvedKind(stat) };
6078
+ }
6079
+ catch (error) {
6080
+ if (path_isNotFoundPathError(error)) {
6081
+ return { exists: false, kind: "missing" };
6082
+ }
6083
+ throw error;
6084
+ }
5697
6085
  }
5698
- function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
5699
- const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
5700
- return createdAtMs !== null && nowMs - createdAtMs > staleMs;
6086
+ function getPathKindSync(absolutePath, preserveFinalSymlink) {
6087
+ try {
6088
+ const stat = preserveFinalSymlink ? fs.lstatSync(absolutePath) : fs.statSync(absolutePath);
6089
+ return { exists: true, kind: toResolvedKind(stat) };
6090
+ }
6091
+ catch (error) {
6092
+ if (isNotFoundPathError(error)) {
6093
+ return { exists: false, kind: "missing" };
6094
+ }
6095
+ throw error;
6096
+ }
5701
6097
  }
5702
- function sidecarLockPayloadCreatedAtMs(payload) {
5703
- const createdAt = payload &&
5704
- typeof payload === "object" &&
5705
- "createdAt" in payload &&
5706
- typeof payload.createdAt === "string"
5707
- ? payload.createdAt
5708
- : "";
5709
- const createdAtMs = Date.parse(createdAt);
5710
- return Number.isFinite(createdAtMs) ? createdAtMs : null;
6098
+ function toResolvedKind(stat) {
6099
+ if (stat.isFile()) {
6100
+ return "file";
6101
+ }
6102
+ if (stat.isDirectory()) {
6103
+ return "directory";
6104
+ }
6105
+ if (stat.isSymbolicLink()) {
6106
+ return "symlink";
6107
+ }
6108
+ return "other";
5711
6109
  }
5712
- async function defaultSidecarLockShouldReclaim(params) {
5713
- const createdAtMs = sidecarLockPayloadCreatedAtMs(params.payload);
5714
- if (createdAtMs !== null)
5715
- return params.nowMs - createdAtMs > params.staleMs;
5716
- try {
5717
- return params.nowMs - (await promises_.stat(params.lockPath)).mtimeMs > params.staleMs;
6110
+ function relativeInsideRoot(rootPath, targetPath) {
6111
+ const relative = external_node_path_.relative(external_node_path_.resolve(rootPath), external_node_path_.resolve(targetPath));
6112
+ if (!relative || relative === ".") {
6113
+ return "";
5718
6114
  }
5719
- catch {
5720
- return true;
6115
+ if (isPathRelativeEscape(relative)) {
6116
+ return "";
5721
6117
  }
6118
+ return relative;
5722
6119
  }
5723
-
5724
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/suppressed-error.js
5725
- function createSuppressedError(error, suppressed, message) {
5726
- const constructor = globalThis.SuppressedError;
5727
- if (constructor) {
5728
- return new constructor(error, suppressed, message);
6120
+ function assertInsideBoundary(params) {
6121
+ if (path_isPathInside(params.rootCanonicalPath, params.candidatePath)) {
6122
+ return;
5729
6123
  }
5730
- const combined = new Error(message, {
5731
- cause: error instanceof Error ? error : undefined,
5732
- });
5733
- combined.name = "SuppressedError";
5734
- Object.defineProperties(combined, {
5735
- error: { configurable: true, value: error },
5736
- suppressed: { configurable: true, value: suppressed },
5737
- });
5738
- return combined;
6124
+ throw new Error(`Path resolves outside ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.absolutePath)}`);
6125
+ }
6126
+ function pathEscapeError(params) {
6127
+ return new Error(`Path escapes ${params.boundaryLabel} (${shortPath(params.rootPath)}): ${shortPath(params.absolutePath)}`);
6128
+ }
6129
+ function symlinkEscapeError(params) {
6130
+ return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
5739
6131
  }
5740
6132
 
5741
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js
5742
-
6133
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-root.js
5743
6134
 
5744
6135
 
5745
6136
 
5746
6137
 
5747
6138
 
5748
6139
 
5749
- async function resolveNormalizedTargetPath(targetPath) {
5750
- const resolved = external_node_path_.resolve(targetPath);
5751
- const dir = external_node_path_.dirname(resolved);
5752
- await promises_.mkdir(dir, { recursive: true });
6140
+ async function openSidecarRoot(lockRoot, relative, discardObservation, onOpenFailure) {
6141
+ const resolved = await lockRoot.resolve(relative);
6142
+ const canonicalPath = async () => (await resolveRootPath({
6143
+ absolutePath: resolved,
6144
+ rootPath: lockRoot.rootReal,
6145
+ rootCanonicalPath: lockRoot.rootReal,
6146
+ boundaryLabel: "sidecar lock root",
6147
+ })).canonicalPath;
6148
+ const expectedRealPath = await canonicalPath();
6149
+ if (expectedRealPath === lockRoot.rootReal)
6150
+ throw new errors_FsSafeError("not-file", "sidecar lock is a directory");
6151
+ const parents = [];
6152
+ let completeParents = true;
6153
+ if (discardObservation) {
6154
+ // Follow permitted in-root aliases first; receipts cover canonical ancestry.
6155
+ for (let dir = external_node_path_.dirname(expectedRealPath);; dir = external_node_path_.dirname(dir)) {
6156
+ try {
6157
+ parents.push({ dir, stat: await inspectDirectoryIdentity(dir) });
6158
+ }
6159
+ catch (error) {
6160
+ if (!path_isNotFoundPathError(error))
6161
+ throw error;
6162
+ completeParents = false;
6163
+ }
6164
+ if (dir === lockRoot.rootReal)
6165
+ break;
6166
+ }
6167
+ }
6168
+ const observation = fileObservation();
5753
6169
  try {
5754
- return external_node_path_.join(await promises_.realpath(dir), external_node_path_.basename(resolved));
6170
+ const opened = await observation.run(() => lockRoot.open(relative));
6171
+ if (opened.realPath !== expectedRealPath) {
6172
+ await opened.handle.close().catch(() => undefined);
6173
+ throw new errors_FsSafeError("path-mismatch", "sidecar lock path changed during open");
6174
+ }
6175
+ return opened;
5755
6176
  }
5756
- catch {
5757
- return resolved;
6177
+ catch (error) {
6178
+ const openFailed = observation.has(error, `open:${resolved}`);
6179
+ if (openFailed)
6180
+ onOpenFailure?.(error);
6181
+ const missing = openFailed && error instanceof errors_FsSafeError && error.code === "not-found";
6182
+ if (missing && !discardObservation)
6183
+ return null;
6184
+ const discardable = observation.has(error, `unlinked:${resolved}`) ||
6185
+ (discardObservation === "changed" && observation.has(error, `changed:${resolved}`));
6186
+ if (!discardObservation || (!missing && (!completeParents || !discardable)))
6187
+ throw error;
6188
+ try {
6189
+ try {
6190
+ const current = await lockRoot.stat(relative);
6191
+ if (!current.isFile || current.isSymbolicLink || current.nlink !== 1)
6192
+ throw error;
6193
+ }
6194
+ catch (probeError) {
6195
+ if (!(probeError instanceof errors_FsSafeError && probeError.code === "not-found"))
6196
+ throw probeError;
6197
+ }
6198
+ for (const { dir, stat } of parents)
6199
+ await inspectDirectoryIdentity(dir, stat);
6200
+ await lockRoot.resolve(relative);
6201
+ if (await canonicalPath() === expectedRealPath)
6202
+ return null;
6203
+ }
6204
+ catch {
6205
+ // Keep the original failure when directory or confinement proof fails.
6206
+ }
6207
+ throw error;
5758
6208
  }
5759
6209
  }
5760
- async function acquireSidecarLock(options, context) {
5761
- const retry = options.retry ?? {};
5762
- validateSidecarLockRetryOptions(retry);
5763
- validateSidecarLockTimeoutMs(options.timeoutMs);
5764
- context.ensureExitCleanupRegistered();
5765
- const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
5766
- const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
5767
- let held = context.held.get(normalizedTargetPath);
5768
- if (held &&
6210
+
6211
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
6212
+ let test_hooks_fsSafeTestHooks;
6213
+ function allowFsSafeTestHooks() {
6214
+ return false || process.env.VITEST === "true";
6215
+ }
6216
+ function getFsSafeTestHooks() {
6217
+ return test_hooks_fsSafeTestHooks;
6218
+ }
6219
+ function __setFsSafeTestHooksForTest(hooks) {
6220
+ if (hooks && !allowFsSafeTestHooks()) {
6221
+ throw new Error("__setFsSafeTestHooksForTest is only available in tests");
6222
+ }
6223
+ test_hooks_fsSafeTestHooks = hooks;
6224
+ }
6225
+
6226
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
6227
+
6228
+
6229
+
6230
+
6231
+
6232
+
6233
+
6234
+
6235
+
6236
+
6237
+ const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
6238
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
6239
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
6240
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
6241
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN = new RegExp(`\\n(${SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX}[ \\t]{${SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS}})\\n$`);
6242
+ function parseSidecarLockSnapshot(snapshot, parser) {
6243
+ return snapshot && { ...snapshot, payload: parseSidecarLockPayload(snapshot.raw, parser) };
6244
+ }
6245
+ function createSidecarLockOwnershipToken() {
6246
+ let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
6247
+ for (const byte of (0,external_node_crypto_.randomBytes)(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
6248
+ for (let bit = 7; bit >= 0; bit -= 1) {
6249
+ token += byte & (1 << bit) ? "\t" : " ";
6250
+ }
6251
+ }
6252
+ return token;
6253
+ }
6254
+ function readSidecarLockOwnershipToken(raw) {
6255
+ return SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN.exec(raw)?.[1];
6256
+ }
6257
+ function serializeSidecarLockPayload(payload) {
6258
+ const ownershipToken = createSidecarLockOwnershipToken();
6259
+ return {
6260
+ raw: `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`,
6261
+ ownershipToken,
6262
+ };
6263
+ }
6264
+ function relativeSidecarLockPath(lockRoot, lockPath) {
6265
+ const resolved = external_node_path_.resolve(lockPath);
6266
+ const lexicalRelative = external_node_path_.relative(lockRoot.rootDir, resolved);
6267
+ const relative = lexicalRelative !== ".." &&
6268
+ !lexicalRelative.startsWith(`..${external_node_path_.sep}`) &&
6269
+ !external_node_path_.isAbsolute(lexicalRelative)
6270
+ ? lexicalRelative
6271
+ : external_node_path_.relative(lockRoot.rootReal, resolved);
6272
+ if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relative)) {
6273
+ throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
6274
+ }
6275
+ return relative.split(external_node_path_.sep).join(external_node_path_.posix.sep);
6276
+ }
6277
+ function parseSidecarLockPayload(raw, parser) {
6278
+ if (parser) {
6279
+ return parser(raw);
6280
+ }
6281
+ try {
6282
+ const parsed = JSON.parse(raw);
6283
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
6284
+ }
6285
+ catch {
6286
+ return null;
6287
+ }
6288
+ }
6289
+ function missingSnapshotPath(error) {
6290
+ if (error.code === "ENOENT")
6291
+ return null;
6292
+ throw error;
6293
+ }
6294
+ async function readSidecarLockSnapshot(lockPath, options = {}) {
6295
+ return parseSidecarLockSnapshot(await readSidecarLockRawSnapshot(lockPath, options), options.parsePayload);
6296
+ }
6297
+ async function readSidecarLockRawSnapshot(lockPath, options = {}) {
6298
+ let handle;
6299
+ try {
6300
+ if (options.lockRoot) {
6301
+ const lockRoot = options.lockRoot;
6302
+ const relative = relativeSidecarLockPath(lockRoot, lockPath);
6303
+ const opened = await openSidecarRoot(lockRoot, relative, options.discardObservation, options.onOpenFailure);
6304
+ if (!opened)
6305
+ return null;
6306
+ try {
6307
+ const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
6308
+ return {
6309
+ raw,
6310
+ stat: opened.stat,
6311
+ };
6312
+ }
6313
+ finally {
6314
+ await opened.handle.close().catch(() => undefined);
6315
+ }
6316
+ }
6317
+ const before = await promises_.lstat(lockPath).catch(missingSnapshotPath);
6318
+ if (!before)
6319
+ return null;
6320
+ if (!before.isFile() || before.isSymbolicLink()) {
6321
+ if (options.rejectNonFile) {
6322
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
6323
+ }
6324
+ return null;
6325
+ }
6326
+ await getFsSafeTestHooks()?.beforeSidecarLockSnapshotOpen?.(lockPath);
6327
+ const noFollow = process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
6328
+ ? external_node_fs_.constants.O_NOFOLLOW
6329
+ : 0;
6330
+ try {
6331
+ handle = await promises_.open(lockPath, external_node_fs_.constants.O_RDONLY |
6332
+ noFollow |
6333
+ (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0));
6334
+ }
6335
+ catch (error) {
6336
+ if (error.code === "ENOENT")
6337
+ return null;
6338
+ if (options.rejectNonFile && error.code === "ELOOP") {
6339
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`, {
6340
+ cause: error,
6341
+ });
6342
+ }
6343
+ options.onOpenFailure?.(error);
6344
+ throw error;
6345
+ }
6346
+ const opened = await handle.stat();
6347
+ if (!opened.isFile()) {
6348
+ if (options.rejectNonFile) {
6349
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
6350
+ }
6351
+ return null;
6352
+ }
6353
+ if (!options.allowDescriptorIdentityDrift && !file_identity_sameFileIdentity(before, opened))
6354
+ return null;
6355
+ const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
6356
+ const after = await promises_.lstat(lockPath).catch(missingSnapshotPath);
6357
+ if (!after || !after.isFile() || !file_identity_sameFileIdentity(before, after))
6358
+ return null;
6359
+ return { raw, stat: after };
6360
+ }
6361
+ finally {
6362
+ await handle?.close().catch(() => undefined);
6363
+ }
6364
+ }
6365
+ function lstatSidecarLockSync(lockPath) {
6366
+ try {
6367
+ return fsSync.lstatSync(lockPath);
6368
+ }
6369
+ catch (error) {
6370
+ return missingSnapshotPath(error);
6371
+ }
6372
+ }
6373
+ function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}) {
6374
+ return parseSidecarLockSnapshot(readSidecarLockRawSnapshotSync(lockPath, options), parsePayload);
6375
+ }
6376
+ function readSidecarLockRawSnapshotSync(lockPath, options = {}) {
6377
+ let fd;
6378
+ try {
6379
+ const before = lstatSidecarLockSync(lockPath);
6380
+ if (!before)
6381
+ return null;
6382
+ if (!before.isFile() || before.isSymbolicLink()) {
6383
+ if (options.rejectNonFile) {
6384
+ throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
6385
+ }
6386
+ return null;
6387
+ }
6388
+ try {
6389
+ fd = fsSync.openSync(lockPath, resolveReadOpenFlags());
6390
+ }
6391
+ catch (error) {
6392
+ if (error.code === "ENOENT")
6393
+ return null;
6394
+ options.onOpenFailure?.(error);
6395
+ throw error;
6396
+ }
6397
+ const opened = fsSync.fstatSync(fd);
6398
+ if (!opened.isFile()) {
6399
+ if (options.rejectNonFile) {
6400
+ throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
6401
+ }
6402
+ return null;
6403
+ }
6404
+ const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
6405
+ const after = lstatSidecarLockSync(lockPath);
6406
+ if (!after || !sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
6407
+ return null;
6408
+ return {
6409
+ raw,
6410
+ stat: after,
6411
+ ownershipToken: readSidecarLockOwnershipToken(raw),
6412
+ };
6413
+ }
6414
+ finally {
6415
+ if (fd !== undefined)
6416
+ fsSync.closeSync(fd);
6417
+ }
6418
+ }
6419
+ function removeSidecarLockIfUnchangedSync(lockPath, observed) {
6420
+ const current = readSidecarLockSnapshotSync(lockPath);
6421
+ if (!current || !sidecarLockSnapshotMatches(current, observed))
6422
+ return false;
6423
+ fsSync.rmSync(lockPath);
6424
+ return true;
6425
+ }
6426
+ function sidecarLockSnapshotMatches(current, observed) {
6427
+ if (observed.ownershipToken !== undefined) {
6428
+ return (current.stat?.isFile() === true &&
6429
+ current.raw !== undefined &&
6430
+ observed.raw !== undefined &&
6431
+ readSidecarLockOwnershipToken(current.raw) === observed.ownershipToken &&
6432
+ readSidecarLockOwnershipToken(observed.raw) === observed.ownershipToken &&
6433
+ current.raw === observed.raw);
6434
+ }
6435
+ if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
6436
+ return false;
6437
+ }
6438
+ if (observed.raw !== undefined) {
6439
+ return current.raw === observed.raw;
6440
+ }
6441
+ return observed.stat !== undefined && current.stat !== undefined;
6442
+ }
6443
+ async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
6444
+ const current = await readSidecarLockSnapshot(lockPath, {
6445
+ ...options,
6446
+ allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
6447
+ });
6448
+ if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
6449
+ return false;
6450
+ }
6451
+ if (options.lockRoot) {
6452
+ await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath));
6453
+ }
6454
+ else {
6455
+ await promises_.rm(lockPath, { force: true });
6456
+ }
6457
+ return true;
6458
+ }
6459
+ async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
6460
+ const current = await readSidecarLockSnapshot(lockPath, {
6461
+ ...options,
6462
+ allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
6463
+ });
6464
+ return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
6465
+ }
6466
+ async function sidecarReclaimGuardExists(pathname) {
6467
+ try {
6468
+ await promises_.lstat(pathname);
6469
+ return true;
6470
+ }
6471
+ catch (err) {
6472
+ if (err.code === "ENOENT") {
6473
+ return false;
6474
+ }
6475
+ throw err;
6476
+ }
6477
+ }
6478
+ async function tryAcquireSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
6479
+ try {
6480
+ await promises_.mkdir(reclaimGuardPath);
6481
+ reclaimGuards.add(reclaimGuardPath);
6482
+ return true;
6483
+ }
6484
+ catch (err) {
6485
+ if (err.code === "EEXIST") {
6486
+ return false;
6487
+ }
6488
+ throw err;
6489
+ }
6490
+ }
6491
+ async function releaseSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
6492
+ await promises_.rmdir(reclaimGuardPath);
6493
+ reclaimGuards.delete(reclaimGuardPath);
6494
+ }
6495
+ async function removeStaleSidecarLockIfAllowed(params) {
6496
+ if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
6497
+ return "not-approved";
6498
+ }
6499
+ const ioOptions = { lockRoot: params.lockRoot, parsePayload: params.parsePayload };
6500
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
6501
+ return "changed";
6502
+ }
6503
+ if (!(await params.shouldRemoveStaleLock({
6504
+ lockPath: params.lockPath,
6505
+ normalizedTargetPath: params.normalizedTargetPath,
6506
+ raw: params.snapshot.raw,
6507
+ payload: params.snapshot.payload,
6508
+ }))) {
6509
+ return "not-approved";
6510
+ }
6511
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
6512
+ return "changed";
6513
+ }
6514
+ try {
6515
+ if (params.lockRoot) {
6516
+ await params.lockRoot.remove(relativeSidecarLockPath(params.lockRoot, params.lockPath));
6517
+ }
6518
+ else {
6519
+ await promises_.rm(params.lockPath);
6520
+ }
6521
+ return "removed";
6522
+ }
6523
+ catch (err) {
6524
+ if (err.code === "ENOENT") {
6525
+ return "changed";
6526
+ }
6527
+ throw err;
6528
+ }
6529
+ }
6530
+
6531
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
6532
+
6533
+ function assertFiniteNonNegative(value, label) {
6534
+ if (!Number.isFinite(value) || value < 0) {
6535
+ throw new RangeError(`${label} must be a finite non-negative number`);
6536
+ }
6537
+ }
6538
+ function validateSidecarLockRetryOptions(retry) {
6539
+ if (retry.retries !== undefined && (!Number.isSafeInteger(retry.retries) || retry.retries < 0)) {
6540
+ throw new RangeError("lock retry.retries must be a non-negative safe integer");
6541
+ }
6542
+ if (retry.factor !== undefined)
6543
+ assertFiniteNonNegative(retry.factor, "lock retry.factor");
6544
+ if (retry.minTimeout !== undefined) {
6545
+ assertFiniteNonNegative(retry.minTimeout, "lock retry.minTimeout");
6546
+ }
6547
+ if (retry.maxTimeout !== undefined) {
6548
+ assertFiniteNonNegative(retry.maxTimeout, "lock retry.maxTimeout");
6549
+ }
6550
+ if (retry.minTimeout !== undefined &&
6551
+ retry.maxTimeout !== undefined &&
6552
+ retry.minTimeout > retry.maxTimeout) {
6553
+ throw new RangeError("lock retry.minTimeout must not exceed retry.maxTimeout");
6554
+ }
6555
+ }
6556
+ function validateSidecarLockTimeoutMs(timeoutMs) {
6557
+ if (timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY)
6558
+ return;
6559
+ assertFiniteNonNegative(timeoutMs, "lock timeoutMs");
6560
+ }
6561
+ function computeSidecarLockDelayMs(retry, attempt) {
6562
+ validateSidecarLockRetryOptions(retry);
6563
+ const minTimeout = retry.minTimeout ?? 50;
6564
+ const maxTimeout = retry.maxTimeout ?? 1000;
6565
+ const factor = retry.factor ?? 1;
6566
+ const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
6567
+ const jitter = retry.randomize ? 1 + Math.random() : 1;
6568
+ return Math.min(maxTimeout, Math.round(base * jitter));
6569
+ }
6570
+ // Windows denies access to a lock file while a just-unlinked directory entry
6571
+ // is still being torn down, so a contended acquire sees EPERM on a name that is
6572
+ // already gone -- both when creating it exclusively and when reading the
6573
+ // holder's snapshot. The next attempt succeeds, so this is contention rather
6574
+ // than a permission failure. The error must name the lock file itself: the
6575
+ // exclusive-create helper opens the parent directory first, and a denial from
6576
+ // that setup step carries no teardown evidence and has to reach the caller.
6577
+ const maxTransientLockDenials = 8;
6578
+ function isTransientLockFileDenial(error, lockPath) {
6579
+ const denial = error;
6580
+ return process.platform === "win32" && denial?.code === "EPERM" && denial.path === lockPath;
6581
+ }
6582
+ function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
6583
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
6584
+ return createdAtMs !== null && nowMs - createdAtMs > staleMs;
6585
+ }
6586
+ function sidecarLockPayloadCreatedAtMs(payload) {
6587
+ const createdAt = payload &&
6588
+ typeof payload === "object" &&
6589
+ "createdAt" in payload &&
6590
+ typeof payload.createdAt === "string"
6591
+ ? payload.createdAt
6592
+ : "";
6593
+ const createdAtMs = Date.parse(createdAt);
6594
+ return Number.isFinite(createdAtMs) ? createdAtMs : null;
6595
+ }
6596
+ async function defaultSidecarLockShouldReclaim(params) {
6597
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(params.payload);
6598
+ if (createdAtMs !== null)
6599
+ return params.nowMs - createdAtMs > params.staleMs;
6600
+ try {
6601
+ return params.nowMs - (await promises_.stat(params.lockPath)).mtimeMs > params.staleMs;
6602
+ }
6603
+ catch {
6604
+ return true;
6605
+ }
6606
+ }
6607
+
6608
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/suppressed-error.js
6609
+ function createSuppressedError(error, suppressed, message) {
6610
+ const constructor = globalThis.SuppressedError;
6611
+ if (constructor) {
6612
+ return new constructor(error, suppressed, message);
6613
+ }
6614
+ const combined = new Error(message, {
6615
+ cause: error instanceof Error ? error : undefined,
6616
+ });
6617
+ combined.name = "SuppressedError";
6618
+ Object.defineProperties(combined, {
6619
+ error: { configurable: true, value: error },
6620
+ suppressed: { configurable: true, value: suppressed },
6621
+ });
6622
+ return combined;
6623
+ }
6624
+
6625
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js
6626
+
6627
+
6628
+
6629
+
6630
+
6631
+
6632
+
6633
+
6634
+
6635
+ async function resolveNormalizedTargetPath(targetPath) {
6636
+ const resolved = external_node_path_.resolve(targetPath);
6637
+ const dir = external_node_path_.dirname(resolved);
6638
+ await promises_.mkdir(dir, { recursive: true });
6639
+ try {
6640
+ return external_node_path_.join(await promises_.realpath(dir), external_node_path_.basename(resolved));
6641
+ }
6642
+ catch {
6643
+ return resolved;
6644
+ }
6645
+ }
6646
+ async function acquireSidecarLock(options, context) {
6647
+ const retry = options.retry ?? {};
6648
+ validateSidecarLockRetryOptions(retry);
6649
+ validateSidecarLockTimeoutMs(options.timeoutMs);
6650
+ context.ensureExitCleanupRegistered();
6651
+ const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
6652
+ const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
6653
+ let held = context.held.get(normalizedTargetPath);
6654
+ if (held &&
5769
6655
  options.reentrantOwner !== undefined &&
5770
6656
  held.reentrantOwner !== undefined &&
5771
6657
  options.reentrantOwner === held.reentrantOwner) {
@@ -5834,13 +6720,14 @@ async function acquireSidecarLock(options, context) {
5834
6720
  let handle = null;
5835
6721
  let createdSnapshot = null;
5836
6722
  let lockFileCreateDenied = false;
6723
+ const payload = await options.payload();
6724
+ const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
5837
6725
  try {
5838
- const payload = await options.payload();
5839
- const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
5840
6726
  if (options.lockRoot) {
5841
- const relativeLockPath = relativeSidecarLockPath(options.lockRoot, lockPath);
6727
+ const lockRoot = options.lockRoot;
6728
+ const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
5842
6729
  try {
5843
- await options.lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
6730
+ await lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
5844
6731
  }
5845
6732
  catch (error) {
5846
6733
  if (error instanceof errors_FsSafeError && error.code === "already-exists") {
@@ -5849,7 +6736,23 @@ async function acquireSidecarLock(options, context) {
5849
6736
  throw error;
5850
6737
  }
5851
6738
  createdSnapshot = { raw, payload, ownershipToken };
5852
- handle = (await options.lockRoot.open(relativeLockPath)).handle;
6739
+ const opened = await openSidecarRoot(lockRoot, relativeLockPath, "unlinked");
6740
+ if (!opened) {
6741
+ await waitForRetry();
6742
+ continue;
6743
+ }
6744
+ try {
6745
+ // Root.open admits the current file, not necessarily the one we created.
6746
+ const currentRaw = await readFileHandleBounded(opened.handle, Buffer.byteLength(raw));
6747
+ if (!currentRaw.equals(Buffer.from(raw))) {
6748
+ throw new errors_FsSafeError("path-mismatch", "created sidecar lock changed before admission");
6749
+ }
6750
+ }
6751
+ catch (error) {
6752
+ await opened.handle.close().catch(() => undefined);
6753
+ throw error;
6754
+ }
6755
+ handle = opened.handle;
5853
6756
  }
5854
6757
  else {
5855
6758
  try {
@@ -5864,6 +6767,12 @@ async function acquireSidecarLock(options, context) {
5864
6767
  await handle.writeFile(raw, "utf8");
5865
6768
  }
5866
6769
  const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
6770
+ if (snapshot.stat.nlink === 0) {
6771
+ await handle.close();
6772
+ handle = null;
6773
+ await waitForRetry();
6774
+ continue;
6775
+ }
5867
6776
  const createdHeld = {
5868
6777
  refCount: 1,
5869
6778
  reentrantOwner: options.reentrantOwner,
@@ -5908,7 +6817,7 @@ async function acquireSidecarLock(options, context) {
5908
6817
  catch (err) {
5909
6818
  try {
5910
6819
  if (handle) {
5911
- const failedSnapshot = { payload: null };
6820
+ const failedSnapshot = createdSnapshot ?? { payload: null };
5912
6821
  try {
5913
6822
  failedSnapshot.stat = await handle.stat();
5914
6823
  }
@@ -5920,8 +6829,8 @@ async function acquireSidecarLock(options, context) {
5920
6829
  context.held.delete(normalizedTargetPath);
5921
6830
  }
5922
6831
  await handle.close().catch(() => undefined);
5923
- // The file may be empty or partial JSON, so remove by the identity
5924
- // captured from our exclusive handle rather than by pathname alone.
6832
+ // Root-created records retain the creator's byte/token receipt;
6833
+ // partial direct writes use the exclusive descriptor's identity.
5925
6834
  await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
5926
6835
  lockRoot: options.lockRoot,
5927
6836
  parsePayload: options.parsePayload,
@@ -5947,24 +6856,29 @@ async function acquireSidecarLock(options, context) {
5947
6856
  if (ownsReclaimGuard) {
5948
6857
  await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
5949
6858
  ownsReclaimGuard = false;
6859
+ await waitForRetry();
5950
6860
  continue;
5951
6861
  }
5952
6862
  const nowMs = Date.now();
5953
- let snapshot;
6863
+ let rawSnapshot;
6864
+ let lockFileOpenDenied = false;
5954
6865
  try {
5955
- snapshot = await readSidecarLockSnapshot(lockPath, {
6866
+ rawSnapshot = await readSidecarLockRawSnapshot(lockPath, {
5956
6867
  lockRoot: options.lockRoot,
5957
- parsePayload: options.parsePayload,
5958
6868
  rejectNonFile: true,
6869
+ discardObservation: "changed",
6870
+ onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
5959
6871
  });
5960
6872
  }
5961
6873
  catch (readErr) {
5962
- if (!isTransientLockFileDenial(readErr, lockPath) || !withinDenialBudget())
6874
+ if (!lockFileOpenDenied || !withinDenialBudget())
5963
6875
  throw readErr;
5964
6876
  await retryOrRethrowDenial(readErr);
5965
6877
  continue;
5966
6878
  }
6879
+ const snapshot = parseSidecarLockSnapshot(rawSnapshot, options.parsePayload);
5967
6880
  if (!snapshot) {
6881
+ await waitForRetry();
5968
6882
  continue;
5969
6883
  }
5970
6884
  if (context.held.has(normalizedTargetPath)) {
@@ -5984,6 +6898,7 @@ async function acquireSidecarLock(options, context) {
5984
6898
  lockRoot: options.lockRoot,
5985
6899
  parsePayload: options.parsePayload,
5986
6900
  }))) {
6901
+ await waitForRetry();
5987
6902
  continue;
5988
6903
  }
5989
6904
  const staleRecovery = options.staleRecovery ?? "fail-closed";
@@ -6002,6 +6917,8 @@ async function acquireSidecarLock(options, context) {
6002
6917
  parsePayload: options.parsePayload,
6003
6918
  });
6004
6919
  if (removal === "removed" || removal === "changed") {
6920
+ if (removal === "changed")
6921
+ await waitForRetry();
6005
6922
  continue;
6006
6923
  }
6007
6924
  await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
@@ -6017,1176 +6934,521 @@ async function acquireSidecarLock(options, context) {
6017
6934
  }
6018
6935
  }
6019
6936
  }
6020
- finally {
6021
- if (ownsReclaimGuard) {
6022
- await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
6023
- }
6024
- }
6025
- }
6026
-
6027
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
6028
-
6029
- function createSidecarLockHandle(params) {
6030
- let released = false;
6031
- let releasePromise;
6032
- let releaseStarted = false;
6033
- const release = async () => {
6034
- if (released)
6035
- return;
6036
- if (!releasePromise) {
6037
- const retry = releaseStarted;
6038
- releaseStarted = true;
6039
- releasePromise = (async () => {
6040
- await params.release({ retry });
6041
- released = true;
6042
- })();
6043
- }
6044
- try {
6045
- await releasePromise;
6046
- }
6047
- finally {
6048
- releasePromise = undefined;
6049
- }
6050
- };
6051
- return {
6052
- lockPath: params.lockPath,
6053
- normalizedTargetPath: params.normalizedTargetPath,
6054
- verifyStillHeld: params.verifyStillHeld,
6055
- release,
6056
- [Symbol.asyncDispose]: release,
6057
- };
6058
- }
6059
- function createHeldSidecarLockHandle(params) {
6060
- return createSidecarLockHandle({
6061
- lockPath: params.held.lockPath,
6062
- normalizedTargetPath: params.normalizedTargetPath,
6063
- verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
6064
- lockRoot: params.held.lockRoot,
6065
- parsePayload: params.held.parsePayload,
6066
- }),
6067
- release: (options) => params.release(options),
6068
- });
6069
- }
6070
-
6071
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
6072
-
6073
-
6074
-
6075
-
6076
-
6077
-
6078
- const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
6079
- const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
6080
- const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
6081
- function getGlobalManagers() {
6082
- const globalWithState = globalThis;
6083
- if (!globalWithState[GLOBAL_STATE_KEY]) {
6084
- globalWithState[GLOBAL_STATE_KEY] = new Map();
6085
- }
6086
- return globalWithState[GLOBAL_STATE_KEY];
6087
- }
6088
- function resolveManagerState(key) {
6089
- const managers = getGlobalManagers();
6090
- let state = managers.get(key);
6091
- if (!state) {
6092
- state = {
6093
- cleanupRegistered: false,
6094
- held: new Map(),
6095
- reclaimCleanupRegistered: false,
6096
- reclaimGuards: new Set(),
6097
- };
6098
- managers.set(key, state);
6099
- }
6100
- else {
6101
- // The global manager symbol is shared across package copies and hot reloads.
6102
- // Backfill state created by fs-safe versions that predate reclaim guards.
6103
- state.reclaimCleanupRegistered ??= false;
6104
- state.reclaimGuards ??= new Set();
6105
- for (const held of state.held.values()) {
6106
- held.refCount ??= 1;
6107
- }
6108
- }
6109
- return state;
6110
- }
6111
- function snapshotMatchesSync(lockPath, observed) {
6112
- let fd;
6113
- try {
6114
- const beforeStat = external_node_fs_.lstatSync(lockPath);
6115
- if (!beforeStat.isFile()) {
6116
- return false;
6117
- }
6118
- const openFlags = external_node_fs_.constants.O_RDONLY |
6119
- (process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
6120
- ? external_node_fs_.constants.O_NOFOLLOW
6121
- : 0) |
6122
- (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0);
6123
- fd = external_node_fs_.openSync(lockPath, openFlags);
6124
- const openedStat = external_node_fs_.fstatSync(fd);
6125
- if (!openedStat.isFile()) {
6126
- return false;
6127
- }
6128
- if (observed.raw !== undefined && openedStat.size !== Buffer.byteLength(observed.raw)) {
6129
- return false;
6130
- }
6131
- const raw = external_node_fs_.readFileSync(fd, "utf8");
6132
- const afterStat = external_node_fs_.lstatSync(lockPath);
6133
- if (!afterStat.isFile() || !file_identity_sameFileIdentity(beforeStat, afterStat)) {
6134
- return false;
6135
- }
6136
- return sidecarLockSnapshotMatches({ raw, payload: null, stat: afterStat }, observed);
6137
- }
6138
- catch {
6139
- return false;
6140
- }
6141
- finally {
6142
- if (fd !== undefined) {
6143
- try {
6144
- external_node_fs_.closeSync(fd);
6145
- }
6146
- catch {
6147
- // Best-effort process-exit cleanup.
6148
- }
6149
- }
6150
- }
6151
- }
6152
- function releaseAllReclaimGuardsSync(state) {
6153
- for (const reclaimGuardPath of state.reclaimGuards) {
6154
- try {
6155
- external_node_fs_.rmdirSync(reclaimGuardPath);
6156
- state.reclaimGuards.delete(reclaimGuardPath);
6157
- }
6158
- catch {
6159
- // Best-effort process-exit cleanup. A surviving guard fails closed.
6160
- }
6161
- }
6162
- }
6163
- function releaseAllLocksSync(state) {
6164
- for (const [normalizedTargetPath, held] of state.held) {
6165
- void held.handle.close().catch(() => undefined);
6166
- try {
6167
- if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
6168
- external_node_fs_.rmSync(held.lockPath, { force: true });
6169
- }
6170
- }
6171
- catch {
6172
- // Best-effort process-exit cleanup.
6173
- }
6174
- state.held.delete(normalizedTargetPath);
6175
- }
6176
- releaseAllReclaimGuardsSync(state);
6177
- }
6178
- function ensureGlobalExitCleanupRegistered() {
6179
- const globalWithCleanup = globalThis;
6180
- if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
6181
- return;
6182
- globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
6183
- const cleanup = () => {
6184
- for (const state of getGlobalManagers().values()) {
6185
- releaseAllLocksSync(state);
6186
- }
6187
- };
6188
- globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
6189
- process.on("exit", cleanup);
6190
- }
6191
- async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
6192
- const current = state.held.get(normalizedTargetPath);
6193
- if (current !== held) {
6194
- return false;
6195
- }
6196
- if (held.releasePromise) {
6197
- await held.releasePromise;
6198
- return true;
6199
- }
6200
- if (options.force) {
6201
- held.refCount = 0;
6202
- }
6203
- else if (!options.retry && held.refCount > 0) {
6204
- held.refCount -= 1;
6205
- }
6206
- if (held.refCount > 0) {
6207
- return false;
6208
- }
6209
- held.releasePromise = (async () => {
6210
- await held.handle.close().catch(() => undefined);
6211
- await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
6212
- lockRoot: held.lockRoot,
6213
- parsePayload: held.parsePayload,
6214
- });
6215
- if (state.held.get(normalizedTargetPath) === held) {
6216
- state.held.delete(normalizedTargetPath);
6217
- }
6218
- if (held.compromiseTimer) {
6219
- clearInterval(held.compromiseTimer);
6220
- held.compromiseTimer = undefined;
6221
- }
6222
- })();
6223
- try {
6224
- await held.releasePromise;
6225
- return true;
6226
- }
6227
- finally {
6228
- held.releasePromise = undefined;
6229
- }
6230
- }
6231
- function handleForHeldLock(state, normalizedTargetPath, held) {
6232
- return createHeldSidecarLockHandle({
6233
- normalizedTargetPath,
6234
- held,
6235
- release: async (options) => await releaseHeldLock(state, normalizedTargetPath, held, { retry: options?.retry }),
6236
- });
6237
- }
6238
- function createSidecarLockManager(key) {
6239
- const state = resolveManagerState(key);
6240
- function ensureExitCleanupRegistered() {
6241
- state.cleanupRegistered = true;
6242
- state.reclaimCleanupRegistered = true;
6243
- ensureGlobalExitCleanupRegistered();
6244
- }
6245
- async function acquire(options) {
6246
- return await acquireSidecarLock(options, {
6247
- held: state.held,
6248
- reclaimGuards: state.reclaimGuards,
6249
- ensureExitCleanupRegistered,
6250
- handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
6251
- releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
6252
- });
6253
- }
6254
- async function withLock(options, fn) {
6255
- const lock = await acquire(options);
6256
- let result;
6257
- try {
6258
- result = await fn();
6259
- }
6260
- catch (bodyError) {
6261
- try {
6262
- await lock.release();
6263
- }
6264
- catch (releaseError) {
6265
- throw createSuppressedError(releaseError, bodyError, "file lock callback and release both failed");
6266
- }
6267
- throw bodyError;
6268
- }
6269
- await lock.release();
6270
- return result;
6271
- }
6272
- async function drain() {
6273
- for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
6274
- await releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
6275
- }
6276
- }
6277
- function reset() {
6278
- releaseAllLocksSync(state);
6279
- }
6280
- function heldEntries() {
6281
- return Array.from(state.held.entries()).map(([normalizedTargetPath, held]) => ({
6282
- normalizedTargetPath,
6283
- lockPath: held.lockPath,
6284
- acquiredAt: held.acquiredAt,
6285
- metadata: held.metadata,
6286
- forceRelease: () => releaseHeldLock(state, normalizedTargetPath, held, { force: true }),
6287
- }));
6288
- }
6289
- return { acquire, withLock, drain, reset, heldEntries };
6290
- }
6291
- async function withSidecarLock(targetPath, options, fn) {
6292
- const manager = createSidecarLockManager(options.managerKey ?? `fs-safe.sidecar-lock:${targetPath}`);
6293
- const { managerKey: _managerKey, ...acquireOptions } = options;
6294
- return await manager.withLock({ ...acquireOptions, targetPath }, fn);
6295
- }
6296
-
6297
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
6298
-
6299
-
6300
-
6301
-
6302
-
6303
-
6304
-
6305
-
6306
-
6307
-
6308
-
6309
-
6310
-
6311
-
6312
-
6313
-
6314
-
6315
- function pinned_write_byteLength(input, encoding) {
6316
- return typeof input === "string"
6317
- ? Buffer.byteLength(input, encoding ?? "utf8")
6318
- : input.byteLength;
6319
- }
6320
- function assertSafeBasename(basename) {
6321
- if (!basename ||
6322
- basename === "." ||
6323
- basename === ".." ||
6324
- basename.includes("/") ||
6325
- basename.includes("\0")) {
6326
- throw new errors_FsSafeError("invalid-path", "invalid target path");
6327
- }
6328
- }
6329
- function assertWithinMaxBytes(bytes, maxBytes) {
6330
- if (maxBytes !== undefined && bytes > maxBytes) {
6331
- throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
6332
- }
6333
- }
6334
- async function syncFileBestEffort(handle) {
6335
- try {
6336
- await handle.sync();
6337
- }
6338
- catch (error) {
6339
- if (error?.code !== "EPERM") {
6340
- throw error;
6341
- }
6342
- }
6343
- }
6344
- async function writeStreamToHandle(stream, handle, maxBytes) {
6345
- let bytes = 0;
6346
- for await (const chunk of stream) {
6347
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
6348
- bytes += buffer.byteLength;
6349
- assertWithinMaxBytes(bytes, maxBytes);
6350
- let offset = 0;
6351
- while (offset < buffer.byteLength) {
6352
- const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
6353
- if (bytesWritten <= 0) {
6354
- throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
6355
- }
6356
- offset += bytesWritten;
6357
- }
6358
- }
6359
- }
6360
- async function runPinnedWriteHelper(params) {
6361
- const normalizedParams = { ...params, maxBytes: byte_budget_normalizeMaxBytes(params.maxBytes) };
6362
- assertSafeBasename(params.basename);
6363
- validatePinnedOperationPayload({
6364
- relativeParentPath: params.relativeParentPath,
6365
- });
6366
- // The explicit compatibility policy uses the guarded Node fallback, where
6367
- // content verification can replace the strict post-rename inode check.
6368
- if (normalizedParams.onRenameIdentityMismatch === "verify-content") {
6369
- return await runPinnedWriteFallback(normalizedParams);
6370
- }
6371
- const native = getNativeBinding();
6372
- if (native) {
6373
- return await runPinnedWriteNative(native, normalizedParams);
6374
- }
6375
- return await runPinnedWriteFallback(normalizedParams);
6376
- }
6377
- async function runPinnedWriteWithRenamePolicy(params) {
6378
- const { targetPath, renameIdentity, ...writeParams } = params;
6379
- if (renameIdentity !== "verify-content-with-lock") {
6380
- return await runPinnedWriteHelper(writeParams);
6381
- }
6382
- const relativeTargetPath = writeParams.relativeParentPath
6383
- ? `${writeParams.relativeParentPath}/${writeParams.basename}`
6384
- : writeParams.basename;
6385
- const lockPath = external_node_path_.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
6386
- return await withSidecarLock(writeParams.rootPath, {
6387
- managerKey: `fs-safe.write:${targetPath}`,
6388
- lockPath,
6389
- staleMs: 30_000,
6390
- timeoutMs: 5_000,
6391
- payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
6392
- retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
6393
- }, async () => await runPinnedWriteHelper({
6394
- ...writeParams,
6395
- onRenameIdentityMismatch: "verify-content",
6396
- }));
6397
- }
6398
- async function runPinnedWriteFallback(params) {
6399
- let parentPath = params.relativeParentPath
6400
- ? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
6401
- : params.rootPath;
6402
- if (params.mkdir) {
6403
- // mkdirPathComponentsWithGuards may resolve the final component through
6404
- // an in-root symlink (e.g. a skill-bank layout). Use its returned real
6405
- // path for the subsequent guard and target path so we don't re-check the
6406
- // original, possibly-symlinked, lexical path and reject it outright.
6407
- parentPath = await mkdirPathComponentsWithGuards({
6408
- rootReal: params.rootPath,
6409
- targetPath: parentPath,
6410
- beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
6411
- });
6412
- }
6413
- const parentGuard = params.mkdir
6414
- ? await directory_guard_createAsyncDirectoryGuard(parentPath)
6415
- : await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
6416
- const targetPath = external_node_path_.join(parentPath, params.basename);
6417
- if (params.overwrite === false) {
6418
- 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), {
6419
- onPostGuardFailure: async (openedHandle) => {
6420
- // The parent failed verification, so targetPath may now resolve
6421
- // somewhere else. Close the fd, but do not clean up by path.
6422
- await openedHandle.close().catch(() => undefined);
6423
- },
6424
- });
6425
- let created = true;
6426
- try {
6427
- const verificationIdentity = await handle.stat({ bigint: true });
6428
- await handle.chmod(params.mode);
6429
- if (params.input.kind === "buffer") {
6430
- assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
6431
- if (typeof params.input.data === "string") {
6432
- await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
6433
- }
6434
- else {
6435
- await handle.writeFile(params.input.data);
6436
- }
6437
- }
6438
- else {
6439
- await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
6440
- }
6441
- await syncFileBestEffort(handle);
6442
- const stat = await handle.stat();
6443
- await syncDirectoryBestEffort(parentPath);
6444
- // Publication is complete. A failed outer check must not remove its target.
6445
- created = false;
6446
- await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
6447
- return { dev: stat.dev, ino: stat.ino };
6448
- }
6449
- finally {
6450
- await handle.close().catch(() => undefined);
6451
- if (created) {
6452
- await promises_.rm(targetPath, { force: true }).catch(() => undefined);
6453
- }
6454
- }
6455
- }
6456
- const tempPath = external_node_path_.join(parentPath, `.${params.basename}.${(0,external_node_crypto_.randomUUID)()}.fallback.tmp`);
6457
- const tempFlags = external_node_fs_.constants.O_WRONLY |
6458
- external_node_fs_.constants.O_CREAT |
6459
- external_node_fs_.constants.O_EXCL |
6460
- (process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_.constants
6461
- ? external_node_fs_.constants.O_NOFOLLOW
6462
- : 0);
6463
- let handle;
6464
- let tempStat;
6465
- let readHandle;
6466
- let renamed = false;
6467
- try {
6468
- handle = await promises_.open(tempPath, tempFlags, params.mode);
6469
- let verificationIdentity = await handle.stat({ bigint: true });
6470
- await handle.chmod(params.mode);
6471
- if (params.input.kind === "buffer") {
6472
- assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
6473
- if (typeof params.input.data === "string") {
6474
- await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
6475
- }
6476
- else {
6477
- await handle.writeFile(params.input.data);
6478
- }
6479
- }
6480
- else {
6481
- await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
6482
- }
6483
- tempStat = await handle.stat();
6484
- const tempPathStat = await promises_.lstat(tempPath);
6485
- if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
6486
- throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
6487
- }
6488
- const expectedTempStat = tempStat;
6489
- await syncFileBestEffort(handle);
6490
- let verifiedIdentity = expectedTempStat;
6491
- await withAsyncDirectoryGuards([parentGuard], async () => {
6492
- await promises_.rename(tempPath, targetPath);
6493
- renamed = true;
6494
- await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
6495
- await syncDirectoryBestEffort(parentPath);
6496
- const targetStat = await promises_.lstat(targetPath);
6497
- if (targetStat.isSymbolicLink()) {
6498
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
6499
- }
6500
- if (!file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
6501
- // On filesystems like rclone FUSE, rename(2) can give the destination a
6502
- // different inode from the source temp fd even with zero concurrency. The
6503
- // caller must ensure mutual exclusion before passing "verify-content";
6504
- // fall back to a content hash for this rename-boundary check only.
6505
- if (params.onRenameIdentityMismatch !== "verify-content") {
6506
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
6507
- }
6508
- if (params.input.kind !== "buffer") {
6509
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
6510
- }
6511
- const expectedHash = sha256Hex(params.input.data, params.input.encoding);
6512
- readHandle = await promises_.open(targetPath, read_open_flags_resolveReadOpenFlags());
6513
- const readHandleStat = await readHandle.stat({ bigint: true });
6514
- const actualHash = sha256Hex(await readHandle.readFile());
6515
- if (actualHash !== expectedHash) {
6516
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
6517
- }
6518
- // The content-verified destination, not the old temp inode, is now pinned.
6519
- verificationIdentity = readHandleStat;
6520
- // Preserve the helper's legacy numeric return facts, not the private proof.
6521
- verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
6522
- }
6523
- });
6524
- await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
6525
- return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
6526
- }
6527
- finally {
6528
- await readHandle?.close().catch(() => undefined);
6529
- await handle?.close().catch(() => undefined);
6530
- if (!renamed) {
6531
- await promises_.rm(tempPath, { force: true }).catch(() => undefined);
6532
- }
6533
- }
6534
- }
6535
-
6536
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/error-detail.js
6537
- const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
6538
- function formatErrorDetail(value) {
6539
- return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
6540
- }
6541
-
6542
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-symlink.js
6543
-
6544
-
6545
-
6546
-
6547
-
6548
-
6549
- function normalizeSymlinkResolutionError(error) {
6550
- if (isSymlinkOpenError(error)) {
6551
- throw new errors_FsSafeError("symlink", "symlink path could not be resolved", {
6552
- cause: error instanceof Error ? error : undefined,
6553
- });
6554
- }
6555
- if (!path_isNotFoundPathError(error))
6556
- throw error;
6557
- }
6558
- async function resolveSymlinkHopPath(symlinkPath) {
6559
- try {
6560
- return external_node_path_.resolve(await promises_.realpath(symlinkPath));
6561
- }
6562
- catch (error) {
6563
- normalizeSymlinkResolutionError(error);
6564
- const linkTarget = await promises_.readlink(symlinkPath);
6565
- return resolvePathViaExistingAncestor(external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget));
6566
- }
6567
- }
6568
- function root_path_symlink_resolveSymlinkHopPathSync(symlinkPath) {
6569
- try {
6570
- return path.resolve(fs.realpathSync(symlinkPath));
6571
- }
6572
- catch (error) {
6573
- normalizeSymlinkResolutionError(error);
6574
- const linkTarget = fs.readlinkSync(symlinkPath);
6575
- return resolvePathViaExistingAncestorSync(path.resolve(path.dirname(symlinkPath), linkTarget));
6576
- }
6577
- }
6578
-
6579
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/safe-path-segment.js
6580
-
6581
- const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
6582
- const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
6583
- // Windows treats "C:name" as relative to the drive's current directory even
6584
- // though path.win32.isAbsolute() reports false.
6585
- const DRIVE_RELATIVE_PREFIX = /^[A-Za-z]:(?![\\/])/;
6586
- const HYPHEN_CHAR_CODE = 0x2d;
6587
- function safe_path_segment_isDriveRelativePath(value) {
6588
- return DRIVE_RELATIVE_PREFIX.test(value);
6589
- }
6590
- function assertNoDriveRelativePathSegments(value, label) {
6591
- if (value.split("/").some(safe_path_segment_isDriveRelativePath)) {
6592
- throw new errors_FsSafeError("invalid-path", `${label} must not contain a drive letter`);
6593
- }
6594
- return value;
6595
- }
6596
- function trimHyphenEdges(value) {
6597
- let start = 0;
6598
- let end = value.length;
6599
- while (start < end && value.charCodeAt(start) === HYPHEN_CHAR_CODE) {
6600
- start += 1;
6601
- }
6602
- while (end > start && value.charCodeAt(end - 1) === HYPHEN_CHAR_CODE) {
6603
- end -= 1;
6604
- }
6605
- return start === 0 && end === value.length ? value : value.slice(start, end);
6606
- }
6607
- function isSafePathSegment(segment, options = {}) {
6608
- return (segment !== "" &&
6609
- segment !== "." &&
6610
- segment !== ".." &&
6611
- !segment.includes("/") &&
6612
- !segment.includes("\\") &&
6613
- !segment.includes("\0") &&
6614
- (options.allowDotPrefix === true || !segment.startsWith(".")) &&
6615
- (options.allowDotPrefix === true
6616
- ? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
6617
- : SAFE_PATH_SEGMENT_PATTERN.test(segment)));
6618
- }
6619
- function assertSafePathSegment(segment, options = {}) {
6620
- // Validate the exact value callers will later join into paths; trimming here
6621
- // would let whitespace-padded ids pass and then be used verbatim.
6622
- if (!isSafePathSegment(segment, options)) {
6623
- throw new FsSafeError("invalid-path", `${options.label ?? "path segment"} must be a safe path segment`);
6624
- }
6625
- return segment;
6626
- }
6627
- function sanitizeSafePathSegment(value, fallback, options = {}) {
6628
- const sanitized = value
6629
- .trim()
6630
- .replace(/[\\/]+/g, "-")
6631
- .replace(/\0/g, "")
6632
- .replace(/[^A-Za-z0-9._-]+/g, "-");
6633
- const trimmed = trimHyphenEdges(sanitized);
6634
- if (isSafePathSegment(trimmed, options)) {
6635
- return trimmed;
6636
- }
6637
- return assertSafePathSegment(fallback, { ...options, label: "fallback path segment" });
6638
- }
6639
- function assertSafePathPrefix(prefix, options = {}) {
6640
- // Prefixes are often derived from safe filenames. Normalize harmless
6641
- // filename characters first, but still reject real path-control bytes.
6642
- if (prefix.includes("/") || prefix.includes("\\") || prefix.includes("\0")) {
6643
- return assertSafePathSegment(prefix, {
6644
- allowDotPrefix: true,
6645
- ...options,
6646
- label: options.label ?? "path prefix",
6647
- });
6937
+ finally {
6938
+ if (ownsReclaimGuard) {
6939
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
6940
+ }
6648
6941
  }
6649
- return assertSafePathSegment(prefix.replace(/[^A-Za-z0-9._-]+/g, "-"), {
6650
- allowDotPrefix: true,
6651
- ...options,
6652
- label: options.label ?? "path prefix",
6653
- });
6654
6942
  }
6655
6943
 
6656
- // EXTERNAL MODULE: external "node:os"
6657
- var external_node_os_ = __webpack_require__(8161);
6658
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/short-path.js
6659
-
6944
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
6660
6945
 
6661
- function shortPath(value) {
6662
- const home = external_node_os_.homedir();
6663
- const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
6664
- return formatErrorDetail(shortened);
6946
+ function createSidecarLockHandle(params) {
6947
+ let released = false;
6948
+ let releasePromise;
6949
+ let releaseStarted = false;
6950
+ const release = async () => {
6951
+ if (released)
6952
+ return;
6953
+ if (!releasePromise) {
6954
+ const retry = releaseStarted;
6955
+ releaseStarted = true;
6956
+ releasePromise = (async () => {
6957
+ await params.release({ retry });
6958
+ released = true;
6959
+ })();
6960
+ }
6961
+ try {
6962
+ await releasePromise;
6963
+ }
6964
+ finally {
6965
+ releasePromise = undefined;
6966
+ }
6967
+ };
6968
+ return {
6969
+ lockPath: params.lockPath,
6970
+ normalizedTargetPath: params.normalizedTargetPath,
6971
+ verifyStillHeld: params.verifyStillHeld,
6972
+ release,
6973
+ [Symbol.asyncDispose]: release,
6974
+ };
6975
+ }
6976
+ function createHeldSidecarLockHandle(params) {
6977
+ return createSidecarLockHandle({
6978
+ lockPath: params.held.lockPath,
6979
+ normalizedTargetPath: params.normalizedTargetPath,
6980
+ verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
6981
+ lockRoot: params.held.lockRoot,
6982
+ parsePayload: params.held.parsePayload,
6983
+ }),
6984
+ release: (options) => params.release(options),
6985
+ });
6665
6986
  }
6666
6987
 
6667
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
6668
-
6669
-
6670
-
6671
-
6672
-
6988
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
6673
6989
 
6674
6990
 
6675
6991
 
6676
6992
 
6677
6993
 
6678
6994
 
6679
- const ROOT_PATH_ALIAS_POLICIES = {
6680
- strict: Object.freeze({
6681
- allowFinalSymlinkForUnlink: false,
6682
- allowFinalHardlinkForUnlink: false,
6683
- }),
6684
- unlinkTarget: Object.freeze({
6685
- allowFinalSymlinkForUnlink: true,
6686
- allowFinalHardlinkForUnlink: true,
6687
- }),
6688
- };
6689
- async function resolveRootPath(params) {
6690
- try {
6691
- return await resolveRootPathInternal(params);
6692
- }
6693
- catch (error) {
6694
- throw sanitizeRootPathError(error);
6995
+ const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
6996
+ const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
6997
+ const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
6998
+ function getGlobalManagers() {
6999
+ const globalWithState = globalThis;
7000
+ if (!globalWithState[GLOBAL_STATE_KEY]) {
7001
+ globalWithState[GLOBAL_STATE_KEY] = new Map();
6695
7002
  }
7003
+ return globalWithState[GLOBAL_STATE_KEY];
6696
7004
  }
6697
- async function resolveRootPathInternal(params) {
6698
- assertValidRootPathInputs(params);
6699
- const rootPath = external_node_path_.resolve(params.rootPath);
6700
- const absolutePath = external_node_path_.resolve(params.absolutePath);
6701
- const rootCanonicalPath = params.rootCanonicalPath
6702
- ? external_node_path_.resolve(params.rootCanonicalPath)
6703
- : await resolvePathViaExistingAncestor(rootPath);
6704
- const context = createBoundaryResolutionContext({
6705
- resolveParams: params,
6706
- rootPath,
6707
- absolutePath,
6708
- rootCanonicalPath,
6709
- outsideLexicalCanonicalPath: await resolveOutsideLexicalCanonicalPathAsync({
6710
- rootPath,
6711
- absolutePath,
6712
- }),
6713
- });
6714
- const outsideResult = await resolveOutsideRootPathAsync({
6715
- boundaryLabel: params.boundaryLabel,
6716
- context,
6717
- });
6718
- if (outsideResult) {
6719
- return outsideResult;
7005
+ function resolveManagerState(key) {
7006
+ const managers = getGlobalManagers();
7007
+ let state = managers.get(key);
7008
+ if (!state) {
7009
+ state = {
7010
+ cleanupRegistered: false,
7011
+ held: new Map(),
7012
+ reclaimCleanupRegistered: false,
7013
+ reclaimGuards: new Set(),
7014
+ };
7015
+ managers.set(key, state);
6720
7016
  }
6721
- return resolveRootPathLexicalAsync({
6722
- params,
6723
- absolutePath: context.absolutePath,
6724
- rootPath: context.rootPath,
6725
- rootCanonicalPath: context.rootCanonicalPath,
6726
- });
7017
+ else {
7018
+ // The global manager symbol is shared across package copies and hot reloads.
7019
+ // Backfill state created by fs-safe versions that predate reclaim guards.
7020
+ state.reclaimCleanupRegistered ??= false;
7021
+ state.reclaimGuards ??= new Set();
7022
+ for (const held of state.held.values()) {
7023
+ held.refCount ??= 1;
7024
+ }
7025
+ }
7026
+ return state;
6727
7027
  }
6728
- function resolveRootPathSync(params) {
7028
+ function snapshotMatchesSync(lockPath, observed) {
7029
+ let fd;
6729
7030
  try {
6730
- return resolveRootPathSyncInternal(params);
7031
+ const beforeStat = external_node_fs_.lstatSync(lockPath);
7032
+ if (!beforeStat.isFile()) {
7033
+ return false;
7034
+ }
7035
+ const openFlags = external_node_fs_.constants.O_RDONLY |
7036
+ (process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
7037
+ ? external_node_fs_.constants.O_NOFOLLOW
7038
+ : 0) |
7039
+ (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0);
7040
+ fd = external_node_fs_.openSync(lockPath, openFlags);
7041
+ const openedStat = external_node_fs_.fstatSync(fd);
7042
+ if (!openedStat.isFile()) {
7043
+ return false;
7044
+ }
7045
+ if (observed.raw !== undefined && openedStat.size !== Buffer.byteLength(observed.raw)) {
7046
+ return false;
7047
+ }
7048
+ const raw = external_node_fs_.readFileSync(fd, "utf8");
7049
+ const afterStat = external_node_fs_.lstatSync(lockPath);
7050
+ if (!afterStat.isFile() || !file_identity_sameFileIdentity(beforeStat, afterStat)) {
7051
+ return false;
7052
+ }
7053
+ return sidecarLockSnapshotMatches({ raw, payload: null, stat: afterStat }, observed);
6731
7054
  }
6732
- catch (error) {
6733
- throw sanitizeRootPathError(error);
7055
+ catch {
7056
+ return false;
6734
7057
  }
6735
- }
6736
- function resolveRootPathSyncInternal(params) {
6737
- assertValidRootPathInputs(params);
6738
- const rootPath = path.resolve(params.rootPath);
6739
- const absolutePath = path.resolve(params.absolutePath);
6740
- const rootCanonicalPath = params.rootCanonicalPath
6741
- ? path.resolve(params.rootCanonicalPath)
6742
- : resolvePathViaExistingAncestorSync(rootPath);
6743
- const context = createBoundaryResolutionContext({
6744
- resolveParams: params,
6745
- rootPath,
6746
- absolutePath,
6747
- rootCanonicalPath,
6748
- outsideLexicalCanonicalPath: resolveOutsideLexicalCanonicalPathSync({
6749
- rootPath,
6750
- absolutePath,
6751
- }),
6752
- });
6753
- const outsideResult = resolveOutsideRootPathSync({
6754
- boundaryLabel: params.boundaryLabel,
6755
- context,
6756
- });
6757
- if (outsideResult) {
6758
- return outsideResult;
7058
+ finally {
7059
+ if (fd !== undefined) {
7060
+ try {
7061
+ external_node_fs_.closeSync(fd);
7062
+ }
7063
+ catch {
7064
+ // Best-effort process-exit cleanup.
7065
+ }
7066
+ }
6759
7067
  }
6760
- return resolveRootPathLexicalSync({
6761
- params,
6762
- absolutePath: context.absolutePath,
6763
- rootPath: context.rootPath,
6764
- rootCanonicalPath: context.rootCanonicalPath,
6765
- });
6766
7068
  }
6767
- function sanitizeRootPathError(error) {
6768
- if (error instanceof Error) {
6769
- error.message = formatErrorDetail(error.message);
7069
+ function releaseAllReclaimGuardsSync(state) {
7070
+ for (const reclaimGuardPath of state.reclaimGuards) {
7071
+ try {
7072
+ external_node_fs_.rmdirSync(reclaimGuardPath);
7073
+ state.reclaimGuards.delete(reclaimGuardPath);
7074
+ }
7075
+ catch {
7076
+ // Best-effort process-exit cleanup. A surviving guard fails closed.
7077
+ }
6770
7078
  }
6771
- return error;
6772
7079
  }
6773
- function assertValidRootPathInputs(params) {
6774
- path_assertNoNulPathInput(params.rootPath, "root path contains a NUL byte");
6775
- path_assertNoNulPathInput(params.absolutePath, "absolute path contains a NUL byte");
6776
- assertNoEmbeddedDriveRelativeSegment(params.rootPath, "root path");
6777
- assertNoEmbeddedDriveRelativeSegment(params.absolutePath, "absolute path");
6778
- if (params.rootCanonicalPath !== undefined) {
6779
- path_assertNoNulPathInput(params.rootCanonicalPath, "canonical root path contains a NUL byte");
6780
- assertNoEmbeddedDriveRelativeSegment(params.rootCanonicalPath, "canonical root path");
7080
+ function releaseAllLocksSync(state) {
7081
+ for (const [normalizedTargetPath, held] of state.held) {
7082
+ void held.handle.close().catch(() => undefined);
7083
+ try {
7084
+ if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
7085
+ external_node_fs_.rmSync(held.lockPath, { force: true });
7086
+ }
7087
+ }
7088
+ catch {
7089
+ // Best-effort process-exit cleanup.
7090
+ }
7091
+ state.held.delete(normalizedTargetPath);
6781
7092
  }
7093
+ releaseAllReclaimGuardsSync(state);
6782
7094
  }
6783
- function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
6784
- if (process.platform !== "win32") {
7095
+ function ensureGlobalExitCleanupRegistered() {
7096
+ const globalWithCleanup = globalThis;
7097
+ if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
6785
7098
  return;
6786
- }
6787
- const root = external_node_path_.parse(filePath).root;
6788
- assertNoDriveRelativePathSegments(filePath.slice(root.length).replaceAll("\\", "/"), label);
6789
- }
6790
- function isPromiseLike(value) {
6791
- return Boolean(value &&
6792
- (typeof value === "object" || typeof value === "function") &&
6793
- "then" in value &&
6794
- typeof value.then === "function");
6795
- }
6796
- function createLexicalTraversalState(params) {
6797
- const rawAbsolutePath = params.params.absolutePath;
6798
- const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
6799
- const relative = rawRelativePath ?? external_node_path_.relative(params.rootPath, params.absolutePath);
6800
- return {
6801
- segments: splitTraversalSegments(relative),
6802
- allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
6803
- canonicalCursor: params.rootCanonicalPath,
6804
- lexicalCursor: params.rootPath,
6805
- preserveFinalSymlink: false,
6806
- };
6807
- }
6808
- function createLexicalTraversalContext(params) {
6809
- return {
6810
- state: createLexicalTraversalState(params),
6811
- resolveParams: params.params,
6812
- rootPath: params.rootPath,
6813
- rootCanonicalPath: params.rootCanonicalPath,
6814
- absolutePath: params.absolutePath,
7099
+ globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
7100
+ const cleanup = () => {
7101
+ for (const state of getGlobalManagers().values()) {
7102
+ releaseAllLocksSync(state);
7103
+ }
6815
7104
  };
7105
+ globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
7106
+ process.on("exit", cleanup);
6816
7107
  }
6817
- function splitTraversalSegments(value) {
6818
- return value
6819
- .split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
6820
- .filter((segment) => Boolean(segment) && segment !== ".");
6821
- }
6822
- function rawPathRelativeToRoot(rootPath, candidatePath) {
6823
- if (!external_node_path_.isAbsolute(candidatePath)) {
6824
- return undefined;
6825
- }
6826
- const root = external_node_path_.resolve(rootPath);
6827
- const candidate = process.platform === "win32"
6828
- ? candidatePath.replaceAll("/", external_node_path_.sep)
6829
- : candidatePath;
6830
- if (candidate === root) {
6831
- return "";
6832
- }
6833
- const rootWithSep = root.endsWith(external_node_path_.sep) ? root : `${root}${external_node_path_.sep}`;
6834
- const candidatePrefix = candidate.slice(0, rootWithSep.length);
6835
- const prefixMatches = process.platform === "win32"
6836
- ? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
6837
- : candidatePrefix === rootWithSep;
6838
- return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
6839
- }
6840
- function assertLexicalCursorInsideBoundary(context, candidatePath) {
6841
- assertInsideBoundary({
6842
- boundaryLabel: context.resolveParams.boundaryLabel,
6843
- rootCanonicalPath: context.rootCanonicalPath,
6844
- candidatePath,
6845
- absolutePath: context.absolutePath,
6846
- });
6847
- }
6848
- function applyMissingSuffixToCanonicalCursor(context, missingFromIndex) {
6849
- const missingSuffix = context.state.segments.slice(missingFromIndex);
6850
- for (const segment of missingSuffix) {
6851
- advanceCanonicalCursorForSegment(context, segment);
6852
- }
6853
- }
6854
- function advanceCanonicalCursorForSegment(context, segment) {
6855
- context.state.canonicalCursor = external_node_path_.resolve(context.state.canonicalCursor, segment);
6856
- assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
6857
- }
6858
- function finalizeLexicalResolution(context, kind) {
6859
- assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
6860
- return buildResolvedRootPath({
6861
- absolutePath: context.absolutePath,
6862
- canonicalPath: context.state.canonicalCursor,
6863
- rootPath: context.rootPath,
6864
- rootCanonicalPath: context.rootCanonicalPath,
6865
- kind,
6866
- });
6867
- }
6868
- function handleLexicalLstatFailure(context, error, missingFromIndex) {
6869
- if (!path_isNotFoundPathError(error)) {
7108
+ async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
7109
+ const current = state.held.get(normalizedTargetPath);
7110
+ if (current !== held) {
6870
7111
  return false;
6871
7112
  }
6872
- applyMissingSuffixToCanonicalCursor(context, missingFromIndex);
6873
- return true;
6874
- }
6875
- function handleLexicalStatReadFailure(context, error, missingFromIndex) {
6876
- if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
6877
- return null;
7113
+ if (held.releasePromise) {
7114
+ await held.releasePromise;
7115
+ return true;
6878
7116
  }
6879
- throw error;
6880
- }
6881
- function handleLexicalStatDisposition(context, params) {
6882
- if (!params.isSymbolicLink) {
6883
- advanceCanonicalCursorForSegment(context, params.segment);
6884
- return "continue";
7117
+ if (options.force) {
7118
+ held.refCount = 0;
6885
7119
  }
6886
- if (context.resolveParams.rejectSymlinks === true && params.isLast) {
6887
- throw new errors_FsSafeError("symlink", "symlink path component not allowed");
7120
+ else if (!options.retry && held.refCount > 0) {
7121
+ held.refCount -= 1;
6888
7122
  }
6889
- if (context.state.allowFinalSymlink && params.isLast) {
6890
- context.state.preserveFinalSymlink = true;
6891
- advanceCanonicalCursorForSegment(context, params.segment);
6892
- return "break";
7123
+ if (held.refCount > 0) {
7124
+ return false;
6893
7125
  }
6894
- return "resolve-link";
6895
- }
6896
- function applyResolvedSymlinkHop(context, linkCanonical) {
6897
- if (!path_isPathInside(context.rootCanonicalPath, linkCanonical)) {
6898
- throw symlinkEscapeError({
6899
- boundaryLabel: context.resolveParams.boundaryLabel,
6900
- rootCanonicalPath: context.rootCanonicalPath,
6901
- symlinkPath: context.state.lexicalCursor,
7126
+ held.releasePromise = (async () => {
7127
+ await held.handle.close().catch(() => undefined);
7128
+ await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
7129
+ lockRoot: held.lockRoot,
7130
+ parsePayload: held.parsePayload,
6902
7131
  });
6903
- }
6904
- context.state.canonicalCursor = linkCanonical;
6905
- context.state.lexicalCursor = linkCanonical;
6906
- }
6907
- function readLexicalStat(context, params) {
6908
- try {
6909
- const stat = params.read(context.state.lexicalCursor);
6910
- if (isPromiseLike(stat)) {
6911
- return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
7132
+ if (state.held.get(normalizedTargetPath) === held) {
7133
+ state.held.delete(normalizedTargetPath);
6912
7134
  }
6913
- return stat;
6914
- }
6915
- catch (error) {
6916
- return handleLexicalStatReadFailure(context, error, params.missingFromIndex);
7135
+ if (held.compromiseTimer) {
7136
+ clearInterval(held.compromiseTimer);
7137
+ held.compromiseTimer = undefined;
7138
+ }
7139
+ })();
7140
+ try {
7141
+ await held.releasePromise;
7142
+ return true;
6917
7143
  }
6918
- }
6919
- function resolveAndApplySymlinkHop(context, params) {
6920
- const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
6921
- if (isPromiseLike(linkCanonical)) {
6922
- return Promise.resolve(linkCanonical).then((value) => {
6923
- applyResolvedSymlinkHop(context, value);
6924
- });
7144
+ finally {
7145
+ held.releasePromise = undefined;
6925
7146
  }
6926
- applyResolvedSymlinkHop(context, linkCanonical);
6927
- }
6928
- function applyParentTraversalStep(context) {
6929
- context.state.lexicalCursor = external_node_path_.resolve(context.state.lexicalCursor, "..");
6930
- advanceCanonicalCursorForSegment(context, "..");
6931
7147
  }
6932
- function* iterateLexicalTraversal(state) {
6933
- for (let idx = 0; idx < state.segments.length; idx += 1) {
6934
- const segment = state.segments[idx] ?? "";
6935
- const isLast = idx === state.segments.length - 1;
6936
- yield { idx, segment, isLast };
6937
- }
7148
+ function handleForHeldLock(state, normalizedTargetPath, held) {
7149
+ return createHeldSidecarLockHandle({
7150
+ normalizedTargetPath,
7151
+ held,
7152
+ release: async (options) => await releaseHeldLock(state, normalizedTargetPath, held, { retry: options?.retry }),
7153
+ });
6938
7154
  }
6939
- async function resolveRootPathLexicalAsync(params) {
6940
- const context = createLexicalTraversalContext(params);
6941
- const { state } = context;
6942
- for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
6943
- if (segment === "..") {
6944
- applyParentTraversalStep(context);
6945
- continue;
6946
- }
6947
- state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
6948
- const stat = await readLexicalStat(context, {
6949
- missingFromIndex: idx,
6950
- read: (cursor) => promises_.lstat(cursor),
6951
- });
6952
- if (!stat) {
6953
- break;
6954
- }
6955
- const disposition = handleLexicalStatDisposition(context, {
6956
- isSymbolicLink: stat.isSymbolicLink(),
6957
- segment,
6958
- isLast,
6959
- });
6960
- if (disposition === "continue") {
6961
- continue;
6962
- }
6963
- if (disposition === "break") {
6964
- break;
6965
- }
6966
- await resolveAndApplySymlinkHop(context, {
6967
- resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
6968
- });
6969
- if (context.resolveParams.rejectSymlinks === true) {
6970
- throw new errors_FsSafeError("symlink", "symlink path component not allowed");
6971
- }
7155
+ function createSidecarLockManager(key) {
7156
+ const state = resolveManagerState(key);
7157
+ function ensureExitCleanupRegistered() {
7158
+ state.cleanupRegistered = true;
7159
+ state.reclaimCleanupRegistered = true;
7160
+ ensureGlobalExitCleanupRegistered();
6972
7161
  }
6973
- const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
6974
- return finalizeLexicalResolution(context, kind);
6975
- }
6976
- function resolveRootPathLexicalSync(params) {
6977
- const context = createLexicalTraversalContext(params);
6978
- const { state } = context;
6979
- for (let idx = 0; idx < state.segments.length; idx += 1) {
6980
- const segment = state.segments[idx] ?? "";
6981
- const isLast = idx === state.segments.length - 1;
6982
- if (segment === "..") {
6983
- applyParentTraversalStep(context);
6984
- continue;
6985
- }
6986
- state.lexicalCursor = path.join(state.lexicalCursor, segment);
6987
- const maybeStat = readLexicalStat(context, {
6988
- missingFromIndex: idx,
6989
- read: (cursor) => fs.lstatSync(cursor),
6990
- });
6991
- if (isPromiseLike(maybeStat)) {
6992
- throw new Error("Unexpected async lexical stat");
6993
- }
6994
- const stat = maybeStat;
6995
- if (!stat) {
6996
- break;
6997
- }
6998
- const disposition = handleLexicalStatDisposition(context, {
6999
- isSymbolicLink: stat.isSymbolicLink(),
7000
- segment,
7001
- isLast,
7002
- });
7003
- if (disposition === "continue") {
7004
- continue;
7005
- }
7006
- if (disposition === "break") {
7007
- break;
7008
- }
7009
- const maybeApplied = resolveAndApplySymlinkHop(context, {
7010
- resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
7162
+ async function acquire(options) {
7163
+ return await acquireSidecarLock(options, {
7164
+ held: state.held,
7165
+ reclaimGuards: state.reclaimGuards,
7166
+ ensureExitCleanupRegistered,
7167
+ handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
7168
+ releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
7011
7169
  });
7012
- if (isPromiseLike(maybeApplied)) {
7013
- throw new Error("Unexpected async symlink resolution");
7170
+ }
7171
+ async function withLock(options, fn) {
7172
+ const lock = await acquire(options);
7173
+ let result;
7174
+ try {
7175
+ result = await fn();
7014
7176
  }
7015
- if (context.resolveParams.rejectSymlinks === true) {
7016
- throw new FsSafeError("symlink", "symlink path component not allowed");
7177
+ catch (bodyError) {
7178
+ try {
7179
+ await lock.release();
7180
+ }
7181
+ catch (releaseError) {
7182
+ throw createSuppressedError(releaseError, bodyError, "file lock callback and release both failed");
7183
+ }
7184
+ throw bodyError;
7017
7185
  }
7186
+ await lock.release();
7187
+ return result;
7018
7188
  }
7019
- const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
7020
- return finalizeLexicalResolution(context, kind);
7021
- }
7022
- function resolveCanonicalOutsideLexicalPath(params) {
7023
- return params.outsideLexicalCanonicalPath ?? params.absolutePath;
7024
- }
7025
- function createBoundaryResolutionContext(params) {
7026
- const lexicalInside = path_isPathInside(params.rootPath, params.absolutePath);
7027
- const canonicalOutsideLexicalPath = resolveCanonicalOutsideLexicalPath({
7028
- absolutePath: params.absolutePath,
7029
- outsideLexicalCanonicalPath: params.outsideLexicalCanonicalPath,
7030
- });
7031
- assertLexicalBoundaryOrCanonicalAlias({
7032
- skipLexicalRootCheck: params.resolveParams.skipLexicalRootCheck,
7033
- lexicalInside,
7034
- canonicalOutsideLexicalPath,
7035
- rootCanonicalPath: params.rootCanonicalPath,
7036
- boundaryLabel: params.resolveParams.boundaryLabel,
7037
- rootPath: params.rootPath,
7038
- absolutePath: params.absolutePath,
7039
- });
7040
- return {
7041
- rootPath: params.rootPath,
7042
- absolutePath: params.absolutePath,
7043
- rootCanonicalPath: params.rootCanonicalPath,
7044
- lexicalInside,
7045
- canonicalOutsideLexicalPath,
7046
- };
7047
- }
7048
- async function resolveOutsideRootPathAsync(params) {
7049
- if (params.context.lexicalInside) {
7050
- return null;
7189
+ async function drain() {
7190
+ for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
7191
+ await releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
7192
+ }
7051
7193
  }
7052
- const kind = await getPathKind(params.context.absolutePath, false);
7053
- return buildOutsideRootPathFromContext({
7054
- boundaryLabel: params.boundaryLabel,
7055
- context: params.context,
7056
- kind,
7057
- });
7058
- }
7059
- function resolveOutsideRootPathSync(params) {
7060
- if (params.context.lexicalInside) {
7061
- return null;
7194
+ function reset() {
7195
+ releaseAllLocksSync(state);
7062
7196
  }
7063
- const kind = getPathKindSync(params.context.absolutePath, false);
7064
- return buildOutsideRootPathFromContext({
7065
- boundaryLabel: params.boundaryLabel,
7066
- context: params.context,
7067
- kind,
7068
- });
7069
- }
7070
- function buildOutsideRootPathFromContext(params) {
7071
- return buildOutsideLexicalRootPath({
7072
- boundaryLabel: params.boundaryLabel,
7073
- rootCanonicalPath: params.context.rootCanonicalPath,
7074
- absolutePath: params.context.absolutePath,
7075
- canonicalOutsideLexicalPath: params.context.canonicalOutsideLexicalPath,
7076
- rootPath: params.context.rootPath,
7077
- kind: params.kind,
7078
- });
7079
- }
7080
- async function resolveOutsideLexicalCanonicalPathAsync(params) {
7081
- if (path_isPathInside(params.rootPath, params.absolutePath)) {
7082
- return undefined;
7197
+ function heldEntries() {
7198
+ return Array.from(state.held.entries()).map(([normalizedTargetPath, held]) => ({
7199
+ normalizedTargetPath,
7200
+ lockPath: held.lockPath,
7201
+ acquiredAt: held.acquiredAt,
7202
+ metadata: held.metadata,
7203
+ forceRelease: () => releaseHeldLock(state, normalizedTargetPath, held, { force: true }),
7204
+ }));
7083
7205
  }
7084
- return await resolvePathViaExistingAncestor(params.absolutePath);
7206
+ return { acquire, withLock, drain, reset, heldEntries };
7085
7207
  }
7086
- function resolveOutsideLexicalCanonicalPathSync(params) {
7087
- if (isPathInside(params.rootPath, params.absolutePath)) {
7088
- return undefined;
7089
- }
7090
- return resolvePathViaExistingAncestorSync(params.absolutePath);
7208
+ async function withSidecarLock(targetPath, options, fn) {
7209
+ const manager = createSidecarLockManager(options.managerKey ?? `fs-safe.sidecar-lock:${targetPath}`);
7210
+ const { managerKey: _managerKey, ...acquireOptions } = options;
7211
+ return await manager.withLock({ ...acquireOptions, targetPath }, fn);
7091
7212
  }
7092
- function buildOutsideLexicalRootPath(params) {
7093
- assertInsideBoundary({
7094
- boundaryLabel: params.boundaryLabel,
7095
- rootCanonicalPath: params.rootCanonicalPath,
7096
- candidatePath: params.canonicalOutsideLexicalPath,
7097
- absolutePath: params.absolutePath,
7098
- });
7099
- return buildResolvedRootPath({
7100
- absolutePath: params.absolutePath,
7101
- canonicalPath: params.canonicalOutsideLexicalPath,
7102
- rootPath: params.rootPath,
7103
- rootCanonicalPath: params.rootCanonicalPath,
7104
- kind: params.kind,
7105
- });
7213
+
7214
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
7215
+
7216
+
7217
+
7218
+
7219
+
7220
+
7221
+
7222
+
7223
+
7224
+
7225
+
7226
+
7227
+
7228
+
7229
+
7230
+
7231
+
7232
+ function pinned_write_byteLength(input, encoding) {
7233
+ return typeof input === "string"
7234
+ ? Buffer.byteLength(input, encoding ?? "utf8")
7235
+ : input.byteLength;
7106
7236
  }
7107
- function assertLexicalBoundaryOrCanonicalAlias(params) {
7108
- if (params.skipLexicalRootCheck || params.lexicalInside) {
7109
- return;
7110
- }
7111
- if (path_isPathInside(params.rootCanonicalPath, params.canonicalOutsideLexicalPath)) {
7112
- return;
7237
+ function assertSafeBasename(basename) {
7238
+ if (!basename ||
7239
+ basename === "." ||
7240
+ basename === ".." ||
7241
+ basename.includes("/") ||
7242
+ basename.includes("\0")) {
7243
+ throw new errors_FsSafeError("invalid-path", "invalid target path");
7113
7244
  }
7114
- throw pathEscapeError({
7115
- boundaryLabel: params.boundaryLabel,
7116
- rootPath: params.rootPath,
7117
- absolutePath: params.absolutePath,
7118
- });
7119
7245
  }
7120
- function buildResolvedRootPath(params) {
7121
- return {
7122
- absolutePath: params.absolutePath,
7123
- canonicalPath: params.canonicalPath,
7124
- rootPath: params.rootPath,
7125
- rootCanonicalPath: params.rootCanonicalPath,
7126
- relativePath: relativeInsideRoot(params.rootCanonicalPath, params.canonicalPath),
7127
- exists: params.kind.exists,
7128
- kind: params.kind.kind,
7129
- };
7246
+ function assertWithinMaxBytes(bytes, maxBytes) {
7247
+ if (maxBytes !== undefined && bytes > maxBytes) {
7248
+ throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
7249
+ }
7130
7250
  }
7131
- async function getPathKind(absolutePath, preserveFinalSymlink) {
7251
+ async function syncFileBestEffort(handle) {
7132
7252
  try {
7133
- const stat = preserveFinalSymlink
7134
- ? await promises_.lstat(absolutePath)
7135
- : await promises_.stat(absolutePath);
7136
- return { exists: true, kind: toResolvedKind(stat) };
7253
+ await handle.sync();
7137
7254
  }
7138
7255
  catch (error) {
7139
- if (path_isNotFoundPathError(error)) {
7140
- return { exists: false, kind: "missing" };
7256
+ if (error?.code !== "EPERM") {
7257
+ throw error;
7141
7258
  }
7142
- throw error;
7143
7259
  }
7144
7260
  }
7145
- function getPathKindSync(absolutePath, preserveFinalSymlink) {
7146
- try {
7147
- const stat = preserveFinalSymlink ? fs.lstatSync(absolutePath) : fs.statSync(absolutePath);
7148
- return { exists: true, kind: toResolvedKind(stat) };
7149
- }
7150
- catch (error) {
7151
- if (isNotFoundPathError(error)) {
7152
- return { exists: false, kind: "missing" };
7261
+ async function writeStreamToHandle(stream, handle, maxBytes) {
7262
+ let bytes = 0;
7263
+ for await (const chunk of stream) {
7264
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
7265
+ bytes += buffer.byteLength;
7266
+ assertWithinMaxBytes(bytes, maxBytes);
7267
+ let offset = 0;
7268
+ while (offset < buffer.byteLength) {
7269
+ const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
7270
+ if (bytesWritten <= 0) {
7271
+ throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
7272
+ }
7273
+ offset += bytesWritten;
7153
7274
  }
7154
- throw error;
7155
7275
  }
7156
7276
  }
7157
- function toResolvedKind(stat) {
7158
- if (stat.isFile()) {
7159
- return "file";
7277
+ async function runPinnedWriteHelper(params) {
7278
+ const normalizedParams = { ...params, maxBytes: byte_budget_normalizeMaxBytes(params.maxBytes) };
7279
+ assertSafeBasename(params.basename);
7280
+ validatePinnedOperationPayload({
7281
+ relativeParentPath: params.relativeParentPath,
7282
+ });
7283
+ // The explicit compatibility policy uses the guarded Node fallback, where
7284
+ // content verification can replace the strict post-rename inode check.
7285
+ if (normalizedParams.onRenameIdentityMismatch === "verify-content") {
7286
+ return await runPinnedWriteFallback(normalizedParams);
7160
7287
  }
7161
- if (stat.isDirectory()) {
7162
- return "directory";
7288
+ const native = getNativeBinding();
7289
+ if (native) {
7290
+ return await runPinnedWriteNative(native, normalizedParams);
7163
7291
  }
7164
- if (stat.isSymbolicLink()) {
7165
- return "symlink";
7292
+ return await runPinnedWriteFallback(normalizedParams);
7293
+ }
7294
+ async function runPinnedWriteWithRenamePolicy(params) {
7295
+ const { targetPath, renameIdentity, ...writeParams } = params;
7296
+ if (renameIdentity !== "verify-content-with-lock") {
7297
+ return await runPinnedWriteHelper(writeParams);
7166
7298
  }
7167
- return "other";
7299
+ const relativeTargetPath = writeParams.relativeParentPath
7300
+ ? `${writeParams.relativeParentPath}/${writeParams.basename}`
7301
+ : writeParams.basename;
7302
+ const lockPath = external_node_path_.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
7303
+ return await withSidecarLock(writeParams.rootPath, {
7304
+ managerKey: `fs-safe.write:${targetPath}`,
7305
+ lockPath,
7306
+ staleMs: 30_000,
7307
+ timeoutMs: 5_000,
7308
+ payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
7309
+ retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
7310
+ }, async () => await runPinnedWriteHelper({
7311
+ ...writeParams,
7312
+ onRenameIdentityMismatch: "verify-content",
7313
+ }));
7168
7314
  }
7169
- function relativeInsideRoot(rootPath, targetPath) {
7170
- const relative = external_node_path_.relative(external_node_path_.resolve(rootPath), external_node_path_.resolve(targetPath));
7171
- if (!relative || relative === ".") {
7172
- return "";
7315
+ async function runPinnedWriteFallback(params) {
7316
+ let parentPath = params.relativeParentPath
7317
+ ? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
7318
+ : params.rootPath;
7319
+ if (params.mkdir) {
7320
+ // mkdirPathComponentsWithGuards may resolve the final component through
7321
+ // an in-root symlink (e.g. a skill-bank layout). Use its returned real
7322
+ // path for the subsequent guard and target path so we don't re-check the
7323
+ // original, possibly-symlinked, lexical path and reject it outright.
7324
+ parentPath = await mkdirPathComponentsWithGuards({
7325
+ rootReal: params.rootPath,
7326
+ targetPath: parentPath,
7327
+ beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
7328
+ });
7173
7329
  }
7174
- if (isPathRelativeEscape(relative)) {
7175
- return "";
7330
+ const parentGuard = params.mkdir
7331
+ ? await directory_guard_createAsyncDirectoryGuard(parentPath)
7332
+ : await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
7333
+ const targetPath = external_node_path_.join(parentPath, params.basename);
7334
+ if (params.overwrite === false) {
7335
+ 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), {
7336
+ onPostGuardFailure: async (openedHandle) => {
7337
+ // The parent failed verification, so targetPath may now resolve
7338
+ // somewhere else. Close the fd, but do not clean up by path.
7339
+ await openedHandle.close().catch(() => undefined);
7340
+ },
7341
+ });
7342
+ let created = true;
7343
+ try {
7344
+ const verificationIdentity = await handle.stat({ bigint: true });
7345
+ await handle.chmod(params.mode);
7346
+ if (params.input.kind === "buffer") {
7347
+ assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
7348
+ if (typeof params.input.data === "string") {
7349
+ await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
7350
+ }
7351
+ else {
7352
+ await handle.writeFile(params.input.data);
7353
+ }
7354
+ }
7355
+ else {
7356
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
7357
+ }
7358
+ await syncFileBestEffort(handle);
7359
+ const stat = await handle.stat();
7360
+ await syncDirectoryBestEffort(parentPath);
7361
+ // Publication is complete. A failed outer check must not remove its target.
7362
+ created = false;
7363
+ await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
7364
+ return { dev: stat.dev, ino: stat.ino };
7365
+ }
7366
+ finally {
7367
+ await handle.close().catch(() => undefined);
7368
+ if (created) {
7369
+ await promises_.rm(targetPath, { force: true }).catch(() => undefined);
7370
+ }
7371
+ }
7176
7372
  }
7177
- return relative;
7178
- }
7179
- function assertInsideBoundary(params) {
7180
- if (path_isPathInside(params.rootCanonicalPath, params.candidatePath)) {
7181
- return;
7373
+ // Private staging must not consume the destination basename's filename budget.
7374
+ const tempPath = external_node_path_.join(parentPath, `.fs-safe-${(0,external_node_crypto_.randomUUID)()}.tmp`);
7375
+ const tempFlags = external_node_fs_.constants.O_WRONLY |
7376
+ external_node_fs_.constants.O_CREAT |
7377
+ external_node_fs_.constants.O_EXCL |
7378
+ (process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_.constants
7379
+ ? external_node_fs_.constants.O_NOFOLLOW
7380
+ : 0);
7381
+ let handle;
7382
+ let tempStat;
7383
+ let readHandle;
7384
+ let renamed = false;
7385
+ try {
7386
+ handle = await promises_.open(tempPath, tempFlags, params.mode);
7387
+ let verificationIdentity = await handle.stat({ bigint: true });
7388
+ await handle.chmod(params.mode);
7389
+ if (params.input.kind === "buffer") {
7390
+ assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
7391
+ if (typeof params.input.data === "string") {
7392
+ await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
7393
+ }
7394
+ else {
7395
+ await handle.writeFile(params.input.data);
7396
+ }
7397
+ }
7398
+ else {
7399
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
7400
+ }
7401
+ tempStat = await handle.stat();
7402
+ const tempPathStat = await promises_.lstat(tempPath);
7403
+ if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
7404
+ throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
7405
+ }
7406
+ const expectedTempStat = tempStat;
7407
+ await syncFileBestEffort(handle);
7408
+ let verifiedIdentity = expectedTempStat;
7409
+ await withAsyncDirectoryGuards([parentGuard], async () => {
7410
+ await promises_.rename(tempPath, targetPath);
7411
+ renamed = true;
7412
+ await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
7413
+ await syncDirectoryBestEffort(parentPath);
7414
+ const targetStat = await promises_.lstat(targetPath);
7415
+ if (targetStat.isSymbolicLink()) {
7416
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
7417
+ }
7418
+ if (!file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
7419
+ // On filesystems like rclone FUSE, rename(2) can give the destination a
7420
+ // different inode from the source temp fd even with zero concurrency. The
7421
+ // caller must ensure mutual exclusion before passing "verify-content";
7422
+ // fall back to a content hash for this rename-boundary check only.
7423
+ if (params.onRenameIdentityMismatch !== "verify-content") {
7424
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
7425
+ }
7426
+ if (params.input.kind !== "buffer") {
7427
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
7428
+ }
7429
+ const expectedHash = sha256Hex(params.input.data, params.input.encoding);
7430
+ readHandle = await promises_.open(targetPath, read_open_flags_resolveReadOpenFlags());
7431
+ const readHandleStat = await readHandle.stat({ bigint: true });
7432
+ const actualHash = sha256Hex(await readHandle.readFile());
7433
+ if (actualHash !== expectedHash) {
7434
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
7435
+ }
7436
+ // The content-verified destination, not the old temp inode, is now pinned.
7437
+ verificationIdentity = readHandleStat;
7438
+ // Preserve the helper's legacy numeric return facts, not the private proof.
7439
+ verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
7440
+ }
7441
+ });
7442
+ await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
7443
+ return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
7444
+ }
7445
+ finally {
7446
+ await readHandle?.close().catch(() => undefined);
7447
+ await handle?.close().catch(() => undefined);
7448
+ if (!renamed) {
7449
+ await promises_.rm(tempPath, { force: true }).catch(() => undefined);
7450
+ }
7182
7451
  }
7183
- throw new Error(`Path resolves outside ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.absolutePath)}`);
7184
- }
7185
- function pathEscapeError(params) {
7186
- return new Error(`Path escapes ${params.boundaryLabel} (${shortPath(params.rootPath)}): ${shortPath(params.absolutePath)}`);
7187
- }
7188
- function symlinkEscapeError(params) {
7189
- return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
7190
7452
  }
7191
7453
 
7192
7454
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path-policy.js
@@ -8135,56 +8397,6 @@ async function verifyAtomicWriteResult(params) {
8135
8397
  }
8136
8398
  }
8137
8399
 
8138
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/strict-file-identity.js
8139
-
8140
- function identityMismatch() {
8141
- return new errors_FsSafeError("path-mismatch", "file identity changed or could not be verified");
8142
- }
8143
- function identityCheck(expected, platform) {
8144
- const known = {};
8145
- const check = (stat) => {
8146
- let complete = true;
8147
- for (const field of ["dev", "ino"]) {
8148
- const value = stat[field];
8149
- // Numeric receipts cannot recover identity bits already lost to rounding.
8150
- if (typeof value !== "bigint")
8151
- throw identityMismatch();
8152
- if (platform === "win32" && value === 0n) {
8153
- complete = false;
8154
- }
8155
- else {
8156
- if (known[field] !== undefined && known[field] !== value)
8157
- throw identityMismatch();
8158
- known[field] = value;
8159
- }
8160
- }
8161
- return complete;
8162
- };
8163
- if (expected && !check(expected))
8164
- throw identityMismatch();
8165
- return check;
8166
- }
8167
- // Retry only unknown Windows identities, retaining every known component so a
8168
- // later observation cannot erase a definite mismatch. Never reopen the file.
8169
- async function inspectFileIdentity(inspect, expected, platform = process.platform) {
8170
- const check = identityCheck(expected, platform);
8171
- for (let attempt = 0; attempt < 2; attempt++) {
8172
- const stat = await inspect();
8173
- if (check(stat))
8174
- return stat;
8175
- }
8176
- throw identityMismatch();
8177
- }
8178
- function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
8179
- const check = identityCheck(expected, platform);
8180
- for (let attempt = 0; attempt < 2; attempt++) {
8181
- const stat = inspect();
8182
- if (check(stat))
8183
- return stat;
8184
- }
8185
- throw identityMismatch();
8186
- }
8187
-
8188
8400
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-impl.js
8189
8401
 
8190
8402
 
@@ -8215,6 +8427,7 @@ function inspectFileIdentitySync(inspect, expected, platform = process.platform)
8215
8427
 
8216
8428
 
8217
8429
 
8430
+
8218
8431
 
8219
8432
 
8220
8433
  function logWarn(message) {
@@ -8290,25 +8503,15 @@ async function openVerifiedLocalFile(filePath, options) {
8290
8503
  if (preOpenStat) {
8291
8504
  await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
8292
8505
  }
8506
+ const openFlags = options?.symlinks === "follow-within-root"
8507
+ ? OPEN_READ_FOLLOW_FLAGS
8508
+ : OPEN_READ_FLAGS;
8509
+ await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
8293
8510
  let handle;
8294
8511
  try {
8295
- const openFlags = options?.symlinks === "follow-within-root"
8296
- ? OPEN_READ_FOLLOW_FLAGS
8297
- : OPEN_READ_FLAGS;
8298
- await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
8299
- handle = await promises_.open(filePath, openFlags);
8300
- try {
8301
- await fsSafeTestHooks?.afterOpen?.(filePath, handle);
8302
- }
8303
- catch (err) {
8304
- await handle.close().catch(() => { });
8305
- throw err;
8306
- }
8512
+ handle = await promises_.open(filePath, openFlags).catch((error) => recordFileOpenFailure(path_isNotFoundPathError(error) ? fileNotFoundError() : error, filePath));
8307
8513
  }
8308
8514
  catch (err) {
8309
- if (path_isNotFoundPathError(err)) {
8310
- throw fileNotFoundError();
8311
- }
8312
8515
  if (isSymlinkOpenError(err)) {
8313
8516
  throw new errors_FsSafeError("symlink", "symlink open blocked", { cause: err });
8314
8517
  }
@@ -8319,16 +8522,35 @@ async function openVerifiedLocalFile(filePath, options) {
8319
8522
  throw err;
8320
8523
  }
8321
8524
  try {
8525
+ await fsSafeTestHooks?.afterOpen?.(filePath, handle);
8322
8526
  const stat = await handle.stat();
8323
8527
  if (!stat.isFile()) {
8324
8528
  throw new errors_FsSafeError("not-file", "not a file");
8325
8529
  }
8326
8530
  // Keep numeric Stats for the public receipt, never for identity verification.
8327
- const identity = await inspectFileIdentity(() => handle.stat({ bigint: true }), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined);
8531
+ let openedIdentity;
8532
+ const identity = await inspectFileIdentity(async () => (openedIdentity = await handle.stat({ bigint: true })), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined).catch(async (error) => {
8533
+ if ([0, 1].includes(stat.nlink)) {
8534
+ await recordPreOpenFileChange(error, handle, filePath, preOpenStat, openedIdentity);
8535
+ }
8536
+ throw error;
8537
+ });
8328
8538
  if (options?.hardlinks === "reject" && stat.nlink > 1) {
8329
8539
  throw hardlinkedPathNotAllowedError();
8330
8540
  }
8331
- await inspectFileIdentity(async () => {
8541
+ const inspectPathIdentity = async (inspect) => {
8542
+ try {
8543
+ return await inspectFileIdentity(inspect, identity);
8544
+ }
8545
+ catch (error) {
8546
+ const failure = path_isNotFoundPathError(error) ? openedPathResolutionError(fileNotFoundError()) : error;
8547
+ if (stat.nlink <= 1 && (!preOpenStat || preOpenStat.nlink <= 1n)) {
8548
+ await recordOpenedFileFailure(failure, handle, filePath, identity);
8549
+ }
8550
+ throw failure;
8551
+ }
8552
+ };
8553
+ await inspectPathIdentity(async () => {
8332
8554
  const pathStat = options?.symlinks === "follow-within-root"
8333
8555
  ? await promises_.stat(filePath, { bigint: true })
8334
8556
  : await promises_.lstat(filePath, { bigint: true });
@@ -8336,26 +8558,26 @@ async function openVerifiedLocalFile(filePath, options) {
8336
8558
  throw new errors_FsSafeError("symlink", "symlink not allowed");
8337
8559
  }
8338
8560
  return pathStat;
8339
- }, identity);
8561
+ });
8340
8562
  await fsSafeTestHooks?.afterOpenedPathIdentityCheck?.(filePath, handle);
8341
- const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath);
8342
- await inspectFileIdentity(async () => {
8563
+ const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath)
8564
+ .catch(async (error) => {
8565
+ if (stat.nlink <= 1 && (!preOpenStat || preOpenStat.nlink <= 1n)) {
8566
+ await recordOpenedFileFailure(error, handle, filePath, identity);
8567
+ }
8568
+ throw error;
8569
+ });
8570
+ await inspectPathIdentity(async () => {
8343
8571
  const realStat = await promises_.stat(realPath, { bigint: true });
8344
8572
  if (options?.hardlinks === "reject" && realStat.nlink > 1n) {
8345
8573
  throw hardlinkedPathNotAllowedError();
8346
8574
  }
8347
8575
  return realStat;
8348
- }, identity);
8576
+ });
8349
8577
  return { opened: openResult({ handle, realPath, stat }), identity };
8350
8578
  }
8351
8579
  catch (err) {
8352
8580
  await handle.close().catch(() => { });
8353
- if (err instanceof errors_FsSafeError) {
8354
- throw err;
8355
- }
8356
- if (path_isNotFoundPathError(err)) {
8357
- throw fileNotFoundError();
8358
- }
8359
8581
  throw err;
8360
8582
  }
8361
8583
  }
@@ -8584,19 +8806,10 @@ async function openFileInRoot(root, params) {
8584
8806
  rejectUnsafeDeviceReads: true,
8585
8807
  rejectSymlinks: params.symlinks !== "follow-within-root",
8586
8808
  });
8587
- let opened;
8588
- try {
8589
- ({ opened } = await openVerifiedLocalFile(resolved, {
8590
- nonBlockingRead: params.nonBlockingRead,
8591
- symlinks: params.symlinks,
8592
- }));
8593
- }
8594
- catch (err) {
8595
- if (err instanceof errors_FsSafeError) {
8596
- throw err;
8597
- }
8598
- throw err;
8599
- }
8809
+ const { opened } = await openVerifiedLocalFile(resolved, {
8810
+ nonBlockingRead: params.nonBlockingRead,
8811
+ symlinks: params.symlinks,
8812
+ });
8600
8813
  if (params.hardlinks !== "allow" && opened.stat.nlink > 1) {
8601
8814
  await opened.handle.close().catch(() => { });
8602
8815
  throw hardlinkedPathNotAllowedError();
@@ -8648,9 +8861,7 @@ function emitWriteBoundaryWarning(reason) {
8648
8861
  logWarn(`security: fs-safe write boundary warning (${reason})`);
8649
8862
  }
8650
8863
  function buildAtomicWriteTempPath(targetPath) {
8651
- const dir = external_node_path_.dirname(targetPath);
8652
- const base = external_node_path_.basename(targetPath);
8653
- return external_node_path_.join(dir, `.${base}.${process.pid}.${(0,external_node_crypto_.randomUUID)()}.tmp`);
8864
+ return external_node_path_.join(external_node_path_.dirname(targetPath), `.fs-safe-${(0,external_node_crypto_.randomUUID)()}.tmp`);
8654
8865
  }
8655
8866
  function rootWriteQueueKey(root, relativePath) {
8656
8867
  return `${root.rootReal}\0${relativePath}`;
@@ -8889,7 +9100,7 @@ async function writeFileInRoot(root, params) {
8889
9100
  await writeFileFallback(root, params);
8890
9101
  return;
8891
9102
  }
8892
- const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
9103
+ const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations, params.overwrite);
8893
9104
  await serializePathWrite(pinned.targetPath, async () => {
8894
9105
  await commitPinnedWriteInRoot(root, pinned, params);
8895
9106
  });
@@ -9011,7 +9222,7 @@ async function removePathIfIdentityUnchanged(targetPath, identity) {
9011
9222
  await promises_.rm(targetPath);
9012
9223
  });
9013
9224
  }
9014
- async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
9225
+ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations, overwrite = true) {
9015
9226
  const { rootReal, rootWithSep, resolved } = await resolveGuardedWritePathInRoot(root, {
9016
9227
  relativePath,
9017
9228
  denyMutations,
@@ -9029,22 +9240,40 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode,
9029
9240
  if (!basename || basename === "." || basename === "/") {
9030
9241
  throw new errors_FsSafeError("invalid-path", "invalid target path");
9031
9242
  }
9243
+ // Exclusive publication cannot inherit an existing file's mode. Keep type
9244
+ // and alias checks, but never open a competing owner's record just to reject it.
9245
+ if (!overwrite) {
9246
+ try {
9247
+ const existing = await promises_.stat(resolved);
9248
+ if (!existing.isFile())
9249
+ throw new errors_FsSafeError("not-file", "not a file");
9250
+ if (existing.nlink > 1)
9251
+ throw hardlinkedPathNotAllowedError();
9252
+ throw new errors_FsSafeError("already-exists", "file already exists");
9253
+ }
9254
+ catch (error) {
9255
+ if (!path_isNotFoundPathError(error))
9256
+ throw error;
9257
+ }
9258
+ }
9032
9259
  let mode = requestedMode ?? 0o600;
9033
9260
  try {
9034
- const opened = await openFileInRoot(root, {
9035
- relativePath,
9036
- hardlinks: "reject",
9037
- nonBlockingRead: true,
9038
- symlinks: "follow-within-root",
9039
- });
9040
- try {
9041
- mode = requestedMode ?? (opened.stat.mode & 0o777);
9042
- if (!path_isPathInside(rootWithSep, opened.realPath)) {
9043
- throw outsideWorkspaceError();
9261
+ if (overwrite) {
9262
+ const opened = await openFileInRoot(root, {
9263
+ relativePath,
9264
+ hardlinks: "reject",
9265
+ nonBlockingRead: true,
9266
+ symlinks: "follow-within-root",
9267
+ });
9268
+ try {
9269
+ mode = requestedMode ?? (opened.stat.mode & 0o777);
9270
+ if (!path_isPathInside(rootWithSep, opened.realPath)) {
9271
+ throw outsideWorkspaceError();
9272
+ }
9273
+ }
9274
+ finally {
9275
+ await opened.handle.close().catch(() => { });
9044
9276
  }
9045
- }
9046
- finally {
9047
- await opened.handle.close().catch(() => { });
9048
9277
  }
9049
9278
  }
9050
9279
  catch (err) {
@@ -94039,4 +94268,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
94039
94268
  // module factories are used so entry inlining is disabled
94040
94269
  // startup
94041
94270
  // Load entry module and return exports
94042
- var __webpack_exports__ = __webpack_require__(8441);
94271
+ var __webpack_exports__ = __webpack_require__(8853);