@lousy-agents/agent-shell 5.21.3 → 5.21.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1979 -1750
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __rspack_createRequire } from "node:module";
3
3
  const __rspack_createRequire_require = __rspack_createRequire(import.meta.url);
4
4
  var __webpack_modules__ = ({
5
- 699(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
5
+ 461(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6
6
 
7
7
  ;// CONCATENATED MODULE: external "node:child_process"
8
8
  const external_node_child_process_namespaceObject = __rspack_createRequire_require("node:child_process");
@@ -154,6 +154,85 @@ function sameFileIdentityForCleanup(left, right, platform = process.platform) {
154
154
  return sameStatValue(left.dev, right.dev) && sameStatValue(left.ino, right.ino);
155
155
  }
156
156
 
157
+ ;// CONCATENATED MODULE: external "node:async_hooks"
158
+ const external_node_async_hooks_namespaceObject = __rspack_createRequire_require("node:async_hooks");
159
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/file-observation.js
160
+
161
+ const active = new external_node_async_hooks_namespaceObject.AsyncLocalStorage();
162
+ function recordFileObservationFailure(error, kind) {
163
+ const failures = active.getStore();
164
+ if (!failures)
165
+ return;
166
+ let kinds = failures.get(error);
167
+ if (!kinds)
168
+ failures.set(error, kinds = new Set());
169
+ kinds.add(kind);
170
+ }
171
+ function isFileObservationFailure(error, kind) {
172
+ return active.getStore()?.get(error)?.has(kind) === true;
173
+ }
174
+ // Each Root observation owns its receipts, including nested and concurrent reads.
175
+ function fileObservation() {
176
+ const failures = new Map();
177
+ return {
178
+ run(operation) { return active.run(failures, operation); },
179
+ has(error, kind) { return failures.get(error)?.has(kind) === true; },
180
+ };
181
+ }
182
+
183
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/strict-file-identity.js
184
+
185
+
186
+ function identityMismatch() {
187
+ const error = new errors_FsSafeError("path-mismatch", "file identity changed or could not be verified");
188
+ recordFileObservationFailure(error, "identity");
189
+ return error;
190
+ }
191
+ function identityCheck(expected, platform) {
192
+ const known = {};
193
+ const check = (stat) => {
194
+ let complete = true;
195
+ for (const field of ["dev", "ino"]) {
196
+ const value = stat[field];
197
+ // Numeric receipts cannot recover identity bits already lost to rounding.
198
+ if (typeof value !== "bigint")
199
+ throw identityMismatch();
200
+ if (platform === "win32" && value === 0n) {
201
+ complete = false;
202
+ }
203
+ else {
204
+ if (known[field] !== undefined && known[field] !== value)
205
+ throw identityMismatch();
206
+ known[field] = value;
207
+ }
208
+ }
209
+ return complete;
210
+ };
211
+ if (expected && !check(expected))
212
+ throw identityMismatch();
213
+ return check;
214
+ }
215
+ // Retry only unknown Windows identities, retaining every known component so a
216
+ // later observation cannot erase a definite mismatch. Never reopen the file.
217
+ async function inspectFileIdentity(inspect, expected, platform = process.platform) {
218
+ const check = identityCheck(expected, platform);
219
+ for (let attempt = 0; attempt < 2; attempt++) {
220
+ const stat = await inspect();
221
+ if (check(stat))
222
+ return stat;
223
+ }
224
+ throw identityMismatch();
225
+ }
226
+ function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
227
+ const check = identityCheck(expected, platform);
228
+ for (let attempt = 0; attempt < 2; attempt++) {
229
+ const stat = inspect();
230
+ if (check(stat))
231
+ return stat;
232
+ }
233
+ throw identityMismatch();
234
+ }
235
+
157
236
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path.js
158
237
 
159
238
 
@@ -368,6 +447,7 @@ function normalizeRemovePathError(error) {
368
447
 
369
448
 
370
449
 
450
+
371
451
  async function directory_guard_createAsyncDirectoryGuard(dir) {
372
452
  const stat = await promises_namespaceObject.lstat(dir);
373
453
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
@@ -432,6 +512,15 @@ function directory_guard_createNearestExistingSyncDirectoryGuard(rootReal, targe
432
512
  }
433
513
  return directory_guard_createSyncDirectoryGuard(root);
434
514
  }
515
+ // Recovery receipts must retain every identity bit, including on Windows.
516
+ async function inspectDirectoryIdentity(dir, expected) {
517
+ return await inspectFileIdentity(async () => {
518
+ const stat = await promises_namespaceObject.lstat(dir, { bigint: true });
519
+ if (stat.isSymbolicLink() || !stat.isDirectory())
520
+ throw root_errors_directoryComponentNotDirectoryError();
521
+ return stat;
522
+ }, expected);
523
+ }
435
524
 
436
525
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/directory-durability.js
437
526
 
@@ -873,6 +962,8 @@ async function guardedRename(params) {
873
962
  ? await createNearestExistingDirectoryGuard(params.targetRoot, path.dirname(params.to))
874
963
  : await createAsyncDirectoryGuard(path.dirname(params.to));
875
964
  await withAsyncDirectoryGuards([sourceGuard, targetGuard], async () => {
965
+ // Authority must survive all awaited guards; do not yield before rename dispatch.
966
+ params.assertBeforeRename?.();
876
967
  await fs.rename(params.from, params.to);
877
968
  }, { verifyAfter: params.verifyAfter });
878
969
  }
@@ -1042,6 +1133,49 @@ function mergeDenyMutationPolicies(defaultPolicy, callPolicy) {
1042
1133
  };
1043
1134
  }
1044
1135
 
1136
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/opened-file-failure.js
1137
+
1138
+
1139
+
1140
+ function recordFileOpenFailure(error, filePath) {
1141
+ recordFileObservationFailure(error, `open:${filePath}`);
1142
+ throw error;
1143
+ }
1144
+ function openedPathResolutionError(error = new errors_FsSafeError("path-mismatch", "unable to resolve opened file path")) {
1145
+ recordFileObservationFailure(error, "resolution");
1146
+ return error;
1147
+ }
1148
+ async function recordPreOpenFileChange(error, handle, filePath, before, opened) {
1149
+ if (!isFileObservationFailure(error, "identity") || !before?.isFile() || !opened?.isFile() ||
1150
+ ![0n, 1n].includes(before.nlink) || ![0n, 1n].includes(opened.nlink))
1151
+ return;
1152
+ try {
1153
+ await inspectFileIdentity(async () => before);
1154
+ const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), opened);
1155
+ if (current.isFile() && [0n, 1n].includes(current.nlink) &&
1156
+ (before.dev !== current.dev || before.ino !== current.ino)) {
1157
+ // A stale pathname sample is not proof that its former inode was unlinked.
1158
+ recordFileObservationFailure(error, `changed:${filePath}`);
1159
+ }
1160
+ }
1161
+ catch {
1162
+ // Unknown, changed, or closed descriptor evidence never permits a discard.
1163
+ }
1164
+ }
1165
+ async function recordOpenedFileFailure(error, handle, filePath, identity) {
1166
+ if ((!isFileObservationFailure(error, "resolution") && !isFileObservationFailure(error, "identity")) ||
1167
+ !identity.isFile() || identity.nlink > 1n)
1168
+ return;
1169
+ try {
1170
+ const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), identity);
1171
+ if (current.isFile() && current.nlink === 0n)
1172
+ recordFileObservationFailure(error, `unlinked:${filePath}`);
1173
+ }
1174
+ catch {
1175
+ // A closed, changed, or unknown descriptor provides no retry evidence.
1176
+ }
1177
+ }
1178
+
1045
1179
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/opened-realpath.js
1046
1180
 
1047
1181
 
@@ -1081,6 +1215,12 @@ async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
1081
1215
  }
1082
1216
  catch (err) {
1083
1217
  if (!path_isNotFoundPathError(err)) {
1218
+ // Windows can fail here on a deleted-but-open file. Brand only this
1219
+ // resolver operation; the caller must still prove unlink on the same fd.
1220
+ if (process.platform === "win32" && err instanceof Error &&
1221
+ ["EPERM", "EBADF"].includes(err.code ?? "")) {
1222
+ throw openedPathResolutionError(err);
1223
+ }
1084
1224
  throw err;
1085
1225
  }
1086
1226
  }
@@ -1088,7 +1228,7 @@ async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
1088
1228
  if (parentResolved) {
1089
1229
  return parentResolved;
1090
1230
  }
1091
- throw new errors_FsSafeError("path-mismatch", "unable to resolve opened file path");
1231
+ throw openedPathResolutionError();
1092
1232
  }
1093
1233
  async function resolveOpenedFileRealPathFromParent(handleStat, ioPath, statOptions) {
1094
1234
  let parentReal;
@@ -1582,7 +1722,7 @@ async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuar
1582
1722
  let renamed = false;
1583
1723
  let completed = false;
1584
1724
  try {
1585
- tempName = `.${params.basename}.${(0,external_node_crypto_namespaceObject.randomUUID)()}.native.tmp`;
1725
+ tempName = `.fs-safe-${(0,external_node_crypto_namespaceObject.randomUUID)()}.tmp`;
1586
1726
  tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(external_node_fs_namespaceObject.constants.O_WRONLY | external_node_fs_namespaceObject.constants.O_CREAT | external_node_fs_namespaceObject.constants.O_EXCL)).fd;
1587
1727
  tempIdentity = external_node_fs_namespaceObject.fstatSync(tempFd);
1588
1728
  const verificationIdentity = external_node_fs_namespaceObject.fstatSync(tempFd, { bigint: true });
@@ -1792,7 +1932,7 @@ const UNCOMMITTED_RENAME_ERRORS = new Set([
1792
1932
  "ENAMETOOLONG", "ENOENT", "ENOSPC", "ENOSYS", "ENOTDIR", "ENOTEMPTY", "ENOTSUP",
1793
1933
  "EPERM", "EROFS", "ETXTBSY", "EXDEV",
1794
1934
  ]);
1795
- function failure(error, details) {
1935
+ function native_staged_file_failure(error, details) {
1796
1936
  const code = error instanceof errors_FsSafeError
1797
1937
  ? error.code
1798
1938
  : error?.code === "EEXIST" ? "already-exists" : "helper-failed";
@@ -1924,10 +2064,10 @@ class NativeStagedFile {
1924
2064
  catch (error) {
1925
2065
  const { receipt: cleanup, error: cleanupError } = this.#finalize();
1926
2066
  if (cleanupError) {
1927
- throw failure(new AggregateError([error, cleanupError], "preparation and cleanup failed"), { phase: "prepare", publication: cleanup.publication, cleanup });
2067
+ throw native_staged_file_failure(new AggregateError([error, cleanupError], "preparation and cleanup failed"), { phase: "prepare", publication: cleanup.publication, cleanup });
1928
2068
  }
1929
2069
  if (cleanup.status === "preserved") {
1930
- throw failure(error, { phase: "prepare", publication: cleanup.publication, cleanup });
2070
+ throw native_staged_file_failure(error, { phase: "prepare", publication: cleanup.publication, cleanup });
1931
2071
  }
1932
2072
  throw error;
1933
2073
  }
@@ -1986,7 +2126,7 @@ class NativeStagedFile {
1986
2126
  const publication = this.#state.status === "closed"
1987
2127
  ? this.#state.receipt.publication
1988
2128
  : this.#state.publication;
1989
- throw failure(error, { phase: "publish", publication });
2129
+ throw native_staged_file_failure(error, { phase: "publish", publication });
1990
2130
  }
1991
2131
  }
1992
2132
  #finalize() {
@@ -2027,7 +2167,7 @@ class NativeStagedFile {
2027
2167
  status: outcome,
2028
2168
  resources,
2029
2169
  });
2030
- const error = errors.length ? failure(errors.length === 1 ? errors[0] : new AggregateError(errors, "staged cleanup failed"), { phase: "cleanup", publication: state.publication, cleanup: receipt }) : undefined;
2170
+ 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;
2031
2171
  this.#state = { status: "closed", receipt, error };
2032
2172
  return this.#state;
2033
2173
  }
@@ -2336,974 +2476,756 @@ function bounded_read_readFileDescriptorBoundedSync(fd, maxBytes) {
2336
2476
  }
2337
2477
  }
2338
2478
 
2339
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
2340
- let test_hooks_fsSafeTestHooks;
2341
- function allowFsSafeTestHooks() {
2342
- return false || process.env.VITEST === "true";
2343
- }
2344
- function getFsSafeTestHooks() {
2345
- return test_hooks_fsSafeTestHooks;
2346
- }
2347
- function __setFsSafeTestHooksForTest(hooks) {
2348
- if (hooks && !allowFsSafeTestHooks()) {
2349
- throw new Error("__setFsSafeTestHooksForTest is only available in tests");
2350
- }
2351
- test_hooks_fsSafeTestHooks = hooks;
2479
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/error-detail.js
2480
+ const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
2481
+ function formatErrorDetail(value) {
2482
+ return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
2352
2483
  }
2353
2484
 
2354
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
2355
-
2356
-
2357
-
2485
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-symlink.js
2358
2486
 
2359
2487
 
2360
2488
 
2361
2489
 
2362
2490
 
2363
2491
 
2364
- const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
2365
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
2366
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
2367
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
2368
- const SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN = new RegExp(`\\n(${SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX}[ \\t]{${SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS}})\\n$`);
2369
- function createSidecarLockOwnershipToken() {
2370
- let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
2371
- for (const byte of (0,external_node_crypto_namespaceObject.randomBytes)(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
2372
- for (let bit = 7; bit >= 0; bit -= 1) {
2373
- token += byte & (1 << bit) ? "\t" : " ";
2374
- }
2375
- }
2376
- return token;
2377
- }
2378
- function readSidecarLockOwnershipToken(raw) {
2379
- return SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN.exec(raw)?.[1];
2380
- }
2381
- function serializeSidecarLockPayload(payload) {
2382
- const ownershipToken = createSidecarLockOwnershipToken();
2383
- return {
2384
- raw: `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`,
2385
- ownershipToken,
2386
- };
2387
- }
2388
- function relativeSidecarLockPath(lockRoot, lockPath) {
2389
- const resolved = external_node_path_namespaceObject.resolve(lockPath);
2390
- const lexicalRelative = external_node_path_namespaceObject.relative(lockRoot.rootDir, resolved);
2391
- const relative = lexicalRelative !== ".." &&
2392
- !lexicalRelative.startsWith(`..${external_node_path_namespaceObject.sep}`) &&
2393
- !external_node_path_namespaceObject.isAbsolute(lexicalRelative)
2394
- ? lexicalRelative
2395
- : external_node_path_namespaceObject.relative(lockRoot.rootReal, resolved);
2396
- if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_namespaceObject.sep}`) || external_node_path_namespaceObject.isAbsolute(relative)) {
2397
- throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
2492
+ function normalizeSymlinkResolutionError(error) {
2493
+ if (isSymlinkOpenError(error)) {
2494
+ throw new errors_FsSafeError("symlink", "symlink path could not be resolved", {
2495
+ cause: error instanceof Error ? error : undefined,
2496
+ });
2398
2497
  }
2399
- return relative.split(external_node_path_namespaceObject.sep).join(external_node_path_namespaceObject.posix.sep);
2498
+ if (!path_isNotFoundPathError(error))
2499
+ throw error;
2400
2500
  }
2401
- function parseSidecarLockPayload(raw, parser) {
2402
- if (parser) {
2403
- return parser(raw);
2404
- }
2501
+ async function resolveSymlinkHopPath(symlinkPath) {
2405
2502
  try {
2406
- const parsed = JSON.parse(raw);
2407
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2503
+ return external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(symlinkPath));
2408
2504
  }
2409
- catch {
2410
- return null;
2505
+ catch (error) {
2506
+ normalizeSymlinkResolutionError(error);
2507
+ const linkTarget = await promises_namespaceObject.readlink(symlinkPath);
2508
+ return resolvePathViaExistingAncestor(external_node_path_namespaceObject.resolve(external_node_path_namespaceObject.dirname(symlinkPath), linkTarget));
2411
2509
  }
2412
2510
  }
2413
- async function readSidecarLockSnapshot(lockPath, options = {}) {
2414
- let handle;
2511
+ function root_path_symlink_resolveSymlinkHopPathSync(symlinkPath) {
2415
2512
  try {
2416
- if (options.lockRoot) {
2417
- const lockRoot = options.lockRoot;
2418
- const relative = relativeSidecarLockPath(lockRoot, lockPath);
2419
- const opened = await lockRoot.open(relative).catch(async (error) => {
2420
- if (error instanceof errors_FsSafeError && error.code === "path-mismatch") {
2421
- // An owner can unlink after open. Prove absence through the same root;
2422
- // acquisition retries create-only, so a later replacement stays guarded.
2423
- try {
2424
- await lockRoot.stat(relative);
2425
- }
2426
- catch (probeError) {
2427
- if (probeError instanceof errors_FsSafeError && probeError.code === "not-found") {
2428
- return null;
2429
- }
2430
- }
2431
- }
2432
- throw error;
2433
- });
2434
- if (!opened)
2435
- return null;
2436
- try {
2437
- const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
2438
- return {
2439
- raw,
2440
- payload: parseSidecarLockPayload(raw, options.parsePayload),
2441
- stat: opened.stat,
2442
- };
2443
- }
2444
- finally {
2445
- await opened.handle.close().catch(() => undefined);
2446
- }
2447
- }
2448
- const before = await promises_namespaceObject.lstat(lockPath);
2449
- if (!before.isFile() || before.isSymbolicLink()) {
2450
- if (options.rejectNonFile) {
2451
- throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
2452
- }
2453
- return null;
2454
- }
2455
- await getFsSafeTestHooks()?.beforeSidecarLockSnapshotOpen?.(lockPath);
2456
- const noFollow = process.platform !== "win32" && typeof external_node_fs_namespaceObject.constants.O_NOFOLLOW === "number"
2457
- ? external_node_fs_namespaceObject.constants.O_NOFOLLOW
2458
- : 0;
2459
- try {
2460
- handle = await promises_namespaceObject.open(lockPath, external_node_fs_namespaceObject.constants.O_RDONLY |
2461
- noFollow |
2462
- (typeof external_node_fs_namespaceObject.constants.O_NONBLOCK === "number" ? external_node_fs_namespaceObject.constants.O_NONBLOCK : 0));
2463
- }
2464
- catch (error) {
2465
- if (options.rejectNonFile && error.code === "ELOOP") {
2466
- throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`, {
2467
- cause: error,
2468
- });
2469
- }
2470
- throw error;
2471
- }
2472
- const opened = await handle.stat();
2473
- if (!opened.isFile()) {
2474
- if (options.rejectNonFile) {
2475
- throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
2476
- }
2477
- return null;
2478
- }
2479
- if (!options.allowDescriptorIdentityDrift && !file_identity_sameFileIdentity(before, opened))
2480
- return null;
2481
- const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
2482
- const after = await promises_namespaceObject.lstat(lockPath);
2483
- if (!after.isFile() || !file_identity_sameFileIdentity(before, after))
2484
- return null;
2485
- return { raw, payload: parseSidecarLockPayload(raw, options.parsePayload), stat: after };
2513
+ return path.resolve(fs.realpathSync(symlinkPath));
2486
2514
  }
