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