2487
- catch (err) {
2488
- if (err.code === "ENOENT" ||
2489
- (err instanceof errors_FsSafeError && err.code === "not-found")) {
2490
- return null;
2491
- }
2492
- throw err;
2515
+ catch (error) {
2516
+ normalizeSymlinkResolutionError(error);
2517
+ const linkTarget = fs.readlinkSync(symlinkPath);
2518
+ return resolvePathViaExistingAncestorSync(path.resolve(path.dirname(symlinkPath), linkTarget));
2493
2519
  }
2494
- finally {
2495
- await handle?.close().catch(() => undefined);
2520
+ }
2521
+
2522
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/safe-path-segment.js
2523
+
2524
+ const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
2525
+ const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
2526
+ // Windows treats "C:name" as relative to the drive's current directory even
2527
+ // though path.win32.isAbsolute() reports false.
2528
+ const DRIVE_RELATIVE_PREFIX = /^[A-Za-z]:(?![\\/])/;
2529
+ const HYPHEN_CHAR_CODE = 0x2d;
2530
+ function safe_path_segment_isDriveRelativePath(value) {
2531
+ return DRIVE_RELATIVE_PREFIX.test(value);
2532
+ }
2533
+ function assertNoDriveRelativePathSegments(value, label) {
2534
+ if (value.split("/").some(safe_path_segment_isDriveRelativePath)) {
2535
+ throw new errors_FsSafeError("invalid-path", `${label} must not contain a drive letter`);
2496
2536
  }
2537
+ return value;
2497
2538
  }
2498
- function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}) {
2499
- let fd;
2500
- try {
2501
- const before = fsSync.lstatSync(lockPath);
2502
- if (!before.isFile() || before.isSymbolicLink()) {
2503
- if (options.rejectNonFile) {
2504
- throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
2505
- }
2506
- return null;
2507
- }
2508
- fd = fsSync.openSync(lockPath, resolveReadOpenFlags());
2509
- const opened = fsSync.fstatSync(fd);
2510
- if (!opened.isFile()) {
2511
- if (options.rejectNonFile) {
2512
- throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
2513
- }
2514
- return null;
2515
- }
2516
- const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
2517
- const after = fsSync.lstatSync(lockPath);
2518
- if (!sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
2519
- return null;
2520
- return {
2521
- raw,
2522
- payload: parseSidecarLockPayload(raw, parsePayload),
2523
- stat: after,
2524
- ownershipToken: readSidecarLockOwnershipToken(raw),
2525
- };
2526
- }
2527
- catch (error) {
2528
- if (error.code === "ENOENT")
2529
- return null;
2530
- throw error;
2539
+ function trimHyphenEdges(value) {
2540
+ let start = 0;
2541
+ let end = value.length;
2542
+ while (start < end && value.charCodeAt(start) === HYPHEN_CHAR_CODE) {
2543
+ start += 1;
2531
2544
  }
2532
- finally {
2533
- if (fd !== undefined)
2534
- fsSync.closeSync(fd);
2545
+ while (end > start && value.charCodeAt(end - 1) === HYPHEN_CHAR_CODE) {
2546
+ end -= 1;
2535
2547
  }
2548
+ return start === 0 && end === value.length ? value : value.slice(start, end);
2536
2549
  }
2537
- function removeSidecarLockIfUnchangedSync(lockPath, observed) {
2538
- const current = readSidecarLockSnapshotSync(lockPath);
2539
- if (!current || !sidecarLockSnapshotMatches(current, observed))
2540
- return false;
2541
- fsSync.rmSync(lockPath);
2542
- return true;
2550
+ function isSafePathSegment(segment, options = {}) {
2551
+ return (segment !== "" &&
2552
+ segment !== "." &&
2553
+ segment !== ".." &&
2554
+ !segment.includes("/") &&
2555
+ !segment.includes("\\") &&
2556
+ !segment.includes("\0") &&
2557
+ (options.allowDotPrefix === true || !segment.startsWith(".")) &&
2558
+ (options.allowDotPrefix === true
2559
+ ? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
2560
+ : SAFE_PATH_SEGMENT_PATTERN.test(segment)));
2543
2561
  }
2544
- function sidecarLockSnapshotMatches(current, observed) {
2545
- if (observed.ownershipToken !== undefined) {
2546
- return (current.stat?.isFile() === true &&
2547
- current.raw !== undefined &&
2548
- observed.raw !== undefined &&
2549
- readSidecarLockOwnershipToken(current.raw) === observed.ownershipToken &&
2550
- readSidecarLockOwnershipToken(observed.raw) === observed.ownershipToken &&
2551
- current.raw === observed.raw);
2552
- }
2553
- if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
2554
- return false;
2562
+ function assertSafePathSegment(segment, options = {}) {
2563
+ // Validate the exact value callers will later join into paths; trimming here
2564
+ // would let whitespace-padded ids pass and then be used verbatim.
2565
+ if (!isSafePathSegment(segment, options)) {
2566
+ throw new FsSafeError("invalid-path", `${options.label ?? "path segment"} must be a safe path segment`);
2555
2567
  }
2556
- if (observed.raw !== undefined) {
2557
- return current.raw === observed.raw;
2568
+ return segment;
2569
+ }
2570
+ function sanitizeSafePathSegment(value, fallback, options = {}) {
2571
+ const sanitized = value
2572
+ .trim()
2573
+ .replace(/[\\/]+/g, "-")
2574
+ .replace(/\0/g, "")
2575
+ .replace(/[^A-Za-z0-9._-]+/g, "-");
2576
+ const trimmed = trimHyphenEdges(sanitized);
2577
+ if (isSafePathSegment(trimmed, options)) {
2578
+ return trimmed;
2558
2579
  }
2559
- return observed.stat !== undefined && current.stat !== undefined;
2580
+ return assertSafePathSegment(fallback, { ...options, label: "fallback path segment" });
2560
2581
  }
2561
- async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
2562
- const current = await readSidecarLockSnapshot(lockPath, {
2582
+ function assertSafePathPrefix(prefix, options = {}) {
2583
+ // Prefixes are often derived from safe filenames. Normalize harmless
2584
+ // filename characters first, but still reject real path-control bytes.
2585
+ if (prefix.includes("/") || prefix.includes("\\") || prefix.includes("\0")) {
2586
+ return assertSafePathSegment(prefix, {
2587
+ allowDotPrefix: true,
2588
+ ...options,
2589
+ label: options.label ?? "path prefix",
2590
+ });
2591
+ }
2592
+ return assertSafePathSegment(prefix.replace(/[^A-Za-z0-9._-]+/g, "-"), {
2593
+ allowDotPrefix: true,
2563
2594
  ...options,
2564
- allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
2595
+ label: options.label ?? "path prefix",
2565
2596
  });
2566
- if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
2567
- return false;
2568
- }
2569
- if (options.lockRoot) {
2570
- await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath));
2597
+ }
2598
+
2599
+ ;// CONCATENATED MODULE: external "node:os"
2600
+ const external_node_os_namespaceObject = __rspack_createRequire_require("node:os");
2601
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/short-path.js
2602
+
2603
+
2604
+ function shortPath(value) {
2605
+ const home = external_node_os_namespaceObject.homedir();
2606
+ const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
2607
+ return formatErrorDetail(shortened);
2608
+ }
2609
+
2610
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
2611
+
2612
+
2613
+
2614
+
2615
+
2616
+
2617
+
2618
+
2619
+
2620
+
2621
+
2622
+ const ROOT_PATH_ALIAS_POLICIES = {
2623
+ strict: Object.freeze({
2624
+ allowFinalSymlinkForUnlink: false,
2625
+ allowFinalHardlinkForUnlink: false,
2626
+ }),
2627
+ unlinkTarget: Object.freeze({
2628
+ allowFinalSymlinkForUnlink: true,
2629
+ allowFinalHardlinkForUnlink: true,
2630
+ }),
2631
+ };
2632
+ async function resolveRootPath(params) {
2633
+ try {
2634
+ return await resolveRootPathInternal(params);
2571
2635
  }
2572
- else {
2573
- await promises_namespaceObject.rm(lockPath, { force: true });
2636
+ catch (error) {
2637
+ throw sanitizeRootPathError(error);
2574
2638
  }
2575
- return true;
2576
2639
  }
2577
- async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
2578
- const current = await readSidecarLockSnapshot(lockPath, {
2579
- ...options,
2580
- allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
2640
+ async function resolveRootPathInternal(params) {
2641
+ assertValidRootPathInputs(params);
2642
+ const rootPath = external_node_path_namespaceObject.resolve(params.rootPath);
2643
+ const absolutePath = external_node_path_namespaceObject.resolve(params.absolutePath);
2644
+ const rootCanonicalPath = params.rootCanonicalPath
2645
+ ? external_node_path_namespaceObject.resolve(params.rootCanonicalPath)
2646
+ : await resolvePathViaExistingAncestor(rootPath);
2647
+ const context = createBoundaryResolutionContext({
2648
+ resolveParams: params,
2649
+ rootPath,
2650
+ absolutePath,
2651
+ rootCanonicalPath,
2652
+ outsideLexicalCanonicalPath: await resolveOutsideLexicalCanonicalPathAsync({
2653
+ rootPath,
2654
+ absolutePath,
2655
+ }),
2656
+ });
2657
+ const outsideResult = await resolveOutsideRootPathAsync({
2658
+ boundaryLabel: params.boundaryLabel,
2659
+ context,
2660
+ });
2661
+ if (outsideResult) {
2662
+ return outsideResult;
2663
+ }
2664
+ return resolveRootPathLexicalAsync({
2665
+ params,
2666
+ absolutePath: context.absolutePath,
2667
+ rootPath: context.rootPath,
2668
+ rootCanonicalPath: context.rootCanonicalPath,
2581
2669
  });
2582
- return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
2583
2670
  }
2584
- async function sidecarReclaimGuardExists(pathname) {
2671
+ function resolveRootPathSync(params) {
2585
2672
  try {
2586
- await promises_namespaceObject.lstat(pathname);
2587
- return true;
2673
+ return resolveRootPathSyncInternal(params);
2588
2674
  }
2589
- catch (err) {
2590
- if (err.code === "ENOENT") {
2591
- return false;
2592
- }
2593
- throw err;
2675
+ catch (error) {
2676
+ throw sanitizeRootPathError(error);
2594
2677
  }
2595
2678
  }
2596
- async function tryAcquireSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
2597
- try {
2598
- await promises_namespaceObject.mkdir(reclaimGuardPath);
2599
- reclaimGuards.add(reclaimGuardPath);
2600
- return true;
2679
+ function resolveRootPathSyncInternal(params) {
2680
+ assertValidRootPathInputs(params);
2681
+ const rootPath = path.resolve(params.rootPath);
2682
+ const absolutePath = path.resolve(params.absolutePath);
2683
+ const rootCanonicalPath = params.rootCanonicalPath
2684
+ ? path.resolve(params.rootCanonicalPath)
2685
+ : resolvePathViaExistingAncestorSync(rootPath);
2686
+ const context = createBoundaryResolutionContext({
2687
+ resolveParams: params,
2688
+ rootPath,
2689
+ absolutePath,
2690
+ rootCanonicalPath,
2691
+ outsideLexicalCanonicalPath: resolveOutsideLexicalCanonicalPathSync({
2692
+ rootPath,
2693
+ absolutePath,
2694
+ }),
2695
+ });
2696
+ const outsideResult = resolveOutsideRootPathSync({
2697
+ boundaryLabel: params.boundaryLabel,
2698
+ context,
2699
+ });
2700
+ if (outsideResult) {
2701
+ return outsideResult;
2601
2702
  }
2602
- catch (err) {
2603
- if (err.code === "EEXIST") {
2604
- return false;
2605
- }
2606
- throw err;
2703
+ return resolveRootPathLexicalSync({
2704
+ params,
2705
+ absolutePath: context.absolutePath,
2706
+ rootPath: context.rootPath,
2707
+ rootCanonicalPath: context.rootCanonicalPath,
2708
+ });
2709
+ }
2710
+ function sanitizeRootPathError(error) {
2711
+ if (error instanceof Error) {
2712
+ error.message = formatErrorDetail(error.message);
2607
2713
  }
2714
+ return error;
2608
2715
  }
2609
- async function releaseSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
2610
- await promises_namespaceObject.rmdir(reclaimGuardPath);
2611
- reclaimGuards.delete(reclaimGuardPath);
2716
+ function assertValidRootPathInputs(params) {
2717
+ path_assertNoNulPathInput(params.rootPath, "root path contains a NUL byte");
2718
+ path_assertNoNulPathInput(params.absolutePath, "absolute path contains a NUL byte");
2719
+ assertNoEmbeddedDriveRelativeSegment(params.rootPath, "root path");
2720
+ assertNoEmbeddedDriveRelativeSegment(params.absolutePath, "absolute path");
2721
+ if (params.rootCanonicalPath !== undefined) {
2722
+ path_assertNoNulPathInput(params.rootCanonicalPath, "canonical root path contains a NUL byte");
2723
+ assertNoEmbeddedDriveRelativeSegment(params.rootCanonicalPath, "canonical root path");
2724
+ }
2612
2725
  }
2613
- async function removeStaleSidecarLockIfAllowed(params) {
2614
- if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
2615
- return "not-approved";
2726
+ function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
2727
+ if (process.platform !== "win32") {
2728
+ return;
2616
2729
  }
2617
- const ioOptions = { lockRoot: params.lockRoot, parsePayload: params.parsePayload };
2618
- if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
2619
- return "changed";
2620
- }
2621
- if (!(await params.shouldRemoveStaleLock({
2622
- lockPath: params.lockPath,
2623
- normalizedTargetPath: params.normalizedTargetPath,
2624
- raw: params.snapshot.raw,
2625
- payload: params.snapshot.payload,
2626
- }))) {
2627
- return "not-approved";
2628
- }
2629
- if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
2630
- return "changed";
2730
+ const root = external_node_path_namespaceObject.parse(filePath).root;
2731
+ assertNoDriveRelativePathSegments(filePath.slice(root.length).replaceAll("\\", "/"), label);
2732
+ }
2733
+ function isPromiseLike(value) {
2734
+ return Boolean(value &&
2735
+ (typeof value === "object" || typeof value === "function") &&
2736
+ "then" in value &&
2737
+ typeof value.then === "function");
2738
+ }
2739
+ function createLexicalTraversalState(params) {
2740
+ const rawAbsolutePath = params.params.absolutePath;
2741
+ const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
2742
+ const relative = rawRelativePath ?? external_node_path_namespaceObject.relative(params.rootPath, params.absolutePath);
2743
+ return {
2744
+ segments: splitTraversalSegments(relative),
2745
+ allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
2746
+ canonicalCursor: params.rootCanonicalPath,
2747
+ lexicalCursor: params.rootPath,
2748
+ preserveFinalSymlink: false,
2749
+ };
2750
+ }
2751
+ function createLexicalTraversalContext(params) {
2752
+ return {
2753
+ state: createLexicalTraversalState(params),
2754
+ resolveParams: params.params,
2755
+ rootPath: params.rootPath,
2756
+ rootCanonicalPath: params.rootCanonicalPath,
2757
+ absolutePath: params.absolutePath,
2758
+ };
2759
+ }
2760
+ function splitTraversalSegments(value) {
2761
+ return value
2762
+ .split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
2763
+ .filter((segment) => Boolean(segment) && segment !== ".");
2764
+ }
2765
+ function rawPathRelativeToRoot(rootPath, candidatePath) {
2766
+ if (!external_node_path_namespaceObject.isAbsolute(candidatePath)) {
2767
+ return undefined;
2631
2768
  }
2632
- try {
2633
- if (params.lockRoot) {
2634
- await params.lockRoot.remove(relativeSidecarLockPath(params.lockRoot, params.lockPath));
2635
- }
2636
- else {
2637
- await promises_namespaceObject.rm(params.lockPath);
2638
- }
2639
- return "removed";
2769
+ const root = external_node_path_namespaceObject.resolve(rootPath);
2770
+ const candidate = process.platform === "win32"
2771
+ ? candidatePath.replaceAll("/", external_node_path_namespaceObject.sep)
2772
+ : candidatePath;
2773
+ if (candidate === root) {
2774
+ return "";
2640
2775
  }
2641
- catch (err) {
2642
- if (err.code === "ENOENT") {
2643
- return "changed";
2644
- }
2645
- throw err;
2776
+ const rootWithSep = root.endsWith(external_node_path_namespaceObject.sep) ? root : `${root}${external_node_path_namespaceObject.sep}`;
2777
+ const candidatePrefix = candidate.slice(0, rootWithSep.length);
2778
+ const prefixMatches = process.platform === "win32"
2779
+ ? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
2780
+ : candidatePrefix === rootWithSep;
2781
+ return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
2782
+ }
2783
+ function assertLexicalCursorInsideBoundary(context, candidatePath) {
2784
+ assertInsideBoundary({
2785
+ boundaryLabel: context.resolveParams.boundaryLabel,
2786
+ rootCanonicalPath: context.rootCanonicalPath,
2787
+ candidatePath,
2788
+ absolutePath: context.absolutePath,
2789
+ });
2790
+ }
2791
+ function applyMissingSuffixToCanonicalCursor(context, missingFromIndex) {
2792
+ const missingSuffix = context.state.segments.slice(missingFromIndex);
2793
+ for (const segment of missingSuffix) {
2794
+ advanceCanonicalCursorForSegment(context, segment);
2646
2795
  }
2647
2796
  }
2648
-
2649
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
2650
-
2651
- function assertFiniteNonNegative(value, label) {
2652
- if (!Number.isFinite(value) || value < 0) {
2653
- throw new RangeError(`${label} must be a finite non-negative number`);
2797
+ function advanceCanonicalCursorForSegment(context, segment) {
2798
+ context.state.canonicalCursor = external_node_path_namespaceObject.resolve(context.state.canonicalCursor, segment);
2799
+ assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
2800
+ }
2801
+ function finalizeLexicalResolution(context, kind) {
2802
+ assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
2803
+ return buildResolvedRootPath({
2804
+ absolutePath: context.absolutePath,
2805
+ canonicalPath: context.state.canonicalCursor,
2806
+ rootPath: context.rootPath,
2807
+ rootCanonicalPath: context.rootCanonicalPath,
2808
+ kind,
2809
+ });
2810
+ }
2811
+ function handleLexicalLstatFailure(context, error, missingFromIndex) {
2812
+ if (!path_isNotFoundPathError(error)) {
2813
+ return false;
2654
2814
  }
2815
+ applyMissingSuffixToCanonicalCursor(context, missingFromIndex);
2816
+ return true;
2655
2817
  }
2656
- function validateSidecarLockRetryOptions(retry) {
2657
- if (retry.retries !== undefined && (!Number.isSafeInteger(retry.retries) || retry.retries < 0)) {
2658
- throw new RangeError("lock retry.retries must be a non-negative safe integer");
2818
+ function handleLexicalStatReadFailure(context, error, missingFromIndex) {
2819
+ if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
2820
+ return null;
2659
2821
  }
2660
- if (retry.factor !== undefined)
2661
- assertFiniteNonNegative(retry.factor, "lock retry.factor");
2662
- if (retry.minTimeout !== undefined) {
2663
- assertFiniteNonNegative(retry.minTimeout, "lock retry.minTimeout");
2822
+ throw error;
2823
+ }
2824
+ function handleLexicalStatDisposition(context, params) {
2825
+ if (!params.isSymbolicLink) {
2826
+ advanceCanonicalCursorForSegment(context, params.segment);
2827
+ return "continue";
2664
2828
  }
2665
- if (retry.maxTimeout !== undefined) {
2666
- assertFiniteNonNegative(retry.maxTimeout, "lock retry.maxTimeout");
2829
+ if (context.resolveParams.rejectSymlinks === true && params.isLast) {
2830
+ throw new errors_FsSafeError("symlink", "symlink path component not allowed");
2667
2831
  }
2668
- if (retry.minTimeout !== undefined &&
2669
- retry.maxTimeout !== undefined &&
2670
- retry.minTimeout > retry.maxTimeout) {
2671
- throw new RangeError("lock retry.minTimeout must not exceed retry.maxTimeout");
2832
+ if (context.state.allowFinalSymlink && params.isLast) {
2833
+ context.state.preserveFinalSymlink = true;
2834
+ advanceCanonicalCursorForSegment(context, params.segment);
2835
+ return "break";
2672
2836
  }
2837
+ return "resolve-link";
2673
2838
  }
2674
- function validateSidecarLockTimeoutMs(timeoutMs) {
2675
- if (timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY)
2676
- return;
2677
- assertFiniteNonNegative(timeoutMs, "lock timeoutMs");
2678
- }
2679
- function computeSidecarLockDelayMs(retry, attempt) {
2680
- validateSidecarLockRetryOptions(retry);
2681
- const minTimeout = retry.minTimeout ?? 50;
2682
- const maxTimeout = retry.maxTimeout ?? 1000;
2683
- const factor = retry.factor ?? 1;
2684
- const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
2685
- const jitter = retry.randomize ? 1 + Math.random() : 1;
2686
- return Math.min(maxTimeout, Math.round(base * jitter));
2687
- }
2688
- // Windows denies access to a lock file while a just-unlinked directory entry
2689
- // is still being torn down, so a contended acquire sees EPERM on a name that is
2690
- // already gone -- both when creating it exclusively and when reading the
2691
- // holder's snapshot. The next attempt succeeds, so this is contention rather
2692
- // than a permission failure. The error must name the lock file itself: the
2693
- // exclusive-create helper opens the parent directory first, and a denial from
2694
- // that setup step carries no teardown evidence and has to reach the caller.
2695
- const maxTransientLockDenials = 8;
2696
- function isTransientLockFileDenial(error, lockPath) {
2697
- const denial = error;
2698
- return process.platform === "win32" && denial?.code === "EPERM" && denial.path === lockPath;
2699
- }
2700
- function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
2701
- const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
2702
- return createdAtMs !== null && nowMs - createdAtMs > staleMs;
2703
- }
2704
- function sidecarLockPayloadCreatedAtMs(payload) {
2705
- const createdAt = payload &&
2706
- typeof payload === "object" &&
2707
- "createdAt" in payload &&
2708
- typeof payload.createdAt === "string"
2709
- ? payload.createdAt
2710
- : "";
2711
- const createdAtMs = Date.parse(createdAt);
2712
- return Number.isFinite(createdAtMs) ? createdAtMs : null;
2839
+ function applyResolvedSymlinkHop(context, linkCanonical) {
2840
+ if (!path_isPathInside(context.rootCanonicalPath, linkCanonical)) {
2841
+ throw symlinkEscapeError({
2842
+ boundaryLabel: context.resolveParams.boundaryLabel,
2843
+ rootCanonicalPath: context.rootCanonicalPath,
2844
+ symlinkPath: context.state.lexicalCursor,
2845
+ });
2846
+ }
2847
+ context.state.canonicalCursor = linkCanonical;
2848
+ context.state.lexicalCursor = linkCanonical;
2713
2849
  }
2714
- async function defaultSidecarLockShouldReclaim(params) {
2715
- const createdAtMs = sidecarLockPayloadCreatedAtMs(params.payload);
2716
- if (createdAtMs !== null)
2717
- return params.nowMs - createdAtMs > params.staleMs;
2850
+ function readLexicalStat(context, params) {
2718
2851
  try {
2719
- return params.nowMs - (await promises_namespaceObject.stat(params.lockPath)).mtimeMs > params.staleMs;
2852
+ const stat = params.read(context.state.lexicalCursor);
2853
+ if (isPromiseLike(stat)) {
2854
+ return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
2855
+ }
2856
+ return stat;
2720
2857
  }
2721
- catch {
2722
- return true;
2858
+ catch (error) {
2859
+ return handleLexicalStatReadFailure(context, error, params.missingFromIndex);
2723
2860
  }
2724
2861
  }
2725
-
2726
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/suppressed-error.js
2727
- function createSuppressedError(error, suppressed, message) {
2728
- const constructor = globalThis.SuppressedError;
2729
- if (constructor) {
2730
- return new constructor(error, suppressed, message);
2862
+ function resolveAndApplySymlinkHop(context, params) {
2863
+ const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
2864
+ if (isPromiseLike(linkCanonical)) {
2865
+ return Promise.resolve(linkCanonical).then((value) => {
2866
+ applyResolvedSymlinkHop(context, value);
2867
+ });
2731
2868
  }
2732
- const combined = new Error(message, {
2733
- cause: error instanceof Error ? error : undefined,
2734
- });
2735
- combined.name = "SuppressedError";
2736
- Object.defineProperties(combined, {
2737
- error: { configurable: true, value: error },
2738
- suppressed: { configurable: true, value: suppressed },
2739
- });
2740
- return combined;
2869
+ applyResolvedSymlinkHop(context, linkCanonical);
2741
2870
  }
2742
-
2743
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js
2744
-
2745
-
2746
-
2747
-
2748
-
2749
-
2750
-
2751
- async function resolveNormalizedTargetPath(targetPath) {
2752
- const resolved = external_node_path_namespaceObject.resolve(targetPath);
2753
- const dir = external_node_path_namespaceObject.dirname(resolved);
2754
- await promises_namespaceObject.mkdir(dir, { recursive: true });
2755
- try {
2756
- return external_node_path_namespaceObject.join(await promises_namespaceObject.realpath(dir), external_node_path_namespaceObject.basename(resolved));
2757
- }
2758
- catch {
2759
- return resolved;
2871
+ function applyParentTraversalStep(context) {
2872
+ context.state.lexicalCursor = external_node_path_namespaceObject.resolve(context.state.lexicalCursor, "..");
2873
+ advanceCanonicalCursorForSegment(context, "..");
2874
+ }
2875
+ function* iterateLexicalTraversal(state) {
2876
+ for (let idx = 0; idx < state.segments.length; idx += 1) {
2877
+ const segment = state.segments[idx] ?? "";
2878
+ const isLast = idx === state.segments.length - 1;
2879
+ yield { idx, segment, isLast };
2760
2880
  }
2761
2881
  }
2762
- async function acquireSidecarLock(options, context) {
2763
- const retry = options.retry ?? {};
2764
- validateSidecarLockRetryOptions(retry);
2765
- validateSidecarLockTimeoutMs(options.timeoutMs);
2766
- context.ensureExitCleanupRegistered();
2767
- const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
2768
- const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
2769
- let held = context.held.get(normalizedTargetPath);
2770
- if (held &&
2771
- options.reentrantOwner !== undefined &&
2772
- held.reentrantOwner !== undefined &&
2773
- options.reentrantOwner === held.reentrantOwner) {
2774
- // A final release may already have decremented the count to zero and be
2775
- // removing the sidecar. Do not admit a new reentrant handle until that
2776
- // cleanup settles: successful cleanup requires a fresh acquisition, while
2777
- // failed cleanup leaves the existing sidecar held for a retry.
2778
- if (held.releasePromise) {
2779
- await held.releasePromise.catch(() => undefined);
2780
- held = context.held.get(normalizedTargetPath);
2781
- }
2782
- if (held &&
2783
- options.reentrantOwner !== undefined &&
2784
- held.reentrantOwner !== undefined &&
2785
- options.reentrantOwner === held.reentrantOwner) {
2786
- held.refCount += 1;
2787
- return context.handleForHeldLock(normalizedTargetPath, held);
2788
- }
2789
- }
2790
- const startedAt = Date.now();
2791
- const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
2792
- const reclaimGuardPath = `${lockPath}.reclaim`;
2793
- let ownsReclaimGuard = false;
2794
- let attempt = 0;
2795
- // Bounded so a genuine denial still surfaces as EPERM, not a lock timeout.
2796
- let transientDenials = 0;
2797
- const withinDenialBudget = () => ++transientDenials <= (/* inlined export .maxTransientLockDenials */8);
2798
- const waitForRetry = async () => {
2799
- const elapsed = Date.now() - startedAt;
2800
- if ((options.timeoutMs !== undefined &&
2801
- options.timeoutMs !== Number.POSITIVE_INFINITY &&
2802
- elapsed >= options.timeoutMs) ||
2803
- (maxRetries !== undefined && attempt >= maxRetries)) {
2804
- throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
2805
- code: "file_lock_timeout",
2806
- lockPath,
2807
- normalizedTargetPath,
2808
- });
2809
- }
2810
- const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
2811
- ? Number.POSITIVE_INFINITY
2812
- : Math.max(0, options.timeoutMs - elapsed);
2813
- const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
2814
- attempt += 1;
2815
- await new Promise((resolve) => setTimeout(resolve, delay));
2816
- };
2817
- // Waiting can fail on the caller's own retry or deadline limits. Classifying
2818
- // a denial as contention must not cost them the original diagnosis, so hand
2819
- // the denial back when no further attempt can be scheduled.
2820
- const retryOrRethrowDenial = async (denial) => {
2821
- try {
2822
- await waitForRetry();
2823
- }
2824
- catch (waitError) {
2825
- if (waitError.code === "file_lock_timeout")
2826
- throw denial;
2827
- throw waitError;
2828
- }
2829
- };
2830
- try {
2831
- while (true) {
2832
- if (!ownsReclaimGuard && (await sidecarReclaimGuardExists(reclaimGuardPath))) {
2833
- await waitForRetry();
2834
- continue;
2835
- }
2836
- let handle = null;
2837
- let createdSnapshot = null;
2838
- let lockFileCreateDenied = false;
2839
- try {
2840
- const payload = await options.payload();
2841
- const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
2842
- if (options.lockRoot) {
2843
- const relativeLockPath = relativeSidecarLockPath(options.lockRoot, lockPath);
2844
- try {
2845
- await options.lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
2846
- }
2847
- catch (error) {
2848
- if (error instanceof errors_FsSafeError && error.code === "already-exists") {
2849
- throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
2850
- }
2851
- throw error;
2852
- }
2853
- createdSnapshot = { raw, payload, ownershipToken };
2854
- handle = (await options.lockRoot.open(relativeLockPath)).handle;
2855
- }
2856
- else {
2857
- try {
2858
- handle =
2859
- (await createNativeExclusiveFile(lockPath, 0o600)) ??
2860
- (await promises_namespaceObject.open(lockPath, "wx"));
2861
- }
2862
- catch (createError) {
2863
- lockFileCreateDenied = isTransientLockFileDenial(createError, lockPath);
2864
- throw createError;
2865
- }
2866
- await handle.writeFile(raw, "utf8");
2867
- }
2868
- const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
2869
- const createdHeld = {
2870
- refCount: 1,
2871
- reentrantOwner: options.reentrantOwner,
2872
- handle,
2873
- lockPath,
2874
- snapshot,
2875
- acquiredAt: Date.now(),
2876
- metadata: options.metadata ?? {},
2877
- lockRoot: options.lockRoot,
2878
- parsePayload: options.parsePayload,
2879
- };
2880
- context.held.set(normalizedTargetPath, createdHeld);
2881
- if (ownsReclaimGuard) {
2882
- try {
2883
- await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
2884
- ownsReclaimGuard = false;
2885
- }
2886
- catch (err) {
2887
- await context.releaseHeldLock(normalizedTargetPath, createdHeld, { force: true });
2888
- throw err;
2889
- }
2890
- }
2891
- const returnedHandle = context.handleForHeldLock(normalizedTargetPath, createdHeld);
2892
- const interval = options.compromiseCheckIntervalMs;
2893
- if (options.onCompromised && interval !== undefined && interval > 0) {
2894
- createdHeld.compromiseTimer = setInterval(() => {
2895
- void returnedHandle
2896
- .verifyStillHeld()
2897
- .catch(() => false)
2898
- .then((stillHeld) => {
2899
- if (!stillHeld && createdHeld.compromiseTimer) {
2900
- clearInterval(createdHeld.compromiseTimer);
2901
- createdHeld.compromiseTimer = undefined;
2902
- options.onCompromised?.({ lockPath, normalizedTargetPath });
2903
- }
2904
- });
2905
- }, interval);
2906
- createdHeld.compromiseTimer.unref();
2907
- }
2908
- return returnedHandle;
2909
- }
2910
- catch (err) {
2911
- try {
2912
- if (handle) {
2913
- const failedSnapshot = { payload: null };
2914
- try {
2915
- failedSnapshot.stat = await handle.stat();
2916
- }
2917
- catch {
2918
- // Best-effort cleanup of a failed exclusive create.
2919
- }
2920
- const current = context.held.get(normalizedTargetPath);
2921
- if (current?.handle === handle) {
2922
- context.held.delete(normalizedTargetPath);
2923
- }
2924
- await handle.close().catch(() => undefined);
2925
- // The file may be empty or partial JSON, so remove by the identity
2926
- // captured from our exclusive handle rather than by pathname alone.
2927
- await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
2928
- lockRoot: options.lockRoot,
2929
- parsePayload: options.parsePayload,
2930
- });
2931
- }
2932
- else if (createdSnapshot) {
2933
- await removeSidecarLockIfUnchanged(lockPath, createdSnapshot, {
2934
- lockRoot: options.lockRoot,
2935
- parsePayload: options.parsePayload,
2936
- });
2937
- }
2938
- }
2939
- catch (cleanupError) {
2940
- throw createSuppressedError(cleanupError, err, "file lock acquisition and cleanup both failed");
2941
- }
2942
- if (lockFileCreateDenied && withinDenialBudget()) {
2943
- await retryOrRethrowDenial(err);
2944
- continue;
2945
- }
2946
- if (err.code !== "EEXIST") {
2947
- throw err;
2948
- }
2949
- if (ownsReclaimGuard) {
2950
- await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
2951
- ownsReclaimGuard = false;
2952
- continue;
2953
- }
2954
- const nowMs = Date.now();
2955
- let snapshot;
2956
- try {
2957
- snapshot = await readSidecarLockSnapshot(lockPath, {
2958
- lockRoot: options.lockRoot,
2959
- parsePayload: options.parsePayload,
2960
- rejectNonFile: true,
2961
- });
2962
- }
2963
- catch (readErr) {
2964
- if (!isTransientLockFileDenial(readErr, lockPath) || !withinDenialBudget())
2965
- throw readErr;
2966
- await retryOrRethrowDenial(readErr);
2967
- continue;
2968
- }
2969
- if (!snapshot) {
2970
- continue;
2971
- }
2972
- if (context.held.has(normalizedTargetPath)) {
2973
- await waitForRetry();
2974
- continue;
2975
- }
2976
- const shouldReclaim = options.shouldReclaim ?? defaultSidecarLockShouldReclaim;
2977
- if (await shouldReclaim({
2978
- lockPath,
2979
- normalizedTargetPath,
2980
- payload: snapshot?.payload ?? null,
2981
- staleMs: options.staleMs,
2982
- nowMs,
2983
- heldByThisProcess: context.held.has(normalizedTargetPath),
2984
- })) {
2985
- if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
2986
- lockRoot: options.lockRoot,
2987
- parsePayload: options.parsePayload,
2988
- }))) {
2989
- continue;
2990
- }
2991
- const staleRecovery = options.staleRecovery ?? "fail-closed";
2992
- if (staleRecovery === "remove-if-unchanged") {
2993
- if (!(await tryAcquireSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath))) {
2994
- await waitForRetry();
2995
- continue;
2996
- }
2997
- ownsReclaimGuard = true;
2998
- const removal = await removeStaleSidecarLockIfAllowed({
2999
- lockPath,
3000
- normalizedTargetPath,
3001
- snapshot,
3002
- shouldRemoveStaleLock: options.shouldRemoveStaleLock,
3003
- lockRoot: options.lockRoot,
3004
- parsePayload: options.parsePayload,
3005
- });
3006
- if (removal === "removed" || removal === "changed") {
3007
- continue;
3008
- }
3009
- await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
3010
- ownsReclaimGuard = false;
3011
- }
3012
- throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
3013
- code: "file_lock_stale",
3014
- lockPath,
3015
- normalizedTargetPath,
3016
- });
3017
- }
3018
- await waitForRetry();
3019
- }
3020
- }
3021
- }
3022
- finally {
3023
- if (ownsReclaimGuard) {
3024
- await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
3025
- }
3026
- }
3027
- }
3028
-
3029
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
3030
-
3031
- function createSidecarLockHandle(params) {
3032
- let released = false;
3033
- let releasePromise;
3034
- let releaseStarted = false;
3035
- const release = async () => {
3036
- if (released)
3037
- return;
3038
- if (!releasePromise) {
3039
- const retry = releaseStarted;
3040
- releaseStarted = true;
3041
- releasePromise = (async () => {
3042
- await params.release({ retry });
3043
- released = true;
3044
- })();
3045
- }
3046
- try {
3047
- await releasePromise;
3048
- }
3049
- finally {
3050
- releasePromise = undefined;
3051
- }
3052
- };
3053
- return {
3054
- lockPath: params.lockPath,
3055
- normalizedTargetPath: params.normalizedTargetPath,
3056
- verifyStillHeld: params.verifyStillHeld,
3057
- release,
3058
- [Symbol.asyncDispose]: release,
3059
- };
3060
- }
3061
- function createHeldSidecarLockHandle(params) {
3062
- return createSidecarLockHandle({
3063
- lockPath: params.held.lockPath,
3064
- normalizedTargetPath: params.normalizedTargetPath,
3065
- verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
3066
- lockRoot: params.held.lockRoot,
3067
- parsePayload: params.held.parsePayload,
3068
- }),
3069
- release: (options) => params.release(options),
3070
- });
3071
- }
3072
-
3073
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
3074
-
3075
-
3076
-
3077
-
3078
-
3079
-
3080
- const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
3081
- const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
3082
- const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
3083
- function getGlobalManagers() {
3084
- const globalWithState = globalThis;
3085
- if (!globalWithState[GLOBAL_STATE_KEY]) {
3086
- globalWithState[GLOBAL_STATE_KEY] = new Map();
3087
- }
3088
- return globalWithState[GLOBAL_STATE_KEY];
3089
- }
3090
- function resolveManagerState(key) {
3091
- const managers = getGlobalManagers();
3092
- let state = managers.get(key);
3093
- if (!state) {
3094
- state = {
3095
- cleanupRegistered: false,
3096
- held: new Map(),
3097
- reclaimCleanupRegistered: false,
3098
- reclaimGuards: new Set(),
3099
- };
3100
- managers.set(key, state);
3101
- }
3102
- else {
3103
- // The global manager symbol is shared across package copies and hot reloads.
3104
- // Backfill state created by fs-safe versions that predate reclaim guards.
3105
- state.reclaimCleanupRegistered ??= false;
3106
- state.reclaimGuards ??= new Set();
3107
- for (const held of state.held.values()) {
3108
- held.refCount ??= 1;
3109
- }
3110
- }
3111
- return state;
3112
- }
3113
- function snapshotMatchesSync(lockPath, observed) {
3114
- let fd;
3115
- try {
3116
- const beforeStat = external_node_fs_namespaceObject.lstatSync(lockPath);
3117
- if (!beforeStat.isFile()) {
3118
- return false;
2882
+ async function resolveRootPathLexicalAsync(params) {
2883
+ const context = createLexicalTraversalContext(params);
2884
+ const { state } = context;
2885
+ for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
2886
+ if (segment === "..") {
2887
+ applyParentTraversalStep(context);
2888
+ continue;
3119
2889
  }
3120
- const openFlags = external_node_fs_namespaceObject.constants.O_RDONLY |
3121
- (process.platform !== "win32" && typeof external_node_fs_namespaceObject.constants.O_NOFOLLOW === "number"
3122
- ? external_node_fs_namespaceObject.constants.O_NOFOLLOW
3123
- : 0) |
3124
- (typeof external_node_fs_namespaceObject.constants.O_NONBLOCK === "number" ? external_node_fs_namespaceObject.constants.O_NONBLOCK : 0);
3125
- fd = external_node_fs_namespaceObject.openSync(lockPath, openFlags);
3126
- const openedStat = external_node_fs_namespaceObject.fstatSync(fd);
3127
- if (!openedStat.isFile()) {
3128
- return false;
2890
+ state.lexicalCursor = external_node_path_namespaceObject.join(state.lexicalCursor, segment);
2891
+ const stat = await readLexicalStat(context, {
2892
+ missingFromIndex: idx,
2893
+ read: (cursor) => promises_namespaceObject.lstat(cursor),
2894
+ });
2895
+ if (!stat) {
2896
+ break;
3129
2897
  }
3130
- if (observed.raw !== undefined && openedStat.size !== Buffer.byteLength(observed.raw)) {
3131
- return false;
2898
+ const disposition = handleLexicalStatDisposition(context, {
2899
+ isSymbolicLink: stat.isSymbolicLink(),
2900
+ segment,
2901
+ isLast,
2902
+ });
2903
+ if (disposition === "continue") {
2904
+ continue;
3132
2905
  }
3133
- const raw = external_node_fs_namespaceObject.readFileSync(fd, "utf8");
3134
- const afterStat = external_node_fs_namespaceObject.lstatSync(lockPath);
3135
- if (!afterStat.isFile() || !file_identity_sameFileIdentity(beforeStat, afterStat)) {
3136
- return false;
2906
+ if (disposition === "break") {
2907
+ break;
3137
2908
  }
3138
- return sidecarLockSnapshotMatches({ raw, payload: null, stat: afterStat }, observed);
3139
- }
3140
- catch {
3141
- return false;
3142
- }
3143
- finally {
3144
- if (fd !== undefined) {
3145
- try {
3146
- external_node_fs_namespaceObject.closeSync(fd);
3147
- }
3148
- catch {
3149
- // Best-effort process-exit cleanup.
3150
- }
2909
+ await resolveAndApplySymlinkHop(context, {
2910
+ resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
2911
+ });
2912
+ if (context.resolveParams.rejectSymlinks === true) {
2913
+ throw new errors_FsSafeError("symlink", "symlink path component not allowed");
3151
2914
  }
3152
2915
  }
2916
+ const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
2917
+ return finalizeLexicalResolution(context, kind);
3153
2918
  }
3154
- function releaseAllReclaimGuardsSync(state) {
3155
- for (const reclaimGuardPath of state.reclaimGuards) {
3156
- try {
3157
- external_node_fs_namespaceObject.rmdirSync(reclaimGuardPath);
3158
- state.reclaimGuards.delete(reclaimGuardPath);
2919
+ function resolveRootPathLexicalSync(params) {
2920
+ const context = createLexicalTraversalContext(params);
2921
+ const { state } = context;
2922
+ for (let idx = 0; idx < state.segments.length; idx += 1) {
2923
+ const segment = state.segments[idx] ?? "";
2924
+ const isLast = idx === state.segments.length - 1;
2925
+ if (segment === "..") {
2926
+ applyParentTraversalStep(context);
2927
+ continue;
3159
2928
  }
3160
- catch {
3161
- // Best-effort process-exit cleanup. A surviving guard fails closed.
2929
+ state.lexicalCursor = path.join(state.lexicalCursor, segment);
2930
+ const maybeStat = readLexicalStat(context, {
2931
+ missingFromIndex: idx,
2932
+ read: (cursor) => fs.lstatSync(cursor),
2933
+ });
2934
+ if (isPromiseLike(maybeStat)) {
2935
+ throw new Error("Unexpected async lexical stat");
3162
2936
  }
3163
- }
3164
- }
3165
- function releaseAllLocksSync(state) {
3166
- for (const [normalizedTargetPath, held] of state.held) {
3167
- void held.handle.close().catch(() => undefined);
3168
- try {
3169
- if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
3170
- external_node_fs_namespaceObject.rmSync(held.lockPath, { force: true });
3171
- }
2937
+ const stat = maybeStat;
2938
+ if (!stat) {
2939
+ break;
3172
2940
  }
3173
- catch {
3174
- // Best-effort process-exit cleanup.
2941
+ const disposition = handleLexicalStatDisposition(context, {
2942
+ isSymbolicLink: stat.isSymbolicLink(),
2943
+ segment,
2944
+ isLast,
2945
+ });
2946
+ if (disposition === "continue") {
2947
+ continue;
2948
+ }
2949
+ if (disposition === "break") {
2950
+ break;
2951
+ }
2952
+ const maybeApplied = resolveAndApplySymlinkHop(context, {
2953
+ resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
2954
+ });
2955
+ if (isPromiseLike(maybeApplied)) {
2956
+ throw new Error("Unexpected async symlink resolution");
2957
+ }
2958
+ if (context.resolveParams.rejectSymlinks === true) {
2959
+ throw new FsSafeError("symlink", "symlink path component not allowed");
3175
2960
  }
3176
- state.held.delete(normalizedTargetPath);
3177
2961
  }
3178
- releaseAllReclaimGuardsSync(state);
2962
+ const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
2963
+ return finalizeLexicalResolution(context, kind);
3179
2964
  }
3180
- function ensureGlobalExitCleanupRegistered() {
3181
- const globalWithCleanup = globalThis;
3182
- if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
3183
- return;
3184
- globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
3185
- const cleanup = () => {
3186
- for (const state of getGlobalManagers().values()) {
3187
- releaseAllLocksSync(state);
3188
- }
2965
+ function resolveCanonicalOutsideLexicalPath(params) {
2966
+ return params.outsideLexicalCanonicalPath ?? params.absolutePath;
2967
+ }
2968
+ function createBoundaryResolutionContext(params) {
2969
+ const lexicalInside = path_isPathInside(params.rootPath, params.absolutePath);
2970
+ const canonicalOutsideLexicalPath = resolveCanonicalOutsideLexicalPath({
2971
+ absolutePath: params.absolutePath,
2972
+ outsideLexicalCanonicalPath: params.outsideLexicalCanonicalPath,
2973
+ });
2974
+ assertLexicalBoundaryOrCanonicalAlias({
2975
+ skipLexicalRootCheck: params.resolveParams.skipLexicalRootCheck,
2976
+ lexicalInside,
2977
+ canonicalOutsideLexicalPath,
2978
+ rootCanonicalPath: params.rootCanonicalPath,
2979
+ boundaryLabel: params.resolveParams.boundaryLabel,
2980
+ rootPath: params.rootPath,
2981
+ absolutePath: params.absolutePath,
2982
+ });
2983
+ return {
2984
+ rootPath: params.rootPath,
2985
+ absolutePath: params.absolutePath,
2986
+ rootCanonicalPath: params.rootCanonicalPath,
2987
+ lexicalInside,
2988
+ canonicalOutsideLexicalPath,
3189
2989
  };
3190
- globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
3191
- process.on("exit", cleanup);
3192
2990
  }
3193
- async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
3194
- const current = state.held.get(normalizedTargetPath);
3195
- if (current !== held) {
3196
- return false;
2991
+ async function resolveOutsideRootPathAsync(params) {
2992
+ if (params.context.lexicalInside) {
2993
+ return null;
3197
2994
  }
3198
- if (held.releasePromise) {
3199
- await held.releasePromise;
3200
- return true;
2995
+ const kind = await getPathKind(params.context.absolutePath, false);
2996
+ return buildOutsideRootPathFromContext({
2997
+ boundaryLabel: params.boundaryLabel,
2998
+ context: params.context,
2999
+ kind,
3000
+ });
3001
+ }
3002
+ function resolveOutsideRootPathSync(params) {
3003
+ if (params.context.lexicalInside) {
3004
+ return null;
3201
3005
  }
3202
- if (options.force) {
3203
- held.refCount = 0;
3006
+ const kind = getPathKindSync(params.context.absolutePath, false);
3007
+ return buildOutsideRootPathFromContext({
3008
+ boundaryLabel: params.boundaryLabel,
3009
+ context: params.context,
3010
+ kind,
3011
+ });
3012
+ }
3013
+ function buildOutsideRootPathFromContext(params) {
3014
+ return buildOutsideLexicalRootPath({
3015
+ boundaryLabel: params.boundaryLabel,
3016
+ rootCanonicalPath: params.context.rootCanonicalPath,
3017
+ absolutePath: params.context.absolutePath,
3018
+ canonicalOutsideLexicalPath: params.context.canonicalOutsideLexicalPath,
3019
+ rootPath: params.context.rootPath,
3020
+ kind: params.kind,
3021
+ });
3022
+ }
3023
+ async function resolveOutsideLexicalCanonicalPathAsync(params) {
3024
+ if (path_isPathInside(params.rootPath, params.absolutePath)) {
3025
+ return undefined;
3204
3026
  }
3205
- else if (!options.retry && held.refCount > 0) {
3206
- held.refCount -= 1;
3027
+ return await resolvePathViaExistingAncestor(params.absolutePath);
3028
+ }
3029
+ function resolveOutsideLexicalCanonicalPathSync(params) {
3030
+ if (isPathInside(params.rootPath, params.absolutePath)) {
3031
+ return undefined;
3207
3032
  }
3208
- if (held.refCount > 0) {
3209
- return false;
3033
+ return resolvePathViaExistingAncestorSync(params.absolutePath);
3034
+ }
3035
+ function buildOutsideLexicalRootPath(params) {
3036
+ assertInsideBoundary({
3037
+ boundaryLabel: params.boundaryLabel,
3038
+ rootCanonicalPath: params.rootCanonicalPath,
3039
+ candidatePath: params.canonicalOutsideLexicalPath,
3040
+ absolutePath: params.absolutePath,
3041
+ });
3042
+ return buildResolvedRootPath({
3043
+ absolutePath: params.absolutePath,
3044
+ canonicalPath: params.canonicalOutsideLexicalPath,
3045
+ rootPath: params.rootPath,
3046
+ rootCanonicalPath: params.rootCanonicalPath,
3047
+ kind: params.kind,
3048
+ });
3049
+ }
3050
+ function assertLexicalBoundaryOrCanonicalAlias(params) {
3051
+ if (params.skipLexicalRootCheck || params.lexicalInside) {
3052
+ return;
3053
+ }
3054
+ if (path_isPathInside(params.rootCanonicalPath, params.canonicalOutsideLexicalPath)) {
3055
+ return;
3056
+ }
3057
+ throw pathEscapeError({
3058
+ boundaryLabel: params.boundaryLabel,
3059
+ rootPath: params.rootPath,
3060
+ absolutePath: params.absolutePath,
3061
+ });
3062
+ }
3063
+ function buildResolvedRootPath(params) {
3064
+ return {
3065
+ absolutePath: params.absolutePath,
3066
+ canonicalPath: params.canonicalPath,
3067
+ rootPath: params.rootPath,
3068
+ rootCanonicalPath: params.rootCanonicalPath,
3069
+ relativePath: relativeInsideRoot(params.rootCanonicalPath, params.canonicalPath),
3070
+ exists: params.kind.exists,
3071
+ kind: params.kind.kind,
3072
+ };
3073
+ }
3074
+ async function getPathKind(absolutePath, preserveFinalSymlink) {
3075
+ try {
3076
+ const stat = preserveFinalSymlink
3077
+ ? await promises_namespaceObject.lstat(absolutePath)
3078
+ : await promises_namespaceObject.stat(absolutePath);
3079
+ return { exists: true, kind: toResolvedKind(stat) };
3210
3080
  }
3211
- held.releasePromise = (async () => {
3212
- await held.handle.close().catch(() => undefined);
3213
- await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
3214
- lockRoot: held.lockRoot,
3215
- parsePayload: held.parsePayload,
3216
- });
3217
- if (state.held.get(normalizedTargetPath) === held) {
3218
- state.held.delete(normalizedTargetPath);
3219
- }
3220
- if (held.compromiseTimer) {
3221
- clearInterval(held.compromiseTimer);
3222
- held.compromiseTimer = undefined;
3081
+ catch (error) {
3082
+ if (path_isNotFoundPathError(error)) {
3083
+ return { exists: false, kind: "missing" };
3223
3084
  }
3224
- })();
3085
+ throw error;
3086
+ }
3087
+ }
3088
+ function getPathKindSync(absolutePath, preserveFinalSymlink) {
3225
3089
  try {
3226
- await held.releasePromise;
3227
- return true;
3090
+ const stat = preserveFinalSymlink ? fs.lstatSync(absolutePath) : fs.statSync(absolutePath);
3091
+ return { exists: true, kind: toResolvedKind(stat) };
3228
3092
  }
3229
- finally {
3230
- held.releasePromise = undefined;
3093
+ catch (error) {
3094
+ if (isNotFoundPathError(error)) {
3095
+ return { exists: false, kind: "missing" };
3096
+ }
3097
+ throw error;
3231
3098
  }
3232
3099
  }
3233
- function handleForHeldLock(state, normalizedTargetPath, held) {
3234
- return createHeldSidecarLockHandle({
3235
- normalizedTargetPath,
3236
- held,
3237
- release: async (options) => await releaseHeldLock(state, normalizedTargetPath, held, { retry: options?.retry }),
3238
- });
3239
- }
3240
- function createSidecarLockManager(key) {
3241
- const state = resolveManagerState(key);
3242
- function ensureExitCleanupRegistered() {
3243
- state.cleanupRegistered = true;
3244
- state.reclaimCleanupRegistered = true;
3245
- ensureGlobalExitCleanupRegistered();
3100
+ function toResolvedKind(stat) {
3101
+ if (stat.isFile()) {
3102
+ return "file";
3246
3103
  }
3247
- async function acquire(options) {
3248
- return await acquireSidecarLock(options, {
3249
- held: state.held,
3250
- reclaimGuards: state.reclaimGuards,
3251
- ensureExitCleanupRegistered,
3252
- handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
3253
- releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
3254
- });
3104
+ if (stat.isDirectory()) {
3105
+ return "directory";
3255
3106
  }
3256
- async function withLock(options, fn) {
3257
- const lock = await acquire(options);
3258
- let result;
3259
- try {
3260
- result = await fn();
3261
- }
3262
- catch (bodyError) {
3263
- try {
3264
- await lock.release();
3265
- }
3266
- catch (releaseError) {
3267
- throw createSuppressedError(releaseError, bodyError, "file lock callback and release both failed");
3268
- }
3269
- throw bodyError;
3270
- }
3271
- await lock.release();
3272
- return result;
3107
+ if (stat.isSymbolicLink()) {
3108
+ return "symlink";
3273
3109
  }
3274
- async function drain() {
3275
- for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
3276
- await releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
3277
- }
3110
+ return "other";
3111
+ }
3112
+ function relativeInsideRoot(rootPath, targetPath) {
3113
+ const relative = external_node_path_namespaceObject.relative(external_node_path_namespaceObject.resolve(rootPath), external_node_path_namespaceObject.resolve(targetPath));
3114
+ if (!relative || relative === ".") {
3115
+ return "";
3278
3116
  }
3279
- function reset() {
3280
- releaseAllLocksSync(state);
3117
+ if (isPathRelativeEscape(relative)) {
3118
+ return "";
3281
3119
  }
3282
- function heldEntries() {
3283
- return Array.from(state.held.entries()).map(([normalizedTargetPath, held]) => ({
3284
- normalizedTargetPath,
3285
- lockPath: held.lockPath,
3286
- acquiredAt: held.acquiredAt,
3287
- metadata: held.metadata,
3288
- forceRelease: () => releaseHeldLock(state, normalizedTargetPath, held, { force: true }),
3289
- }));
3120
+ return relative;
3121
+ }
3122
+ function assertInsideBoundary(params) {
3123
+ if (path_isPathInside(params.rootCanonicalPath, params.candidatePath)) {
3124
+ return;
3290
3125
  }
3291
- return { acquire, withLock, drain, reset, heldEntries };
3126
+ throw new Error(`Path resolves outside ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.absolutePath)}`);
3292
3127
  }
3293
- async function withSidecarLock(targetPath, options, fn) {
3294
- const manager = createSidecarLockManager(options.managerKey ?? `fs-safe.sidecar-lock:${targetPath}`);
3295
- const { managerKey: _managerKey, ...acquireOptions } = options;
3296
- return await manager.withLock({ ...acquireOptions, targetPath }, fn);
3128
+ function pathEscapeError(params) {
3129
+ return new Error(`Path escapes ${params.boundaryLabel} (${shortPath(params.rootPath)}): ${shortPath(params.absolutePath)}`);
3130
+ }
3131
+ function symlinkEscapeError(params) {
3132
+ return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
3297
3133
  }
3298
3134
 
3299
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
3135
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-root.js
3300
3136
 
3301
3137
 
3302
3138
 
3303
3139
 
3304
3140
 
3305
3141
 
3142
+ async function openSidecarRoot(lockRoot, relative, discardObservation, onOpenFailure) {
3143
+ const resolved = await lockRoot.resolve(relative);
3144
+ const canonicalPath = async () => (await resolveRootPath({
3145
+ absolutePath: resolved,
3146
+ rootPath: lockRoot.rootReal,
3147
+ rootCanonicalPath: lockRoot.rootReal,
3148
+ boundaryLabel: "sidecar lock root",
3149
+ })).canonicalPath;
3150
+ const expectedRealPath = await canonicalPath();
3151
+ if (expectedRealPath === lockRoot.rootReal)
3152
+ throw new errors_FsSafeError("not-file", "sidecar lock is a directory");
3153
+ const parents = [];
3154
+ let completeParents = true;
3155
+ if (discardObservation) {
3156
+ // Follow permitted in-root aliases first; receipts cover canonical ancestry.
3157
+ for (let dir = external_node_path_namespaceObject.dirname(expectedRealPath);; dir = external_node_path_namespaceObject.dirname(dir)) {
3158
+ try {
3159
+ parents.push({ dir, stat: await inspectDirectoryIdentity(dir) });
3160
+ }
3161
+ catch (error) {
3162
+ if (!path_isNotFoundPathError(error))
3163
+ throw error;
3164
+ completeParents = false;
3165
+ }
3166
+ if (dir === lockRoot.rootReal)
3167
+ break;
3168
+ }
3169
+ }
3170
+ const observation = fileObservation();
3171
+ try {
3172
+ const opened = await observation.run(() => lockRoot.open(relative));
3173
+ if (opened.realPath !== expectedRealPath) {
3174
+ await opened.handle.close().catch(() => undefined);
3175
+ throw new errors_FsSafeError("path-mismatch", "sidecar lock path changed during open");
3176
+ }
3177
+ return opened;
3178
+ }
3179
+ catch (error) {
3180
+ const openFailed = observation.has(error, `open:${resolved}`);
3181
+ if (openFailed)
3182
+ onOpenFailure?.(error);
3183
+ const missing = openFailed && error instanceof errors_FsSafeError && error.code === "not-found";
3184
+ if (missing && !discardObservation)
3185
+ return null;
3186
+ const discardable = observation.has(error, `unlinked:${resolved}`) ||
3187
+ (discardObservation === "changed" && observation.has(error, `changed:${resolved}`));
3188
+ if (!discardObservation || (!missing && (!completeParents || !discardable)))
3189
+ throw error;
3190
+ try {
3191
+ try {
3192
+ const current = await lockRoot.stat(relative);
3193
+ if (!current.isFile || current.isSymbolicLink || current.nlink !== 1)
3194
+ throw error;
3195
+ }
3196
+ catch (probeError) {
3197
+ if (!(probeError instanceof errors_FsSafeError && probeError.code === "not-found"))
3198
+ throw probeError;
3199
+ }
3200
+ for (const { dir, stat } of parents)
3201
+ await inspectDirectoryIdentity(dir, stat);
3202
+ await lockRoot.resolve(relative);
3203
+ if (await canonicalPath() === expectedRealPath)
3204
+ return null;
3205
+ }
3206
+ catch {
3207
+ // Keep the original failure when directory or confinement proof fails.
3208
+ }
3209
+ throw error;
3210
+ }
3211
+ }
3212
+
3213
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
3214
+ let test_hooks_fsSafeTestHooks;
3215
+ function allowFsSafeTestHooks() {
3216
+ return false || process.env.VITEST === "true";
3217
+ }
3218
+ function getFsSafeTestHooks() {
3219
+ return test_hooks_fsSafeTestHooks;
3220
+ }
3221
+ function __setFsSafeTestHooksForTest(hooks) {
3222
+ if (hooks && !allowFsSafeTestHooks()) {
3223
+ throw new Error("__setFsSafeTestHooksForTest is only available in tests");
3224
+ }
3225
+ test_hooks_fsSafeTestHooks = hooks;
3226
+ }
3306
3227
 
3228
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
3307
3229
 
3308
3230
 
3309
3231
 
@@ -3314,361 +3236,395 @@ async function withSidecarLock(targetPath, options, fn) {
3314
3236
 
3315
3237
 
3316
3238
 
3317
- function byteLength(input, encoding) {
3318
- return typeof input === "string"
3319
- ? Buffer.byteLength(input, encoding ?? "utf8")
3320
- : input.byteLength;
3239
+ const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
3240
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
3241
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
3242
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
3243
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN = new RegExp(`\\n(${SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX}[ \\t]{${SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS}})\\n$`);
3244
+ function parseSidecarLockSnapshot(snapshot, parser) {
3245
+ return snapshot && { ...snapshot, payload: parseSidecarLockPayload(snapshot.raw, parser) };
3321
3246
  }
3322
- function assertSafeBasename(basename) {
3323
- if (!basename ||
3324
- basename === "." ||
3325
- basename === ".." ||
3326
- basename.includes("/") ||
3327
- basename.includes("\0")) {
3328
- throw new errors_FsSafeError("invalid-path", "invalid target path");
3247
+ function createSidecarLockOwnershipToken() {
3248
+ let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
3249
+ for (const byte of (0,external_node_crypto_namespaceObject.randomBytes)(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
3250
+ for (let bit = 7; bit >= 0; bit -= 1) {
3251
+ token += byte & (1 << bit) ? "\t" : " ";
3252
+ }
3329
3253
  }
3254
+ return token;
3330
3255
  }
3331
- function assertWithinMaxBytes(bytes, maxBytes) {
3332
- if (maxBytes !== undefined && bytes > maxBytes) {
3333
- throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
3256
+ function readSidecarLockOwnershipToken(raw) {
3257
+ return SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN.exec(raw)?.[1];
3258
+ }
3259
+ function serializeSidecarLockPayload(payload) {
3260
+ const ownershipToken = createSidecarLockOwnershipToken();
3261
+ return {
3262
+ raw: `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`,
3263
+ ownershipToken,
3264
+ };
3265
+ }
3266
+ function relativeSidecarLockPath(lockRoot, lockPath) {
3267
+ const resolved = external_node_path_namespaceObject.resolve(lockPath);
3268
+ const lexicalRelative = external_node_path_namespaceObject.relative(lockRoot.rootDir, resolved);
3269
+ const relative = lexicalRelative !== ".." &&
3270
+ !lexicalRelative.startsWith(`..${external_node_path_namespaceObject.sep}`) &&
3271
+ !external_node_path_namespaceObject.isAbsolute(lexicalRelative)
3272
+ ? lexicalRelative
3273
+ : external_node_path_namespaceObject.relative(lockRoot.rootReal, resolved);
3274
+ if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_namespaceObject.sep}`) || external_node_path_namespaceObject.isAbsolute(relative)) {
3275
+ throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
3334
3276
  }
3277
+ return relative.split(external_node_path_namespaceObject.sep).join(external_node_path_namespaceObject.posix.sep);
3335
3278
  }
3336
- async function syncFileBestEffort(handle) {
3279
+ function parseSidecarLockPayload(raw, parser) {
3280
+ if (parser) {
3281
+ return parser(raw);
3282
+ }
3337
3283
  try {
3338
- await handle.sync();
3284
+ const parsed = JSON.parse(raw);
3285
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
3339
3286
  }
3340
- catch (error) {
3341
- if (error?.code !== "EPERM") {
3287
+ catch {
3288
+ return null;
3289
+ }
3290
+ }
3291
+ function missingSnapshotPath(error) {
3292
+ if (error.code === "ENOENT")
3293
+ return null;
3294
+ throw error;
3295
+ }
3296
+ async function readSidecarLockSnapshot(lockPath, options = {}) {
3297
+ return parseSidecarLockSnapshot(await readSidecarLockRawSnapshot(lockPath, options), options.parsePayload);
3298
+ }
3299
+ async function readSidecarLockRawSnapshot(lockPath, options = {}) {
3300
+ let handle;
3301
+ try {
3302
+ if (options.lockRoot) {
3303
+ const lockRoot = options.lockRoot;
3304
+ const relative = relativeSidecarLockPath(lockRoot, lockPath);
3305
+ const opened = await openSidecarRoot(lockRoot, relative, options.discardObservation, options.onOpenFailure);
3306
+ if (!opened)
3307
+ return null;
3308
+ try {
3309
+ const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
3310
+ return {
3311
+ raw,
3312
+ stat: opened.stat,
3313
+ };
3314
+ }
3315
+ finally {
3316
+ await opened.handle.close().catch(() => undefined);
3317
+ }
3318
+ }
3319
+ const before = await promises_namespaceObject.lstat(lockPath).catch(missingSnapshotPath);
3320
+ if (!before)
3321
+ return null;
3322
+ if (!before.isFile() || before.isSymbolicLink()) {
3323
+ if (options.rejectNonFile) {
3324
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
3325
+ }
3326
+ return null;
3327
+ }
3328
+ await getFsSafeTestHooks()?.beforeSidecarLockSnapshotOpen?.(lockPath);
3329
+ const noFollow = process.platform !== "win32" && typeof external_node_fs_namespaceObject.constants.O_NOFOLLOW === "number"
3330
+ ? external_node_fs_namespaceObject.constants.O_NOFOLLOW
3331
+ : 0;
3332
+ try {
3333
+ handle = await promises_namespaceObject.open(lockPath, external_node_fs_namespaceObject.constants.O_RDONLY |
3334
+ noFollow |
3335
+ (typeof external_node_fs_namespaceObject.constants.O_NONBLOCK === "number" ? external_node_fs_namespaceObject.constants.O_NONBLOCK : 0));
3336
+ }
3337
+ catch (error) {
3338
+ if (error.code === "ENOENT")
3339
+ return null;
3340
+ if (options.rejectNonFile && error.code === "ELOOP") {
3341
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`, {
3342
+ cause: error,
3343
+ });
3344
+ }
3345
+ options.onOpenFailure?.(error);
3342
3346
  throw error;
3343
3347
  }
3344
- }
3345
- }
3346
- async function writeStreamToHandle(stream, handle, maxBytes) {
3347
- let bytes = 0;
3348
- for await (const chunk of stream) {
3349
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
3350
- bytes += buffer.byteLength;
3351
- assertWithinMaxBytes(bytes, maxBytes);
3352
- let offset = 0;
3353
- while (offset < buffer.byteLength) {
3354
- const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
3355
- if (bytesWritten <= 0) {
3356
- throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
3348
+ const opened = await handle.stat();
3349
+ if (!opened.isFile()) {
3350
+ if (options.rejectNonFile) {
3351
+ throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
3357
3352
  }
3358
- offset += bytesWritten;
3353
+ return null;
3359
3354
  }
3355
+ if (!options.allowDescriptorIdentityDrift && !file_identity_sameFileIdentity(before, opened))
3356
+ return null;
3357
+ const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
3358
+ const after = await promises_namespaceObject.lstat(lockPath).catch(missingSnapshotPath);
3359
+ if (!after || !after.isFile() || !file_identity_sameFileIdentity(before, after))
3360
+ return null;
3361
+ return { raw, stat: after };
3362
+ }
3363
+ finally {
3364
+ await handle?.close().catch(() => undefined);
3360
3365
  }
3361
3366
  }
3362
- async function runPinnedWriteHelper(params) {
3363
- const normalizedParams = { ...params, maxBytes: byte_budget_normalizeMaxBytes(params.maxBytes) };
3364
- assertSafeBasename(params.basename);
3365
- validatePinnedOperationPayload({
3366
- relativeParentPath: params.relativeParentPath,
3367
- });
3368
- // The explicit compatibility policy uses the guarded Node fallback, where
3369
- // content verification can replace the strict post-rename inode check.
3370
- if (normalizedParams.onRenameIdentityMismatch === "verify-content") {
3371
- return await runPinnedWriteFallback(normalizedParams);
3367
+ function lstatSidecarLockSync(lockPath) {
3368
+ try {
3369
+ return fsSync.lstatSync(lockPath);
3372
3370
  }
3373
- const native = getNativeBinding();
3374
- if (native) {
3375
- return await runPinnedWriteNative(native, normalizedParams);
3371
+ catch (error) {
3372
+ return missingSnapshotPath(error);
3376
3373
  }
3377
- return await runPinnedWriteFallback(normalizedParams);
3378
3374
  }
3379
- async function runPinnedWriteWithRenamePolicy(params) {
3380
- const { targetPath, renameIdentity, ...writeParams } = params;
3381
- if (renameIdentity !== "verify-content-with-lock") {
3382
- return await runPinnedWriteHelper(writeParams);
3383
- }
3384
- const relativeTargetPath = writeParams.relativeParentPath
3385
- ? `${writeParams.relativeParentPath}/${writeParams.basename}`
3386
- : writeParams.basename;
3387
- const lockPath = external_node_path_namespaceObject.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
3388
- return await withSidecarLock(writeParams.rootPath, {
3389
- managerKey: `fs-safe.write:${targetPath}`,
3390
- lockPath,
3391
- staleMs: 30_000,
3392
- timeoutMs: 5_000,
3393
- payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
3394
- retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
3395
- }, async () => await runPinnedWriteHelper({
3396
- ...writeParams,
3397
- onRenameIdentityMismatch: "verify-content",
3398
- }));
3375
+ function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}) {
3376
+ return parseSidecarLockSnapshot(readSidecarLockRawSnapshotSync(lockPath, options), parsePayload);
3399
3377
  }
3400
- async function runPinnedWriteFallback(params) {
3401
- let parentPath = params.relativeParentPath
3402
- ? external_node_path_namespaceObject.join(params.rootPath, ...params.relativeParentPath.split("/"))
3403
- : params.rootPath;
3404
- if (params.mkdir) {
3405
- // mkdirPathComponentsWithGuards may resolve the final component through
3406
- // an in-root symlink (e.g. a skill-bank layout). Use its returned real
3407
- // path for the subsequent guard and target path so we don't re-check the
3408
- // original, possibly-symlinked, lexical path and reject it outright.
3409
- parentPath = await mkdirPathComponentsWithGuards({
3410
- rootReal: params.rootPath,
3411
- targetPath: parentPath,
3412
- beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
3413
- });
3414
- }
3415
- const parentGuard = params.mkdir
3416
- ? await directory_guard_createAsyncDirectoryGuard(parentPath)
3417
- : await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
3418
- const targetPath = external_node_path_namespaceObject.join(parentPath, params.basename);
3419
- if (params.overwrite === false) {
3420
- const handle = await withAsyncDirectoryGuards([parentGuard], async () => await promises_namespaceObject.open(targetPath, external_node_fs_namespaceObject.constants.O_WRONLY | external_node_fs_namespaceObject.constants.O_CREAT | external_node_fs_namespaceObject.constants.O_EXCL, params.mode), {
3421
- onPostGuardFailure: async (openedHandle) => {
3422
- // The parent failed verification, so targetPath may now resolve
3423
- // somewhere else. Close the fd, but do not clean up by path.
3424
- await openedHandle.close().catch(() => undefined);
3425
- },
3426
- });
3427
- let created = true;
3428
- try {
3429
- const verificationIdentity = await handle.stat({ bigint: true });
3430
- await handle.chmod(params.mode);
3431
- if (params.input.kind === "buffer") {
3432
- assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
3433
- if (typeof params.input.data === "string") {
3434
- await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
3435
- }
3436
- else {
3437
- await handle.writeFile(params.input.data);
3438
- }
3439
- }
3440
- else {
3441
- await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
3442
- }
3443
- await syncFileBestEffort(handle);
3444
- const stat = await handle.stat();
3445
- await syncDirectoryBestEffort(parentPath);
3446
- // Publication is complete. A failed outer check must not remove its target.
3447
- created = false;
3448
- await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
3449
- return { dev: stat.dev, ino: stat.ino };
3450
- }
3451
- finally {
3452
- await handle.close().catch(() => undefined);
3453
- if (created) {
3454
- await promises_namespaceObject.rm(targetPath, { force: true }).catch(() => undefined);
3455
- }
3456
- }
3457
- }
3458
- const tempPath = external_node_path_namespaceObject.join(parentPath, `.${params.basename}.${(0,external_node_crypto_namespaceObject.randomUUID)()}.fallback.tmp`);
3459
- const tempFlags = external_node_fs_namespaceObject.constants.O_WRONLY |
3460
- external_node_fs_namespaceObject.constants.O_CREAT |
3461
- external_node_fs_namespaceObject.constants.O_EXCL |
3462
- (process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_namespaceObject.constants
3463
- ? external_node_fs_namespaceObject.constants.O_NOFOLLOW
3464
- : 0);
3465
- let handle;
3466
- let tempStat;
3467
- let readHandle;
3468
- let renamed = false;
3378
+ function readSidecarLockRawSnapshotSync(lockPath, options = {}) {
3379
+ let fd;
3469
3380
  try {
3470
- handle = await promises_namespaceObject.open(tempPath, tempFlags, params.mode);
3471
- let verificationIdentity = await handle.stat({ bigint: true });
3472
- await handle.chmod(params.mode);
3473
- if (params.input.kind === "buffer") {
3474
- assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
3475
- if (typeof params.input.data === "string") {
3476
- await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
3477
- }
3478
- else {
3479
- await handle.writeFile(params.input.data);
3381
+ const before = lstatSidecarLockSync(lockPath);
3382
+ if (!before)
3383
+ return null;
3384
+ if (!before.isFile() || before.isSymbolicLink()) {
3385
+ if (options.rejectNonFile) {
3386
+ throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
3480
3387
  }
3388
+ return null;
3481
3389
  }
3482
- else {
3483
- await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
3390
+ try {
3391
+ fd = fsSync.openSync(lockPath, resolveReadOpenFlags());
3484
3392
  }
3485
- tempStat = await handle.stat();
3486
- const tempPathStat = await promises_namespaceObject.lstat(tempPath);
3487
- if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
3488
- throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
3393
+ catch (error) {
3394
+ if (error.code === "ENOENT")
3395
+ return null;
3396
+ options.onOpenFailure?.(error);
3397
+ throw error;
3489
3398
  }
3490
- const expectedTempStat = tempStat;
3491
- await syncFileBestEffort(handle);
3492
- let verifiedIdentity = expectedTempStat;
3493
- await withAsyncDirectoryGuards([parentGuard], async () => {
3494
- await promises_namespaceObject.rename(tempPath, targetPath);
3495
- renamed = true;
3496
- await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
3497
- await syncDirectoryBestEffort(parentPath);
3498
- const targetStat = await promises_namespaceObject.lstat(targetPath);
3499
- if (targetStat.isSymbolicLink()) {
3500
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
3501
- }
3502
- if (!file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
3503
- // On filesystems like rclone FUSE, rename(2) can give the destination a
3504
- // different inode from the source temp fd even with zero concurrency. The
3505
- // caller must ensure mutual exclusion before passing "verify-content";
3506
- // fall back to a content hash for this rename-boundary check only.
3507
- if (params.onRenameIdentityMismatch !== "verify-content") {
3508
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
3509
- }
3510
- if (params.input.kind !== "buffer") {
3511
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
3512
- }
3513
- const expectedHash = sha256Hex(params.input.data, params.input.encoding);
3514
- readHandle = await promises_namespaceObject.open(targetPath, read_open_flags_resolveReadOpenFlags());
3515
- const readHandleStat = await readHandle.stat({ bigint: true });
3516
- const actualHash = sha256Hex(await readHandle.readFile());
3517
- if (actualHash !== expectedHash) {
3518
- throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
3519
- }
3520
- // The content-verified destination, not the old temp inode, is now pinned.
3521
- verificationIdentity = readHandleStat;
3522
- // Preserve the helper's legacy numeric return facts, not the private proof.
3523
- verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
3399
+ const opened = fsSync.fstatSync(fd);
3400
+ if (!opened.isFile()) {
3401
+ if (options.rejectNonFile) {
3402
+ throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
3524
3403
  }
3525
- });
3526
- await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
3527
- return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
3404
+ return null;
3405
+ }
3406
+ const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
3407
+ const after = lstatSidecarLockSync(lockPath);
3408
+ if (!after || !sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
3409
+ return null;
3410
+ return {
3411
+ raw,
3412
+ stat: after,
3413
+ ownershipToken: readSidecarLockOwnershipToken(raw),
3414
+ };
3528
3415
  }
3529
3416
  finally {
3530
- await readHandle?.close().catch(() => undefined);
3531
- await handle?.close().catch(() => undefined);
3532
- if (!renamed) {
3533
- await promises_namespaceObject.rm(tempPath, { force: true }).catch(() => undefined);
3534
- }
3417
+ if (fd !== undefined)
3418
+ fsSync.closeSync(fd);
3419
+ }
3420
+ }
3421
+ function removeSidecarLockIfUnchangedSync(lockPath, observed) {
3422
+ const current = readSidecarLockSnapshotSync(lockPath);
3423
+ if (!current || !sidecarLockSnapshotMatches(current, observed))
3424
+ return false;
3425
+ fsSync.rmSync(lockPath);
3426
+ return true;
3427
+ }
3428
+ function sidecarLockSnapshotMatches(current, observed) {
3429
+ if (observed.ownershipToken !== undefined) {
3430
+ return (current.stat?.isFile() === true &&
3431
+ current.raw !== undefined &&
3432
+ observed.raw !== undefined &&
3433
+ readSidecarLockOwnershipToken(current.raw) === observed.ownershipToken &&
3434
+ readSidecarLockOwnershipToken(observed.raw) === observed.ownershipToken &&
3435
+ current.raw === observed.raw);
3436
+ }
3437
+ if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
3438
+ return false;
3439
+ }
3440
+ if (observed.raw !== undefined) {
3441
+ return current.raw === observed.raw;
3442
+ }
3443
+ return observed.stat !== undefined && current.stat !== undefined;
3444
+ }
3445
+ async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
3446
+ const current = await readSidecarLockSnapshot(lockPath, {
3447
+ ...options,
3448
+ allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
3449
+ });
3450
+ if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
3451
+ return false;
3535
3452
  }
3453
+ if (options.lockRoot) {
3454
+ await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath));
3455
+ }
3456
+ else {
3457
+ await promises_namespaceObject.rm(lockPath, { force: true });
3458
+ }
3459
+ return true;
3536
3460
  }
3537
-
3538
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/error-detail.js
3539
- const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
3540
- function formatErrorDetail(value) {
3541
- return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
3461
+ async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
3462
+ const current = await readSidecarLockSnapshot(lockPath, {
3463
+ ...options,
3464
+ allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
3465
+ });
3466
+ return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
3542
3467
  }
3543
-
3544
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-symlink.js
3545
-
3546
-
3547
-
3548
-
3549
-
3550
-
3551
- function normalizeSymlinkResolutionError(error) {
3552
- if (isSymlinkOpenError(error)) {
3553
- throw new errors_FsSafeError("symlink", "symlink path could not be resolved", {
3554
- cause: error instanceof Error ? error : undefined,
3555
- });
3468
+ async function sidecarReclaimGuardExists(pathname) {
3469
+ try {
3470
+ await promises_namespaceObject.lstat(pathname);
3471
+ return true;
3472
+ }
3473
+ catch (err) {
3474
+ if (err.code === "ENOENT") {
3475
+ return false;
3476
+ }
3477
+ throw err;
3556
3478
  }
3557
- if (!path_isNotFoundPathError(error))
3558
- throw error;
3559
3479
  }
3560
- async function resolveSymlinkHopPath(symlinkPath) {
3480
+ async function tryAcquireSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
3561
3481
  try {
3562
- return external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(symlinkPath));
3482
+ await promises_namespaceObject.mkdir(reclaimGuardPath);
3483
+ reclaimGuards.add(reclaimGuardPath);
3484
+ return true;
3563
3485
  }
3564
- catch (error) {
3565
- normalizeSymlinkResolutionError(error);
3566
- const linkTarget = await promises_namespaceObject.readlink(symlinkPath);
3567
- return resolvePathViaExistingAncestor(external_node_path_namespaceObject.resolve(external_node_path_namespaceObject.dirname(symlinkPath), linkTarget));
3486
+ catch (err) {
3487
+ if (err.code === "EEXIST") {
3488
+ return false;
3489
+ }
3490
+ throw err;
3568
3491
  }
3569
3492
  }
3570
- function root_path_symlink_resolveSymlinkHopPathSync(symlinkPath) {
3493
+ async function releaseSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
3494
+ await promises_namespaceObject.rmdir(reclaimGuardPath);
3495
+ reclaimGuards.delete(reclaimGuardPath);
3496
+ }
3497
+ async function removeStaleSidecarLockIfAllowed(params) {
3498
+ if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
3499
+ return "not-approved";
3500
+ }
3501
+ const ioOptions = { lockRoot: params.lockRoot, parsePayload: params.parsePayload };
3502
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
3503
+ return "changed";
3504
+ }
3505
+ if (!(await params.shouldRemoveStaleLock({
3506
+ lockPath: params.lockPath,
3507
+ normalizedTargetPath: params.normalizedTargetPath,
3508
+ raw: params.snapshot.raw,
3509
+ payload: params.snapshot.payload,
3510
+ }))) {
3511
+ return "not-approved";
3512
+ }
3513
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
3514
+ return "changed";
3515
+ }
3571
3516
  try {
3572
- return path.resolve(fs.realpathSync(symlinkPath));
3517
+ if (params.lockRoot) {
3518
+ await params.lockRoot.remove(relativeSidecarLockPath(params.lockRoot, params.lockPath));
3519
+ }
3520
+ else {
3521
+ await promises_namespaceObject.rm(params.lockPath);
3522
+ }
3523
+ return "removed";
3573
3524
  }
3574
- catch (error) {
3575
- normalizeSymlinkResolutionError(error);
3576
- const linkTarget = fs.readlinkSync(symlinkPath);
3577
- return resolvePathViaExistingAncestorSync(path.resolve(path.dirname(symlinkPath), linkTarget));
3525
+ catch (err) {
3526
+ if (err.code === "ENOENT") {
3527
+ return "changed";
3528
+ }
3529
+ throw err;
3578
3530
  }
3579
3531
  }
3580
3532
 
3581
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/safe-path-segment.js
3533
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
3582
3534
 
3583
- const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
3584
- const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
3585
- // Windows treats "C:name" as relative to the drive's current directory even
3586
- // though path.win32.isAbsolute() reports false.
3587
- const DRIVE_RELATIVE_PREFIX = /^[A-Za-z]:(?![\\/])/;
3588
- const HYPHEN_CHAR_CODE = 0x2d;
3589
- function safe_path_segment_isDriveRelativePath(value) {
3590
- return DRIVE_RELATIVE_PREFIX.test(value);
3591
- }
3592
- function assertNoDriveRelativePathSegments(value, label) {
3593
- if (value.split("/").some(safe_path_segment_isDriveRelativePath)) {
3594
- throw new errors_FsSafeError("invalid-path", `${label} must not contain a drive letter`);
3535
+ function assertFiniteNonNegative(value, label) {
3536
+ if (!Number.isFinite(value) || value < 0) {
3537
+ throw new RangeError(`${label} must be a finite non-negative number`);
3595
3538
  }
3596
- return value;
3597
3539
  }
3598
- function trimHyphenEdges(value) {
3599
- let start = 0;
3600
- let end = value.length;
3601
- while (start < end && value.charCodeAt(start) === HYPHEN_CHAR_CODE) {
3602
- start += 1;
3540
+ function validateSidecarLockRetryOptions(retry) {
3541
+ if (retry.retries !== undefined && (!Number.isSafeInteger(retry.retries) || retry.retries < 0)) {
3542
+ throw new RangeError("lock retry.retries must be a non-negative safe integer");
3603
3543
  }
3604
- while (end > start && value.charCodeAt(end - 1) === HYPHEN_CHAR_CODE) {
3605
- end -= 1;
3544
+ if (retry.factor !== undefined)
3545
+ assertFiniteNonNegative(retry.factor, "lock retry.factor");
3546
+ if (retry.minTimeout !== undefined) {
3547
+ assertFiniteNonNegative(retry.minTimeout, "lock retry.minTimeout");
3548
+ }
3549
+ if (retry.maxTimeout !== undefined) {
3550
+ assertFiniteNonNegative(retry.maxTimeout, "lock retry.maxTimeout");
3551
+ }
3552
+ if (retry.minTimeout !== undefined &&
3553
+ retry.maxTimeout !== undefined &&
3554
+ retry.minTimeout > retry.maxTimeout) {
3555
+ throw new RangeError("lock retry.minTimeout must not exceed retry.maxTimeout");
3606
3556
  }
3607
- return start === 0 && end === value.length ? value : value.slice(start, end);
3608
3557
  }
3609
- function isSafePathSegment(segment, options = {}) {
3610
- return (segment !== "" &&
3611
- segment !== "." &&
3612
- segment !== ".." &&
3613
- !segment.includes("/") &&
3614
- !segment.includes("\\") &&
3615
- !segment.includes("\0") &&
3616
- (options.allowDotPrefix === true || !segment.startsWith(".")) &&
3617
- (options.allowDotPrefix === true
3618
- ? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
3619
- : SAFE_PATH_SEGMENT_PATTERN.test(segment)));
3558
+ function validateSidecarLockTimeoutMs(timeoutMs) {
3559
+ if (timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY)
3560
+ return;
3561
+ assertFiniteNonNegative(timeoutMs, "lock timeoutMs");
3620
3562
  }
3621
- function assertSafePathSegment(segment, options = {}) {
3622
- // Validate the exact value callers will later join into paths; trimming here
3623
- // would let whitespace-padded ids pass and then be used verbatim.
3624
- if (!isSafePathSegment(segment, options)) {
3625
- throw new FsSafeError("invalid-path", `${options.label ?? "path segment"} must be a safe path segment`);
3626
- }
3627
- return segment;
3563
+ function computeSidecarLockDelayMs(retry, attempt) {
3564
+ validateSidecarLockRetryOptions(retry);
3565
+ const minTimeout = retry.minTimeout ?? 50;
3566
+ const maxTimeout = retry.maxTimeout ?? 1000;
3567
+ const factor = retry.factor ?? 1;
3568
+ const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
3569
+ const jitter = retry.randomize ? 1 + Math.random() : 1;
3570
+ return Math.min(maxTimeout, Math.round(base * jitter));
3628
3571
  }
3629
- function sanitizeSafePathSegment(value, fallback, options = {}) {
3630
- const sanitized = value
3631
- .trim()
3632
- .replace(/[\\/]+/g, "-")
3633
- .replace(/\0/g, "")
3634
- .replace(/[^A-Za-z0-9._-]+/g, "-");
3635
- const trimmed = trimHyphenEdges(sanitized);
3636
- if (isSafePathSegment(trimmed, options)) {
3637
- return trimmed;
3638
- }
3639
- return assertSafePathSegment(fallback, { ...options, label: "fallback path segment" });
3572
+ // Windows denies access to a lock file while a just-unlinked directory entry
3573
+ // is still being torn down, so a contended acquire sees EPERM on a name that is
3574
+ // already gone -- both when creating it exclusively and when reading the
3575
+ // holder's snapshot. The next attempt succeeds, so this is contention rather
3576
+ // than a permission failure. The error must name the lock file itself: the
3577
+ // exclusive-create helper opens the parent directory first, and a denial from
3578
+ // that setup step carries no teardown evidence and has to reach the caller.
3579
+ const maxTransientLockDenials = 8;
3580
+ function isTransientLockFileDenial(error, lockPath) {
3581
+ const denial = error;
3582
+ return process.platform === "win32" && denial?.code === "EPERM" && denial.path === lockPath;
3640
3583
  }
3641
- function assertSafePathPrefix(prefix, options = {}) {
3642
- // Prefixes are often derived from safe filenames. Normalize harmless
3643
- // filename characters first, but still reject real path-control bytes.
3644
- if (prefix.includes("/") || prefix.includes("\\") || prefix.includes("\0")) {
3645
- return assertSafePathSegment(prefix, {
3646
- allowDotPrefix: true,
3647
- ...options,
3648
- label: options.label ?? "path prefix",
3649
- });
3584
+ function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
3585
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
3586
+ return createdAtMs !== null && nowMs - createdAtMs > staleMs;
3587
+ }
3588
+ function sidecarLockPayloadCreatedAtMs(payload) {
3589
+ const createdAt = payload &&
3590
+ typeof payload === "object" &&
3591
+ "createdAt" in payload &&
3592
+ typeof payload.createdAt === "string"
3593
+ ? payload.createdAt
3594
+ : "";
3595
+ const createdAtMs = Date.parse(createdAt);
3596
+ return Number.isFinite(createdAtMs) ? createdAtMs : null;
3597
+ }
3598
+ async function defaultSidecarLockShouldReclaim(params) {
3599
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(params.payload);
3600
+ if (createdAtMs !== null)
3601
+ return params.nowMs - createdAtMs > params.staleMs;
3602
+ try {
3603
+ return params.nowMs - (await promises_namespaceObject.stat(params.lockPath)).mtimeMs > params.staleMs;
3604
+ }
3605
+ catch {
3606
+ return true;
3650
3607
  }
3651
- return assertSafePathSegment(prefix.replace(/[^A-Za-z0-9._-]+/g, "-"), {
3652
- allowDotPrefix: true,
3653
- ...options,
3654
- label: options.label ?? "path prefix",
3655
- });
3656
3608
  }
3657
3609
 
3658
- ;// CONCATENATED MODULE: external "node:os"
3659
- const external_node_os_namespaceObject = __rspack_createRequire_require("node:os");
3660
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/short-path.js
3661
-
3662
-
3663
- function shortPath(value) {
3664
- const home = external_node_os_namespaceObject.homedir();
3665
- const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
3666
- return formatErrorDetail(shortened);
3610
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/suppressed-error.js
3611
+ function createSuppressedError(error, suppressed, message) {
3612
+ const constructor = globalThis.SuppressedError;
3613
+ if (constructor) {
3614
+ return new constructor(error, suppressed, message);
3615
+ }
3616
+ const combined = new Error(message, {
3617
+ cause: error instanceof Error ? error : undefined,
3618
+ });
3619
+ combined.name = "SuppressedError";
3620
+ Object.defineProperties(combined, {
3621
+ error: { configurable: true, value: error },
3622
+ suppressed: { configurable: true, value: suppressed },
3623
+ });
3624
+ return combined;
3667
3625
  }
3668
3626
 
3669
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
3670
-
3671
-
3627
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js
3672
3628
 
3673
3629
 
3674
3630
 
@@ -3678,517 +3634,823 @@ function shortPath(value) {
3678
3634
 
3679
3635
 
3680
3636
 
3681
- const ROOT_PATH_ALIAS_POLICIES = {
3682
- strict: Object.freeze({
3683
- allowFinalSymlinkForUnlink: false,
3684
- allowFinalHardlinkForUnlink: false,
3685
- }),
3686
- unlinkTarget: Object.freeze({
3687
- allowFinalSymlinkForUnlink: true,
3688
- allowFinalHardlinkForUnlink: true,
3689
- }),
3690
- };
3691
- async function resolveRootPath(params) {
3637
+ async function resolveNormalizedTargetPath(targetPath) {
3638
+ const resolved = external_node_path_namespaceObject.resolve(targetPath);
3639
+ const dir = external_node_path_namespaceObject.dirname(resolved);
3640
+ await promises_namespaceObject.mkdir(dir, { recursive: true });
3692
3641
  try {
3693
- return await resolveRootPathInternal(params);
3642
+ return external_node_path_namespaceObject.join(await promises_namespaceObject.realpath(dir), external_node_path_namespaceObject.basename(resolved));
3694
3643
  }
3695
- catch (error) {
3696
- throw sanitizeRootPathError(error);
3644
+ catch {
3645
+ return resolved;
3697
3646
  }
3698
3647
  }
3699
- async function resolveRootPathInternal(params) {
3700
- assertValidRootPathInputs(params);
3701
- const rootPath = external_node_path_namespaceObject.resolve(params.rootPath);
3702
- const absolutePath = external_node_path_namespaceObject.resolve(params.absolutePath);
3703
- const rootCanonicalPath = params.rootCanonicalPath
3704
- ? external_node_path_namespaceObject.resolve(params.rootCanonicalPath)
3705
- : await resolvePathViaExistingAncestor(rootPath);
3706
- const context = createBoundaryResolutionContext({
3707
- resolveParams: params,
3708
- rootPath,
3709
- absolutePath,
3710
- rootCanonicalPath,
3711
- outsideLexicalCanonicalPath: await resolveOutsideLexicalCanonicalPathAsync({
3712
- rootPath,
3713
- absolutePath,
3714
- }),
3715
- });
3716
- const outsideResult = await resolveOutsideRootPathAsync({
3717
- boundaryLabel: params.boundaryLabel,
3718
- context,
3719
- });
3720
- if (outsideResult) {
3721
- return outsideResult;
3648
+ async function acquireSidecarLock(options, context) {
3649
+ const retry = options.retry ?? {};
3650
+ validateSidecarLockRetryOptions(retry);
3651
+ validateSidecarLockTimeoutMs(options.timeoutMs);
3652
+ context.ensureExitCleanupRegistered();
3653
+ const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
3654
+ const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
3655
+ let held = context.held.get(normalizedTargetPath);
3656
+ if (held &&
3657
+ options.reentrantOwner !== undefined &&
3658
+ held.reentrantOwner !== undefined &&
3659
+ options.reentrantOwner === held.reentrantOwner) {
3660
+ // A final release may already have decremented the count to zero and be
3661
+ // removing the sidecar. Do not admit a new reentrant handle until that
3662
+ // cleanup settles: successful cleanup requires a fresh acquisition, while
3663
+ // failed cleanup leaves the existing sidecar held for a retry.
3664
+ if (held.releasePromise) {
3665
+ await held.releasePromise.catch(() => undefined);
3666
+ held = context.held.get(normalizedTargetPath);
3667
+ }
3668
+ if (held &&
3669
+ options.reentrantOwner !== undefined &&
3670
+ held.reentrantOwner !== undefined &&
3671
+ options.reentrantOwner === held.reentrantOwner) {
3672
+ held.refCount += 1;
3673
+ return context.handleForHeldLock(normalizedTargetPath, held);
3674
+ }
3722
3675
  }
3723
- return resolveRootPathLexicalAsync({
3724
- params,
3725
- absolutePath: context.absolutePath,
3726
- rootPath: context.rootPath,
3727
- rootCanonicalPath: context.rootCanonicalPath,
3728
- });
3729
- }
3730
- function resolveRootPathSync(params) {
3676
+ const startedAt = Date.now();
3677
+ const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
3678
+ const reclaimGuardPath = `${lockPath}.reclaim`;
3679
+ let ownsReclaimGuard = false;
3680
+ let attempt = 0;
3681
+ // Bounded so a genuine denial still surfaces as EPERM, not a lock timeout.
3682
+ let transientDenials = 0;
3683
+ const withinDenialBudget = () => ++transientDenials <= (/* inlined export .maxTransientLockDenials */8);
3684
+ const waitForRetry = async () => {
3685
+ const elapsed = Date.now() - startedAt;
3686
+ if ((options.timeoutMs !== undefined &&
3687
+ options.timeoutMs !== Number.POSITIVE_INFINITY &&
3688
+ elapsed >= options.timeoutMs) ||
3689
+ (maxRetries !== undefined && attempt >= maxRetries)) {
3690
+ throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
3691
+ code: "file_lock_timeout",
3692
+ lockPath,
3693
+ normalizedTargetPath,
3694
+ });
3695
+ }
3696
+ const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
3697
+ ? Number.POSITIVE_INFINITY
3698
+ : Math.max(0, options.timeoutMs - elapsed);
3699
+ const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
3700
+ attempt += 1;
3701
+ await new Promise((resolve) => setTimeout(resolve, delay));
3702
+ };
3703
+ // Waiting can fail on the caller's own retry or deadline limits. Classifying
3704
+ // a denial as contention must not cost them the original diagnosis, so hand
3705
+ // the denial back when no further attempt can be scheduled.
3706
+ const retryOrRethrowDenial = async (denial) => {
3707
+ try {
3708
+ await waitForRetry();
3709
+ }
3710
+ catch (waitError) {
3711
+ if (waitError.code === "file_lock_timeout")
3712
+ throw denial;
3713
+ throw waitError;
3714
+ }
3715
+ };
3731
3716
  try {
3732
- return resolveRootPathSyncInternal(params);
3733
- }
3734
- catch (error) {
3735
- throw sanitizeRootPathError(error);
3736
- }
3737
- }
3738
- function resolveRootPathSyncInternal(params) {
3739
- assertValidRootPathInputs(params);
3740
- const rootPath = path.resolve(params.rootPath);
3741
- const absolutePath = path.resolve(params.absolutePath);
3742
- const rootCanonicalPath = params.rootCanonicalPath
3743
- ? path.resolve(params.rootCanonicalPath)
3744
- : resolvePathViaExistingAncestorSync(rootPath);
3745
- const context = createBoundaryResolutionContext({
3746
- resolveParams: params,
3747
- rootPath,
3748
- absolutePath,
3749
- rootCanonicalPath,
3750
- outsideLexicalCanonicalPath: resolveOutsideLexicalCanonicalPathSync({
3751
- rootPath,
3752
- absolutePath,
3753
- }),
3754
- });
3755
- const outsideResult = resolveOutsideRootPathSync({
3756
- boundaryLabel: params.boundaryLabel,
3757
- context,
3758
- });
3759
- if (outsideResult) {
3760
- return outsideResult;
3761
- }
3762
- return resolveRootPathLexicalSync({
3763
- params,
3764
- absolutePath: context.absolutePath,
3765
- rootPath: context.rootPath,
3766
- rootCanonicalPath: context.rootCanonicalPath,
3767
- });
3768
- }
3769
- function sanitizeRootPathError(error) {
3770
- if (error instanceof Error) {
3771
- error.message = formatErrorDetail(error.message);
3772
- }
3773
- return error;
3774
- }
3775
- function assertValidRootPathInputs(params) {
3776
- path_assertNoNulPathInput(params.rootPath, "root path contains a NUL byte");
3777
- path_assertNoNulPathInput(params.absolutePath, "absolute path contains a NUL byte");
3778
- assertNoEmbeddedDriveRelativeSegment(params.rootPath, "root path");
3779
- assertNoEmbeddedDriveRelativeSegment(params.absolutePath, "absolute path");
3780
- if (params.rootCanonicalPath !== undefined) {
3781
- path_assertNoNulPathInput(params.rootCanonicalPath, "canonical root path contains a NUL byte");
3782
- assertNoEmbeddedDriveRelativeSegment(params.rootCanonicalPath, "canonical root path");
3717
+ while (true) {
3718
+ if (!ownsReclaimGuard && (await sidecarReclaimGuardExists(reclaimGuardPath))) {
3719
+ await waitForRetry();
3720
+ continue;
3721
+ }
3722
+ let handle = null;
3723
+ let createdSnapshot = null;
3724
+ let lockFileCreateDenied = false;
3725
+ const payload = await options.payload();
3726
+ const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
3727
+ try {
3728
+ if (options.lockRoot) {
3729
+ const lockRoot = options.lockRoot;
3730
+ const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
3731
+ try {
3732
+ await lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
3733
+ }
3734
+ catch (error) {
3735
+ if (error instanceof errors_FsSafeError && error.code === "already-exists") {
3736
+ throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
3737
+ }
3738
+ throw error;
3739
+ }
3740
+ createdSnapshot = { raw, payload, ownershipToken };
3741
+ const opened = await openSidecarRoot(lockRoot, relativeLockPath, "unlinked");
3742
+ if (!opened) {
3743
+ await waitForRetry();
3744
+ continue;
3745
+ }
3746
+ try {
3747
+ // Root.open admits the current file, not necessarily the one we created.
3748
+ const currentRaw = await readFileHandleBounded(opened.handle, Buffer.byteLength(raw));
3749
+ if (!currentRaw.equals(Buffer.from(raw))) {
3750
+ throw new errors_FsSafeError("path-mismatch", "created sidecar lock changed before admission");
3751
+ }
3752
+ }
3753
+ catch (error) {
3754
+ await opened.handle.close().catch(() => undefined);
3755
+ throw error;
3756
+ }
3757
+ handle = opened.handle;
3758
+ }
3759
+ else {
3760
+ try {
3761
+ handle =
3762
+ (await createNativeExclusiveFile(lockPath, 0o600)) ??
3763
+ (await promises_namespaceObject.open(lockPath, "wx"));
3764
+ }
3765
+ catch (createError) {
3766
+ lockFileCreateDenied = isTransientLockFileDenial(createError, lockPath);
3767
+ throw createError;
3768
+ }
3769
+ await handle.writeFile(raw, "utf8");
3770
+ }
3771
+ const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
3772
+ if (snapshot.stat.nlink === 0) {
3773
+ await handle.close();
3774
+ handle = null;
3775
+ await waitForRetry();
3776
+ continue;
3777
+ }
3778
+ const createdHeld = {
3779
+ refCount: 1,
3780
+ reentrantOwner: options.reentrantOwner,
3781
+ handle,
3782
+ lockPath,
3783
+ snapshot,
3784
+ acquiredAt: Date.now(),
3785
+ metadata: options.metadata ?? {},
3786
+ lockRoot: options.lockRoot,
3787
+ parsePayload: options.parsePayload,
3788
+ };
3789
+ context.held.set(normalizedTargetPath, createdHeld);
3790
+ if (ownsReclaimGuard) {
3791
+ try {
3792
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
3793
+ ownsReclaimGuard = false;
3794
+ }
3795
+ catch (err) {
3796
+ await context.releaseHeldLock(normalizedTargetPath, createdHeld, { force: true });
3797
+ throw err;
3798
+ }
3799
+ }
3800
+ const returnedHandle = context.handleForHeldLock(normalizedTargetPath, createdHeld);
3801
+ const interval = options.compromiseCheckIntervalMs;
3802
+ if (options.onCompromised && interval !== undefined && interval > 0) {
3803
+ createdHeld.compromiseTimer = setInterval(() => {
3804
+ void returnedHandle
3805
+ .verifyStillHeld()
3806
+ .catch(() => false)
3807
+ .then((stillHeld) => {
3808
+ if (!stillHeld && createdHeld.compromiseTimer) {
3809
+ clearInterval(createdHeld.compromiseTimer);
3810
+ createdHeld.compromiseTimer = undefined;
3811
+ options.onCompromised?.({ lockPath, normalizedTargetPath });
3812
+ }
3813
+ });
3814
+ }, interval);
3815
+ createdHeld.compromiseTimer.unref();
3816
+ }
3817
+ return returnedHandle;
3818
+ }
3819
+ catch (err) {
3820
+ try {
3821
+ if (handle) {
3822
+ const failedSnapshot = createdSnapshot ?? { payload: null };
3823
+ try {
3824
+ failedSnapshot.stat = await handle.stat();
3825
+ }
3826
+ catch {
3827
+ // Best-effort cleanup of a failed exclusive create.
3828
+ }
3829
+ const current = context.held.get(normalizedTargetPath);
3830
+ if (current?.handle === handle) {
3831
+ context.held.delete(normalizedTargetPath);
3832
+ }
3833
+ await handle.close().catch(() => undefined);
3834
+ // Root-created records retain the creator's byte/token receipt;
3835
+ // partial direct writes use the exclusive descriptor's identity.
3836
+ await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
3837
+ lockRoot: options.lockRoot,
3838
+ parsePayload: options.parsePayload,
3839
+ });
3840
+ }
3841
+ else if (createdSnapshot) {
3842
+ await removeSidecarLockIfUnchanged(lockPath, createdSnapshot, {
3843
+ lockRoot: options.lockRoot,
3844
+ parsePayload: options.parsePayload,
3845
+ });
3846
+ }
3847
+ }
3848
+ catch (cleanupError) {
3849
+ throw createSuppressedError(cleanupError, err, "file lock acquisition and cleanup both failed");
3850
+ }
3851
+ if (lockFileCreateDenied && withinDenialBudget()) {
3852
+ await retryOrRethrowDenial(err);
3853
+ continue;
3854
+ }
3855
+ if (err.code !== "EEXIST") {
3856
+ throw err;
3857
+ }
3858
+ if (ownsReclaimGuard) {
3859
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
3860
+ ownsReclaimGuard = false;
3861
+ await waitForRetry();
3862
+ continue;
3863
+ }
3864
+ const nowMs = Date.now();
3865
+ let rawSnapshot;
3866
+ let lockFileOpenDenied = false;
3867
+ try {
3868
+ rawSnapshot = await readSidecarLockRawSnapshot(lockPath, {
3869
+ lockRoot: options.lockRoot,
3870
+ rejectNonFile: true,
3871
+ discardObservation: "changed",
3872
+ onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
3873
+ });
3874
+ }
3875
+ catch (readErr) {
3876
+ if (!lockFileOpenDenied || !withinDenialBudget())
3877
+ throw readErr;
3878
+ await retryOrRethrowDenial(readErr);
3879
+ continue;
3880
+ }
3881
+ const snapshot = parseSidecarLockSnapshot(rawSnapshot, options.parsePayload);
3882
+ if (!snapshot) {
3883
+ await waitForRetry();
3884
+ continue;
3885
+ }
3886
+ if (context.held.has(normalizedTargetPath)) {
3887
+ await waitForRetry();
3888
+ continue;
3889
+ }
3890
+ const shouldReclaim = options.shouldReclaim ?? defaultSidecarLockShouldReclaim;
3891
+ if (await shouldReclaim({
3892
+ lockPath,
3893
+ normalizedTargetPath,
3894
+ payload: snapshot?.payload ?? null,
3895
+ staleMs: options.staleMs,
3896
+ nowMs,
3897
+ heldByThisProcess: context.held.has(normalizedTargetPath),
3898
+ })) {
3899
+ if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
3900
+ lockRoot: options.lockRoot,
3901
+ parsePayload: options.parsePayload,
3902
+ }))) {
3903
+ await waitForRetry();
3904
+ continue;
3905
+ }
3906
+ const staleRecovery = options.staleRecovery ?? "fail-closed";
3907
+ if (staleRecovery === "remove-if-unchanged") {
3908
+ if (!(await tryAcquireSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath))) {
3909
+ await waitForRetry();
3910
+ continue;
3911
+ }
3912
+ ownsReclaimGuard = true;
3913
+ const removal = await removeStaleSidecarLockIfAllowed({
3914
+ lockPath,
3915
+ normalizedTargetPath,
3916
+ snapshot,
3917
+ shouldRemoveStaleLock: options.shouldRemoveStaleLock,
3918
+ lockRoot: options.lockRoot,
3919
+ parsePayload: options.parsePayload,
3920
+ });
3921
+ if (removal === "removed" || removal === "changed") {
3922
+ if (removal === "changed")
3923
+ await waitForRetry();
3924
+ continue;
3925
+ }
3926
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
3927
+ ownsReclaimGuard = false;
3928
+ }
3929
+ throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
3930
+ code: "file_lock_stale",
3931
+ lockPath,
3932
+ normalizedTargetPath,
3933
+ });
3934
+ }
3935
+ await waitForRetry();
3936
+ }
3937
+ }
3783
3938
  }
3784
- }
3785
- function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
3786
- if (process.platform !== "win32") {
3787
- return;
3939
+ finally {
3940
+ if (ownsReclaimGuard) {
3941
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
3942
+ }
3788
3943
  }
3789
- const root = external_node_path_namespaceObject.parse(filePath).root;
3790
- assertNoDriveRelativePathSegments(filePath.slice(root.length).replaceAll("\\", "/"), label);
3791
- }
3792
- function isPromiseLike(value) {
3793
- return Boolean(value &&
3794
- (typeof value === "object" || typeof value === "function") &&
3795
- "then" in value &&
3796
- typeof value.then === "function");
3797
3944
  }
3798
- function createLexicalTraversalState(params) {
3799
- const rawAbsolutePath = params.params.absolutePath;
3800
- const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
3801
- const relative = rawRelativePath ?? external_node_path_namespaceObject.relative(params.rootPath, params.absolutePath);
3802
- return {
3803
- segments: splitTraversalSegments(relative),
3804
- allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
3805
- canonicalCursor: params.rootCanonicalPath,
3806
- lexicalCursor: params.rootPath,
3807
- preserveFinalSymlink: false,
3945
+
3946
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
3947
+
3948
+ function createSidecarLockHandle(params) {
3949
+ let released = false;
3950
+ let releasePromise;
3951
+ let releaseStarted = false;
3952
+ const release = async () => {
3953
+ if (released)
3954
+ return;
3955
+ if (!releasePromise) {
3956
+ const retry = releaseStarted;
3957
+ releaseStarted = true;
3958
+ releasePromise = (async () => {
3959
+ await params.release({ retry });
3960
+ released = true;
3961
+ })();
3962
+ }
3963
+ try {
3964
+ await releasePromise;
3965
+ }
3966
+ finally {
3967
+ releasePromise = undefined;
3968
+ }
3808
3969
  };
3809
- }
3810
- function createLexicalTraversalContext(params) {
3811
3970
  return {
3812
- state: createLexicalTraversalState(params),
3813
- resolveParams: params.params,
3814
- rootPath: params.rootPath,
3815
- rootCanonicalPath: params.rootCanonicalPath,
3816
- absolutePath: params.absolutePath,
3971
+ lockPath: params.lockPath,
3972
+ normalizedTargetPath: params.normalizedTargetPath,
3973
+ verifyStillHeld: params.verifyStillHeld,
3974
+ release,
3975
+ [Symbol.asyncDispose]: release,
3817
3976
  };
3818
3977
  }
3819
- function splitTraversalSegments(value) {
3820
- return value
3821
- .split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
3822
- .filter((segment) => Boolean(segment) && segment !== ".");
3823
- }
3824
- function rawPathRelativeToRoot(rootPath, candidatePath) {
3825
- if (!external_node_path_namespaceObject.isAbsolute(candidatePath)) {
3826
- return undefined;
3827
- }
3828
- const root = external_node_path_namespaceObject.resolve(rootPath);
3829
- const candidate = process.platform === "win32"
3830
- ? candidatePath.replaceAll("/", external_node_path_namespaceObject.sep)
3831
- : candidatePath;
3832
- if (candidate === root) {
3833
- return "";
3834
- }
3835
- const rootWithSep = root.endsWith(external_node_path_namespaceObject.sep) ? root : `${root}${external_node_path_namespaceObject.sep}`;
3836
- const candidatePrefix = candidate.slice(0, rootWithSep.length);
3837
- const prefixMatches = process.platform === "win32"
3838
- ? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
3839
- : candidatePrefix === rootWithSep;
3840
- return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
3841
- }
3842
- function assertLexicalCursorInsideBoundary(context, candidatePath) {
3843
- assertInsideBoundary({
3844
- boundaryLabel: context.resolveParams.boundaryLabel,
3845
- rootCanonicalPath: context.rootCanonicalPath,
3846
- candidatePath,
3847
- absolutePath: context.absolutePath,
3848
- });
3849
- }
3850
- function applyMissingSuffixToCanonicalCursor(context, missingFromIndex) {
3851
- const missingSuffix = context.state.segments.slice(missingFromIndex);
3852
- for (const segment of missingSuffix) {
3853
- advanceCanonicalCursorForSegment(context, segment);
3854
- }
3855
- }
3856
- function advanceCanonicalCursorForSegment(context, segment) {
3857
- context.state.canonicalCursor = external_node_path_namespaceObject.resolve(context.state.canonicalCursor, segment);
3858
- assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
3859
- }
3860
- function finalizeLexicalResolution(context, kind) {
3861
- assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
3862
- return buildResolvedRootPath({
3863
- absolutePath: context.absolutePath,
3864
- canonicalPath: context.state.canonicalCursor,
3865
- rootPath: context.rootPath,
3866
- rootCanonicalPath: context.rootCanonicalPath,
3867
- kind,
3978
+ function createHeldSidecarLockHandle(params) {
3979
+ return createSidecarLockHandle({
3980
+ lockPath: params.held.lockPath,
3981
+ normalizedTargetPath: params.normalizedTargetPath,
3982
+ verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
3983
+ lockRoot: params.held.lockRoot,
3984
+ parsePayload: params.held.parsePayload,
3985
+ }),
3986
+ release: (options) => params.release(options),
3868
3987
  });
3869
3988
  }
3870
- function handleLexicalLstatFailure(context, error, missingFromIndex) {
3871
- if (!path_isNotFoundPathError(error)) {
3872
- return false;
3873
- }
3874
- applyMissingSuffixToCanonicalCursor(context, missingFromIndex);
3875
- return true;
3876
- }
3877
- function handleLexicalStatReadFailure(context, error, missingFromIndex) {
3878
- if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
3879
- return null;
3880
- }
3881
- throw error;
3882
- }
3883
- function handleLexicalStatDisposition(context, params) {
3884
- if (!params.isSymbolicLink) {
3885
- advanceCanonicalCursorForSegment(context, params.segment);
3886
- return "continue";
3887
- }
3888
- if (context.resolveParams.rejectSymlinks === true && params.isLast) {
3889
- throw new errors_FsSafeError("symlink", "symlink path component not allowed");
3890
- }
3891
- if (context.state.allowFinalSymlink && params.isLast) {
3892
- context.state.preserveFinalSymlink = true;
3893
- advanceCanonicalCursorForSegment(context, params.segment);
3894
- return "break";
3989
+
3990
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
3991
+
3992
+
3993
+
3994
+
3995
+
3996
+
3997
+ const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
3998
+ const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
3999
+ const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
4000
+ function getGlobalManagers() {
4001
+ const globalWithState = globalThis;
4002
+ if (!globalWithState[GLOBAL_STATE_KEY]) {
4003
+ globalWithState[GLOBAL_STATE_KEY] = new Map();
3895
4004
  }
3896
- return "resolve-link";
4005
+ return globalWithState[GLOBAL_STATE_KEY];
3897
4006
  }
3898
- function applyResolvedSymlinkHop(context, linkCanonical) {
3899
- if (!path_isPathInside(context.rootCanonicalPath, linkCanonical)) {
3900
- throw symlinkEscapeError({
3901
- boundaryLabel: context.resolveParams.boundaryLabel,
3902
- rootCanonicalPath: context.rootCanonicalPath,
3903
- symlinkPath: context.state.lexicalCursor,
3904
- });
4007
+ function resolveManagerState(key) {
4008
+ const managers = getGlobalManagers();
4009
+ let state = managers.get(key);
4010
+ if (!state) {
4011
+ state = {
4012
+ cleanupRegistered: false,
4013
+ held: new Map(),
4014
+ reclaimCleanupRegistered: false,
4015
+ reclaimGuards: new Set(),
4016
+ };
4017
+ managers.set(key, state);
3905
4018
  }
3906
- context.state.canonicalCursor = linkCanonical;
3907
- context.state.lexicalCursor = linkCanonical;
4019
+ else {
4020
+ // The global manager symbol is shared across package copies and hot reloads.
4021
+ // Backfill state created by fs-safe versions that predate reclaim guards.
4022
+ state.reclaimCleanupRegistered ??= false;
4023
+ state.reclaimGuards ??= new Set();
4024
+ for (const held of state.held.values()) {
4025
+ held.refCount ??= 1;
4026
+ }
4027
+ }
4028
+ return state;
3908
4029
  }
3909
- function readLexicalStat(context, params) {
4030
+ function snapshotMatchesSync(lockPath, observed) {
4031
+ let fd;
3910
4032
  try {
3911
- const stat = params.read(context.state.lexicalCursor);
3912
- if (isPromiseLike(stat)) {
3913
- return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
4033
+ const beforeStat = external_node_fs_namespaceObject.lstatSync(lockPath);
4034
+ if (!beforeStat.isFile()) {
4035
+ return false;
3914
4036
  }
3915
- return stat;
3916
- }
3917
- catch (error) {
3918
- return handleLexicalStatReadFailure(context, error, params.missingFromIndex);
4037
+ const openFlags = external_node_fs_namespaceObject.constants.O_RDONLY |
4038
+ (process.platform !== "win32" && typeof external_node_fs_namespaceObject.constants.O_NOFOLLOW === "number"
4039
+ ? external_node_fs_namespaceObject.constants.O_NOFOLLOW
4040
+ : 0) |
4041
+ (typeof external_node_fs_namespaceObject.constants.O_NONBLOCK === "number" ? external_node_fs_namespaceObject.constants.O_NONBLOCK : 0);
4042
+ fd = external_node_fs_namespaceObject.openSync(lockPath, openFlags);
4043
+ const openedStat = external_node_fs_namespaceObject.fstatSync(fd);
4044
+ if (!openedStat.isFile()) {
4045
+ return false;
4046
+ }
4047
+ if (observed.raw !== undefined && openedStat.size !== Buffer.byteLength(observed.raw)) {
4048
+ return false;
4049
+ }
4050
+ const raw = external_node_fs_namespaceObject.readFileSync(fd, "utf8");
4051
+ const afterStat = external_node_fs_namespaceObject.lstatSync(lockPath);
4052
+ if (!afterStat.isFile() || !file_identity_sameFileIdentity(beforeStat, afterStat)) {
4053
+ return false;
4054
+ }
4055
+ return sidecarLockSnapshotMatches({ raw, payload: null, stat: afterStat }, observed);
3919
4056
  }
3920
- }
3921
- function resolveAndApplySymlinkHop(context, params) {
3922
- const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
3923
- if (isPromiseLike(linkCanonical)) {
3924
- return Promise.resolve(linkCanonical).then((value) => {
3925
- applyResolvedSymlinkHop(context, value);
3926
- });
4057
+ catch {
4058
+ return false;
3927
4059
  }
3928
- applyResolvedSymlinkHop(context, linkCanonical);
3929
- }
3930
- function applyParentTraversalStep(context) {
3931
- context.state.lexicalCursor = external_node_path_namespaceObject.resolve(context.state.lexicalCursor, "..");
3932
- advanceCanonicalCursorForSegment(context, "..");
3933
- }
3934
- function* iterateLexicalTraversal(state) {
3935
- for (let idx = 0; idx < state.segments.length; idx += 1) {
3936
- const segment = state.segments[idx] ?? "";
3937
- const isLast = idx === state.segments.length - 1;
3938
- yield { idx, segment, isLast };
4060
+ finally {
4061
+ if (fd !== undefined) {
4062
+ try {
4063
+ external_node_fs_namespaceObject.closeSync(fd);
4064
+ }
4065
+ catch {
4066
+ // Best-effort process-exit cleanup.
4067
+ }
4068
+ }
3939
4069
  }
3940
4070
  }
3941
- async function resolveRootPathLexicalAsync(params) {
3942
- const context = createLexicalTraversalContext(params);
3943
- const { state } = context;
3944
- for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
3945
- if (segment === "..") {
3946
- applyParentTraversalStep(context);
3947
- continue;
3948
- }
3949
- state.lexicalCursor = external_node_path_namespaceObject.join(state.lexicalCursor, segment);
3950
- const stat = await readLexicalStat(context, {
3951
- missingFromIndex: idx,
3952
- read: (cursor) => promises_namespaceObject.lstat(cursor),
3953
- });
3954
- if (!stat) {
3955
- break;
4071
+ function releaseAllReclaimGuardsSync(state) {
4072
+ for (const reclaimGuardPath of state.reclaimGuards) {
4073
+ try {
4074
+ external_node_fs_namespaceObject.rmdirSync(reclaimGuardPath);
4075
+ state.reclaimGuards.delete(reclaimGuardPath);
3956
4076
  }
3957
- const disposition = handleLexicalStatDisposition(context, {
3958
- isSymbolicLink: stat.isSymbolicLink(),
3959
- segment,
3960
- isLast,
3961
- });
3962
- if (disposition === "continue") {
3963
- continue;
4077
+ catch {
4078
+ // Best-effort process-exit cleanup. A surviving guard fails closed.
3964
4079
  }
3965
- if (disposition === "break") {
3966
- break;
4080
+ }
4081
+ }
4082
+ function releaseAllLocksSync(state) {
4083
+ for (const [normalizedTargetPath, held] of state.held) {
4084
+ void held.handle.close().catch(() => undefined);
4085
+ try {
4086
+ if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
4087
+ external_node_fs_namespaceObject.rmSync(held.lockPath, { force: true });
4088
+ }
3967
4089
  }
3968
- await resolveAndApplySymlinkHop(context, {
3969
- resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
3970
- });
3971
- if (context.resolveParams.rejectSymlinks === true) {
3972
- throw new errors_FsSafeError("symlink", "symlink path component not allowed");
4090
+ catch {
4091
+ // Best-effort process-exit cleanup.
3973
4092
  }
4093
+ state.held.delete(normalizedTargetPath);
3974
4094
  }
3975
- const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
3976
- return finalizeLexicalResolution(context, kind);
4095
+ releaseAllReclaimGuardsSync(state);
3977
4096
  }
3978
- function resolveRootPathLexicalSync(params) {
3979
- const context = createLexicalTraversalContext(params);
3980
- const { state } = context;
3981
- for (let idx = 0; idx < state.segments.length; idx += 1) {
3982
- const segment = state.segments[idx] ?? "";
3983
- const isLast = idx === state.segments.length - 1;
3984
- if (segment === "..") {
3985
- applyParentTraversalStep(context);
3986
- continue;
4097
+ function ensureGlobalExitCleanupRegistered() {
4098
+ const globalWithCleanup = globalThis;
4099
+ if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
4100
+ return;
4101
+ globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
4102
+ const cleanup = () => {
4103
+ for (const state of getGlobalManagers().values()) {
4104
+ releaseAllLocksSync(state);
3987
4105
  }
3988
- state.lexicalCursor = path.join(state.lexicalCursor, segment);
3989
- const maybeStat = readLexicalStat(context, {
3990
- missingFromIndex: idx,
3991
- read: (cursor) => fs.lstatSync(cursor),
4106
+ };
4107
+ globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
4108
+ process.on("exit", cleanup);
4109
+ }
4110
+ async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
4111
+ const current = state.held.get(normalizedTargetPath);
4112
+ if (current !== held) {
4113
+ return false;
4114
+ }
4115
+ if (held.releasePromise) {
4116
+ await held.releasePromise;
4117
+ return true;
4118
+ }
4119
+ if (options.force) {
4120
+ held.refCount = 0;
4121
+ }
4122
+ else if (!options.retry && held.refCount > 0) {
4123
+ held.refCount -= 1;
4124
+ }
4125
+ if (held.refCount > 0) {
4126
+ return false;
4127
+ }
4128
+ held.releasePromise = (async () => {
4129
+ await held.handle.close().catch(() => undefined);
4130
+ await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
4131
+ lockRoot: held.lockRoot,
4132
+ parsePayload: held.parsePayload,
3992
4133
  });
3993
- if (isPromiseLike(maybeStat)) {
3994
- throw new Error("Unexpected async lexical stat");
4134
+ if (state.held.get(normalizedTargetPath) === held) {
4135
+ state.held.delete(normalizedTargetPath);
3995
4136
  }
3996
- const stat = maybeStat;
3997
- if (!stat) {
3998
- break;
4137
+ if (held.compromiseTimer) {
4138
+ clearInterval(held.compromiseTimer);
4139
+ held.compromiseTimer = undefined;
3999
4140
  }
4000
- const disposition = handleLexicalStatDisposition(context, {
4001
- isSymbolicLink: stat.isSymbolicLink(),
4002
- segment,
4003
- isLast,
4141
+ })();
4142
+ try {
4143
+ await held.releasePromise;
4144
+ return true;
4145
+ }
4146
+ finally {
4147
+ held.releasePromise = undefined;
4148
+ }
4149
+ }
4150
+ function handleForHeldLock(state, normalizedTargetPath, held) {
4151
+ return createHeldSidecarLockHandle({
4152
+ normalizedTargetPath,
4153
+ held,
4154
+ release: async (options) => await releaseHeldLock(state, normalizedTargetPath, held, { retry: options?.retry }),
4155
+ });
4156
+ }
4157
+ function createSidecarLockManager(key) {
4158
+ const state = resolveManagerState(key);
4159
+ function ensureExitCleanupRegistered() {
4160
+ state.cleanupRegistered = true;
4161
+ state.reclaimCleanupRegistered = true;
4162
+ ensureGlobalExitCleanupRegistered();
4163
+ }
4164
+ async function acquire(options) {
4165
+ return await acquireSidecarLock(options, {
4166
+ held: state.held,
4167
+ reclaimGuards: state.reclaimGuards,
4168
+ ensureExitCleanupRegistered,
4169
+ handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
4170
+ releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
4004
4171
  });
4005
- if (disposition === "continue") {
4006
- continue;
4007
- }
4008
- if (disposition === "break") {
4009
- break;
4172
+ }
4173
+ async function withLock(options, fn) {
4174
+ const lock = await acquire(options);
4175
+ let result;
4176
+ try {
4177
+ result = await fn();
4010
4178
  }
4011
- const maybeApplied = resolveAndApplySymlinkHop(context, {
4012
- resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
4013
- });
4014
- if (isPromiseLike(maybeApplied)) {
4015
- throw new Error("Unexpected async symlink resolution");
4179
+ catch (bodyError) {
4180
+ try {
4181
+ await lock.release();
4182
+ }
4183
+ catch (releaseError) {
4184
+ throw createSuppressedError(releaseError, bodyError, "file lock callback and release both failed");
4185
+ }
4186
+ throw bodyError;
4016
4187
  }
4017
- if (context.resolveParams.rejectSymlinks === true) {
4018
- throw new FsSafeError("symlink", "symlink path component not allowed");
4188
+ await lock.release();
4189
+ return result;
4190
+ }
4191
+ async function drain() {
4192
+ for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
4193
+ await releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
4019
4194
  }
4020
4195
  }
4021
- const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
4022
- return finalizeLexicalResolution(context, kind);
4196
+ function reset() {
4197
+ releaseAllLocksSync(state);
4198
+ }
4199
+ function heldEntries() {
4200
+ return Array.from(state.held.entries()).map(([normalizedTargetPath, held]) => ({
4201
+ normalizedTargetPath,
4202
+ lockPath: held.lockPath,
4203
+ acquiredAt: held.acquiredAt,
4204
+ metadata: held.metadata,
4205
+ forceRelease: () => releaseHeldLock(state, normalizedTargetPath, held, { force: true }),
4206
+ }));
4207
+ }
4208
+ return { acquire, withLock, drain, reset, heldEntries };
4023
4209
  }
4024
- function resolveCanonicalOutsideLexicalPath(params) {
4025
- return params.outsideLexicalCanonicalPath ?? params.absolutePath;
4210
+ async function withSidecarLock(targetPath, options, fn) {
4211
+ const manager = createSidecarLockManager(options.managerKey ?? `fs-safe.sidecar-lock:${targetPath}`);
4212
+ const { managerKey: _managerKey, ...acquireOptions } = options;
4213
+ return await manager.withLock({ ...acquireOptions, targetPath }, fn);
4026
4214
  }
4027
- function createBoundaryResolutionContext(params) {
4028
- const lexicalInside = path_isPathInside(params.rootPath, params.absolutePath);
4029
- const canonicalOutsideLexicalPath = resolveCanonicalOutsideLexicalPath({
4030
- absolutePath: params.absolutePath,
4031
- outsideLexicalCanonicalPath: params.outsideLexicalCanonicalPath,
4032
- });
4033
- assertLexicalBoundaryOrCanonicalAlias({
4034
- skipLexicalRootCheck: params.resolveParams.skipLexicalRootCheck,
4035
- lexicalInside,
4036
- canonicalOutsideLexicalPath,
4037
- rootCanonicalPath: params.rootCanonicalPath,
4038
- boundaryLabel: params.resolveParams.boundaryLabel,
4039
- rootPath: params.rootPath,
4040
- absolutePath: params.absolutePath,
4041
- });
4042
- return {
4043
- rootPath: params.rootPath,
4044
- absolutePath: params.absolutePath,
4045
- rootCanonicalPath: params.rootCanonicalPath,
4046
- lexicalInside,
4047
- canonicalOutsideLexicalPath,
4048
- };
4215
+
4216
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
4217
+
4218
+
4219
+
4220
+
4221
+
4222
+
4223
+
4224
+
4225
+
4226
+
4227
+
4228
+
4229
+
4230
+
4231
+
4232
+
4233
+
4234
+ function byteLength(input, encoding) {
4235
+ return typeof input === "string"
4236
+ ? Buffer.byteLength(input, encoding ?? "utf8")
4237
+ : input.byteLength;
4049
4238
  }
4050
- async function resolveOutsideRootPathAsync(params) {
4051
- if (params.context.lexicalInside) {
4052
- return null;
4239
+ function assertSafeBasename(basename) {
4240
+ if (!basename ||
4241
+ basename === "." ||
4242
+ basename === ".." ||
4243
+ basename.includes("/") ||
4244
+ basename.includes("\0")) {
4245
+ throw new errors_FsSafeError("invalid-path", "invalid target path");
4053
4246
  }
4054
- const kind = await getPathKind(params.context.absolutePath, false);
4055
- return buildOutsideRootPathFromContext({
4056
- boundaryLabel: params.boundaryLabel,
4057
- context: params.context,
4058
- kind,
4059
- });
4060
4247
  }
4061
- function resolveOutsideRootPathSync(params) {
4062
- if (params.context.lexicalInside) {
4063
- return null;
4248
+ function assertWithinMaxBytes(bytes, maxBytes) {
4249
+ if (maxBytes !== undefined && bytes > maxBytes) {
4250
+ throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
4064
4251
  }
4065
- const kind = getPathKindSync(params.context.absolutePath, false);
4066
- return buildOutsideRootPathFromContext({
4067
- boundaryLabel: params.boundaryLabel,
4068
- context: params.context,
4069
- kind,
4070
- });
4071
- }
4072
- function buildOutsideRootPathFromContext(params) {
4073
- return buildOutsideLexicalRootPath({
4074
- boundaryLabel: params.boundaryLabel,
4075
- rootCanonicalPath: params.context.rootCanonicalPath,
4076
- absolutePath: params.context.absolutePath,
4077
- canonicalOutsideLexicalPath: params.context.canonicalOutsideLexicalPath,
4078
- rootPath: params.context.rootPath,
4079
- kind: params.kind,
4080
- });
4081
4252
  }
4082
- async function resolveOutsideLexicalCanonicalPathAsync(params) {
4083
- if (path_isPathInside(params.rootPath, params.absolutePath)) {
4084
- return undefined;
4253
+ async function syncFileBestEffort(handle) {
4254
+ try {
4255
+ await handle.sync();
4256
+ }
4257
+ catch (error) {
4258
+ if (error?.code !== "EPERM") {
4259
+ throw error;
4260
+ }
4085
4261
  }
4086
- return await resolvePathViaExistingAncestor(params.absolutePath);
4087
4262
  }
4088
- function resolveOutsideLexicalCanonicalPathSync(params) {
4089
- if (isPathInside(params.rootPath, params.absolutePath)) {
4090
- return undefined;
4263
+ async function writeStreamToHandle(stream, handle, maxBytes) {
4264
+ let bytes = 0;
4265
+ for await (const chunk of stream) {
4266
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
4267
+ bytes += buffer.byteLength;
4268
+ assertWithinMaxBytes(bytes, maxBytes);
4269
+ let offset = 0;
4270
+ while (offset < buffer.byteLength) {
4271
+ const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
4272
+ if (bytesWritten <= 0) {
4273
+ throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
4274
+ }
4275
+ offset += bytesWritten;
4276
+ }
4091
4277
  }
4092
- return resolvePathViaExistingAncestorSync(params.absolutePath);
4093
4278
  }
4094
- function buildOutsideLexicalRootPath(params) {
4095
- assertInsideBoundary({
4096
- boundaryLabel: params.boundaryLabel,
4097
- rootCanonicalPath: params.rootCanonicalPath,
4098
- candidatePath: params.canonicalOutsideLexicalPath,
4099
- absolutePath: params.absolutePath,
4100
- });
4101
- return buildResolvedRootPath({
4102
- absolutePath: params.absolutePath,
4103
- canonicalPath: params.canonicalOutsideLexicalPath,
4104
- rootPath: params.rootPath,
4105
- rootCanonicalPath: params.rootCanonicalPath,
4106
- kind: params.kind,
4279
+ async function runPinnedWriteHelper(params) {
4280
+ const normalizedParams = { ...params, maxBytes: byte_budget_normalizeMaxBytes(params.maxBytes) };
4281
+ assertSafeBasename(params.basename);
4282
+ validatePinnedOperationPayload({
4283
+ relativeParentPath: params.relativeParentPath,
4107
4284
  });
4108
- }
4109
- function assertLexicalBoundaryOrCanonicalAlias(params) {
4110
- if (params.skipLexicalRootCheck || params.lexicalInside) {
4111
- return;
4285
+ // The explicit compatibility policy uses the guarded Node fallback, where
4286
+ // content verification can replace the strict post-rename inode check.
4287
+ if (normalizedParams.onRenameIdentityMismatch === "verify-content") {
4288
+ return await runPinnedWriteFallback(normalizedParams);
4112
4289
  }
4113
- if (path_isPathInside(params.rootCanonicalPath, params.canonicalOutsideLexicalPath)) {
4114
- return;
4290
+ const native = getNativeBinding();
4291
+ if (native) {
4292
+ return await runPinnedWriteNative(native, normalizedParams);
4115
4293
  }
4116
- throw pathEscapeError({
4117
- boundaryLabel: params.boundaryLabel,
4118
- rootPath: params.rootPath,
4119
- absolutePath: params.absolutePath,
4120
- });
4294
+ return await runPinnedWriteFallback(normalizedParams);
4121
4295
  }
4122
- function buildResolvedRootPath(params) {
4123
- return {
4124
- absolutePath: params.absolutePath,
4125
- canonicalPath: params.canonicalPath,
4126
- rootPath: params.rootPath,
4127
- rootCanonicalPath: params.rootCanonicalPath,
4128
- relativePath: relativeInsideRoot(params.rootCanonicalPath, params.canonicalPath),
4129
- exists: params.kind.exists,
4130
- kind: params.kind.kind,
4131
- };
4296
+ async function runPinnedWriteWithRenamePolicy(params) {
4297
+ const { targetPath, renameIdentity, ...writeParams } = params;
4298
+ if (renameIdentity !== "verify-content-with-lock") {
4299
+ return await runPinnedWriteHelper(writeParams);
4300
+ }
4301
+ const relativeTargetPath = writeParams.relativeParentPath
4302
+ ? `${writeParams.relativeParentPath}/${writeParams.basename}`
4303
+ : writeParams.basename;
4304
+ const lockPath = external_node_path_namespaceObject.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
4305
+ return await withSidecarLock(writeParams.rootPath, {
4306
+ managerKey: `fs-safe.write:${targetPath}`,
4307
+ lockPath,
4308
+ staleMs: 30_000,
4309
+ timeoutMs: 5_000,
4310
+ payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
4311
+ retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
4312
+ }, async () => await runPinnedWriteHelper({
4313
+ ...writeParams,
4314
+ onRenameIdentityMismatch: "verify-content",
4315
+ }));
4132
4316
  }
4133
- async function getPathKind(absolutePath, preserveFinalSymlink) {
4134
- try {
4135
- const stat = preserveFinalSymlink
4136
- ? await promises_namespaceObject.lstat(absolutePath)
4137
- : await promises_namespaceObject.stat(absolutePath);
4138
- return { exists: true, kind: toResolvedKind(stat) };
4317
+ async function runPinnedWriteFallback(params) {
4318
+ let parentPath = params.relativeParentPath
4319
+ ? external_node_path_namespaceObject.join(params.rootPath, ...params.relativeParentPath.split("/"))
4320
+ : params.rootPath;
4321
+ if (params.mkdir) {
4322
+ // mkdirPathComponentsWithGuards may resolve the final component through
4323
+ // an in-root symlink (e.g. a skill-bank layout). Use its returned real
4324
+ // path for the subsequent guard and target path so we don't re-check the
4325
+ // original, possibly-symlinked, lexical path and reject it outright.
4326
+ parentPath = await mkdirPathComponentsWithGuards({
4327
+ rootReal: params.rootPath,
4328
+ targetPath: parentPath,
4329
+ beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
4330
+ });
4139
4331
  }
4140
- catch (error) {
4141
- if (path_isNotFoundPathError(error)) {
4142
- return { exists: false, kind: "missing" };
4332
+ const parentGuard = params.mkdir
4333
+ ? await directory_guard_createAsyncDirectoryGuard(parentPath)
4334
+ : await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
4335
+ const targetPath = external_node_path_namespaceObject.join(parentPath, params.basename);
4336
+ if (params.overwrite === false) {
4337
+ const handle = await withAsyncDirectoryGuards([parentGuard], async () => await promises_namespaceObject.open(targetPath, external_node_fs_namespaceObject.constants.O_WRONLY | external_node_fs_namespaceObject.constants.O_CREAT | external_node_fs_namespaceObject.constants.O_EXCL, params.mode), {
4338
+ onPostGuardFailure: async (openedHandle) => {
4339
+ // The parent failed verification, so targetPath may now resolve
4340
+ // somewhere else. Close the fd, but do not clean up by path.
4341
+ await openedHandle.close().catch(() => undefined);
4342
+ },
4343
+ });
4344
+ let created = true;
4345
+ try {
4346
+ const verificationIdentity = await handle.stat({ bigint: true });
4347
+ await handle.chmod(params.mode);
4348
+ if (params.input.kind === "buffer") {
4349
+ assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
4350
+ if (typeof params.input.data === "string") {
4351
+ await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
4352
+ }
4353
+ else {
4354
+ await handle.writeFile(params.input.data);
4355
+ }
4356
+ }
4357
+ else {
4358
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
4359
+ }
4360
+ await syncFileBestEffort(handle);
4361
+ const stat = await handle.stat();
4362
+ await syncDirectoryBestEffort(parentPath);
4363
+ // Publication is complete. A failed outer check must not remove its target.
4364
+ created = false;
4365
+ await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
4366
+ return { dev: stat.dev, ino: stat.ino };
4367
+ }
4368
+ finally {
4369
+ await handle.close().catch(() => undefined);
4370
+ if (created) {
4371
+ await promises_namespaceObject.rm(targetPath, { force: true }).catch(() => undefined);
4372
+ }
4143
4373
  }
4144
- throw error;
4145
4374
  }
4146
- }
4147
- function getPathKindSync(absolutePath, preserveFinalSymlink) {
4375
+ // Private staging must not consume the destination basename's filename budget.
4376
+ const tempPath = external_node_path_namespaceObject.join(parentPath, `.fs-safe-${(0,external_node_crypto_namespaceObject.randomUUID)()}.tmp`);
4377
+ const tempFlags = external_node_fs_namespaceObject.constants.O_WRONLY |
4378
+ external_node_fs_namespaceObject.constants.O_CREAT |
4379
+ external_node_fs_namespaceObject.constants.O_EXCL |
4380
+ (process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_namespaceObject.constants
4381
+ ? external_node_fs_namespaceObject.constants.O_NOFOLLOW
4382
+ : 0);
4383
+ let handle;
4384
+ let tempStat;
4385
+ let readHandle;
4386
+ let renamed = false;
4148
4387
  try {
4149
- const stat = preserveFinalSymlink ? fs.lstatSync(absolutePath) : fs.statSync(absolutePath);
4150
- return { exists: true, kind: toResolvedKind(stat) };
4151
- }
4152
- catch (error) {
4153
- if (isNotFoundPathError(error)) {
4154
- return { exists: false, kind: "missing" };
4388
+ handle = await promises_namespaceObject.open(tempPath, tempFlags, params.mode);
4389
+ let verificationIdentity = await handle.stat({ bigint: true });
4390
+ await handle.chmod(params.mode);
4391
+ if (params.input.kind === "buffer") {
4392
+ assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
4393
+ if (typeof params.input.data === "string") {
4394
+ await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
4395
+ }
4396
+ else {
4397
+ await handle.writeFile(params.input.data);
4398
+ }
4155
4399
  }
4156
- throw error;
4157
- }
4158
- }
4159
- function toResolvedKind(stat) {
4160
- if (stat.isFile()) {
4161
- return "file";
4162
- }
4163
- if (stat.isDirectory()) {
4164
- return "directory";
4165
- }
4166
- if (stat.isSymbolicLink()) {
4167
- return "symlink";
4168
- }
4169
- return "other";
4170
- }
4171
- function relativeInsideRoot(rootPath, targetPath) {
4172
- const relative = external_node_path_namespaceObject.relative(external_node_path_namespaceObject.resolve(rootPath), external_node_path_namespaceObject.resolve(targetPath));
4173
- if (!relative || relative === ".") {
4174
- return "";
4175
- }
4176
- if (isPathRelativeEscape(relative)) {
4177
- return "";
4400
+ else {
4401
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
4402
+ }
4403
+ tempStat = await handle.stat();
4404
+ const tempPathStat = await promises_namespaceObject.lstat(tempPath);
4405
+ if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
4406
+ throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
4407
+ }
4408
+ const expectedTempStat = tempStat;
4409
+ await syncFileBestEffort(handle);
4410
+ let verifiedIdentity = expectedTempStat;
4411
+ await withAsyncDirectoryGuards([parentGuard], async () => {
4412
+ await promises_namespaceObject.rename(tempPath, targetPath);
4413
+ renamed = true;
4414
+ await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
4415
+ await syncDirectoryBestEffort(parentPath);
4416
+ const targetStat = await promises_namespaceObject.lstat(targetPath);
4417
+ if (targetStat.isSymbolicLink()) {
4418
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
4419
+ }
4420
+ if (!file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
4421
+ // On filesystems like rclone FUSE, rename(2) can give the destination a
4422
+ // different inode from the source temp fd even with zero concurrency. The
4423
+ // caller must ensure mutual exclusion before passing "verify-content";
4424
+ // fall back to a content hash for this rename-boundary check only.
4425
+ if (params.onRenameIdentityMismatch !== "verify-content") {
4426
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
4427
+ }
4428
+ if (params.input.kind !== "buffer") {
4429
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
4430
+ }
4431
+ const expectedHash = sha256Hex(params.input.data, params.input.encoding);
4432
+ readHandle = await promises_namespaceObject.open(targetPath, read_open_flags_resolveReadOpenFlags());
4433
+ const readHandleStat = await readHandle.stat({ bigint: true });
4434
+ const actualHash = sha256Hex(await readHandle.readFile());
4435
+ if (actualHash !== expectedHash) {
4436
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
4437
+ }
4438
+ // The content-verified destination, not the old temp inode, is now pinned.
4439
+ verificationIdentity = readHandleStat;
4440
+ // Preserve the helper's legacy numeric return facts, not the private proof.
4441
+ verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
4442
+ }
4443
+ });
4444
+ await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
4445
+ return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
4178
4446
  }
4179
- return relative;
4180
- }
4181
- function assertInsideBoundary(params) {
4182
- if (path_isPathInside(params.rootCanonicalPath, params.candidatePath)) {
4183
- return;
4447
+ finally {
4448
+ await readHandle?.close().catch(() => undefined);
4449
+ await handle?.close().catch(() => undefined);
4450
+ if (!renamed) {
4451
+ await promises_namespaceObject.rm(tempPath, { force: true }).catch(() => undefined);
4452
+ }
4184
4453
  }
4185
- throw new Error(`Path resolves outside ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.absolutePath)}`);
4186
- }
4187
- function pathEscapeError(params) {
4188
- return new Error(`Path escapes ${params.boundaryLabel} (${shortPath(params.rootPath)}): ${shortPath(params.absolutePath)}`);
4189
- }
4190
- function symlinkEscapeError(params) {
4191
- return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
4192
4454
  }
4193
4455
 
4194
4456
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path-policy.js
@@ -5137,56 +5399,6 @@ async function verifyAtomicWriteResult(params) {
5137
5399
  }
5138
5400
  }
5139
5401
 
5140
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/strict-file-identity.js
5141
-
5142
- function identityMismatch() {
5143
- return new errors_FsSafeError("path-mismatch", "file identity changed or could not be verified");
5144
- }
5145
- function identityCheck(expected, platform) {
5146
- const known = {};
5147
- const check = (stat) => {
5148
- let complete = true;
5149
- for (const field of ["dev", "ino"]) {
5150
- const value = stat[field];
5151
- // Numeric receipts cannot recover identity bits already lost to rounding.
5152
- if (typeof value !== "bigint")
5153
- throw identityMismatch();
5154
- if (platform === "win32" && value === 0n) {
5155
- complete = false;
5156
- }
5157
- else {
5158
- if (known[field] !== undefined && known[field] !== value)
5159
- throw identityMismatch();
5160
- known[field] = value;
5161
- }
5162
- }
5163
- return complete;
5164
- };
5165
- if (expected && !check(expected))
5166
- throw identityMismatch();
5167
- return check;
5168
- }
5169
- // Retry only unknown Windows identities, retaining every known component so a
5170
- // later observation cannot erase a definite mismatch. Never reopen the file.
5171
- async function inspectFileIdentity(inspect, expected, platform = process.platform) {
5172
- const check = identityCheck(expected, platform);
5173
- for (let attempt = 0; attempt < 2; attempt++) {
5174
- const stat = await inspect();
5175
- if (check(stat))
5176
- return stat;
5177
- }
5178
- throw identityMismatch();
5179
- }
5180
- function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
5181
- const check = identityCheck(expected, platform);
5182
- for (let attempt = 0; attempt < 2; attempt++) {
5183
- const stat = inspect();
5184
- if (check(stat))
5185
- return stat;
5186
- }
5187
- throw identityMismatch();
5188
- }
5189
-
5190
5402
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-impl.js
5191
5403
 
5192
5404
 
@@ -5217,6 +5429,7 @@ function inspectFileIdentitySync(inspect, expected, platform = process.platform)
5217
5429
 
5218
5430
 
5219
5431
 
5432
+
5220
5433
 
5221
5434
 
5222
5435
  function logWarn(message) {
@@ -5292,25 +5505,15 @@ async function openVerifiedLocalFile(filePath, options) {
5292
5505
  if (preOpenStat) {
5293
5506
  await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
5294
5507
  }
5508
+ const openFlags = options?.symlinks === "follow-within-root"
5509
+ ? OPEN_READ_FOLLOW_FLAGS
5510
+ : OPEN_READ_FLAGS;
5511
+ await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
5295
5512
  let handle;
5296
5513
  try {
5297
- const openFlags = options?.symlinks === "follow-within-root"
5298
- ? OPEN_READ_FOLLOW_FLAGS
5299
- : OPEN_READ_FLAGS;
5300
- await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
5301
- handle = await promises_namespaceObject.open(filePath, openFlags);
5302
- try {
5303
- await fsSafeTestHooks?.afterOpen?.(filePath, handle);
5304
- }
5305
- catch (err) {
5306
- await handle.close().catch(() => { });
5307
- throw err;
5308
- }
5514
+ handle = await promises_namespaceObject.open(filePath, openFlags).catch((error) => recordFileOpenFailure(path_isNotFoundPathError(error) ? fileNotFoundError() : error, filePath));
5309
5515
  }
5310
5516
  catch (err) {
5311
- if (path_isNotFoundPathError(err)) {
5312
- throw fileNotFoundError();
5313
- }
5314
5517
  if (isSymlinkOpenError(err)) {
5315
5518
  throw new errors_FsSafeError("symlink", "symlink open blocked", { cause: err });
5316
5519
  }
@@ -5321,16 +5524,35 @@ async function openVerifiedLocalFile(filePath, options) {
5321
5524
  throw err;
5322
5525
  }
5323
5526
  try {
5527
+ await fsSafeTestHooks?.afterOpen?.(filePath, handle);
5324
5528
  const stat = await handle.stat();
5325
5529
  if (!stat.isFile()) {
5326
5530
  throw new errors_FsSafeError("not-file", "not a file");
5327
5531
  }
5328
5532
  // Keep numeric Stats for the public receipt, never for identity verification.
5329
- const identity = await inspectFileIdentity(() => handle.stat({ bigint: true }), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined);
5533
+ let openedIdentity;
5534
+ const identity = await inspectFileIdentity(async () => (openedIdentity = await handle.stat({ bigint: true })), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined).catch(async (error) => {
5535
+ if ([0, 1].includes(stat.nlink)) {
5536
+ await recordPreOpenFileChange(error, handle, filePath, preOpenStat, openedIdentity);
5537
+ }
5538
+ throw error;
5539
+ });
5330
5540
  if (options?.hardlinks === "reject" && stat.nlink > 1) {
5331
5541
  throw hardlinkedPathNotAllowedError();
5332
5542
  }
5333
- await inspectFileIdentity(async () => {
5543
+ const inspectPathIdentity = async (inspect) => {
5544
+ try {
5545
+ return await inspectFileIdentity(inspect, identity);
5546
+ }
5547
+ catch (error) {
5548
+ const failure = path_isNotFoundPathError(error) ? openedPathResolutionError(fileNotFoundError()) : error;
5549
+ if (stat.nlink <= 1 && (!preOpenStat || preOpenStat.nlink <= 1n)) {
5550
+ await recordOpenedFileFailure(failure, handle, filePath, identity);
5551
+ }
5552
+ throw failure;
5553
+ }
5554
+ };
5555
+ await inspectPathIdentity(async () => {
5334
5556
  const pathStat = options?.symlinks === "follow-within-root"
5335
5557
  ? await promises_namespaceObject.stat(filePath, { bigint: true })
5336
5558
  : await promises_namespaceObject.lstat(filePath, { bigint: true });
@@ -5338,26 +5560,26 @@ async function openVerifiedLocalFile(filePath, options) {
5338
5560
  throw new errors_FsSafeError("symlink", "symlink not allowed");
5339
5561
  }
5340
5562
  return pathStat;
5341
- }, identity);
5563
+ });
5342
5564
  await fsSafeTestHooks?.afterOpenedPathIdentityCheck?.(filePath, handle);
5343
- const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath);
5344
- await inspectFileIdentity(async () => {
5565
+ const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath)
5566
+ .catch(async (error) => {
5567
+ if (stat.nlink <= 1 && (!preOpenStat || preOpenStat.nlink <= 1n)) {
5568
+ await recordOpenedFileFailure(error, handle, filePath, identity);
5569
+ }
5570
+ throw error;
5571
+ });
5572
+ await inspectPathIdentity(async () => {
5345
5573
  const realStat = await promises_namespaceObject.stat(realPath, { bigint: true });
5346
5574
  if (options?.hardlinks === "reject" && realStat.nlink > 1n) {
5347
5575
  throw hardlinkedPathNotAllowedError();
5348
5576
  }
5349
5577
  return realStat;
5350
- }, identity);
5578
+ });
5351
5579
  return { opened: openResult({ handle, realPath, stat }), identity };
5352
5580
  }
5353
5581
  catch (err) {
5354
5582
  await handle.close().catch(() => { });
5355
- if (err instanceof errors_FsSafeError) {
5356
- throw err;
5357
- }
5358
- if (path_isNotFoundPathError(err)) {
5359
- throw fileNotFoundError();
5360
- }
5361
5583
  throw err;
5362
5584
  }
5363
5585
  }
@@ -5586,19 +5808,10 @@ async function openFileInRoot(root, params) {
5586
5808
  rejectUnsafeDeviceReads: true,
5587
5809
  rejectSymlinks: params.symlinks !== "follow-within-root",
5588
5810
  });
5589
- let opened;
5590
- try {
5591
- ({ opened } = await openVerifiedLocalFile(resolved, {
5592
- nonBlockingRead: params.nonBlockingRead,
5593
- symlinks: params.symlinks,
5594
- }));
5595
- }
5596
- catch (err) {
5597
- if (err instanceof errors_FsSafeError) {
5598
- throw err;
5599
- }
5600
- throw err;
5601
- }
5811
+ const { opened } = await openVerifiedLocalFile(resolved, {
5812
+ nonBlockingRead: params.nonBlockingRead,
5813
+ symlinks: params.symlinks,
5814
+ });
5602
5815
  if (params.hardlinks !== "allow" && opened.stat.nlink > 1) {
5603
5816
  await opened.handle.close().catch(() => { });
5604
5817
  throw hardlinkedPathNotAllowedError();
@@ -5650,9 +5863,7 @@ function emitWriteBoundaryWarning(reason) {
5650
5863
  logWarn(`security: fs-safe write boundary warning (${reason})`);
5651
5864
  }
5652
5865
  function buildAtomicWriteTempPath(targetPath) {
5653
- const dir = external_node_path_namespaceObject.dirname(targetPath);
5654
- const base = external_node_path_namespaceObject.basename(targetPath);
5655
- return external_node_path_namespaceObject.join(dir, `.${base}.${process.pid}.${(0,external_node_crypto_namespaceObject.randomUUID)()}.tmp`);
5866
+ return external_node_path_namespaceObject.join(external_node_path_namespaceObject.dirname(targetPath), `.fs-safe-${(0,external_node_crypto_namespaceObject.randomUUID)()}.tmp`);
5656
5867
  }
5657
5868
  function rootWriteQueueKey(root, relativePath) {
5658
5869
  return `${root.rootReal}\0${relativePath}`;
@@ -5891,7 +6102,7 @@ async function writeFileInRoot(root, params) {
5891
6102
  await writeFileFallback(root, params);
5892
6103
  return;
5893
6104
  }
5894
- const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
6105
+ const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations, params.overwrite);
5895
6106
  await serializePathWrite(pinned.targetPath, async () => {
5896
6107
  await commitPinnedWriteInRoot(root, pinned, params);
5897
6108
  });
@@ -6013,7 +6224,7 @@ async function removePathIfIdentityUnchanged(targetPath, identity) {
6013
6224
  await promises_namespaceObject.rm(targetPath);
6014
6225
  });
6015
6226
  }
6016
- async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
6227
+ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations, overwrite = true) {
6017
6228
  const { rootReal, rootWithSep, resolved } = await resolveGuardedWritePathInRoot(root, {
6018
6229
  relativePath,
6019
6230
  denyMutations,
@@ -6031,22 +6242,40 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode,
6031
6242
  if (!basename || basename === "." || basename === "/") {
6032
6243
  throw new errors_FsSafeError("invalid-path", "invalid target path");
6033
6244
  }
6245
+ // Exclusive publication cannot inherit an existing file's mode. Keep type
6246
+ // and alias checks, but never open a competing owner's record just to reject it.
6247
+ if (!overwrite) {
6248
+ try {
6249
+ const existing = await promises_namespaceObject.stat(resolved);
6250
+ if (!existing.isFile())
6251
+ throw new errors_FsSafeError("not-file", "not a file");
6252
+ if (existing.nlink > 1)
6253
+ throw hardlinkedPathNotAllowedError();
6254
+ throw new errors_FsSafeError("already-exists", "file already exists");
6255
+ }
6256
+ catch (error) {
6257
+ if (!path_isNotFoundPathError(error))
6258
+ throw error;
6259
+ }
6260
+ }
6034
6261
  let mode = requestedMode ?? 0o600;
6035
6262
  try {
6036
- const opened = await openFileInRoot(root, {
6037
- relativePath,
6038
- hardlinks: "reject",
6039
- nonBlockingRead: true,
6040
- symlinks: "follow-within-root",
6041
- });
6042
- try {
6043
- mode = requestedMode ?? (opened.stat.mode & 0o777);
6044
- if (!path_isPathInside(rootWithSep, opened.realPath)) {
6045
- throw outsideWorkspaceError();
6263
+ if (overwrite) {
6264
+ const opened = await openFileInRoot(root, {
6265
+ relativePath,
6266
+ hardlinks: "reject",
6267
+ nonBlockingRead: true,
6268
+ symlinks: "follow-within-root",
6269
+ });
6270
+ try {
6271
+ mode = requestedMode ?? (opened.stat.mode & 0o777);
6272
+ if (!path_isPathInside(rootWithSep, opened.realPath)) {
6273
+ throw outsideWorkspaceError();
6274
+ }
6275
+ }
6276
+ finally {
6277
+ await opened.handle.close().catch(() => { });
6046
6278
  }
6047
- }
6048
- finally {
6049
- await opened.handle.close().catch(() => { });
6050
6279
  }
6051
6280
  }
6052
6281
  catch (err) {
@@ -6980,7 +7209,7 @@ async function runShim(options) {
6980
7209
  });
6981
7210
  }
6982
7211
 
6983
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/util.js
7212
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/util.js
6984
7213
 
6985
7214
  // functions
6986
7215
  function assertEqual(val) {
@@ -7828,7 +8057,7 @@ function constantCatch(value) {
7828
8057
  return fn;
7829
8058
  }
7830
8059
 
7831
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/core.js
8060
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/core.js
7832
8061
  var core_a;
7833
8062
 
7834
8063
  /** A special constant with type `never` */
@@ -7971,7 +8200,7 @@ function core_config(newConfig) {
7971
8200
  return globalConfig;
7972
8201
  }
7973
8202
 
7974
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/memoizer.js
8203
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/memoizer.js
7975
8204
  class $ZodCyclicError extends Error {
7976
8205
  constructor() {
7977
8206
  super(`Cannot parse a reference cycle that closes through a transform`);
@@ -8161,7 +8390,7 @@ function isBackEdge(ctx, value) {
8161
8390
  return backEdges !== undefined && value !== null && typeof value === "object" && backEdges.has(value);
8162
8391
  }
8163
8392
 
8164
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/regexes.js
8393
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/regexes.js
8165
8394
 
8166
8395
  /**
8167
8396
  * @deprecated CUID v1 is deprecated by its authors due to information leakage
@@ -8313,7 +8542,7 @@ const sha512_hex = /^[0-9a-fA-F]{128}$/;
8313
8542
  const sha512_base64 = /*@__PURE__*/ (/* unused pure expression or super */ null && (fixedBase64(86, "==")));
8314
8543
  const sha512_base64url = /*@__PURE__*/ (/* unused pure expression or super */ null && (fixedBase64url(86)));
8315
8544
 
8316
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/checks.js
8545
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/checks.js
8317
8546
  // import { $ZodType } from "./schemas.js";
8318
8547
 
8319
8548
 
@@ -8896,7 +9125,7 @@ const $ZodCheckOverwrite = /*@__PURE__*/ $constructor("$ZodCheckOverwrite", (ins
8896
9125
  };
8897
9126
  });
8898
9127
 
8899
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/doc.js
9128
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/doc.js
8900
9129
  class Doc {
8901
9130
  constructor(args = [], closed = {}) {
8902
9131
  this.content = [];
@@ -8931,7 +9160,7 @@ class Doc {
8931
9160
  }
8932
9161
  }
8933
9162
 
8934
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/errors.js
9163
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/errors.js
8935
9164
 
8936
9165
 
8937
9166
  /* Computing the message eagerly is expensive (pretty-printed JSON of all
@@ -9203,7 +9432,7 @@ function prettifyError(error) {
9203
9432
  return lines.join("\n");
9204
9433
  }
9205
9434
 
9206
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/parse.js
9435
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/parse.js
9207
9436
 
9208
9437
 
9209
9438
 
@@ -9359,14 +9588,14 @@ const _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
9359
9588
  };
9360
9589
  const safeDecodeAsync = /* @__PURE__*/ _safeDecodeAsync($ZodRealError);
9361
9590
 
9362
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/versions.js
9591
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/versions.js
9363
9592
  const versions_version = {
9364
9593
  major: 4,
9365
9594
  minor: 5,
9366
9595
  patch: 2,
9367
9596
  };
9368
9597
 
9369
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/schemas.js
9598
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/schemas.js
9370
9599
 
9371
9600
 
9372
9601
 
@@ -11808,7 +12037,7 @@ function handleRefineResult(result, payload, input, inst) {
11808
12037
  }
11809
12038
  }
11810
12039
 
11811
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/registries.js
12040
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/registries.js
11812
12041
  var registries_a;
11813
12042
  const $output = /*@__PURE__*/ (/* unused pure expression or super */ null && (Symbol("ZodOutput")));
11814
12043
  const $input = /*@__PURE__*/ (/* unused pure expression or super */ null && (Symbol("ZodInput")));
@@ -11861,7 +12090,7 @@ function registries_registry() {
11861
12090
  (registries_a = globalThis).__zod_globalRegistry ?? (registries_a.__zod_globalRegistry = registries_registry());
11862
12091
  const globalRegistry = globalThis.__zod_globalRegistry;
11863
12092
 
11864
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/api.js
12093
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/api.js
11865
12094
 
11866
12095
 
11867
12096
 
@@ -12963,7 +13192,7 @@ function _stringFormat(Class, format, fnOrRegex, _params = {}) {
12963
13192
  return inst;
12964
13193
  }
12965
13194
 
12966
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/to-json-schema.js
13195
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/to-json-schema.js
12967
13196
 
12968
13197
 
12969
13198
  function assignProps(target, ...sources) {
@@ -13612,7 +13841,7 @@ const createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params)
13612
13841
  return to_json_schema_finalize(ctx, schema);
13613
13842
  };
13614
13843
 
13615
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/json-schema-processors.js
13844
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/json-schema-processors.js
13616
13845
 
13617
13846
 
13618
13847
  const formatMap = {
@@ -14272,7 +14501,7 @@ function toJSONSchema(input, params) {
14272
14501
  return finalize(ctx, input);
14273
14502
  }
14274
14503
 
14275
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/locales/en.js
14504
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/locales/en.js
14276
14505
 
14277
14506
  const en_error = () => {
14278
14507
  const Sizable = {
@@ -14397,7 +14626,7 @@ const en_error = () => {
14397
14626
  };
14398
14627
  }
14399
14628
 
14400
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/classic/errors.js
14629
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/classic/errors.js
14401
14630
 
14402
14631
 
14403
14632
 
@@ -14456,7 +14685,7 @@ const ZodRealError = /*@__PURE__*/ $constructor("ZodError", classic_errors_initi
14456
14685
  // /** @deprecated Use `z.core.$ZodErrorMapCtx` instead. */
14457
14686
  // export type ErrorMapCtx = core.$ZodErrorMapCtx;
14458
14687
 
14459
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/classic/parse.js
14688
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/classic/parse.js
14460
14689
 
14461
14690
 
14462
14691
  const classic_parse_parse = /* @__PURE__ */ parse_parse(ZodRealError);
@@ -14474,7 +14703,7 @@ const parse_safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);
14474
14703
  const parse_safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
14475
14704
  const parse_safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
14476
14705
 
14477
- ;// CONCATENATED MODULE: ../../node_modules/zod/v4/classic/schemas.js
14706
+ ;// CONCATENATED MODULE: ./node_modules/zod/v4/classic/schemas.js
14478
14707
 
14479
14708
 
14480
14709
 
@@ -18836,4 +19065,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
18836
19065
  // module factories are used so entry inlining is disabled
18837
19066
  // startup
18838
19067
  // Load entry module and return exports
18839
- var __webpack_exports__ = __webpack_require__(699);
19068
+ var __webpack_exports__ = __webpack_require__(461);