@lousy-agents/cli 5.17.16 → 5.18.1
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/api/copilot-with-fastify/.agents/skills/feature-to-plan/SKILL.md +1 -1
- package/api/copilot-with-fastify/package-lock.json +4 -4
- package/api/copilot-with-fastify/package.json +1 -1
- package/cli/copilot-with-citty/.agents/skills/feature-to-plan/SKILL.md +1 -1
- package/cli/copilot-with-citty/package.json +1 -1
- package/dist/index.js +1872 -1472
- package/package.json +1 -1
- package/ui/copilot-with-react/.agents/skills/feature-to-plan/SKILL.md +1 -1
- package/ui/copilot-with-react/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3032,7 +3032,7 @@ exports.basename = (path, { windows } = {}) => {
|
|
|
3032
3032
|
|
|
3033
3033
|
|
|
3034
3034
|
},
|
|
3035
|
-
|
|
3035
|
+
7015(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
|
|
3036
3036
|
// NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
|
|
3037
3037
|
var constructs_namespaceObject = {};
|
|
3038
3038
|
__webpack_require__.r(constructs_namespaceObject);
|
|
@@ -3070,40 +3070,16 @@ function categorizeFsSafeError(code) {
|
|
|
3070
3070
|
class errors_FsSafeError extends Error {
|
|
3071
3071
|
code;
|
|
3072
3072
|
category;
|
|
3073
|
+
details;
|
|
3073
3074
|
constructor(code, message, options = {}) {
|
|
3074
3075
|
super(message, options);
|
|
3075
3076
|
this.name = "FsSafeError";
|
|
3076
3077
|
this.code = code;
|
|
3077
3078
|
this.category = categorizeFsSafeError(code);
|
|
3079
|
+
this.details = options.details;
|
|
3078
3080
|
}
|
|
3079
3081
|
}
|
|
3080
3082
|
|
|
3081
|
-
// EXTERNAL MODULE: external "node:stream/promises"
|
|
3082
|
-
var external_node_stream_promises_ = __webpack_require__(6466);
|
|
3083
|
-
// EXTERNAL MODULE: external "node:stream"
|
|
3084
|
-
var external_node_stream_ = __webpack_require__(7075);
|
|
3085
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read-stream.js
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
function createMaxBytesTransform(maxBytes) {
|
|
3089
|
-
let bytes = 0;
|
|
3090
|
-
return new external_node_stream_.Transform({
|
|
3091
|
-
transform(chunk, _encoding, callback) {
|
|
3092
|
-
const buffer = chunk instanceof Buffer ? chunk : Buffer.from(chunk);
|
|
3093
|
-
bytes += buffer.byteLength;
|
|
3094
|
-
if (bytes > maxBytes) {
|
|
3095
|
-
callback(new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`));
|
|
3096
|
-
return;
|
|
3097
|
-
}
|
|
3098
|
-
callback(null, buffer);
|
|
3099
|
-
},
|
|
3100
|
-
});
|
|
3101
|
-
}
|
|
3102
|
-
function createBoundedReadStream(opened, maxBytes) {
|
|
3103
|
-
const stream = opened.handle.createReadStream();
|
|
3104
|
-
return maxBytes === undefined ? stream : stream.pipe(createMaxBytesTransform(maxBytes));
|
|
3105
|
-
}
|
|
3106
|
-
|
|
3107
3083
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/file-identity.js
|
|
3108
3084
|
|
|
3109
3085
|
function isZero(value) {
|
|
@@ -3128,86 +3104,11 @@ function file_identity_sameFileIdentity(left, right, platform = process.platform
|
|
|
3128
3104
|
return platform === "win32" && (isZero(left.dev) || isZero(right.dev));
|
|
3129
3105
|
}
|
|
3130
3106
|
|
|
3131
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/string-coerce.js
|
|
3132
|
-
function readStringValue(value) {
|
|
3133
|
-
return typeof value === "string" ? value : undefined;
|
|
3134
|
-
}
|
|
3135
|
-
function normalizeNullableString(value) {
|
|
3136
|
-
if (typeof value !== "string") {
|
|
3137
|
-
return null;
|
|
3138
|
-
}
|
|
3139
|
-
const trimmed = value.trim();
|
|
3140
|
-
return trimmed ? trimmed : null;
|
|
3141
|
-
}
|
|
3142
|
-
function normalizeOptionalString(value) {
|
|
3143
|
-
return normalizeNullableString(value) ?? undefined;
|
|
3144
|
-
}
|
|
3145
|
-
function normalizeStringifiedOptionalString(value) {
|
|
3146
|
-
if (typeof value === "string") {
|
|
3147
|
-
return normalizeOptionalString(value);
|
|
3148
|
-
}
|
|
3149
|
-
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
3150
|
-
return normalizeOptionalString(String(value));
|
|
3151
|
-
}
|
|
3152
|
-
return undefined;
|
|
3153
|
-
}
|
|
3154
|
-
function normalizeOptionalLowercaseString(value) {
|
|
3155
|
-
return normalizeOptionalString(value)?.toLowerCase();
|
|
3156
|
-
}
|
|
3157
|
-
function normalizeLowercaseStringOrEmpty(value) {
|
|
3158
|
-
return normalizeOptionalLowercaseString(value) ?? "";
|
|
3159
|
-
}
|
|
3160
|
-
function normalizeFastMode(raw) {
|
|
3161
|
-
if (typeof raw === "boolean") {
|
|
3162
|
-
return raw;
|
|
3163
|
-
}
|
|
3164
|
-
if (!raw) {
|
|
3165
|
-
return undefined;
|
|
3166
|
-
}
|
|
3167
|
-
const key = normalizeLowercaseStringOrEmpty(raw);
|
|
3168
|
-
if (["off", "false", "no", "0", "disable", "disabled", "normal"].includes(key)) {
|
|
3169
|
-
return false;
|
|
3170
|
-
}
|
|
3171
|
-
if (["on", "true", "yes", "1", "enable", "enabled", "fast"].includes(key)) {
|
|
3172
|
-
return true;
|
|
3173
|
-
}
|
|
3174
|
-
return undefined;
|
|
3175
|
-
}
|
|
3176
|
-
function lowercasePreservingWhitespace(value) {
|
|
3177
|
-
return value.toLowerCase();
|
|
3178
|
-
}
|
|
3179
|
-
function localeLowercasePreservingWhitespace(value) {
|
|
3180
|
-
return value.toLocaleLowerCase();
|
|
3181
|
-
}
|
|
3182
|
-
function resolvePrimaryStringValue(value) {
|
|
3183
|
-
if (typeof value === "string") {
|
|
3184
|
-
return normalizeOptionalString(value);
|
|
3185
|
-
}
|
|
3186
|
-
if (!value || typeof value !== "object") {
|
|
3187
|
-
return undefined;
|
|
3188
|
-
}
|
|
3189
|
-
return normalizeOptionalString(value.primary);
|
|
3190
|
-
}
|
|
3191
|
-
function normalizeOptionalThreadValue(value) {
|
|
3192
|
-
if (typeof value === "number") {
|
|
3193
|
-
return Number.isFinite(value) ? Math.trunc(value) : undefined;
|
|
3194
|
-
}
|
|
3195
|
-
return normalizeOptionalString(value);
|
|
3196
|
-
}
|
|
3197
|
-
function normalizeOptionalStringifiedId(value) {
|
|
3198
|
-
const normalized = normalizeOptionalThreadValue(value);
|
|
3199
|
-
return normalized == null ? undefined : String(normalized);
|
|
3200
|
-
}
|
|
3201
|
-
function hasNonEmptyString(value) {
|
|
3202
|
-
return normalizeOptionalString(value) !== undefined;
|
|
3203
|
-
}
|
|
3204
|
-
|
|
3205
3107
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path.js
|
|
3206
3108
|
|
|
3207
3109
|
|
|
3208
3110
|
|
|
3209
3111
|
|
|
3210
|
-
|
|
3211
3112
|
const NOT_FOUND_CODES = new Set(["ENOENT", "ENOTDIR"]);
|
|
3212
3113
|
const SYMLINK_OPEN_CODES = new Set(["ELOOP", "EINVAL", "ENOTSUP"]);
|
|
3213
3114
|
const POSIX_SEPARATOR_CHAR_CODE = 0x2f;
|
|
@@ -3219,7 +3120,9 @@ function normalizeWindowsPathForComparison(input) {
|
|
|
3219
3120
|
normalized = `\\\\${normalized.slice(4)}`;
|
|
3220
3121
|
}
|
|
3221
3122
|
}
|
|
3222
|
-
|
|
3123
|
+
// Lowercase only: this value feeds Windows containment math, so surrounding
|
|
3124
|
+
// whitespace is part of the path and must not be trimmed away.
|
|
3125
|
+
return normalized.replaceAll("/", "\\").toLowerCase();
|
|
3223
3126
|
}
|
|
3224
3127
|
function isNodeError(value) {
|
|
3225
3128
|
return Boolean(value && typeof value === "object" && "code" in value);
|
|
@@ -3718,6 +3621,27 @@ function isSameOrChildPath(candidate, parent) {
|
|
|
3718
3621
|
const parentPrefix = parent.endsWith(external_node_path_.sep) ? parent : `${parent}${external_node_path_.sep}`;
|
|
3719
3622
|
return candidate === parent || candidate.startsWith(parentPrefix);
|
|
3720
3623
|
}
|
|
3624
|
+
async function realpathOrThrowNotFile(target) {
|
|
3625
|
+
try {
|
|
3626
|
+
return external_node_path_.resolve(await promises_.realpath(target));
|
|
3627
|
+
}
|
|
3628
|
+
catch (error) {
|
|
3629
|
+
if (path_isNotFoundPathError(error)) {
|
|
3630
|
+
// A dangling symlink (or a component removed between lstat and
|
|
3631
|
+
// realpath) is not a usable directory component.
|
|
3632
|
+
throw new errors_FsSafeError("not-file", "directory component must be a directory", {
|
|
3633
|
+
cause: error instanceof Error ? error : undefined,
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3636
|
+
throw error;
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
/**
|
|
3640
|
+
* Creates each missing path component from `rootReal` down to `targetPath`,
|
|
3641
|
+
* guarding every step. Returns the real (symlink-resolved) path of the final
|
|
3642
|
+
* component so callers can guard/use that path directly instead of
|
|
3643
|
+
* re-deriving it from the original, possibly-symlinked, lexical path.
|
|
3644
|
+
*/
|
|
3721
3645
|
async function mkdirPathComponentsWithGuards(params) {
|
|
3722
3646
|
const root = external_node_path_.resolve(params.rootReal);
|
|
3723
3647
|
const rootCanonical = external_node_path_.resolve(await promises_.realpath(root));
|
|
@@ -3741,18 +3665,39 @@ async function mkdirPathComponentsWithGuards(params) {
|
|
|
3741
3665
|
}
|
|
3742
3666
|
}
|
|
3743
3667
|
const stat = await promises_.lstat(next);
|
|
3744
|
-
if (stat.isSymbolicLink()
|
|
3668
|
+
if (!stat.isSymbolicLink() && !stat.isDirectory()) {
|
|
3745
3669
|
throw new errors_FsSafeError("not-file", "directory component must be a directory");
|
|
3746
3670
|
}
|
|
3747
3671
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
3748
3672
|
// segment at a time and realpath-check each segment before descending.
|
|
3749
|
-
|
|
3673
|
+
const nextReal = await realpathOrThrowNotFile(next);
|
|
3674
|
+
if (!isSameOrChildPath(nextReal, rootCanonical)) {
|
|
3750
3675
|
throw new errors_FsSafeError("outside-workspace", "directory escaped workspace root");
|
|
3751
3676
|
}
|
|
3677
|
+
if (stat.isSymbolicLink()) {
|
|
3678
|
+
// An existing path component may legitimately be a symlink to a real
|
|
3679
|
+
// directory inside the root (e.g. a skill-bank layout). We already
|
|
3680
|
+
// verified above that it resolves inside the root, so treat the
|
|
3681
|
+
// resolved real path as the directory for the rest of this walk
|
|
3682
|
+
// instead of rejecting it outright. Guard checks from here on operate
|
|
3683
|
+
// on the real (non-symlink) path, preserving TOCTOU protection for
|
|
3684
|
+
// every subsequent segment. Callers that need the final directory
|
|
3685
|
+
// (e.g. to guard it themselves after this function returns) must use
|
|
3686
|
+
// the returned resolved path, not their own lexical parent path.
|
|
3687
|
+
const targetStat = await promises_.stat(nextReal);
|
|
3688
|
+
if (!targetStat.isDirectory()) {
|
|
3689
|
+
throw new errors_FsSafeError("not-file", "directory component must be a directory");
|
|
3690
|
+
}
|
|
3691
|
+
await directory_guard_createAsyncDirectoryGuard(nextReal);
|
|
3692
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
3693
|
+
current = nextReal;
|
|
3694
|
+
continue;
|
|
3695
|
+
}
|
|
3752
3696
|
await directory_guard_createAsyncDirectoryGuard(next);
|
|
3753
3697
|
await assertAsyncDirectoryGuard(parentGuard);
|
|
3754
3698
|
current = next;
|
|
3755
3699
|
}
|
|
3700
|
+
return current;
|
|
3756
3701
|
}
|
|
3757
3702
|
|
|
3758
3703
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/guarded-mutation.js
|
|
@@ -4018,8 +3963,20 @@ async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
|
|
|
4018
3963
|
return null;
|
|
4019
3964
|
}
|
|
4020
3965
|
|
|
4021
|
-
|
|
3966
|
+
// EXTERNAL MODULE: external "node:module"
|
|
3967
|
+
var external_node_module_ = __webpack_require__(8995);
|
|
3968
|
+
// EXTERNAL MODULE: external "node:url"
|
|
3969
|
+
var external_node_url_ = __webpack_require__(3136);
|
|
3970
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native-config.js
|
|
4022
3971
|
let overrideConfig = {};
|
|
3972
|
+
let legacyWarningEmitted = false;
|
|
3973
|
+
const legacyModeEnvKeys = ["FS_SAFE_PYTHON_MODE", "OPENCLAW_FS_SAFE_PYTHON_MODE"];
|
|
3974
|
+
const legacyPathEnvKeys = [
|
|
3975
|
+
"FS_SAFE_PYTHON",
|
|
3976
|
+
"OPENCLAW_FS_SAFE_PYTHON",
|
|
3977
|
+
"OPENCLAW_PINNED_PYTHON",
|
|
3978
|
+
"OPENCLAW_PINNED_WRITE_PYTHON",
|
|
3979
|
+
];
|
|
4023
3980
|
function parseMode(value) {
|
|
4024
3981
|
if (!value) {
|
|
4025
3982
|
return undefined;
|
|
@@ -4036,682 +3993,495 @@ function parseMode(value) {
|
|
|
4036
3993
|
}
|
|
4037
3994
|
return undefined;
|
|
4038
3995
|
}
|
|
4039
|
-
function
|
|
3996
|
+
function configureFsSafeNative(config) {
|
|
4040
3997
|
overrideConfig = { ...overrideConfig, ...config };
|
|
4041
3998
|
}
|
|
4042
|
-
function
|
|
3999
|
+
function warnLegacyPythonConfiguration(source, mappedMode) {
|
|
4000
|
+
if (legacyWarningEmitted) {
|
|
4001
|
+
return;
|
|
4002
|
+
}
|
|
4003
|
+
legacyWarningEmitted = true;
|
|
4004
|
+
process.emitWarning(`${source} is a deprecated 0.4 compatibility bridge; mapped to native mode ` +
|
|
4005
|
+
`"${mappedMode}". Use configureFsSafeNative({ mode: "${mappedMode}" }) or ` +
|
|
4006
|
+
`FS_SAFE_NATIVE_MODE=${mappedMode}. Python interpreter paths are no longer used.`, { code: "FS_SAFE_PYTHON_DEPRECATED", type: "DeprecationWarning" });
|
|
4007
|
+
}
|
|
4008
|
+
function readLegacyPythonMode() {
|
|
4009
|
+
const configuredKeys = [...legacyModeEnvKeys, ...legacyPathEnvKeys].filter((key) => process.env[key] !== undefined);
|
|
4010
|
+
if (configuredKeys.length === 0) {
|
|
4011
|
+
return undefined;
|
|
4012
|
+
}
|
|
4013
|
+
const rawMode = legacyModeEnvKeys.map((key) => process.env[key]).find((value) => value !== undefined);
|
|
4014
|
+
const mappedMode = parseMode(rawMode) ?? "auto";
|
|
4015
|
+
warnLegacyPythonConfiguration(`Legacy ${configuredKeys.join(", ")}`, mappedMode);
|
|
4016
|
+
return mappedMode;
|
|
4017
|
+
}
|
|
4018
|
+
/**
|
|
4019
|
+
* @deprecated Compatibility bridge for 0.4 upgrades. Use configureFsSafeNative.
|
|
4020
|
+
*/
|
|
4021
|
+
function configureFsSafePython(config) {
|
|
4022
|
+
const mappedMode = config.mode ?? "auto";
|
|
4023
|
+
warnLegacyPythonConfiguration("configureFsSafePython()", mappedMode);
|
|
4024
|
+
if (config.mode !== undefined) {
|
|
4025
|
+
configureFsSafeNative({ mode: config.mode });
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
function getFsSafeNativeConfig() {
|
|
4029
|
+
const legacyMode = readLegacyPythonMode();
|
|
4043
4030
|
return {
|
|
4044
4031
|
mode: overrideConfig.mode ??
|
|
4045
|
-
parseMode(process.env.
|
|
4046
|
-
parseMode(process.env.
|
|
4032
|
+
parseMode(process.env.FS_SAFE_NATIVE_MODE) ??
|
|
4033
|
+
parseMode(process.env.OPENCLAW_FS_SAFE_NATIVE_MODE) ??
|
|
4034
|
+
legacyMode ??
|
|
4047
4035
|
"auto",
|
|
4048
|
-
pythonPath: overrideConfig.pythonPath ??
|
|
4049
|
-
process.env.FS_SAFE_PYTHON ??
|
|
4050
|
-
process.env.OPENCLAW_FS_SAFE_PYTHON ??
|
|
4051
|
-
process.env.OPENCLAW_PINNED_PYTHON ??
|
|
4052
|
-
process.env.OPENCLAW_PINNED_WRITE_PYTHON,
|
|
4053
4036
|
};
|
|
4054
4037
|
}
|
|
4055
|
-
function
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
(code === "helper-unavailable" || code === "unsupported-platform"));
|
|
4038
|
+
function __resetFsSafeNativeConfigForTest() {
|
|
4039
|
+
overrideConfig = {};
|
|
4040
|
+
legacyWarningEmitted = false;
|
|
4059
4041
|
}
|
|
4060
4042
|
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
const
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
4077
|
-
if hasattr(os, "O_NOFOLLOW"):
|
|
4078
|
-
WRITE_FLAGS |= os.O_NOFOLLOW
|
|
4079
|
-
def split_relative(value):
|
|
4080
|
-
if value in ("", "."):
|
|
4081
|
-
return []
|
|
4082
|
-
if "\x00" in value or value.startswith("/") or value.startswith("//"):
|
|
4083
|
-
raise OSError(errno.EPERM, "invalid relative path")
|
|
4084
|
-
if value.startswith("..\\"):
|
|
4085
|
-
raise OSError(errno.EPERM, "path traversal is not allowed")
|
|
4086
|
-
parts = [part for part in value.split("/") if part and part != "."]
|
|
4087
|
-
for part in parts:
|
|
4088
|
-
if part == "..":
|
|
4089
|
-
raise OSError(errno.EPERM, "path traversal is not allowed")
|
|
4090
|
-
return parts
|
|
4091
|
-
def open_dir(path_value, dir_fd=None):
|
|
4092
|
-
return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
|
|
4093
|
-
def walk_dir(root_fd, segments, mkdir_enabled=False):
|
|
4094
|
-
current_fd = os.dup(root_fd)
|
|
4095
|
-
try:
|
|
4096
|
-
for segment in segments:
|
|
4097
|
-
try:
|
|
4098
|
-
next_fd = open_dir(segment, dir_fd=current_fd)
|
|
4099
|
-
except FileNotFoundError:
|
|
4100
|
-
if not mkdir_enabled:
|
|
4101
|
-
raise
|
|
4102
|
-
os.mkdir(segment, 0o777, dir_fd=current_fd)
|
|
4103
|
-
next_fd = open_dir(segment, dir_fd=current_fd)
|
|
4104
|
-
os.close(current_fd)
|
|
4105
|
-
current_fd = next_fd
|
|
4106
|
-
return current_fd
|
|
4107
|
-
except Exception:
|
|
4108
|
-
os.close(current_fd)
|
|
4109
|
-
raise
|
|
4110
|
-
def parent_and_basename(root_fd, relative):
|
|
4111
|
-
segments = split_relative(relative)
|
|
4112
|
-
if not segments:
|
|
4113
|
-
raise OSError(errno.EPERM, "operation requires a non-root path")
|
|
4114
|
-
parent_fd = walk_dir(root_fd, segments[:-1])
|
|
4115
|
-
return parent_fd, segments[-1]
|
|
4116
|
-
def encode_stat(st):
|
|
4117
|
-
mode = st.st_mode
|
|
4118
|
-
return {
|
|
4119
|
-
"dev": st.st_dev,
|
|
4120
|
-
"gid": st.st_gid,
|
|
4121
|
-
"ino": st.st_ino,
|
|
4122
|
-
"isDirectory": stat.S_ISDIR(mode),
|
|
4123
|
-
"isFile": stat.S_ISREG(mode),
|
|
4124
|
-
"isSymbolicLink": stat.S_ISLNK(mode),
|
|
4125
|
-
"mode": mode,
|
|
4126
|
-
"mtimeMs": st.st_mtime * 1000,
|
|
4127
|
-
"nlink": st.st_nlink,
|
|
4128
|
-
"size": st.st_size,
|
|
4129
|
-
"uid": st.st_uid,
|
|
4130
|
-
}
|
|
4131
|
-
def reject_unsafe_endpoint(st):
|
|
4132
|
-
mode = st.st_mode
|
|
4133
|
-
if stat.S_ISLNK(mode):
|
|
4134
|
-
raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
|
|
4135
|
-
if stat.S_ISREG(mode) and st.st_nlink > 1:
|
|
4136
|
-
raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
|
|
4137
|
-
def copy_bytes(source_fd, dest_fd):
|
|
4138
|
-
while True:
|
|
4139
|
-
chunk = os.read(source_fd, 65536)
|
|
4140
|
-
if not chunk:
|
|
4141
|
-
break
|
|
4142
|
-
view = memoryview(chunk)
|
|
4143
|
-
while view:
|
|
4144
|
-
written = os.write(dest_fd, view)
|
|
4145
|
-
if written <= 0:
|
|
4146
|
-
raise OSError(errno.EIO, "short write")
|
|
4147
|
-
view = view[written:]
|
|
4148
|
-
def write_all(fd, data):
|
|
4149
|
-
view = memoryview(data)
|
|
4150
|
-
while view:
|
|
4151
|
-
written = os.write(fd, view)
|
|
4152
|
-
if written <= 0:
|
|
4153
|
-
raise OSError(errno.EIO, "short write")
|
|
4154
|
-
view = view[written:]
|
|
4155
|
-
def fsync_best_effort(fd):
|
|
4156
|
-
try: os.fsync(fd)
|
|
4157
|
-
except OSError as error:
|
|
4158
|
-
if error.errno != errno.EPERM: raise
|
|
4159
|
-
def link_unsupported(exc):
|
|
4160
|
-
unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
|
|
4161
|
-
return getattr(exc, "errno", None) in unsupported
|
|
4162
|
-
def link_no_replace(name, new_name, source_fd, target_fd):
|
|
4163
|
-
linked = False
|
|
4164
|
-
try:
|
|
4165
|
-
os.link(name, new_name, src_dir_fd=source_fd, dst_dir_fd=target_fd, follow_symlinks=False)
|
|
4166
|
-
linked = True
|
|
4167
|
-
os.unlink(name, dir_fd=source_fd)
|
|
4168
|
-
except Exception:
|
|
4169
|
-
if linked:
|
|
4170
|
-
try: os.unlink(new_name, dir_fd=target_fd)
|
|
4171
|
-
except FileNotFoundError: pass
|
|
4172
|
-
raise
|
|
4173
|
-
os.fsync(source_fd)
|
|
4174
|
-
if source_fd != target_fd:
|
|
4175
|
-
os.fsync(target_fd)
|
|
4176
|
-
def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
|
|
4177
|
-
source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
|
|
4178
|
-
dest_fd = None; success = False; dest_stat = None
|
|
4179
|
-
try:
|
|
4180
|
-
if expected is not None:
|
|
4181
|
-
source_stat = os.fstat(source_fd)
|
|
4182
|
-
if source_stat.st_dev != expected.st_dev or source_stat.st_ino != expected.st_ino:
|
|
4183
|
-
raise RuntimeError("fs-safe-source-mismatch")
|
|
4184
|
-
dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
|
|
4185
|
-
copy_bytes(source_fd, dest_fd)
|
|
4186
|
-
os.fsync(dest_fd)
|
|
4187
|
-
dest_stat = os.fstat(dest_fd)
|
|
4188
|
-
success = True
|
|
4189
|
-
finally:
|
|
4190
|
-
os.close(source_fd)
|
|
4191
|
-
if dest_fd is not None:
|
|
4192
|
-
os.close(dest_fd)
|
|
4193
|
-
if dest_fd is not None and not success:
|
|
4194
|
-
try: os.unlink(basename, dir_fd=target_parent_fd)
|
|
4195
|
-
except FileNotFoundError: pass
|
|
4196
|
-
if unlink_source:
|
|
4197
|
-
try:
|
|
4198
|
-
os.unlink(source_name, dir_fd=source_parent_fd)
|
|
4199
|
-
except Exception:
|
|
4200
|
-
try: os.unlink(basename, dir_fd=target_parent_fd)
|
|
4201
|
-
except FileNotFoundError: pass
|
|
4202
|
-
raise
|
|
4203
|
-
return dest_stat
|
|
4204
|
-
def same_identity(left, right):
|
|
4205
|
-
return left.st_dev == right.st_dev and left.st_ino == right.st_ino
|
|
4206
|
-
def verify_temp_name(parent_fd, temp_name, expected_stat):
|
|
4207
|
-
current_stat = os.lstat(temp_name, dir_fd=parent_fd)
|
|
4208
|
-
if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
|
|
4209
|
-
raise RuntimeError("fs-safe-temp-mismatch")
|
|
4210
|
-
def verify_committed_temp(parent_fd, basename, expected_stat):
|
|
4211
|
-
final_stat = os.lstat(basename, dir_fd=parent_fd)
|
|
4212
|
-
if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
|
|
4213
|
-
return final_stat
|
|
4214
|
-
try: os.unlink(basename, dir_fd=parent_fd)
|
|
4215
|
-
except FileNotFoundError: pass
|
|
4216
|
-
raise RuntimeError("fs-safe-temp-mismatch")
|
|
4217
|
-
def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
|
|
4218
|
-
verify_temp_name(parent_fd, temp_name, expected_stat)
|
|
4219
|
-
if overwrite:
|
|
4220
|
-
os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
|
|
4221
|
-
return verify_committed_temp(parent_fd, basename, expected_stat)
|
|
4222
|
-
else:
|
|
4223
|
-
try:
|
|
4224
|
-
os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
|
|
4225
|
-
final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
|
|
4226
|
-
os.unlink(temp_name, dir_fd=parent_fd)
|
|
4227
|
-
return final_stat
|
|
4228
|
-
except OSError as exc:
|
|
4229
|
-
if not link_unsupported(exc):
|
|
4230
|
-
raise
|
|
4231
|
-
return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
|
|
4232
|
-
def assert_expected_root(root_fd, payload):
|
|
4233
|
-
if "rootDev" in payload or "rootIno" in payload:
|
|
4234
|
-
root_stat = os.fstat(root_fd)
|
|
4235
|
-
if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
|
|
4236
|
-
raise RuntimeError("fs-safe-root-mismatch")
|
|
4237
|
-
def stat_path(root_fd, payload):
|
|
4238
|
-
relative = payload.get("relativePath", "")
|
|
4239
|
-
segments = split_relative(relative)
|
|
4240
|
-
if not segments:
|
|
4241
|
-
return encode_stat(os.fstat(root_fd))
|
|
4242
|
-
parent_fd, basename = parent_and_basename(root_fd, relative)
|
|
4243
|
-
try:
|
|
4244
|
-
st = os.lstat(basename, dir_fd=parent_fd)
|
|
4245
|
-
if payload.get("rejectSymlink", True) and stat.S_ISLNK(st.st_mode):
|
|
4246
|
-
raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
|
|
4247
|
-
return encode_stat(st)
|
|
4248
|
-
finally:
|
|
4249
|
-
os.close(parent_fd)
|
|
4250
|
-
def readdir_path(root_fd, payload):
|
|
4251
|
-
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
|
|
4252
|
-
try:
|
|
4253
|
-
names = sorted(os.listdir(dir_fd))
|
|
4254
|
-
if not payload.get("withFileTypes", False):
|
|
4255
|
-
return names
|
|
4256
|
-
entries = []
|
|
4257
|
-
for name in names:
|
|
4258
|
-
st = os.lstat(name, dir_fd=dir_fd)
|
|
4259
|
-
entry = encode_stat(st)
|
|
4260
|
-
entry["name"] = name
|
|
4261
|
-
entries.append(entry)
|
|
4262
|
-
return entries
|
|
4263
|
-
finally:
|
|
4264
|
-
os.close(dir_fd)
|
|
4265
|
-
def mkdirp_path(root_fd, payload):
|
|
4266
|
-
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
|
|
4267
|
-
os.close(dir_fd); return None
|
|
4268
|
-
def remove_tree(parent_fd, basename):
|
|
4269
|
-
st = os.lstat(basename, dir_fd=parent_fd)
|
|
4270
|
-
if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
|
|
4271
|
-
dir_fd = open_dir(basename, dir_fd=parent_fd)
|
|
4272
|
-
try:
|
|
4273
|
-
for child in os.listdir(dir_fd):
|
|
4274
|
-
remove_tree(dir_fd, child)
|
|
4275
|
-
finally:
|
|
4276
|
-
os.close(dir_fd)
|
|
4277
|
-
os.rmdir(basename, dir_fd=parent_fd)
|
|
4278
|
-
else:
|
|
4279
|
-
os.unlink(basename, dir_fd=parent_fd)
|
|
4280
|
-
def remove_path(root_fd, payload):
|
|
4281
|
-
parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
|
|
4282
|
-
try:
|
|
4283
|
-
try:
|
|
4284
|
-
st = os.lstat(basename, dir_fd=parent_fd)
|
|
4285
|
-
except FileNotFoundError:
|
|
4286
|
-
if payload.get("force", True):
|
|
4287
|
-
return None
|
|
4288
|
-
raise
|
|
4289
|
-
if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
|
|
4290
|
-
if payload.get("recursive", False):
|
|
4291
|
-
remove_tree(parent_fd, basename)
|
|
4292
|
-
else:
|
|
4293
|
-
os.rmdir(basename, dir_fd=parent_fd)
|
|
4294
|
-
else:
|
|
4295
|
-
os.unlink(basename, dir_fd=parent_fd)
|
|
4296
|
-
return None
|
|
4297
|
-
finally:
|
|
4298
|
-
os.close(parent_fd)
|
|
4299
|
-
|
|
4300
|
-
def rename_path(root_fd, payload):
|
|
4301
|
-
from_parent_fd, from_base = parent_and_basename(root_fd, payload["from"])
|
|
4302
|
-
to_parent_fd, to_base = parent_and_basename(root_fd, payload["to"])
|
|
4303
|
-
try:
|
|
4304
|
-
from_stat = os.lstat(from_base, dir_fd=from_parent_fd)
|
|
4305
|
-
reject_unsafe_endpoint(from_stat)
|
|
4306
|
-
overwrite = payload.get("overwrite", True)
|
|
4307
|
-
if not overwrite and stat.S_ISREG(from_stat.st_mode):
|
|
4308
|
-
try:
|
|
4309
|
-
link_no_replace(from_base, to_base, from_parent_fd, to_parent_fd)
|
|
4310
|
-
except OSError as exc:
|
|
4311
|
-
if not link_unsupported(exc):
|
|
4312
|
-
raise
|
|
4313
|
-
copy_file_no_replace(from_parent_fd, from_base, to_parent_fd, to_base, stat.S_IMODE(from_stat.st_mode), from_stat, True)
|
|
4314
|
-
return None
|
|
4315
|
-
if not overwrite and stat.S_ISDIR(from_stat.st_mode):
|
|
4316
|
-
raise RuntimeError("fs-safe-directory-noreplace-unsupported")
|
|
4317
|
-
if not overwrite:
|
|
4318
|
-
try:
|
|
4319
|
-
os.lstat(to_base, dir_fd=to_parent_fd)
|
|
4320
|
-
raise FileExistsError(errno.EEXIST, "destination exists", to_base)
|
|
4321
|
-
except FileNotFoundError:
|
|
4322
|
-
pass
|
|
4323
|
-
os.rename(from_base, to_base, src_dir_fd=from_parent_fd, dst_dir_fd=to_parent_fd)
|
|
4324
|
-
os.fsync(from_parent_fd)
|
|
4325
|
-
if from_parent_fd != to_parent_fd:
|
|
4326
|
-
os.fsync(to_parent_fd)
|
|
4327
|
-
return None
|
|
4328
|
-
finally:
|
|
4329
|
-
os.close(from_parent_fd)
|
|
4330
|
-
os.close(to_parent_fd)
|
|
4331
|
-
|
|
4332
|
-
def create_temp_file(parent_fd, basename, mode):
|
|
4333
|
-
prefix = "." + basename + "."
|
|
4334
|
-
for _ in range(128):
|
|
4335
|
-
candidate = prefix + secrets.token_hex(6) + ".tmp"
|
|
4336
|
-
try:
|
|
4337
|
-
fd = os.open(candidate, WRITE_FLAGS, mode, dir_fd=parent_fd)
|
|
4338
|
-
return candidate, fd
|
|
4339
|
-
except FileExistsError:
|
|
4340
|
-
continue
|
|
4341
|
-
raise RuntimeError("failed to allocate pinned temp file")
|
|
4342
|
-
|
|
4343
|
-
def write_path(root_fd, payload):
|
|
4344
|
-
parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
|
|
4345
|
-
temp_fd = None
|
|
4346
|
-
temp_name = None
|
|
4347
|
-
basename = payload["basename"]
|
|
4348
|
-
mode = int(payload.get("mode", 0o600))
|
|
4349
|
-
overwrite = bool(payload.get("overwrite", True))
|
|
4350
|
-
max_bytes = int(payload.get("maxBytes", -1))
|
|
4351
|
-
data = base64.b64decode(payload.get("base64", ""))
|
|
4352
|
-
try:
|
|
4353
|
-
if max_bytes >= 0 and len(data) > max_bytes:
|
|
4354
|
-
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, len(data)))
|
|
4355
|
-
if not overwrite:
|
|
4356
|
-
try:
|
|
4357
|
-
os.lstat(basename, dir_fd=parent_fd)
|
|
4358
|
-
raise FileExistsError(errno.EEXIST, "destination exists", basename)
|
|
4359
|
-
except FileNotFoundError:
|
|
4360
|
-
pass
|
|
4361
|
-
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
4362
|
-
os.fchmod(temp_fd, mode)
|
|
4363
|
-
write_all(temp_fd, data)
|
|
4364
|
-
fsync_best_effort(temp_fd)
|
|
4365
|
-
temp_stat = os.fstat(temp_fd)
|
|
4366
|
-
os.close(temp_fd)
|
|
4367
|
-
temp_fd = None
|
|
4368
|
-
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
4369
|
-
temp_name = None
|
|
4370
|
-
fsync_best_effort(parent_fd)
|
|
4371
|
-
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
4372
|
-
finally:
|
|
4373
|
-
if temp_fd is not None:
|
|
4374
|
-
os.close(temp_fd)
|
|
4375
|
-
if temp_name is not None:
|
|
4376
|
-
try:
|
|
4377
|
-
os.unlink(temp_name, dir_fd=parent_fd)
|
|
4378
|
-
except FileNotFoundError:
|
|
4379
|
-
pass
|
|
4380
|
-
os.close(parent_fd)
|
|
4381
|
-
|
|
4382
|
-
def copy_path(root_fd, payload):
|
|
4383
|
-
source_fd = os.open(payload["sourcePath"], READ_FLAGS)
|
|
4384
|
-
parent_fd = None
|
|
4385
|
-
temp_fd = None
|
|
4386
|
-
temp_name = None
|
|
4387
|
-
try:
|
|
4388
|
-
source_stat = os.fstat(source_fd)
|
|
4389
|
-
if not stat.S_ISREG(source_stat.st_mode):
|
|
4390
|
-
raise RuntimeError("fs-safe-not-file")
|
|
4391
|
-
if source_stat.st_dev != int(payload["sourceDev"]) or source_stat.st_ino != int(payload["sourceIno"]):
|
|
4392
|
-
raise RuntimeError("fs-safe-source-mismatch")
|
|
4393
|
-
basename = payload["basename"]
|
|
4394
|
-
mode = int(payload.get("mode", 0o600))
|
|
4395
|
-
overwrite = bool(payload.get("overwrite", True))
|
|
4396
|
-
max_bytes = int(payload.get("maxBytes", -1))
|
|
4397
|
-
if max_bytes >= 0 and source_stat.st_size > max_bytes:
|
|
4398
|
-
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
|
|
4399
|
-
parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
|
|
4400
|
-
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
4401
|
-
os.fchmod(temp_fd, mode)
|
|
4402
|
-
written_bytes = 0
|
|
4403
|
-
while True:
|
|
4404
|
-
chunk = os.read(source_fd, 65536)
|
|
4405
|
-
if not chunk:
|
|
4406
|
-
break
|
|
4407
|
-
written_bytes += len(chunk)
|
|
4408
|
-
if max_bytes >= 0 and written_bytes > max_bytes:
|
|
4409
|
-
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, written_bytes))
|
|
4410
|
-
view = memoryview(chunk)
|
|
4411
|
-
while view:
|
|
4412
|
-
written = os.write(temp_fd, view)
|
|
4413
|
-
if written <= 0:
|
|
4414
|
-
raise OSError(errno.EIO, "short write")
|
|
4415
|
-
view = view[written:]
|
|
4416
|
-
os.fsync(temp_fd)
|
|
4417
|
-
temp_stat = os.fstat(temp_fd)
|
|
4418
|
-
os.close(temp_fd)
|
|
4419
|
-
temp_fd = None
|
|
4420
|
-
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
4421
|
-
temp_name = None
|
|
4422
|
-
os.fsync(parent_fd)
|
|
4423
|
-
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
4424
|
-
finally:
|
|
4425
|
-
os.close(source_fd)
|
|
4426
|
-
if temp_fd is not None:
|
|
4427
|
-
os.close(temp_fd)
|
|
4428
|
-
if temp_name is not None and parent_fd is not None:
|
|
4429
|
-
try:
|
|
4430
|
-
os.unlink(temp_name, dir_fd=parent_fd)
|
|
4431
|
-
except FileNotFoundError:
|
|
4432
|
-
pass
|
|
4433
|
-
if parent_fd is not None:
|
|
4434
|
-
os.close(parent_fd)
|
|
4435
|
-
|
|
4436
|
-
def run_operation(operation, root_path, payload):
|
|
4437
|
-
root_fd = open_dir(root_path)
|
|
4438
|
-
try:
|
|
4439
|
-
assert_expected_root(root_fd, payload)
|
|
4440
|
-
if operation == "stat":
|
|
4441
|
-
return stat_path(root_fd, payload)
|
|
4442
|
-
if operation == "readdir":
|
|
4443
|
-
return readdir_path(root_fd, payload)
|
|
4444
|
-
if operation == "mkdirp":
|
|
4445
|
-
return mkdirp_path(root_fd, payload)
|
|
4446
|
-
if operation == "remove":
|
|
4447
|
-
return remove_path(root_fd, payload)
|
|
4448
|
-
if operation == "rename":
|
|
4449
|
-
return rename_path(root_fd, payload)
|
|
4450
|
-
if operation == "write":
|
|
4451
|
-
return write_path(root_fd, payload)
|
|
4452
|
-
if operation == "copy":
|
|
4453
|
-
return copy_path(root_fd, payload)
|
|
4454
|
-
raise RuntimeError("unknown operation: " + operation)
|
|
4455
|
-
finally:
|
|
4456
|
-
os.close(root_fd)
|
|
4457
|
-
|
|
4458
|
-
for line in sys.stdin:
|
|
4459
|
-
try:
|
|
4460
|
-
request = json.loads(line)
|
|
4461
|
-
result = run_operation(request["operation"], request["rootPath"], request.get("payload") or {})
|
|
4462
|
-
response = {"id": request["id"], "ok": True, "result": result}
|
|
4463
|
-
except Exception as exc:
|
|
4464
|
-
response = {
|
|
4465
|
-
"id": request.get("id") if isinstance(locals().get("request"), dict) else None,
|
|
4466
|
-
"ok": False,
|
|
4467
|
-
"code": exc.__class__.__name__,
|
|
4468
|
-
"errno": getattr(exc, "errno", None),
|
|
4469
|
-
"message": str(exc),
|
|
4470
|
-
}
|
|
4471
|
-
print(json.dumps(response, separators=(",", ":")), flush=True)
|
|
4472
|
-
`;
|
|
4473
|
-
let nextRequestId = 1;
|
|
4474
|
-
let worker = null;
|
|
4475
|
-
function __resetPinnedPythonWorkerForTest() {
|
|
4476
|
-
const currentWorker = worker;
|
|
4477
|
-
worker = null;
|
|
4478
|
-
if (!currentWorker) {
|
|
4479
|
-
return;
|
|
4480
|
-
}
|
|
4481
|
-
currentWorker.pending.clear();
|
|
4482
|
-
currentWorker.child.kill("SIGTERM");
|
|
4043
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native.js
|
|
4044
|
+
|
|
4045
|
+
|
|
4046
|
+
|
|
4047
|
+
|
|
4048
|
+
|
|
4049
|
+
|
|
4050
|
+
const native_require = (0,external_node_module_.createRequire)(import.meta.url);
|
|
4051
|
+
const bundledNativeDirectory = (0,external_node_url_.fileURLToPath)(new URL("../dist/native/", import.meta.url));
|
|
4052
|
+
let native_binding;
|
|
4053
|
+
let loadError;
|
|
4054
|
+
let attempted = false;
|
|
4055
|
+
let loadBinding = loadBundledBinding;
|
|
4056
|
+
function isMuslFilename(file) {
|
|
4057
|
+
return file.includes("libc.musl-") || file.includes("ld-musl-");
|
|
4483
4058
|
}
|
|
4484
|
-
|
|
4485
|
-
"/usr/bin/python3",
|
|
4486
|
-
"/opt/homebrew/bin/python3",
|
|
4487
|
-
"/usr/local/bin/python3",
|
|
4488
|
-
];
|
|
4489
|
-
function canExecute(binPath) {
|
|
4059
|
+
function isMuslFromReport() {
|
|
4490
4060
|
try {
|
|
4491
|
-
|
|
4492
|
-
|
|
4061
|
+
if (!process.report || typeof process.report.getReport !== "function")
|
|
4062
|
+
return undefined;
|
|
4063
|
+
const report = process.report.getReport();
|
|
4064
|
+
if (report.header?.glibcVersionRuntime)
|
|
4065
|
+
return false;
|
|
4066
|
+
if (report.sharedObjects?.some(isMuslFilename))
|
|
4067
|
+
return true;
|
|
4493
4068
|
}
|
|
4494
4069
|
catch {
|
|
4495
|
-
|
|
4070
|
+
// Continue with filesystem and ELF inspection.
|
|
4496
4071
|
}
|
|
4072
|
+
return undefined;
|
|
4497
4073
|
}
|
|
4498
|
-
function
|
|
4499
|
-
const
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4074
|
+
function isMuslFromFilesystem() {
|
|
4075
|
+
for (const directory of ["/lib", "/usr/lib"]) {
|
|
4076
|
+
try {
|
|
4077
|
+
if ((0,external_node_fs_.readdirSync)(directory).some(isMuslFilename))
|
|
4078
|
+
return true;
|
|
4079
|
+
}
|
|
4080
|
+
catch {
|
|
4081
|
+
// A missing or unreadable conventional library directory is inconclusive.
|
|
4506
4082
|
}
|
|
4507
4083
|
}
|
|
4508
|
-
return
|
|
4084
|
+
return undefined;
|
|
4509
4085
|
}
|
|
4510
|
-
function
|
|
4511
|
-
if (
|
|
4512
|
-
|
|
4086
|
+
function readUInt(buffer, offset, bytes, littleEndian) {
|
|
4087
|
+
if (offset < 0 || offset + bytes > buffer.length)
|
|
4088
|
+
return undefined;
|
|
4089
|
+
if (bytes === 2)
|
|
4090
|
+
return littleEndian ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
|
|
4091
|
+
if (bytes === 4)
|
|
4092
|
+
return littleEndian ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
|
|
4093
|
+
const value = littleEndian ? buffer.readBigUInt64LE(offset) : buffer.readBigUInt64BE(offset);
|
|
4094
|
+
return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : undefined;
|
|
4095
|
+
}
|
|
4096
|
+
function isMuslFromElfInterpreter() {
|
|
4097
|
+
let fd;
|
|
4098
|
+
try {
|
|
4099
|
+
fd = (0,external_node_fs_.openSync)(process.execPath, "r");
|
|
4100
|
+
const header = Buffer.alloc(64);
|
|
4101
|
+
if ((0,external_node_fs_.readSync)(fd, header, 0, header.length, 0) < 52)
|
|
4102
|
+
return undefined;
|
|
4103
|
+
if (!header.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46])))
|
|
4104
|
+
return undefined;
|
|
4105
|
+
const elfClass = header[4];
|
|
4106
|
+
const dataEncoding = header[5];
|
|
4107
|
+
if ((elfClass !== 1 && elfClass !== 2) || (dataEncoding !== 1 && dataEncoding !== 2)) {
|
|
4108
|
+
return undefined;
|
|
4109
|
+
}
|
|
4110
|
+
const littleEndian = dataEncoding === 1;
|
|
4111
|
+
const is64Bit = elfClass === 2;
|
|
4112
|
+
const tableOffset = readUInt(header, is64Bit ? 32 : 28, is64Bit ? 8 : 4, littleEndian);
|
|
4113
|
+
const entrySize = readUInt(header, is64Bit ? 54 : 42, 2, littleEndian);
|
|
4114
|
+
const entryCount = readUInt(header, is64Bit ? 56 : 44, 2, littleEndian);
|
|
4115
|
+
if (tableOffset === undefined ||
|
|
4116
|
+
entrySize === undefined ||
|
|
4117
|
+
entryCount === undefined ||
|
|
4118
|
+
entrySize < (is64Bit ? 56 : 32) ||
|
|
4119
|
+
entryCount > 1024) {
|
|
4120
|
+
return undefined;
|
|
4121
|
+
}
|
|
4122
|
+
const programHeader = Buffer.alloc(entrySize);
|
|
4123
|
+
for (let index = 0; index < entryCount; index += 1) {
|
|
4124
|
+
const offset = tableOffset + index * entrySize;
|
|
4125
|
+
if ((0,external_node_fs_.readSync)(fd, programHeader, 0, entrySize, offset) !== entrySize)
|
|
4126
|
+
return undefined;
|
|
4127
|
+
if (readUInt(programHeader, 0, 4, littleEndian) !== 3)
|
|
4128
|
+
continue;
|
|
4129
|
+
const interpreterOffset = readUInt(programHeader, is64Bit ? 8 : 4, is64Bit ? 8 : 4, littleEndian);
|
|
4130
|
+
const interpreterSize = readUInt(programHeader, is64Bit ? 32 : 16, is64Bit ? 8 : 4, littleEndian);
|
|
4131
|
+
if (interpreterOffset === undefined ||
|
|
4132
|
+
interpreterSize === undefined ||
|
|
4133
|
+
interpreterSize < 1 ||
|
|
4134
|
+
interpreterSize > 4096) {
|
|
4135
|
+
return undefined;
|
|
4136
|
+
}
|
|
4137
|
+
const interpreter = Buffer.alloc(interpreterSize);
|
|
4138
|
+
if ((0,external_node_fs_.readSync)(fd, interpreter, 0, interpreterSize, interpreterOffset) !== interpreterSize) {
|
|
4139
|
+
return undefined;
|
|
4140
|
+
}
|
|
4141
|
+
return isMuslFilename(interpreter.toString("utf8"));
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
catch {
|
|
4145
|
+
return undefined;
|
|
4513
4146
|
}
|
|
4514
|
-
|
|
4515
|
-
|
|
4147
|
+
finally {
|
|
4148
|
+
if (fd !== undefined) {
|
|
4149
|
+
try {
|
|
4150
|
+
(0,external_node_fs_.closeSync)(fd);
|
|
4151
|
+
}
|
|
4152
|
+
catch {
|
|
4153
|
+
// Best-effort detection must never turn close failure into import failure.
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4516
4156
|
}
|
|
4157
|
+
return undefined;
|
|
4517
4158
|
}
|
|
4518
|
-
function
|
|
4519
|
-
if (
|
|
4159
|
+
function isMusl() {
|
|
4160
|
+
if (process.platform !== "linux")
|
|
4520
4161
|
return false;
|
|
4162
|
+
for (const detector of [isMuslFromReport, isMuslFromFilesystem, isMuslFromElfInterpreter]) {
|
|
4163
|
+
const result = detector();
|
|
4164
|
+
if (result !== undefined)
|
|
4165
|
+
return result;
|
|
4521
4166
|
}
|
|
4522
|
-
|
|
4523
|
-
return
|
|
4524
|
-
maybeErrno.syscall.startsWith("spawn") &&
|
|
4525
|
-
["EACCES", "ENOENT", "ENOEXEC"].includes(maybeErrno.code ?? ""));
|
|
4167
|
+
// Unknown Linux libc: try the glibc binary and let its require fail normally.
|
|
4168
|
+
return false;
|
|
4526
4169
|
}
|
|
4527
|
-
function
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
if (
|
|
4535
|
-
|
|
4536
|
-
return new errors_FsSafeError("too-large", `file exceeds limit of ${limit} bytes (got at least ${got})`);
|
|
4537
|
-
}
|
|
4538
|
-
if (message.includes("fs-safe-not-file")) {
|
|
4539
|
-
return new errors_FsSafeError("not-file", "not a file");
|
|
4540
|
-
}
|
|
4541
|
-
if (message.includes("fs-safe-source-mismatch")) {
|
|
4542
|
-
return new errors_FsSafeError("path-mismatch", "source path changed during copy");
|
|
4543
|
-
}
|
|
4544
|
-
if (message.includes("fs-safe-temp-mismatch")) {
|
|
4545
|
-
return new errors_FsSafeError("path-mismatch", "temp path changed during write");
|
|
4546
|
-
}
|
|
4547
|
-
if (message.includes("fs-safe-root-mismatch")) {
|
|
4548
|
-
return new errors_FsSafeError("path-mismatch", "root path changed during operation");
|
|
4549
|
-
}
|
|
4550
|
-
if (message.includes("fs-safe-directory-noreplace-unsupported")) {
|
|
4551
|
-
return new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
|
|
4552
|
-
}
|
|
4553
|
-
if (code === "FileNotFoundError" || errno === 2) {
|
|
4554
|
-
return new errors_FsSafeError("not-found", "file not found");
|
|
4170
|
+
function targetFor(platform, arch, musl) {
|
|
4171
|
+
if (platform === "win32" && arch === "x64")
|
|
4172
|
+
return "win32-x64-msvc";
|
|
4173
|
+
if (platform === "darwin" && arch === "x64")
|
|
4174
|
+
return "darwin-x64";
|
|
4175
|
+
if (platform === "darwin" && arch === "arm64")
|
|
4176
|
+
return "darwin-arm64";
|
|
4177
|
+
if (platform === "linux" && (arch === "x64" || arch === "arm64")) {
|
|
4178
|
+
return `linux-${arch}-${musl ? "musl" : "gnu"}`;
|
|
4555
4179
|
}
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4180
|
+
return undefined;
|
|
4181
|
+
}
|
|
4182
|
+
function bundledTarget() {
|
|
4183
|
+
return targetFor(process.platform, process.arch, isMusl());
|
|
4184
|
+
}
|
|
4185
|
+
function loadBundledBinding() {
|
|
4186
|
+
const target = bundledTarget();
|
|
4187
|
+
if (!target) {
|
|
4188
|
+
throw new Error(`Unsupported OS or architecture: ${process.platform}-${process.arch}`);
|
|
4561
4189
|
}
|
|
4562
|
-
|
|
4563
|
-
|
|
4190
|
+
return native_require((0,external_node_path_.join)(bundledNativeDirectory, target, "fs-safe-native.node"));
|
|
4191
|
+
}
|
|
4192
|
+
function __loadBundledNativeForTest() {
|
|
4193
|
+
return loadBundledBinding();
|
|
4194
|
+
}
|
|
4195
|
+
function __nativeLoaderDetectorsForTest() {
|
|
4196
|
+
return {
|
|
4197
|
+
report: isMuslFromReport(),
|
|
4198
|
+
filesystem: isMuslFromFilesystem(),
|
|
4199
|
+
elfInterpreter: isMuslFromElfInterpreter(),
|
|
4200
|
+
};
|
|
4201
|
+
}
|
|
4202
|
+
function __nativeTargetForTest(platform, arch, musl = false) {
|
|
4203
|
+
return targetFor(platform, arch, musl);
|
|
4204
|
+
}
|
|
4205
|
+
function getNativeBinding() {
|
|
4206
|
+
const { mode } = getFsSafeNativeConfig();
|
|
4207
|
+
if (mode === "off")
|
|
4208
|
+
return undefined;
|
|
4209
|
+
if (!attempted) {
|
|
4210
|
+
attempted = true;
|
|
4211
|
+
try {
|
|
4212
|
+
native_binding = loadBinding();
|
|
4213
|
+
}
|
|
4214
|
+
catch (error) {
|
|
4215
|
+
loadError = error;
|
|
4216
|
+
}
|
|
4564
4217
|
}
|
|
4565
|
-
if (
|
|
4566
|
-
return
|
|
4218
|
+
if (native_binding)
|
|
4219
|
+
return native_binding;
|
|
4220
|
+
if (mode === "require") {
|
|
4221
|
+
throw new errors_FsSafeError("helper-unavailable", "native fs-safe helper is unavailable", {
|
|
4222
|
+
cause: loadError,
|
|
4223
|
+
});
|
|
4567
4224
|
}
|
|
4568
|
-
return
|
|
4225
|
+
return undefined;
|
|
4569
4226
|
}
|
|
4570
|
-
function
|
|
4571
|
-
|
|
4572
|
-
|
|
4227
|
+
function requireNativeBinding() {
|
|
4228
|
+
const native = getNativeBinding();
|
|
4229
|
+
if (!native) {
|
|
4230
|
+
throw new FsSafeError("helper-unavailable", "native fs-safe helper is unavailable", {
|
|
4231
|
+
cause: loadError,
|
|
4232
|
+
});
|
|
4573
4233
|
}
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4234
|
+
return native;
|
|
4235
|
+
}
|
|
4236
|
+
function __setNativeLoaderForTest(loader) {
|
|
4237
|
+
native_binding = undefined;
|
|
4238
|
+
loadError = undefined;
|
|
4239
|
+
attempted = false;
|
|
4240
|
+
loadBinding = loader;
|
|
4241
|
+
}
|
|
4242
|
+
function __resetNativeLoaderForTest() {
|
|
4243
|
+
native_binding = undefined;
|
|
4244
|
+
loadError = undefined;
|
|
4245
|
+
attempted = false;
|
|
4246
|
+
loadBinding = loadBundledBinding;
|
|
4247
|
+
}
|
|
4248
|
+
|
|
4249
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native-operations.js
|
|
4250
|
+
|
|
4251
|
+
|
|
4252
|
+
|
|
4253
|
+
|
|
4254
|
+
|
|
4255
|
+
function nativeOpenFlags(flags) {
|
|
4256
|
+
const closeOnExec = external_node_fs_.constants.O_CLOEXEC;
|
|
4257
|
+
return (flags |
|
|
4258
|
+
(closeOnExec ?? 0) |
|
|
4259
|
+
(typeof external_node_fs_.constants.O_NOFOLLOW === "number" ? external_node_fs_.constants.O_NOFOLLOW : 0));
|
|
4260
|
+
}
|
|
4261
|
+
function writeAll(fd, data) {
|
|
4262
|
+
let offset = 0;
|
|
4263
|
+
while (offset < data.byteLength) {
|
|
4264
|
+
const written = external_node_fs_.writeSync(fd, data, offset, data.byteLength - offset);
|
|
4265
|
+
if (written <= 0) {
|
|
4266
|
+
throw Object.assign(new Error("native file write made no progress"), { code: "EIO" });
|
|
4267
|
+
}
|
|
4268
|
+
offset += written;
|
|
4577
4269
|
}
|
|
4578
|
-
targetWorker.pending.clear();
|
|
4579
|
-
worker = null;
|
|
4580
4270
|
}
|
|
4581
|
-
function
|
|
4582
|
-
|
|
4271
|
+
function wrapNativeFd(fd, containment) {
|
|
4272
|
+
let open = true;
|
|
4273
|
+
return {
|
|
4274
|
+
fd,
|
|
4275
|
+
containment,
|
|
4276
|
+
async close() {
|
|
4277
|
+
if (open) {
|
|
4278
|
+
open = false;
|
|
4279
|
+
external_node_fs_.closeSync(fd);
|
|
4280
|
+
}
|
|
4281
|
+
},
|
|
4282
|
+
async stat() {
|
|
4283
|
+
return external_node_fs_.fstatSync(fd);
|
|
4284
|
+
},
|
|
4285
|
+
async writeFile(data, encoding) {
|
|
4286
|
+
writeAll(fd, Buffer.isBuffer(data) ? data : Buffer.from(data, encoding ?? "utf8"));
|
|
4287
|
+
},
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
4290
|
+
function removeNativeCreatedFileIfStillPinned(params) {
|
|
4291
|
+
if (!params.created) {
|
|
4583
4292
|
return;
|
|
4584
4293
|
}
|
|
4585
|
-
let decoded;
|
|
4586
4294
|
try {
|
|
4587
|
-
|
|
4295
|
+
const parentPathStat = external_node_fs_.lstatSync(params.parentPath);
|
|
4296
|
+
const parentFdStat = params.binding.fstatIdentity(params.parentFd);
|
|
4297
|
+
const targetPath = external_node_path_.join(params.parentPath, params.basename);
|
|
4298
|
+
const target = external_node_fs_.lstatSync(targetPath);
|
|
4299
|
+
if (!parentPathStat.isSymbolicLink() &&
|
|
4300
|
+
parentPathStat.dev === parentFdStat.dev &&
|
|
4301
|
+
parentPathStat.ino === parentFdStat.ino &&
|
|
4302
|
+
!target.isSymbolicLink() &&
|
|
4303
|
+
file_identity_sameFileIdentity(target, params.created)) {
|
|
4304
|
+
external_node_fs_.rmSync(targetPath);
|
|
4305
|
+
}
|
|
4588
4306
|
}
|
|
4589
4307
|
catch {
|
|
4590
|
-
|
|
4591
|
-
|
|
4308
|
+
// Failed cleanup leaves a fail-closed artifact instead of deleting an
|
|
4309
|
+
// unverified pathname.
|
|
4592
4310
|
}
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4311
|
+
}
|
|
4312
|
+
async function createNativeExclusiveFile(targetPath, mode) {
|
|
4313
|
+
const binding = getNativeBinding();
|
|
4314
|
+
if (!binding) {
|
|
4315
|
+
return undefined;
|
|
4596
4316
|
}
|
|
4597
|
-
const
|
|
4598
|
-
const
|
|
4599
|
-
|
|
4600
|
-
|
|
4317
|
+
const parentPath = external_node_path_.dirname(targetPath);
|
|
4318
|
+
const basename = external_node_path_.basename(targetPath);
|
|
4319
|
+
const parent = await promises_.open(parentPath, external_node_fs_.constants.O_RDONLY |
|
|
4320
|
+
(typeof external_node_fs_.constants.O_DIRECTORY === "number" ? external_node_fs_.constants.O_DIRECTORY : 0));
|
|
4321
|
+
let fd;
|
|
4322
|
+
let created;
|
|
4323
|
+
try {
|
|
4324
|
+
const opened = binding.openBeneath(parent.fd, basename, nativeOpenFlags(external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL));
|
|
4325
|
+
fd = opened.fd;
|
|
4326
|
+
external_node_fs_.fchmodSync(fd, mode);
|
|
4327
|
+
created = external_node_fs_.fstatSync(fd);
|
|
4328
|
+
return wrapNativeFd(fd, opened.containment);
|
|
4601
4329
|
}
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4330
|
+
catch (error) {
|
|
4331
|
+
if (fd !== undefined) {
|
|
4332
|
+
try {
|
|
4333
|
+
external_node_fs_.closeSync(fd);
|
|
4334
|
+
}
|
|
4335
|
+
catch {
|
|
4336
|
+
// Preserve the original error.
|
|
4337
|
+
}
|
|
4338
|
+
removeNativeCreatedFileIfStillPinned({
|
|
4339
|
+
binding,
|
|
4340
|
+
parentPath,
|
|
4341
|
+
parentFd: parent.fd,
|
|
4342
|
+
basename,
|
|
4343
|
+
created,
|
|
4344
|
+
});
|
|
4345
|
+
}
|
|
4346
|
+
throw error;
|
|
4605
4347
|
}
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
setWorkerRef(currentWorker, false);
|
|
4348
|
+
finally {
|
|
4349
|
+
await parent.close().catch(() => undefined);
|
|
4609
4350
|
}
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4351
|
+
}
|
|
4352
|
+
function syncNativeFileBestEffort(fd) {
|
|
4353
|
+
try {
|
|
4354
|
+
external_node_fs_.fsyncSync(fd);
|
|
4355
|
+
}
|
|
4356
|
+
catch (error) {
|
|
4357
|
+
if (error.code !== "EPERM") {
|
|
4358
|
+
throw error;
|
|
4359
|
+
}
|
|
4613
4360
|
}
|
|
4614
|
-
pending.reject(mapWorkerError(decoded));
|
|
4615
4361
|
}
|
|
4616
|
-
function
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4362
|
+
function writeNativeFd(fd, data) {
|
|
4363
|
+
writeAll(fd, data);
|
|
4364
|
+
}
|
|
4365
|
+
|
|
4366
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native-pinned-write.js
|
|
4367
|
+
|
|
4368
|
+
|
|
4369
|
+
|
|
4370
|
+
|
|
4371
|
+
|
|
4372
|
+
|
|
4373
|
+
function assertWithinMaxBytes(bytes, maxBytes) {
|
|
4374
|
+
if (maxBytes !== undefined && bytes > maxBytes) {
|
|
4375
|
+
throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
4620
4376
|
}
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4377
|
+
}
|
|
4378
|
+
async function inputToBuffer(input, maxBytes) {
|
|
4379
|
+
if (input.kind === "buffer") {
|
|
4380
|
+
const data = typeof input.data === "string"
|
|
4381
|
+
? Buffer.from(input.data, input.encoding ?? "utf8")
|
|
4382
|
+
: Buffer.from(input.data);
|
|
4383
|
+
assertWithinMaxBytes(data.byteLength, maxBytes);
|
|
4384
|
+
return data;
|
|
4385
|
+
}
|
|
4386
|
+
const chunks = [];
|
|
4387
|
+
let bytes = 0;
|
|
4388
|
+
for await (const chunk of input.stream) {
|
|
4389
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4390
|
+
bytes += buffer.byteLength;
|
|
4391
|
+
assertWithinMaxBytes(bytes, maxBytes);
|
|
4392
|
+
chunks.push(buffer);
|
|
4393
|
+
}
|
|
4394
|
+
return Buffer.concat(chunks, bytes);
|
|
4395
|
+
}
|
|
4396
|
+
function native_pinned_write_nativeOpenFlags(flags) {
|
|
4397
|
+
const closeOnExec = external_node_fs_.constants.O_CLOEXEC;
|
|
4398
|
+
return (flags |
|
|
4399
|
+
(closeOnExec ?? 0) |
|
|
4400
|
+
(typeof external_node_fs_.constants.O_NOFOLLOW === "number" ? external_node_fs_.constants.O_NOFOLLOW : 0));
|
|
4401
|
+
}
|
|
4402
|
+
function sameNativeIdentity(left, right) {
|
|
4403
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
4404
|
+
}
|
|
4405
|
+
async function runPinnedWriteNative(binding, params) {
|
|
4406
|
+
const data = await inputToBuffer(params.input, params.maxBytes);
|
|
4407
|
+
const root = await promises_.open(params.rootPath, external_node_fs_.constants.O_RDONLY |
|
|
4408
|
+
(typeof external_node_fs_.constants.O_DIRECTORY === "number" ? external_node_fs_.constants.O_DIRECTORY : 0));
|
|
4409
|
+
let parentFd;
|
|
4410
|
+
let tempFd;
|
|
4411
|
+
let targetFd;
|
|
4412
|
+
let tempIdentity;
|
|
4413
|
+
let parentPath = params.rootPath;
|
|
4414
|
+
const tempName = `.${params.basename}.${(0,external_node_crypto_.randomUUID)()}.native.tmp`;
|
|
4415
|
+
let renamed = false;
|
|
4416
|
+
try {
|
|
4417
|
+
const rootIdentity = binding.fstatIdentity(root.fd);
|
|
4418
|
+
if (params.rootIdentity && !sameNativeIdentity(params.rootIdentity, rootIdentity)) {
|
|
4419
|
+
throw new errors_FsSafeError("path-mismatch", "root path changed during native write");
|
|
4420
|
+
}
|
|
4421
|
+
if (params.mkdir) {
|
|
4422
|
+
binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
|
|
4423
|
+
}
|
|
4424
|
+
const parentFlags = external_node_fs_.constants.O_RDONLY |
|
|
4425
|
+
(typeof external_node_fs_.constants.O_DIRECTORY === "number" ? external_node_fs_.constants.O_DIRECTORY : 0);
|
|
4426
|
+
parentFd = binding.openBeneath(root.fd, params.relativeParentPath, parentFlags).fd;
|
|
4427
|
+
parentPath = await promises_.realpath(params.relativeParentPath
|
|
4428
|
+
? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
4429
|
+
: params.rootPath);
|
|
4430
|
+
const parentPathStat = await promises_.lstat(parentPath);
|
|
4431
|
+
const parentIdentity = binding.fstatIdentity(parentFd);
|
|
4432
|
+
if (parentPathStat.isSymbolicLink() ||
|
|
4433
|
+
!sameNativeIdentity(parentPathStat, parentIdentity)) {
|
|
4434
|
+
throw new errors_FsSafeError("path-mismatch", "native write parent changed during resolution");
|
|
4631
4435
|
}
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4436
|
+
try {
|
|
4437
|
+
await promises_.lstat(external_node_path_.join(parentPath, params.basename));
|
|
4438
|
+
throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
|
|
4439
|
+
}
|
|
4440
|
+
catch (error) {
|
|
4441
|
+
if (error.code !== "ENOENT") {
|
|
4442
|
+
throw error;
|
|
4637
4443
|
}
|
|
4638
|
-
const line = currentWorker.stdoutBuffer.slice(0, newline);
|
|
4639
|
-
currentWorker.stdoutBuffer = currentWorker.stdoutBuffer.slice(newline + 1);
|
|
4640
|
-
handleWorkerLine(currentWorker, line);
|
|
4641
4444
|
}
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4445
|
+
tempFd = binding.openBeneath(parentFd, tempName, native_pinned_write_nativeOpenFlags(external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL)).fd;
|
|
4446
|
+
external_node_fs_.fchmodSync(tempFd, params.mode || 0o600);
|
|
4447
|
+
writeNativeFd(tempFd, data);
|
|
4448
|
+
syncNativeFileBestEffort(tempFd);
|
|
4449
|
+
tempIdentity = external_node_fs_.fstatSync(tempFd);
|
|
4450
|
+
binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
|
|
4451
|
+
renamed = true;
|
|
4452
|
+
targetFd = binding.openBeneath(parentFd, params.basename, native_pinned_write_nativeOpenFlags(external_node_fs_.constants.O_RDONLY)).fd;
|
|
4453
|
+
const targetIdentity = binding.fstatIdentity(targetFd);
|
|
4454
|
+
if (!targetIdentity.isFile || !sameNativeIdentity(tempIdentity, targetIdentity)) {
|
|
4455
|
+
throw new errors_FsSafeError("path-mismatch", "native write target changed after rename");
|
|
4646
4456
|
}
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
const mapped = isSpawnUnavailable(error)
|
|
4650
|
-
? new errors_FsSafeError("helper-unavailable", "Python helper is unavailable", { cause: error })
|
|
4651
|
-
: error instanceof Error
|
|
4652
|
-
? error
|
|
4653
|
-
: new Error(String(error));
|
|
4654
|
-
rejectPending(mapped, currentWorker);
|
|
4655
|
-
});
|
|
4656
|
-
child.once("close", (code, signal) => {
|
|
4657
|
-
const stderr = currentWorker.stderr.trim();
|
|
4658
|
-
rejectPending(new errors_FsSafeError("helper-failed", stderr || `pinned helper exited with code ${code ?? "null"} (${signal ?? "?"})`), currentWorker);
|
|
4659
|
-
});
|
|
4660
|
-
process.once("exit", () => {
|
|
4661
|
-
child.kill("SIGTERM");
|
|
4662
|
-
});
|
|
4663
|
-
setWorkerRef(currentWorker, false);
|
|
4664
|
-
return currentWorker;
|
|
4665
|
-
}
|
|
4666
|
-
function setRefable(value, ref) {
|
|
4667
|
-
if (!value) {
|
|
4668
|
-
return;
|
|
4457
|
+
syncNativeFileBestEffort(parentFd);
|
|
4458
|
+
return { dev: targetIdentity.dev, ino: targetIdentity.ino };
|
|
4669
4459
|
}
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
resolve: (value) => resolve(value),
|
|
4691
|
-
});
|
|
4692
|
-
const request = JSON.stringify({
|
|
4693
|
-
id: requestId,
|
|
4694
|
-
operation: params.operation,
|
|
4695
|
-
rootPath: params.rootPath,
|
|
4696
|
-
payload: params.payload,
|
|
4697
|
-
});
|
|
4698
|
-
currentWorker.child.stdin.write(`${request}\n`, (error) => {
|
|
4699
|
-
if (error) {
|
|
4700
|
-
currentWorker.pending.delete(requestId);
|
|
4701
|
-
if (currentWorker.pending.size === 0) {
|
|
4702
|
-
setWorkerRef(currentWorker, false);
|
|
4703
|
-
}
|
|
4704
|
-
reject(error);
|
|
4705
|
-
}
|
|
4706
|
-
});
|
|
4707
|
-
});
|
|
4708
|
-
}
|
|
4709
|
-
function assertPinnedPythonOperationAvailable() {
|
|
4710
|
-
const currentWorker = getWorker();
|
|
4711
|
-
if (typeof currentWorker.child.stdin?.write !== "function") {
|
|
4712
|
-
throw new errors_FsSafeError("helper-unavailable", "Python helper stdin is unavailable");
|
|
4460
|
+
finally {
|
|
4461
|
+
if (targetFd !== undefined) {
|
|
4462
|
+
external_node_fs_.closeSync(targetFd);
|
|
4463
|
+
}
|
|
4464
|
+
if (tempFd !== undefined) {
|
|
4465
|
+
external_node_fs_.closeSync(tempFd);
|
|
4466
|
+
}
|
|
4467
|
+
if (!renamed && parentFd !== undefined) {
|
|
4468
|
+
removeNativeCreatedFileIfStillPinned({
|
|
4469
|
+
binding,
|
|
4470
|
+
parentPath,
|
|
4471
|
+
parentFd,
|
|
4472
|
+
basename: tempName,
|
|
4473
|
+
created: tempIdentity,
|
|
4474
|
+
});
|
|
4475
|
+
}
|
|
4476
|
+
if (parentFd !== undefined) {
|
|
4477
|
+
external_node_fs_.closeSync(parentFd);
|
|
4478
|
+
}
|
|
4479
|
+
await root.close().catch(() => undefined);
|
|
4713
4480
|
}
|
|
4714
4481
|
}
|
|
4482
|
+
|
|
4483
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-operation.js
|
|
4484
|
+
|
|
4715
4485
|
function validatePinnedOperationPayload(payload) {
|
|
4716
4486
|
if (typeof payload.relativePath === "string") {
|
|
4717
4487
|
validatePinnedRelativePath(payload.relativePath);
|
|
@@ -4726,9 +4496,6 @@ function validatePinnedOperationPayload(payload) {
|
|
|
4726
4496
|
validatePinnedRelativePath(payload.to);
|
|
4727
4497
|
}
|
|
4728
4498
|
}
|
|
4729
|
-
function isPinnedHelperUnavailable(error) {
|
|
4730
|
-
return error instanceof Error && "code" in error && error.code === "helper-unavailable";
|
|
4731
|
-
}
|
|
4732
4499
|
function validatePinnedRelativePath(relativePath) {
|
|
4733
4500
|
if (relativePath.length === 0 || relativePath === ".") {
|
|
4734
4501
|
return;
|
|
@@ -4750,47 +4517,91 @@ function validatePinnedRelativePath(relativePath) {
|
|
|
4750
4517
|
}
|
|
4751
4518
|
}
|
|
4752
4519
|
|
|
4753
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/
|
|
4520
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
4754
4521
|
|
|
4755
4522
|
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
}
|
|
4764
|
-
async function helperStat(rootDir, relativePath) {
|
|
4765
|
-
return await runPinnedHelper("stat", rootDir, { relativePath });
|
|
4523
|
+
const READ_CHUNK_BYTES = 64 * 1024;
|
|
4524
|
+
function assertMaxBytes(maxBytes) {
|
|
4525
|
+
if (maxBytes === Number.POSITIVE_INFINITY) {
|
|
4526
|
+
return;
|
|
4527
|
+
}
|
|
4528
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
4529
|
+
throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
|
|
4530
|
+
}
|
|
4766
4531
|
}
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4532
|
+
function createScratchBuffer(maxBytes) {
|
|
4533
|
+
const initialReadBytes = Number.isFinite(maxBytes)
|
|
4534
|
+
? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
|
|
4535
|
+
: READ_CHUNK_BYTES;
|
|
4536
|
+
return Buffer.allocUnsafe(Math.max(1, initialReadBytes));
|
|
4772
4537
|
}
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
function isPinnedPathHelperSpawnError(error) {
|
|
4779
|
-
return canFallbackFromPythonError(error);
|
|
4538
|
+
function nextReadLength(total, maxBytes, capacity) {
|
|
4539
|
+
return Number.isFinite(maxBytes)
|
|
4540
|
+
? Math.min(capacity, maxBytes - total + 1)
|
|
4541
|
+
: capacity;
|
|
4780
4542
|
}
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
});
|
|
4543
|
+
function appendChunk(params) {
|
|
4544
|
+
const total = params.total + params.bytesRead;
|
|
4545
|
+
if (total > params.maxBytes) {
|
|
4546
|
+
throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got at least ${total})`);
|
|
4786
4547
|
}
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4548
|
+
params.chunks.push(Buffer.from(params.scratch.subarray(0, params.bytesRead)));
|
|
4549
|
+
return total;
|
|
4550
|
+
}
|
|
4551
|
+
async function readBoundedAsync(maxBytes, readChunk) {
|
|
4552
|
+
assertMaxBytes(maxBytes);
|
|
4553
|
+
const chunks = [];
|
|
4554
|
+
const scratch = createScratchBuffer(maxBytes);
|
|
4555
|
+
let total = 0;
|
|
4556
|
+
while (true) {
|
|
4557
|
+
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
4558
|
+
const bytesRead = await readChunk(scratch, length);
|
|
4559
|
+
if (bytesRead === 0) {
|
|
4560
|
+
return Buffer.concat(chunks, total);
|
|
4790
4561
|
}
|
|
4791
|
-
|
|
4792
|
-
|
|
4562
|
+
total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
/**
|
|
4566
|
+
* Reads from the handle's current offset without closing it. A bounded read
|
|
4567
|
+
* consumes at most maxBytes + 1 bytes so growth after an earlier stat cannot
|
|
4568
|
+
* force an unbounded allocation.
|
|
4569
|
+
*/
|
|
4570
|
+
async function readFileHandleBounded(handle, maxBytes) {
|
|
4571
|
+
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
4572
|
+
return (await handle.read(scratch, 0, length, null)).bytesRead;
|
|
4573
|
+
});
|
|
4574
|
+
}
|
|
4575
|
+
function readDescriptorChunk(fd, scratch, length) {
|
|
4576
|
+
return new Promise((resolve, reject) => {
|
|
4577
|
+
fs.read(fd, scratch, 0, length, null, (error, bytesRead) => {
|
|
4578
|
+
if (error) {
|
|
4579
|
+
reject(error);
|
|
4580
|
+
return;
|
|
4581
|
+
}
|
|
4582
|
+
resolve(bytesRead);
|
|
4793
4583
|
});
|
|
4584
|
+
});
|
|
4585
|
+
}
|
|
4586
|
+
/** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
4587
|
+
async function readFileDescriptorBounded(fd, maxBytes) {
|
|
4588
|
+
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
4589
|
+
return await readDescriptorChunk(fd, scratch, length);
|
|
4590
|
+
});
|
|
4591
|
+
}
|
|
4592
|
+
/** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
4593
|
+
function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
4594
|
+
assertMaxBytes(maxBytes);
|
|
4595
|
+
const chunks = [];
|
|
4596
|
+
const scratch = createScratchBuffer(maxBytes);
|
|
4597
|
+
let total = 0;
|
|
4598
|
+
while (true) {
|
|
4599
|
+
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
4600
|
+
const bytesRead = fs.readSync(fd, scratch, 0, length, null);
|
|
4601
|
+
if (bytesRead === 0) {
|
|
4602
|
+
return Buffer.concat(chunks, total);
|
|
4603
|
+
}
|
|
4604
|
+
total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
|
|
4794
4605
|
}
|
|
4795
4606
|
}
|
|
4796
4607
|
|
|
@@ -4798,6 +4609,11 @@ async function runPinnedPathHelper(params) {
|
|
|
4798
4609
|
|
|
4799
4610
|
|
|
4800
4611
|
|
|
4612
|
+
|
|
4613
|
+
|
|
4614
|
+
|
|
4615
|
+
|
|
4616
|
+
const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
|
|
4801
4617
|
const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
|
|
4802
4618
|
const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
|
|
4803
4619
|
const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
|
|
@@ -4821,28 +4637,98 @@ function serializeSidecarLockPayload(payload) {
|
|
|
4821
4637
|
ownershipToken,
|
|
4822
4638
|
};
|
|
4823
4639
|
}
|
|
4824
|
-
|
|
4640
|
+
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
4641
|
+
const resolved = external_node_path_.resolve(lockPath);
|
|
4642
|
+
const lexicalRelative = external_node_path_.relative(lockRoot.rootDir, resolved);
|
|
4643
|
+
const relative = lexicalRelative !== ".." &&
|
|
4644
|
+
!lexicalRelative.startsWith(`..${external_node_path_.sep}`) &&
|
|
4645
|
+
!external_node_path_.isAbsolute(lexicalRelative)
|
|
4646
|
+
? lexicalRelative
|
|
4647
|
+
: external_node_path_.relative(lockRoot.rootReal, resolved);
|
|
4648
|
+
if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relative)) {
|
|
4649
|
+
throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
4650
|
+
}
|
|
4651
|
+
return relative.split(external_node_path_.sep).join(external_node_path_.posix.sep);
|
|
4652
|
+
}
|
|
4653
|
+
function parseSidecarLockPayload(raw, parser) {
|
|
4654
|
+
if (parser) {
|
|
4655
|
+
return parser(raw);
|
|
4656
|
+
}
|
|
4657
|
+
try {
|
|
4658
|
+
const parsed = JSON.parse(raw);
|
|
4659
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4660
|
+
}
|
|
4661
|
+
catch {
|
|
4662
|
+
return null;
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
async function readSidecarLockSnapshot(lockPath, options = {}) {
|
|
4825
4666
|
try {
|
|
4667
|
+
if (options.lockRoot) {
|
|
4668
|
+
const opened = await options.lockRoot.open(relativeSidecarLockPath(options.lockRoot, lockPath));
|
|
4669
|
+
try {
|
|
4670
|
+
const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
|
|
4671
|
+
return {
|
|
4672
|
+
raw,
|
|
4673
|
+
payload: parseSidecarLockPayload(raw, options.parsePayload),
|
|
4674
|
+
stat: opened.stat,
|
|
4675
|
+
};
|
|
4676
|
+
}
|
|
4677
|
+
finally {
|
|
4678
|
+
await opened.handle.close().catch(() => undefined);
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4826
4681
|
const stat = await promises_.lstat(lockPath);
|
|
4827
4682
|
const raw = await promises_.readFile(lockPath, "utf8");
|
|
4828
|
-
|
|
4829
|
-
const parsed = JSON.parse(raw);
|
|
4830
|
-
const payload = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
4831
|
-
? parsed
|
|
4832
|
-
: null;
|
|
4833
|
-
return { raw, payload, stat };
|
|
4834
|
-
}
|
|
4835
|
-
catch {
|
|
4836
|
-
return { raw, payload: null, stat };
|
|
4837
|
-
}
|
|
4683
|
+
return { raw, payload: parseSidecarLockPayload(raw, options.parsePayload), stat };
|
|
4838
4684
|
}
|
|
4839
4685
|
catch (err) {
|
|
4840
|
-
if (err.code === "ENOENT"
|
|
4686
|
+
if (err.code === "ENOENT" ||
|
|
4687
|
+
(err instanceof errors_FsSafeError && err.code === "not-found")) {
|
|
4841
4688
|
return null;
|
|
4842
4689
|
}
|
|
4843
4690
|
throw err;
|
|
4844
4691
|
}
|
|
4845
4692
|
}
|
|
4693
|
+
function readSidecarLockSnapshotSync(lockPath, parsePayload) {
|
|
4694
|
+
let fd;
|
|
4695
|
+
try {
|
|
4696
|
+
const before = fsSync.lstatSync(lockPath);
|
|
4697
|
+
if (!before.isFile() || before.isSymbolicLink())
|
|
4698
|
+
return null;
|
|
4699
|
+
const noFollow = process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
|
|
4700
|
+
? fsSync.constants.O_NOFOLLOW
|
|
4701
|
+
: 0;
|
|
4702
|
+
fd = fsSync.openSync(lockPath, fsSync.constants.O_RDONLY | noFollow);
|
|
4703
|
+
const opened = fsSync.fstatSync(fd);
|
|
4704
|
+
const raw = fsSync.readFileSync(fd, "utf8");
|
|
4705
|
+
const after = fsSync.lstatSync(lockPath);
|
|
4706
|
+
if (!sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
|
|
4707
|
+
return null;
|
|
4708
|
+
return {
|
|
4709
|
+
raw,
|
|
4710
|
+
payload: parseSidecarLockPayload(raw, parsePayload),
|
|
4711
|
+
stat: after,
|
|
4712
|
+
ownershipToken: readSidecarLockOwnershipToken(raw),
|
|
4713
|
+
};
|
|
4714
|
+
}
|
|
4715
|
+
catch (error) {
|
|
4716
|
+
if (error.code === "ENOENT")
|
|
4717
|
+
return null;
|
|
4718
|
+
throw error;
|
|
4719
|
+
}
|
|
4720
|
+
finally {
|
|
4721
|
+
if (fd !== undefined)
|
|
4722
|
+
fsSync.closeSync(fd);
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
function removeSidecarLockIfUnchangedSync(lockPath, observed) {
|
|
4726
|
+
const current = readSidecarLockSnapshotSync(lockPath);
|
|
4727
|
+
if (!current || !sidecarLockSnapshotMatches(current, observed))
|
|
4728
|
+
return false;
|
|
4729
|
+
fsSync.rmSync(lockPath);
|
|
4730
|
+
return true;
|
|
4731
|
+
}
|
|
4846
4732
|
function sidecarLockSnapshotMatches(current, observed) {
|
|
4847
4733
|
if (observed.ownershipToken !== undefined) {
|
|
4848
4734
|
return (current.stat?.isFile() === true &&
|
|
@@ -4860,16 +4746,21 @@ function sidecarLockSnapshotMatches(current, observed) {
|
|
|
4860
4746
|
}
|
|
4861
4747
|
return observed.stat !== undefined && current.stat !== undefined;
|
|
4862
4748
|
}
|
|
4863
|
-
async function removeSidecarLockIfUnchanged(lockPath, observed) {
|
|
4864
|
-
const current = await readSidecarLockSnapshot(lockPath);
|
|
4749
|
+
async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
|
|
4750
|
+
const current = await readSidecarLockSnapshot(lockPath, options);
|
|
4865
4751
|
if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
|
|
4866
4752
|
return false;
|
|
4867
4753
|
}
|
|
4868
|
-
|
|
4754
|
+
if (options.lockRoot) {
|
|
4755
|
+
await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath)).catch(() => undefined);
|
|
4756
|
+
}
|
|
4757
|
+
else {
|
|
4758
|
+
await promises_.rm(lockPath, { force: true }).catch(() => undefined);
|
|
4759
|
+
}
|
|
4869
4760
|
return true;
|
|
4870
4761
|
}
|
|
4871
|
-
async function sidecarLockSnapshotStillPresent(lockPath, observed) {
|
|
4872
|
-
const current = await readSidecarLockSnapshot(lockPath);
|
|
4762
|
+
async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
|
|
4763
|
+
const current = await readSidecarLockSnapshot(lockPath, options);
|
|
4873
4764
|
return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
|
|
4874
4765
|
}
|
|
4875
4766
|
async function sidecarReclaimGuardExists(pathname) {
|
|
@@ -4905,7 +4796,8 @@ async function removeStaleSidecarLockIfAllowed(params) {
|
|
|
4905
4796
|
if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
|
|
4906
4797
|
return "not-approved";
|
|
4907
4798
|
}
|
|
4908
|
-
|
|
4799
|
+
const ioOptions = { lockRoot: params.lockRoot, parsePayload: params.parsePayload };
|
|
4800
|
+
if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
|
|
4909
4801
|
return "changed";
|
|
4910
4802
|
}
|
|
4911
4803
|
if (!(await params.shouldRemoveStaleLock({
|
|
@@ -4916,11 +4808,16 @@ async function removeStaleSidecarLockIfAllowed(params) {
|
|
|
4916
4808
|
}))) {
|
|
4917
4809
|
return "not-approved";
|
|
4918
4810
|
}
|
|
4919
|
-
if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot))) {
|
|
4811
|
+
if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
|
|
4920
4812
|
return "changed";
|
|
4921
4813
|
}
|
|
4922
4814
|
try {
|
|
4923
|
-
|
|
4815
|
+
if (params.lockRoot) {
|
|
4816
|
+
await params.lockRoot.remove(relativeSidecarLockPath(params.lockRoot, params.lockPath));
|
|
4817
|
+
}
|
|
4818
|
+
else {
|
|
4819
|
+
await promises_.rm(params.lockPath);
|
|
4820
|
+
}
|
|
4924
4821
|
return "removed";
|
|
4925
4822
|
}
|
|
4926
4823
|
catch (err) {
|
|
@@ -4931,12 +4828,77 @@ async function removeStaleSidecarLockIfAllowed(params) {
|
|
|
4931
4828
|
}
|
|
4932
4829
|
}
|
|
4933
4830
|
|
|
4831
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
|
|
4832
|
+
|
|
4833
|
+
function createSidecarLockHandle(params) {
|
|
4834
|
+
let released = false;
|
|
4835
|
+
const release = async () => {
|
|
4836
|
+
if (released)
|
|
4837
|
+
return;
|
|
4838
|
+
released = true;
|
|
4839
|
+
await params.release();
|
|
4840
|
+
};
|
|
4841
|
+
return {
|
|
4842
|
+
lockPath: params.lockPath,
|
|
4843
|
+
normalizedTargetPath: params.normalizedTargetPath,
|
|
4844
|
+
verifyStillHeld: params.verifyStillHeld,
|
|
4845
|
+
release,
|
|
4846
|
+
[Symbol.asyncDispose]: release,
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
function createHeldSidecarLockHandle(params) {
|
|
4850
|
+
return createSidecarLockHandle({
|
|
4851
|
+
lockPath: params.held.lockPath,
|
|
4852
|
+
normalizedTargetPath: params.normalizedTargetPath,
|
|
4853
|
+
verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
|
|
4854
|
+
lockRoot: params.held.lockRoot,
|
|
4855
|
+
parsePayload: params.held.parsePayload,
|
|
4856
|
+
}),
|
|
4857
|
+
release: params.release,
|
|
4858
|
+
});
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4861
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
|
|
4862
|
+
|
|
4863
|
+
function computeSidecarLockDelayMs(retry, attempt) {
|
|
4864
|
+
const minTimeout = retry.minTimeout ?? 50;
|
|
4865
|
+
const maxTimeout = retry.maxTimeout ?? 1000;
|
|
4866
|
+
const factor = retry.factor ?? 1;
|
|
4867
|
+
const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
|
|
4868
|
+
const jitter = retry.randomize ? 1 + Math.random() : 1;
|
|
4869
|
+
return Math.min(maxTimeout, Math.round(base * jitter));
|
|
4870
|
+
}
|
|
4871
|
+
function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
|
|
4872
|
+
const createdAt = payload &&
|
|
4873
|
+
typeof payload === "object" &&
|
|
4874
|
+
"createdAt" in payload &&
|
|
4875
|
+
typeof payload.createdAt === "string"
|
|
4876
|
+
? payload.createdAt
|
|
4877
|
+
: "";
|
|
4878
|
+
const createdAtMs = Date.parse(createdAt);
|
|
4879
|
+
return Number.isFinite(createdAtMs) && nowMs - createdAtMs > staleMs;
|
|
4880
|
+
}
|
|
4881
|
+
async function defaultSidecarLockShouldReclaim(params) {
|
|
4882
|
+
if (sidecarLockPayloadIsStale(params.payload, params.staleMs, params.nowMs))
|
|
4883
|
+
return true;
|
|
4884
|
+
try {
|
|
4885
|
+
return params.nowMs - (await promises_.stat(params.lockPath)).mtimeMs > params.staleMs;
|
|
4886
|
+
}
|
|
4887
|
+
catch {
|
|
4888
|
+
return true;
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
|
|
4934
4892
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
|
|
4935
4893
|
|
|
4936
4894
|
|
|
4937
4895
|
|
|
4938
4896
|
|
|
4939
4897
|
|
|
4898
|
+
|
|
4899
|
+
|
|
4900
|
+
|
|
4901
|
+
|
|
4940
4902
|
const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
|
|
4941
4903
|
function getGlobalManagers() {
|
|
4942
4904
|
const globalWithState = globalThis;
|
|
@@ -4962,6 +4924,9 @@ function resolveManagerState(key) {
|
|
|
4962
4924
|
// Backfill state created by fs-safe versions that predate reclaim guards.
|
|
4963
4925
|
state.reclaimCleanupRegistered ??= false;
|
|
4964
4926
|
state.reclaimGuards ??= new Set();
|
|
4927
|
+
for (const held of state.held.values()) {
|
|
4928
|
+
held.refCount ??= 1;
|
|
4929
|
+
}
|
|
4965
4930
|
}
|
|
4966
4931
|
return state;
|
|
4967
4932
|
}
|
|
@@ -5017,28 +4982,6 @@ async function resolveNormalizedTargetPath(targetPath) {
|
|
|
5017
4982
|
return resolved;
|
|
5018
4983
|
}
|
|
5019
4984
|
}
|
|
5020
|
-
function computeDelayMs(retry, attempt) {
|
|
5021
|
-
const minTimeout = retry.minTimeout ?? 50;
|
|
5022
|
-
const maxTimeout = retry.maxTimeout ?? 1000;
|
|
5023
|
-
const factor = retry.factor ?? 1;
|
|
5024
|
-
const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
|
|
5025
|
-
const jitter = retry.randomize ? 1 + Math.random() : 1;
|
|
5026
|
-
return Math.min(maxTimeout, Math.round(base * jitter));
|
|
5027
|
-
}
|
|
5028
|
-
async function defaultShouldReclaim(params) {
|
|
5029
|
-
const createdAt = typeof params.payload?.createdAt === "string" ? params.payload.createdAt : "";
|
|
5030
|
-
const createdAtMs = Date.parse(createdAt);
|
|
5031
|
-
if (Number.isFinite(createdAtMs) && params.nowMs - createdAtMs > params.staleMs) {
|
|
5032
|
-
return true;
|
|
5033
|
-
}
|
|
5034
|
-
try {
|
|
5035
|
-
const stat = await promises_.stat(params.lockPath);
|
|
5036
|
-
return params.nowMs - stat.mtimeMs > params.staleMs;
|
|
5037
|
-
}
|
|
5038
|
-
catch {
|
|
5039
|
-
return true;
|
|
5040
|
-
}
|
|
5041
|
-
}
|
|
5042
4985
|
function releaseAllReclaimGuardsSync(state) {
|
|
5043
4986
|
for (const reclaimGuardPath of state.reclaimGuards) {
|
|
5044
4987
|
try {
|
|
@@ -5054,7 +4997,7 @@ function releaseAllLocksSync(state) {
|
|
|
5054
4997
|
for (const [normalizedTargetPath, held] of state.held) {
|
|
5055
4998
|
void held.handle.close().catch(() => undefined);
|
|
5056
4999
|
try {
|
|
5057
|
-
if (snapshotMatchesSync(held.lockPath, held.snapshot)) {
|
|
5000
|
+
if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
|
|
5058
5001
|
external_node_fs_.rmSync(held.lockPath, { force: true });
|
|
5059
5002
|
}
|
|
5060
5003
|
}
|
|
@@ -5065,17 +5008,17 @@ function releaseAllLocksSync(state) {
|
|
|
5065
5008
|
}
|
|
5066
5009
|
releaseAllReclaimGuardsSync(state);
|
|
5067
5010
|
}
|
|
5068
|
-
async function releaseHeldLock(state, normalizedTargetPath, held,
|
|
5011
|
+
async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
|
|
5069
5012
|
const current = state.held.get(normalizedTargetPath);
|
|
5070
5013
|
if (current !== held) {
|
|
5071
5014
|
return false;
|
|
5072
5015
|
}
|
|
5073
|
-
if (
|
|
5074
|
-
held.
|
|
5016
|
+
if (options.force) {
|
|
5017
|
+
held.refCount = 0;
|
|
5075
5018
|
}
|
|
5076
5019
|
else {
|
|
5077
|
-
held.
|
|
5078
|
-
if (held.
|
|
5020
|
+
held.refCount -= 1;
|
|
5021
|
+
if (held.refCount > 0) {
|
|
5079
5022
|
return false;
|
|
5080
5023
|
}
|
|
5081
5024
|
}
|
|
@@ -5084,9 +5027,16 @@ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
|
|
|
5084
5027
|
return true;
|
|
5085
5028
|
}
|
|
5086
5029
|
state.held.delete(normalizedTargetPath);
|
|
5030
|
+
if (held.compromiseTimer) {
|
|
5031
|
+
clearInterval(held.compromiseTimer);
|
|
5032
|
+
held.compromiseTimer = undefined;
|
|
5033
|
+
}
|
|
5087
5034
|
held.releasePromise = (async () => {
|
|
5088
5035
|
await held.handle.close().catch(() => undefined);
|
|
5089
|
-
await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot
|
|
5036
|
+
await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
|
|
5037
|
+
lockRoot: held.lockRoot,
|
|
5038
|
+
parsePayload: held.parsePayload,
|
|
5039
|
+
});
|
|
5090
5040
|
})();
|
|
5091
5041
|
try {
|
|
5092
5042
|
await held.releasePromise;
|
|
@@ -5096,6 +5046,13 @@ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
|
|
|
5096
5046
|
held.releasePromise = undefined;
|
|
5097
5047
|
}
|
|
5098
5048
|
}
|
|
5049
|
+
function handleForHeldLock(state, normalizedTargetPath, held) {
|
|
5050
|
+
return createHeldSidecarLockHandle({
|
|
5051
|
+
normalizedTargetPath,
|
|
5052
|
+
held,
|
|
5053
|
+
release: async () => await releaseHeldLock(state, normalizedTargetPath, held),
|
|
5054
|
+
});
|
|
5055
|
+
}
|
|
5099
5056
|
function createSidecarLockManager(key) {
|
|
5100
5057
|
const state = resolveManagerState(key);
|
|
5101
5058
|
function ensureExitCleanupRegistered() {
|
|
@@ -5115,15 +5072,12 @@ function createSidecarLockManager(key) {
|
|
|
5115
5072
|
const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
|
|
5116
5073
|
const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
|
|
5117
5074
|
const held = state.held.get(normalizedTargetPath);
|
|
5118
|
-
if (held &&
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
release,
|
|
5125
|
-
[Symbol.asyncDispose]: release,
|
|
5126
|
-
};
|
|
5075
|
+
if (held &&
|
|
5076
|
+
options.reentrantOwner !== undefined &&
|
|
5077
|
+
held.reentrantOwner !== undefined &&
|
|
5078
|
+
options.reentrantOwner === held.reentrantOwner) {
|
|
5079
|
+
held.refCount += 1;
|
|
5080
|
+
return handleForHeldLock(state, normalizedTargetPath, held);
|
|
5127
5081
|
}
|
|
5128
5082
|
const startedAt = Date.now();
|
|
5129
5083
|
const retry = options.retry ?? {};
|
|
@@ -5146,7 +5100,7 @@ function createSidecarLockManager(key) {
|
|
|
5146
5100
|
const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
|
|
5147
5101
|
? Number.POSITIVE_INFINITY
|
|
5148
5102
|
: Math.max(0, options.timeoutMs - elapsed);
|
|
5149
|
-
const delay = Math.min(
|
|
5103
|
+
const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
|
|
5150
5104
|
attempt += 1;
|
|
5151
5105
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
5152
5106
|
};
|
|
@@ -5158,18 +5112,36 @@ function createSidecarLockManager(key) {
|
|
|
5158
5112
|
}
|
|
5159
5113
|
let handle = null;
|
|
5160
5114
|
try {
|
|
5161
|
-
handle = await promises_.open(lockPath, "wx");
|
|
5162
5115
|
const payload = await options.payload();
|
|
5163
5116
|
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
5164
|
-
|
|
5117
|
+
if (options.lockRoot) {
|
|
5118
|
+
const relativeLockPath = relativeSidecarLockPath(options.lockRoot, lockPath);
|
|
5119
|
+
try {
|
|
5120
|
+
await options.lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
|
|
5121
|
+
}
|
|
5122
|
+
catch (error) {
|
|
5123
|
+
if (error instanceof errors_FsSafeError && error.code === "already-exists") {
|
|
5124
|
+
throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
|
|
5125
|
+
}
|
|
5126
|
+
throw error;
|
|
5127
|
+
}
|
|
5128
|
+
handle = (await options.lockRoot.open(relativeLockPath)).handle;
|
|
5129
|
+
}
|
|
5130
|
+
else {
|
|
5131
|
+
handle = (await createNativeExclusiveFile(lockPath, 0o600)) ?? await promises_.open(lockPath, "wx");
|
|
5132
|
+
await handle.writeFile(raw, "utf8");
|
|
5133
|
+
}
|
|
5165
5134
|
const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
|
|
5166
5135
|
const createdHeld = {
|
|
5167
|
-
|
|
5136
|
+
refCount: 1,
|
|
5137
|
+
reentrantOwner: options.reentrantOwner,
|
|
5168
5138
|
handle,
|
|
5169
5139
|
lockPath,
|
|
5170
5140
|
snapshot,
|
|
5171
5141
|
acquiredAt: Date.now(),
|
|
5172
5142
|
metadata: options.metadata ?? {},
|
|
5143
|
+
lockRoot: options.lockRoot,
|
|
5144
|
+
parsePayload: options.parsePayload,
|
|
5173
5145
|
};
|
|
5174
5146
|
state.held.set(normalizedTargetPath, createdHeld);
|
|
5175
5147
|
if (ownsReclaimGuard) {
|
|
@@ -5182,13 +5154,21 @@ function createSidecarLockManager(key) {
|
|
|
5182
5154
|
throw err;
|
|
5183
5155
|
}
|
|
5184
5156
|
}
|
|
5185
|
-
const
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5157
|
+
const returnedHandle = handleForHeldLock(state, normalizedTargetPath, createdHeld);
|
|
5158
|
+
const interval = options.compromiseCheckIntervalMs;
|
|
5159
|
+
if (options.onCompromised && interval !== undefined && interval > 0) {
|
|
5160
|
+
createdHeld.compromiseTimer = setInterval(() => {
|
|
5161
|
+
void returnedHandle.verifyStillHeld().then((stillHeld) => {
|
|
5162
|
+
if (!stillHeld && createdHeld.compromiseTimer) {
|
|
5163
|
+
clearInterval(createdHeld.compromiseTimer);
|
|
5164
|
+
createdHeld.compromiseTimer = undefined;
|
|
5165
|
+
options.onCompromised?.({ lockPath, normalizedTargetPath });
|
|
5166
|
+
}
|
|
5167
|
+
});
|
|
5168
|
+
}, interval);
|
|
5169
|
+
createdHeld.compromiseTimer.unref();
|
|
5170
|
+
}
|
|
5171
|
+
return returnedHandle;
|
|
5192
5172
|
}
|
|
5193
5173
|
catch (err) {
|
|
5194
5174
|
if (handle) {
|
|
@@ -5205,11 +5185,16 @@ function createSidecarLockManager(key) {
|
|
|
5205
5185
|
}
|
|
5206
5186
|
// If payload serialization/write fails, the file may be empty or
|
|
5207
5187
|
// partial JSON, so remove while our exclusive handle is still open.
|
|
5208
|
-
|
|
5188
|
+
if (!options.lockRoot) {
|
|
5189
|
+
await promises_.rm(lockPath, { force: true }).catch(() => undefined);
|
|
5190
|
+
}
|
|
5209
5191
|
await handle.close().catch(() => undefined);
|
|
5210
5192
|
// Windows can refuse removing an open file; retry after close but
|
|
5211
5193
|
// only if the path still points at the file identity we created.
|
|
5212
|
-
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot
|
|
5194
|
+
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
|
|
5195
|
+
lockRoot: options.lockRoot,
|
|
5196
|
+
parsePayload: options.parsePayload,
|
|
5197
|
+
});
|
|
5213
5198
|
}
|
|
5214
5199
|
if (err.code !== "EEXIST") {
|
|
5215
5200
|
throw err;
|
|
@@ -5220,11 +5205,18 @@ function createSidecarLockManager(key) {
|
|
|
5220
5205
|
continue;
|
|
5221
5206
|
}
|
|
5222
5207
|
const nowMs = Date.now();
|
|
5223
|
-
const snapshot = await readSidecarLockSnapshot(lockPath
|
|
5208
|
+
const snapshot = await readSidecarLockSnapshot(lockPath, {
|
|
5209
|
+
lockRoot: options.lockRoot,
|
|
5210
|
+
parsePayload: options.parsePayload,
|
|
5211
|
+
});
|
|
5224
5212
|
if (!snapshot) {
|
|
5225
5213
|
continue;
|
|
5226
5214
|
}
|
|
5227
|
-
|
|
5215
|
+
if (state.held.has(normalizedTargetPath)) {
|
|
5216
|
+
await waitForRetry();
|
|
5217
|
+
continue;
|
|
5218
|
+
}
|
|
5219
|
+
const shouldReclaim = options.shouldReclaim ?? defaultSidecarLockShouldReclaim;
|
|
5228
5220
|
if (await shouldReclaim({
|
|
5229
5221
|
lockPath,
|
|
5230
5222
|
normalizedTargetPath,
|
|
@@ -5233,7 +5225,10 @@ function createSidecarLockManager(key) {
|
|
|
5233
5225
|
nowMs,
|
|
5234
5226
|
heldByThisProcess: state.held.has(normalizedTargetPath),
|
|
5235
5227
|
})) {
|
|
5236
|
-
if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot
|
|
5228
|
+
if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
|
|
5229
|
+
lockRoot: options.lockRoot,
|
|
5230
|
+
parsePayload: options.parsePayload,
|
|
5231
|
+
}))) {
|
|
5237
5232
|
continue;
|
|
5238
5233
|
}
|
|
5239
5234
|
const staleRecovery = options.staleRecovery ?? "fail-closed";
|
|
@@ -5248,6 +5243,8 @@ function createSidecarLockManager(key) {
|
|
|
5248
5243
|
normalizedTargetPath,
|
|
5249
5244
|
snapshot,
|
|
5250
5245
|
shouldRemoveStaleLock: options.shouldRemoveStaleLock,
|
|
5246
|
+
lockRoot: options.lockRoot,
|
|
5247
|
+
parsePayload: options.parsePayload,
|
|
5251
5248
|
});
|
|
5252
5249
|
if (removal === "removed" || removal === "changed") {
|
|
5253
5250
|
continue;
|
|
@@ -5335,6 +5332,7 @@ function __setFsSafeTestHooksForTest(hooks) {
|
|
|
5335
5332
|
|
|
5336
5333
|
|
|
5337
5334
|
|
|
5335
|
+
|
|
5338
5336
|
function pinned_write_byteLength(input, encoding) {
|
|
5339
5337
|
return typeof input === "string"
|
|
5340
5338
|
? Buffer.byteLength(input, encoding ?? "utf8")
|
|
@@ -5349,7 +5347,7 @@ function assertSafeBasename(basename) {
|
|
|
5349
5347
|
throw new errors_FsSafeError("invalid-path", "invalid target path");
|
|
5350
5348
|
}
|
|
5351
5349
|
}
|
|
5352
|
-
function
|
|
5350
|
+
function pinned_write_assertWithinMaxBytes(bytes, maxBytes) {
|
|
5353
5351
|
if (maxBytes !== undefined && bytes > maxBytes) {
|
|
5354
5352
|
throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
5355
5353
|
}
|
|
@@ -5369,7 +5367,7 @@ async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
|
5369
5367
|
for await (const chunk of stream) {
|
|
5370
5368
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
5371
5369
|
bytes += buffer.byteLength;
|
|
5372
|
-
|
|
5370
|
+
pinned_write_assertWithinMaxBytes(bytes, maxBytes);
|
|
5373
5371
|
let offset = 0;
|
|
5374
5372
|
while (offset < buffer.byteLength) {
|
|
5375
5373
|
const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
|
|
@@ -5380,74 +5378,21 @@ async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
|
5380
5378
|
}
|
|
5381
5379
|
}
|
|
5382
5380
|
}
|
|
5383
|
-
async function inputToBase64(input, maxBytes) {
|
|
5384
|
-
if (input.kind === "buffer") {
|
|
5385
|
-
assertWithinMaxBytes(pinned_write_byteLength(input.data, input.encoding), maxBytes);
|
|
5386
|
-
return (typeof input.data === "string"
|
|
5387
|
-
? Buffer.from(input.data, input.encoding ?? "utf8")
|
|
5388
|
-
: input.data).toString("base64");
|
|
5389
|
-
}
|
|
5390
|
-
const chunks = [];
|
|
5391
|
-
let bytes = 0;
|
|
5392
|
-
for await (const chunk of input.stream) {
|
|
5393
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
5394
|
-
bytes += buffer.byteLength;
|
|
5395
|
-
assertWithinMaxBytes(bytes, maxBytes);
|
|
5396
|
-
chunks.push(buffer);
|
|
5397
|
-
}
|
|
5398
|
-
return Buffer.concat(chunks, bytes).toString("base64");
|
|
5399
|
-
}
|
|
5400
5381
|
async function runPinnedWriteHelper(params) {
|
|
5401
5382
|
assertSafeBasename(params.basename);
|
|
5402
5383
|
validatePinnedOperationPayload({
|
|
5403
5384
|
relativeParentPath: params.relativeParentPath,
|
|
5404
5385
|
});
|
|
5405
|
-
// The
|
|
5406
|
-
//
|
|
5407
|
-
// Node fallback, where content verification can replace that one check.
|
|
5386
|
+
// The explicit compatibility policy uses the guarded Node fallback, where
|
|
5387
|
+
// content verification can replace the strict post-rename inode check.
|
|
5408
5388
|
if (params.onRenameIdentityMismatch === "verify-content") {
|
|
5409
5389
|
return await runPinnedWriteFallback(params);
|
|
5410
5390
|
}
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
if (params.input.kind === "stream") {
|
|
5415
|
-
try {
|
|
5416
|
-
assertPinnedPythonOperationAvailable();
|
|
5417
|
-
}
|
|
5418
|
-
catch (error) {
|
|
5419
|
-
if (canFallbackFromPythonError(error)) {
|
|
5420
|
-
return await runPinnedWriteFallback(params);
|
|
5421
|
-
}
|
|
5422
|
-
throw error;
|
|
5423
|
-
}
|
|
5424
|
-
}
|
|
5425
|
-
const input = params.input.kind === "stream"
|
|
5426
|
-
? { kind: "buffer", data: Buffer.from(await inputToBase64(params.input, params.maxBytes), "base64") }
|
|
5427
|
-
: params.input;
|
|
5428
|
-
const payload = {
|
|
5429
|
-
base64: await inputToBase64(input, params.maxBytes),
|
|
5430
|
-
basename: params.basename,
|
|
5431
|
-
maxBytes: params.maxBytes ?? -1,
|
|
5432
|
-
mkdir: params.mkdir,
|
|
5433
|
-
mode: params.mode || 0o600,
|
|
5434
|
-
overwrite: params.overwrite !== false,
|
|
5435
|
-
relativeParentPath: params.relativeParentPath,
|
|
5436
|
-
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
5437
|
-
};
|
|
5438
|
-
try {
|
|
5439
|
-
return await runPinnedPythonOperation({
|
|
5440
|
-
operation: "write",
|
|
5441
|
-
rootPath: params.rootPath,
|
|
5442
|
-
payload,
|
|
5443
|
-
});
|
|
5444
|
-
}
|
|
5445
|
-
catch (error) {
|
|
5446
|
-
if (canFallbackFromPythonError(error)) {
|
|
5447
|
-
return await runPinnedWriteFallback({ ...params, input });
|
|
5448
|
-
}
|
|
5449
|
-
throw error;
|
|
5391
|
+
const native = getNativeBinding();
|
|
5392
|
+
if (native && params.overwrite === false) {
|
|
5393
|
+
return await runPinnedWriteNative(native, params);
|
|
5450
5394
|
}
|
|
5395
|
+
return await runPinnedWriteFallback(params);
|
|
5451
5396
|
}
|
|
5452
5397
|
async function runPinnedWriteWithRenamePolicy(params) {
|
|
5453
5398
|
const { targetPath, renameIdentity, ...writeParams } = params;
|
|
@@ -5470,34 +5415,19 @@ async function runPinnedWriteWithRenamePolicy(params) {
|
|
|
5470
5415
|
onRenameIdentityMismatch: "verify-content",
|
|
5471
5416
|
}));
|
|
5472
5417
|
}
|
|
5473
|
-
async function runPinnedCopyHelper(params) {
|
|
5474
|
-
assertSafeBasename(params.basename);
|
|
5475
|
-
validatePinnedOperationPayload({
|
|
5476
|
-
relativeParentPath: params.relativeParentPath,
|
|
5477
|
-
});
|
|
5478
|
-
return await runPinnedPythonOperation({
|
|
5479
|
-
operation: "copy",
|
|
5480
|
-
rootPath: params.rootPath,
|
|
5481
|
-
payload: {
|
|
5482
|
-
basename: params.basename,
|
|
5483
|
-
maxBytes: params.maxBytes ?? -1,
|
|
5484
|
-
mkdir: params.mkdir,
|
|
5485
|
-
mode: params.mode || 0o600,
|
|
5486
|
-
overwrite: params.overwrite !== false,
|
|
5487
|
-
relativeParentPath: params.relativeParentPath,
|
|
5488
|
-
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
5489
|
-
sourceDev: params.sourceIdentity.dev,
|
|
5490
|
-
sourceIno: params.sourceIdentity.ino,
|
|
5491
|
-
sourcePath: params.sourcePath,
|
|
5492
|
-
},
|
|
5493
|
-
});
|
|
5494
|
-
}
|
|
5495
5418
|
async function runPinnedWriteFallback(params) {
|
|
5496
|
-
|
|
5419
|
+
let parentPath = params.relativeParentPath
|
|
5497
5420
|
? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
5498
5421
|
: params.rootPath;
|
|
5499
5422
|
if (params.mkdir) {
|
|
5500
|
-
|
|
5423
|
+
// mkdirPathComponentsWithGuards may resolve the final component through
|
|
5424
|
+
// an in-root symlink (e.g. a skill-bank layout). Use its returned real
|
|
5425
|
+
// path for the subsequent guard and target path so we don't re-check the
|
|
5426
|
+
// original, possibly-symlinked, lexical path and reject it outright.
|
|
5427
|
+
parentPath = await mkdirPathComponentsWithGuards({
|
|
5428
|
+
rootReal: params.rootPath,
|
|
5429
|
+
targetPath: parentPath,
|
|
5430
|
+
});
|
|
5501
5431
|
}
|
|
5502
5432
|
const parentGuard = params.mkdir
|
|
5503
5433
|
? await directory_guard_createAsyncDirectoryGuard(parentPath)
|
|
@@ -5513,8 +5443,9 @@ async function runPinnedWriteFallback(params) {
|
|
|
5513
5443
|
});
|
|
5514
5444
|
let created = true;
|
|
5515
5445
|
try {
|
|
5446
|
+
await handle.chmod(params.mode);
|
|
5516
5447
|
if (params.input.kind === "buffer") {
|
|
5517
|
-
|
|
5448
|
+
pinned_write_assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
|
|
5518
5449
|
if (typeof params.input.data === "string") {
|
|
5519
5450
|
await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
|
|
5520
5451
|
}
|
|
@@ -5552,8 +5483,9 @@ async function runPinnedWriteFallback(params) {
|
|
|
5552
5483
|
let renamed = false;
|
|
5553
5484
|
try {
|
|
5554
5485
|
handle = await promises_.open(tempPath, tempFlags, params.mode);
|
|
5486
|
+
await handle.chmod(params.mode);
|
|
5555
5487
|
if (params.input.kind === "buffer") {
|
|
5556
|
-
|
|
5488
|
+
pinned_write_assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
|
|
5557
5489
|
if (typeof params.input.data === "string") {
|
|
5558
5490
|
await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
|
|
5559
5491
|
}
|
|
@@ -5636,12 +5568,85 @@ async function runPinnedWriteFallback(params) {
|
|
|
5636
5568
|
|
|
5637
5569
|
// EXTERNAL MODULE: external "node:os"
|
|
5638
5570
|
var external_node_os_ = __webpack_require__(8161);
|
|
5571
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-existing.js
|
|
5572
|
+
|
|
5573
|
+
|
|
5574
|
+
|
|
5575
|
+
|
|
5576
|
+
function isFilesystemRoot(candidate) {
|
|
5577
|
+
return external_node_path_.parse(candidate).root === candidate;
|
|
5578
|
+
}
|
|
5579
|
+
async function root_path_existing_pathExists(targetPath) {
|
|
5580
|
+
try {
|
|
5581
|
+
await promises_.lstat(targetPath);
|
|
5582
|
+
return true;
|
|
5583
|
+
}
|
|
5584
|
+
catch (error) {
|
|
5585
|
+
if (path_isNotFoundPathError(error)) {
|
|
5586
|
+
return false;
|
|
5587
|
+
}
|
|
5588
|
+
throw error;
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
async function root_path_existing_resolvePathViaExistingAncestor(targetPath) {
|
|
5592
|
+
const normalized = external_node_path_.resolve(targetPath);
|
|
5593
|
+
let cursor = normalized;
|
|
5594
|
+
const missingSuffix = [];
|
|
5595
|
+
while (!isFilesystemRoot(cursor) && !(await root_path_existing_pathExists(cursor))) {
|
|
5596
|
+
missingSuffix.unshift(external_node_path_.basename(cursor));
|
|
5597
|
+
const parent = external_node_path_.dirname(cursor);
|
|
5598
|
+
if (parent === cursor) {
|
|
5599
|
+
break;
|
|
5600
|
+
}
|
|
5601
|
+
cursor = parent;
|
|
5602
|
+
}
|
|
5603
|
+
if (!(await root_path_existing_pathExists(cursor))) {
|
|
5604
|
+
return normalized;
|
|
5605
|
+
}
|
|
5606
|
+
try {
|
|
5607
|
+
const resolvedAncestor = external_node_path_.resolve(await promises_.realpath(cursor));
|
|
5608
|
+
return missingSuffix.length === 0
|
|
5609
|
+
? resolvedAncestor
|
|
5610
|
+
: external_node_path_.resolve(resolvedAncestor, ...missingSuffix);
|
|
5611
|
+
}
|
|
5612
|
+
catch {
|
|
5613
|
+
return normalized;
|
|
5614
|
+
}
|
|
5615
|
+
}
|
|
5616
|
+
function root_path_existing_resolvePathViaExistingAncestorSync(targetPath) {
|
|
5617
|
+
const normalized = path.resolve(targetPath);
|
|
5618
|
+
let cursor = normalized;
|
|
5619
|
+
const missingSuffix = [];
|
|
5620
|
+
while (!isFilesystemRoot(cursor) && !fs.existsSync(cursor)) {
|
|
5621
|
+
missingSuffix.unshift(path.basename(cursor));
|
|
5622
|
+
const parent = path.dirname(cursor);
|
|
5623
|
+
if (parent === cursor) {
|
|
5624
|
+
break;
|
|
5625
|
+
}
|
|
5626
|
+
cursor = parent;
|
|
5627
|
+
}
|
|
5628
|
+
if (!fs.existsSync(cursor)) {
|
|
5629
|
+
return normalized;
|
|
5630
|
+
}
|
|
5631
|
+
try {
|
|
5632
|
+
const resolvedAncestor = path.resolve(fs.realpathSync(cursor));
|
|
5633
|
+
return missingSuffix.length === 0
|
|
5634
|
+
? resolvedAncestor
|
|
5635
|
+
: path.resolve(resolvedAncestor, ...missingSuffix);
|
|
5636
|
+
}
|
|
5637
|
+
catch {
|
|
5638
|
+
return normalized;
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5641
|
+
|
|
5639
5642
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
|
|
5640
5643
|
|
|
5641
5644
|
|
|
5642
5645
|
|
|
5643
5646
|
|
|
5644
5647
|
|
|
5648
|
+
|
|
5649
|
+
|
|
5645
5650
|
const ROOT_PATH_ALIAS_POLICIES = {
|
|
5646
5651
|
strict: Object.freeze({
|
|
5647
5652
|
allowFinalSymlinkForUnlink: false,
|
|
@@ -5657,7 +5662,7 @@ async function resolveRootPath(params) {
|
|
|
5657
5662
|
const absolutePath = external_node_path_.resolve(params.absolutePath);
|
|
5658
5663
|
const rootCanonicalPath = params.rootCanonicalPath
|
|
5659
5664
|
? external_node_path_.resolve(params.rootCanonicalPath)
|
|
5660
|
-
: await
|
|
5665
|
+
: await root_path_existing_resolvePathViaExistingAncestor(rootPath);
|
|
5661
5666
|
const context = createBoundaryResolutionContext({
|
|
5662
5667
|
resolveParams: params,
|
|
5663
5668
|
rootPath,
|
|
@@ -5719,15 +5724,40 @@ function isPromiseLike(value) {
|
|
|
5719
5724
|
typeof value.then === "function");
|
|
5720
5725
|
}
|
|
5721
5726
|
function createLexicalTraversalState(params) {
|
|
5722
|
-
const
|
|
5727
|
+
const rawAbsolutePath = params.params.absolutePath;
|
|
5728
|
+
const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
|
|
5729
|
+
const relative = rawRelativePath ?? external_node_path_.relative(params.rootPath, params.absolutePath);
|
|
5723
5730
|
return {
|
|
5724
|
-
segments: relative
|
|
5731
|
+
segments: splitTraversalSegments(relative),
|
|
5725
5732
|
allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
|
|
5726
5733
|
canonicalCursor: params.rootCanonicalPath,
|
|
5727
5734
|
lexicalCursor: params.rootPath,
|
|
5728
5735
|
preserveFinalSymlink: false,
|
|
5729
5736
|
};
|
|
5730
5737
|
}
|
|
5738
|
+
function splitTraversalSegments(value) {
|
|
5739
|
+
return value
|
|
5740
|
+
.split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
|
|
5741
|
+
.filter((segment) => Boolean(segment) && segment !== ".");
|
|
5742
|
+
}
|
|
5743
|
+
function rawPathRelativeToRoot(rootPath, candidatePath) {
|
|
5744
|
+
if (!external_node_path_.isAbsolute(candidatePath)) {
|
|
5745
|
+
return undefined;
|
|
5746
|
+
}
|
|
5747
|
+
const root = external_node_path_.resolve(rootPath);
|
|
5748
|
+
const candidate = process.platform === "win32"
|
|
5749
|
+
? candidatePath.replaceAll("/", external_node_path_.sep)
|
|
5750
|
+
: candidatePath;
|
|
5751
|
+
if (candidate === root) {
|
|
5752
|
+
return "";
|
|
5753
|
+
}
|
|
5754
|
+
const rootWithSep = root.endsWith(external_node_path_.sep) ? root : `${root}${external_node_path_.sep}`;
|
|
5755
|
+
const candidatePrefix = candidate.slice(0, rootWithSep.length);
|
|
5756
|
+
const prefixMatches = process.platform === "win32"
|
|
5757
|
+
? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
|
|
5758
|
+
: candidatePrefix === rootWithSep;
|
|
5759
|
+
return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
|
|
5760
|
+
}
|
|
5731
5761
|
function assertLexicalCursorInsideBoundary(params) {
|
|
5732
5762
|
assertInsideBoundary({
|
|
5733
5763
|
boundaryLabel: params.params.boundaryLabel,
|
|
@@ -5738,13 +5768,15 @@ function assertLexicalCursorInsideBoundary(params) {
|
|
|
5738
5768
|
}
|
|
5739
5769
|
function applyMissingSuffixToCanonicalCursor(params) {
|
|
5740
5770
|
const missingSuffix = params.state.segments.slice(params.missingFromIndex);
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5771
|
+
for (const segment of missingSuffix) {
|
|
5772
|
+
advanceCanonicalCursorForSegment({
|
|
5773
|
+
state: params.state,
|
|
5774
|
+
segment,
|
|
5775
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
5776
|
+
params: params.params,
|
|
5777
|
+
absolutePath: params.absolutePath,
|
|
5778
|
+
});
|
|
5779
|
+
}
|
|
5748
5780
|
}
|
|
5749
5781
|
function advanceCanonicalCursorForSegment(params) {
|
|
5750
5782
|
params.state.canonicalCursor = external_node_path_.resolve(params.state.canonicalCursor, params.segment);
|
|
@@ -5860,11 +5892,20 @@ function resolveAndApplySymlinkHop(params) {
|
|
|
5860
5892
|
boundaryLabel: params.boundaryLabel,
|
|
5861
5893
|
});
|
|
5862
5894
|
}
|
|
5895
|
+
function applyParentTraversalStep(params) {
|
|
5896
|
+
params.state.lexicalCursor = external_node_path_.resolve(params.state.lexicalCursor, "..");
|
|
5897
|
+
advanceCanonicalCursorForSegment({
|
|
5898
|
+
state: params.state,
|
|
5899
|
+
segment: "..",
|
|
5900
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
5901
|
+
params: params.resolveParams,
|
|
5902
|
+
absolutePath: params.absolutePath,
|
|
5903
|
+
});
|
|
5904
|
+
}
|
|
5863
5905
|
function* iterateLexicalTraversal(state) {
|
|
5864
5906
|
for (let idx = 0; idx < state.segments.length; idx += 1) {
|
|
5865
5907
|
const segment = state.segments[idx] ?? "";
|
|
5866
5908
|
const isLast = idx === state.segments.length - 1;
|
|
5867
|
-
state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
|
|
5868
5909
|
yield { idx, segment, isLast };
|
|
5869
5910
|
}
|
|
5870
5911
|
}
|
|
@@ -5877,6 +5918,14 @@ async function resolveRootPathLexicalAsync(params) {
|
|
|
5877
5918
|
absolutePath: params.absolutePath,
|
|
5878
5919
|
};
|
|
5879
5920
|
for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
|
|
5921
|
+
if (segment === "..") {
|
|
5922
|
+
applyParentTraversalStep({
|
|
5923
|
+
...sharedStepParams,
|
|
5924
|
+
resolveParams: params.params,
|
|
5925
|
+
});
|
|
5926
|
+
continue;
|
|
5927
|
+
}
|
|
5928
|
+
state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
|
|
5880
5929
|
const stat = await readLexicalStat({
|
|
5881
5930
|
...sharedStepParams,
|
|
5882
5931
|
missingFromIndex: idx,
|
|
@@ -5904,7 +5953,7 @@ async function resolveRootPathLexicalAsync(params) {
|
|
|
5904
5953
|
resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
|
|
5905
5954
|
});
|
|
5906
5955
|
}
|
|
5907
|
-
const kind = await getPathKind(
|
|
5956
|
+
const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
|
|
5908
5957
|
return finalizeLexicalResolution({
|
|
5909
5958
|
...params,
|
|
5910
5959
|
state,
|
|
@@ -5916,6 +5965,15 @@ function resolveRootPathLexicalSync(params) {
|
|
|
5916
5965
|
for (let idx = 0; idx < state.segments.length; idx += 1) {
|
|
5917
5966
|
const segment = state.segments[idx] ?? "";
|
|
5918
5967
|
const isLast = idx === state.segments.length - 1;
|
|
5968
|
+
if (segment === "..") {
|
|
5969
|
+
applyParentTraversalStep({
|
|
5970
|
+
state,
|
|
5971
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
5972
|
+
resolveParams: params.params,
|
|
5973
|
+
absolutePath: params.absolutePath,
|
|
5974
|
+
});
|
|
5975
|
+
continue;
|
|
5976
|
+
}
|
|
5919
5977
|
state.lexicalCursor = path.join(state.lexicalCursor, segment);
|
|
5920
5978
|
const maybeStat = readLexicalStat({
|
|
5921
5979
|
state,
|
|
@@ -5957,7 +6015,7 @@ function resolveRootPathLexicalSync(params) {
|
|
|
5957
6015
|
throw new Error("Unexpected async symlink resolution");
|
|
5958
6016
|
}
|
|
5959
6017
|
}
|
|
5960
|
-
const kind = getPathKindSync(
|
|
6018
|
+
const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
|
|
5961
6019
|
return finalizeLexicalResolution({
|
|
5962
6020
|
...params,
|
|
5963
6021
|
state,
|
|
@@ -6026,7 +6084,7 @@ async function resolveOutsideLexicalCanonicalPathAsync(params) {
|
|
|
6026
6084
|
if (path_isPathInside(params.rootPath, params.absolutePath)) {
|
|
6027
6085
|
return undefined;
|
|
6028
6086
|
}
|
|
6029
|
-
return await
|
|
6087
|
+
return await root_path_existing_resolvePathViaExistingAncestor(params.absolutePath);
|
|
6030
6088
|
}
|
|
6031
6089
|
function resolveOutsideLexicalCanonicalPathSync(params) {
|
|
6032
6090
|
if (isPathInside(params.rootPath, params.absolutePath)) {
|
|
@@ -6073,60 +6131,6 @@ function buildResolvedRootPath(params) {
|
|
|
6073
6131
|
kind: params.kind.kind,
|
|
6074
6132
|
};
|
|
6075
6133
|
}
|
|
6076
|
-
async function root_path_resolvePathViaExistingAncestor(targetPath) {
|
|
6077
|
-
const normalized = external_node_path_.resolve(targetPath);
|
|
6078
|
-
let cursor = normalized;
|
|
6079
|
-
const missingSuffix = [];
|
|
6080
|
-
while (!isFilesystemRoot(cursor) && !(await root_path_pathExists(cursor))) {
|
|
6081
|
-
missingSuffix.unshift(external_node_path_.basename(cursor));
|
|
6082
|
-
const parent = external_node_path_.dirname(cursor);
|
|
6083
|
-
if (parent === cursor) {
|
|
6084
|
-
break;
|
|
6085
|
-
}
|
|
6086
|
-
cursor = parent;
|
|
6087
|
-
}
|
|
6088
|
-
if (!(await root_path_pathExists(cursor))) {
|
|
6089
|
-
return normalized;
|
|
6090
|
-
}
|
|
6091
|
-
try {
|
|
6092
|
-
const resolvedAncestor = external_node_path_.resolve(await promises_.realpath(cursor));
|
|
6093
|
-
if (missingSuffix.length === 0) {
|
|
6094
|
-
return resolvedAncestor;
|
|
6095
|
-
}
|
|
6096
|
-
return external_node_path_.resolve(resolvedAncestor, ...missingSuffix);
|
|
6097
|
-
}
|
|
6098
|
-
catch {
|
|
6099
|
-
return normalized;
|
|
6100
|
-
}
|
|
6101
|
-
}
|
|
6102
|
-
function resolvePathViaExistingAncestorSync(targetPath) {
|
|
6103
|
-
const normalized = path.resolve(targetPath);
|
|
6104
|
-
let cursor = normalized;
|
|
6105
|
-
const missingSuffix = [];
|
|
6106
|
-
while (!isFilesystemRoot(cursor) && !fs.existsSync(cursor)) {
|
|
6107
|
-
missingSuffix.unshift(path.basename(cursor));
|
|
6108
|
-
const parent = path.dirname(cursor);
|
|
6109
|
-
if (parent === cursor) {
|
|
6110
|
-
break;
|
|
6111
|
-
}
|
|
6112
|
-
cursor = parent;
|
|
6113
|
-
}
|
|
6114
|
-
if (!fs.existsSync(cursor)) {
|
|
6115
|
-
return normalized;
|
|
6116
|
-
}
|
|
6117
|
-
try {
|
|
6118
|
-
// Keep sync behavior aligned with async (`fsp.realpath`) to avoid
|
|
6119
|
-
// platform-specific canonical alias drift (notably on Windows).
|
|
6120
|
-
const resolvedAncestor = path.resolve(fs.realpathSync(cursor));
|
|
6121
|
-
if (missingSuffix.length === 0) {
|
|
6122
|
-
return resolvedAncestor;
|
|
6123
|
-
}
|
|
6124
|
-
return path.resolve(resolvedAncestor, ...missingSuffix);
|
|
6125
|
-
}
|
|
6126
|
-
catch {
|
|
6127
|
-
return normalized;
|
|
6128
|
-
}
|
|
6129
|
-
}
|
|
6130
6134
|
async function getPathKind(absolutePath, preserveFinalSymlink) {
|
|
6131
6135
|
try {
|
|
6132
6136
|
const stat = preserveFinalSymlink
|
|
@@ -6194,21 +6198,6 @@ function shortPath(value) {
|
|
|
6194
6198
|
}
|
|
6195
6199
|
return value;
|
|
6196
6200
|
}
|
|
6197
|
-
function isFilesystemRoot(candidate) {
|
|
6198
|
-
return external_node_path_.parse(candidate).root === candidate;
|
|
6199
|
-
}
|
|
6200
|
-
async function root_path_pathExists(targetPath) {
|
|
6201
|
-
try {
|
|
6202
|
-
await promises_.lstat(targetPath);
|
|
6203
|
-
return true;
|
|
6204
|
-
}
|
|
6205
|
-
catch (error) {
|
|
6206
|
-
if (path_isNotFoundPathError(error)) {
|
|
6207
|
-
return false;
|
|
6208
|
-
}
|
|
6209
|
-
throw error;
|
|
6210
|
-
}
|
|
6211
|
-
}
|
|
6212
6201
|
async function resolveSymlinkHopPath(symlinkPath) {
|
|
6213
6202
|
try {
|
|
6214
6203
|
return external_node_path_.resolve(await promises_.realpath(symlinkPath));
|
|
@@ -6219,7 +6208,7 @@ async function resolveSymlinkHopPath(symlinkPath) {
|
|
|
6219
6208
|
}
|
|
6220
6209
|
const linkTarget = await promises_.readlink(symlinkPath);
|
|
6221
6210
|
const linkAbsolute = external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget);
|
|
6222
|
-
return
|
|
6211
|
+
return root_path_existing_resolvePathViaExistingAncestor(linkAbsolute);
|
|
6223
6212
|
}
|
|
6224
6213
|
}
|
|
6225
6214
|
function resolveSymlinkHopPathSync(symlinkPath) {
|
|
@@ -6254,7 +6243,7 @@ async function assertNoPathAliasEscape(params) {
|
|
|
6254
6243
|
return;
|
|
6255
6244
|
}
|
|
6256
6245
|
await assertNoHardlinkedFinalPath({
|
|
6257
|
-
filePath: resolved.
|
|
6246
|
+
filePath: resolved.canonicalPath,
|
|
6258
6247
|
root: resolved.rootPath,
|
|
6259
6248
|
boundaryLabel: params.boundaryLabel,
|
|
6260
6249
|
allowFinalHardlinkForUnlink: params.policy?.allowFinalHardlinkForUnlink,
|
|
@@ -6288,8 +6277,80 @@ function path_policy_shortPath(value) {
|
|
|
6288
6277
|
return value;
|
|
6289
6278
|
}
|
|
6290
6279
|
|
|
6291
|
-
|
|
6292
|
-
|
|
6280
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/string-coerce.js
|
|
6281
|
+
function readStringValue(value) {
|
|
6282
|
+
return typeof value === "string" ? value : undefined;
|
|
6283
|
+
}
|
|
6284
|
+
function normalizeNullableString(value) {
|
|
6285
|
+
if (typeof value !== "string") {
|
|
6286
|
+
return null;
|
|
6287
|
+
}
|
|
6288
|
+
const trimmed = value.trim();
|
|
6289
|
+
return trimmed ? trimmed : null;
|
|
6290
|
+
}
|
|
6291
|
+
function normalizeOptionalString(value) {
|
|
6292
|
+
return normalizeNullableString(value) ?? undefined;
|
|
6293
|
+
}
|
|
6294
|
+
function normalizeStringifiedOptionalString(value) {
|
|
6295
|
+
if (typeof value === "string") {
|
|
6296
|
+
return normalizeOptionalString(value);
|
|
6297
|
+
}
|
|
6298
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
6299
|
+
return normalizeOptionalString(String(value));
|
|
6300
|
+
}
|
|
6301
|
+
return undefined;
|
|
6302
|
+
}
|
|
6303
|
+
function normalizeOptionalLowercaseString(value) {
|
|
6304
|
+
return normalizeOptionalString(value)?.toLowerCase();
|
|
6305
|
+
}
|
|
6306
|
+
function normalizeLowercaseStringOrEmpty(value) {
|
|
6307
|
+
return normalizeOptionalLowercaseString(value) ?? "";
|
|
6308
|
+
}
|
|
6309
|
+
function normalizeFastMode(raw) {
|
|
6310
|
+
if (typeof raw === "boolean") {
|
|
6311
|
+
return raw;
|
|
6312
|
+
}
|
|
6313
|
+
if (!raw) {
|
|
6314
|
+
return undefined;
|
|
6315
|
+
}
|
|
6316
|
+
const key = normalizeLowercaseStringOrEmpty(raw);
|
|
6317
|
+
if (["off", "false", "no", "0", "disable", "disabled", "normal"].includes(key)) {
|
|
6318
|
+
return false;
|
|
6319
|
+
}
|
|
6320
|
+
if (["on", "true", "yes", "1", "enable", "enabled", "fast"].includes(key)) {
|
|
6321
|
+
return true;
|
|
6322
|
+
}
|
|
6323
|
+
return undefined;
|
|
6324
|
+
}
|
|
6325
|
+
function lowercasePreservingWhitespace(value) {
|
|
6326
|
+
return value.toLowerCase();
|
|
6327
|
+
}
|
|
6328
|
+
function localeLowercasePreservingWhitespace(value) {
|
|
6329
|
+
return value.toLocaleLowerCase();
|
|
6330
|
+
}
|
|
6331
|
+
function resolvePrimaryStringValue(value) {
|
|
6332
|
+
if (typeof value === "string") {
|
|
6333
|
+
return normalizeOptionalString(value);
|
|
6334
|
+
}
|
|
6335
|
+
if (!value || typeof value !== "object") {
|
|
6336
|
+
return undefined;
|
|
6337
|
+
}
|
|
6338
|
+
return normalizeOptionalString(value.primary);
|
|
6339
|
+
}
|
|
6340
|
+
function normalizeOptionalThreadValue(value) {
|
|
6341
|
+
if (typeof value === "number") {
|
|
6342
|
+
return Number.isFinite(value) ? Math.trunc(value) : undefined;
|
|
6343
|
+
}
|
|
6344
|
+
return normalizeOptionalString(value);
|
|
6345
|
+
}
|
|
6346
|
+
function normalizeOptionalStringifiedId(value) {
|
|
6347
|
+
const normalized = normalizeOptionalThreadValue(value);
|
|
6348
|
+
return normalized == null ? undefined : String(normalized);
|
|
6349
|
+
}
|
|
6350
|
+
function hasNonEmptyString(value) {
|
|
6351
|
+
return normalizeOptionalString(value) !== undefined;
|
|
6352
|
+
}
|
|
6353
|
+
|
|
6293
6354
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/local-file-access.js
|
|
6294
6355
|
|
|
6295
6356
|
|
|
@@ -6421,6 +6482,28 @@ const WINDOWS_RESERVED_DEVICE_NAMES = new Set([
|
|
|
6421
6482
|
"LPT²",
|
|
6422
6483
|
"LPT³",
|
|
6423
6484
|
]);
|
|
6485
|
+
const WINDOWS_SEPARATOR_CHAR_CODE = 0x5c;
|
|
6486
|
+
const WINDOWS_IGNORED_SPACE_CHAR_CODE = 0x20;
|
|
6487
|
+
const WINDOWS_IGNORED_DOT_CHAR_CODE = 0x2e;
|
|
6488
|
+
function trimTrailingWindowsSeparators(value) {
|
|
6489
|
+
let end = value.length;
|
|
6490
|
+
while (end > 0 && value.charCodeAt(end - 1) === WINDOWS_SEPARATOR_CHAR_CODE) {
|
|
6491
|
+
end -= 1;
|
|
6492
|
+
}
|
|
6493
|
+
return end === value.length ? value : value.slice(0, end);
|
|
6494
|
+
}
|
|
6495
|
+
function trimTrailingWindowsIgnoredChars(value) {
|
|
6496
|
+
let end = value.length;
|
|
6497
|
+
while (end > 0) {
|
|
6498
|
+
const charCode = value.charCodeAt(end - 1);
|
|
6499
|
+
if (charCode !== WINDOWS_IGNORED_SPACE_CHAR_CODE &&
|
|
6500
|
+
charCode !== WINDOWS_IGNORED_DOT_CHAR_CODE) {
|
|
6501
|
+
break;
|
|
6502
|
+
}
|
|
6503
|
+
end -= 1;
|
|
6504
|
+
}
|
|
6505
|
+
return end === value.length ? value : value.slice(0, end);
|
|
6506
|
+
}
|
|
6424
6507
|
function candidateReadPaths(filePath) {
|
|
6425
6508
|
if (!filePath.startsWith("file://")) {
|
|
6426
6509
|
return [filePath];
|
|
@@ -6449,10 +6532,10 @@ function matchPosixDeviceReadPath(filePath, cwd) {
|
|
|
6449
6532
|
return undefined;
|
|
6450
6533
|
}
|
|
6451
6534
|
function normalizeWindowsDeviceBaseName(filePath) {
|
|
6452
|
-
const normalized = filePath.replace(/\//g, "\\")
|
|
6535
|
+
const normalized = trimTrailingWindowsSeparators(filePath.replace(/\//g, "\\"));
|
|
6453
6536
|
const lastSegment = normalized.split("\\").filter(Boolean).at(-1) ?? normalized;
|
|
6454
6537
|
const withoutStream = lastSegment.split(":")[0] ?? lastSegment;
|
|
6455
|
-
const withoutTrailingIgnoredChars = withoutStream
|
|
6538
|
+
const withoutTrailingIgnoredChars = trimTrailingWindowsIgnoredChars(withoutStream);
|
|
6456
6539
|
return (withoutTrailingIgnoredChars.split(".")[0] ?? withoutTrailingIgnoredChars).toUpperCase();
|
|
6457
6540
|
}
|
|
6458
6541
|
function matchWindowsDeviceReadPath(filePath) {
|
|
@@ -6487,94 +6570,6 @@ function assertNoUnsafeDeviceReadPath(filePath, options) {
|
|
|
6487
6570
|
}
|
|
6488
6571
|
}
|
|
6489
6572
|
|
|
6490
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
const READ_CHUNK_BYTES = 64 * 1024;
|
|
6494
|
-
function assertMaxBytes(maxBytes) {
|
|
6495
|
-
if (maxBytes === Number.POSITIVE_INFINITY) {
|
|
6496
|
-
return;
|
|
6497
|
-
}
|
|
6498
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
6499
|
-
throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
|
|
6500
|
-
}
|
|
6501
|
-
}
|
|
6502
|
-
function createScratchBuffer(maxBytes) {
|
|
6503
|
-
const initialReadBytes = Number.isFinite(maxBytes)
|
|
6504
|
-
? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
|
|
6505
|
-
: READ_CHUNK_BYTES;
|
|
6506
|
-
return Buffer.allocUnsafe(Math.max(1, initialReadBytes));
|
|
6507
|
-
}
|
|
6508
|
-
function nextReadLength(total, maxBytes, capacity) {
|
|
6509
|
-
return Number.isFinite(maxBytes)
|
|
6510
|
-
? Math.min(capacity, maxBytes - total + 1)
|
|
6511
|
-
: capacity;
|
|
6512
|
-
}
|
|
6513
|
-
function appendChunk(params) {
|
|
6514
|
-
const total = params.total + params.bytesRead;
|
|
6515
|
-
if (total > params.maxBytes) {
|
|
6516
|
-
throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got at least ${total})`);
|
|
6517
|
-
}
|
|
6518
|
-
params.chunks.push(Buffer.from(params.scratch.subarray(0, params.bytesRead)));
|
|
6519
|
-
return total;
|
|
6520
|
-
}
|
|
6521
|
-
async function readBoundedAsync(maxBytes, readChunk) {
|
|
6522
|
-
assertMaxBytes(maxBytes);
|
|
6523
|
-
const chunks = [];
|
|
6524
|
-
const scratch = createScratchBuffer(maxBytes);
|
|
6525
|
-
let total = 0;
|
|
6526
|
-
while (true) {
|
|
6527
|
-
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
6528
|
-
const bytesRead = await readChunk(scratch, length);
|
|
6529
|
-
if (bytesRead === 0) {
|
|
6530
|
-
return Buffer.concat(chunks, total);
|
|
6531
|
-
}
|
|
6532
|
-
total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
|
|
6533
|
-
}
|
|
6534
|
-
}
|
|
6535
|
-
/**
|
|
6536
|
-
* Reads from the handle's current offset without closing it. A bounded read
|
|
6537
|
-
* consumes at most maxBytes + 1 bytes so growth after an earlier stat cannot
|
|
6538
|
-
* force an unbounded allocation.
|
|
6539
|
-
*/
|
|
6540
|
-
async function readFileHandleBounded(handle, maxBytes) {
|
|
6541
|
-
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
6542
|
-
return (await handle.read(scratch, 0, length, null)).bytesRead;
|
|
6543
|
-
});
|
|
6544
|
-
}
|
|
6545
|
-
function readDescriptorChunk(fd, scratch, length) {
|
|
6546
|
-
return new Promise((resolve, reject) => {
|
|
6547
|
-
fs.read(fd, scratch, 0, length, null, (error, bytesRead) => {
|
|
6548
|
-
if (error) {
|
|
6549
|
-
reject(error);
|
|
6550
|
-
return;
|
|
6551
|
-
}
|
|
6552
|
-
resolve(bytesRead);
|
|
6553
|
-
});
|
|
6554
|
-
});
|
|
6555
|
-
}
|
|
6556
|
-
/** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
6557
|
-
async function readFileDescriptorBounded(fd, maxBytes) {
|
|
6558
|
-
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
6559
|
-
return await readDescriptorChunk(fd, scratch, length);
|
|
6560
|
-
});
|
|
6561
|
-
}
|
|
6562
|
-
/** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
6563
|
-
function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
6564
|
-
assertMaxBytes(maxBytes);
|
|
6565
|
-
const chunks = [];
|
|
6566
|
-
const scratch = createScratchBuffer(maxBytes);
|
|
6567
|
-
let total = 0;
|
|
6568
|
-
while (true) {
|
|
6569
|
-
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
6570
|
-
const bytesRead = fs.readSync(fd, scratch, 0, length, null);
|
|
6571
|
-
if (bytesRead === 0) {
|
|
6572
|
-
return Buffer.concat(chunks, total);
|
|
6573
|
-
}
|
|
6574
|
-
total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
|
|
6575
|
-
}
|
|
6576
|
-
}
|
|
6577
|
-
|
|
6578
6573
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/read-opened-file.js
|
|
6579
6574
|
|
|
6580
6575
|
|
|
@@ -6587,6 +6582,7 @@ async function read_opened_file_readOpenedFileSafely(params) {
|
|
|
6587
6582
|
: await readFileHandleBounded(params.opened.handle, params.maxBytes);
|
|
6588
6583
|
return {
|
|
6589
6584
|
buffer,
|
|
6585
|
+
containment: params.opened.containment,
|
|
6590
6586
|
realPath: params.opened.realPath,
|
|
6591
6587
|
stat: params.opened.stat,
|
|
6592
6588
|
};
|
|
@@ -6730,6 +6726,7 @@ function resolveOsHomeRelativePath(input, opts) {
|
|
|
6730
6726
|
|
|
6731
6727
|
|
|
6732
6728
|
|
|
6729
|
+
|
|
6733
6730
|
const ensureTrailingSep = (value) => value.endsWith(external_node_path_.sep) ? value : value + external_node_path_.sep;
|
|
6734
6731
|
function assertValidRootRelativePath(relativePath) {
|
|
6735
6732
|
path_assertNoNulPathInput(relativePath, "relative path contains a NUL byte");
|
|
@@ -6774,13 +6771,31 @@ async function resolveRootContext(rootDir) {
|
|
|
6774
6771
|
rootWithSep: ensureTrailingSep(rootReal),
|
|
6775
6772
|
};
|
|
6776
6773
|
}
|
|
6777
|
-
async function resolvePathInRoot(root, relativePath) {
|
|
6774
|
+
async function resolvePathInRoot(root, relativePath, options) {
|
|
6778
6775
|
assertValidRootRelativePath(relativePath);
|
|
6779
6776
|
const expanded = await expandRelativePathWithHome(relativePath);
|
|
6780
6777
|
const resolved = external_node_path_.resolve(root.rootWithSep, expanded);
|
|
6781
6778
|
if (!path_isPathInside(root.rootWithSep, resolved)) {
|
|
6782
6779
|
throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
|
|
6783
6780
|
}
|
|
6781
|
+
const rawAbsolutePath = external_node_path_.isAbsolute(expanded)
|
|
6782
|
+
? expanded
|
|
6783
|
+
: `${root.rootWithSep}${expanded}`;
|
|
6784
|
+
try {
|
|
6785
|
+
await resolveRootPath({
|
|
6786
|
+
absolutePath: rawAbsolutePath,
|
|
6787
|
+
rootPath: root.rootReal,
|
|
6788
|
+
rootCanonicalPath: root.rootReal,
|
|
6789
|
+
boundaryLabel: "root",
|
|
6790
|
+
policy: options?.allowFinalSymlink ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
|
|
6791
|
+
});
|
|
6792
|
+
}
|
|
6793
|
+
catch (error) {
|
|
6794
|
+
const code = options?.aliasErrorCode ?? "outside-workspace";
|
|
6795
|
+
throw new errors_FsSafeError(code, code === "path-alias" ? "path alias escape blocked" : "file is outside workspace root", {
|
|
6796
|
+
cause: error instanceof Error ? error : undefined,
|
|
6797
|
+
});
|
|
6798
|
+
}
|
|
6784
6799
|
return { rootReal: root.rootReal, rootWithSep: root.rootWithSep, resolved };
|
|
6785
6800
|
}
|
|
6786
6801
|
async function resolvePathWithinRoot(params) {
|
|
@@ -6819,14 +6834,143 @@ function stringifyJsonDocument(value, replacer, space) {
|
|
|
6819
6834
|
return text;
|
|
6820
6835
|
}
|
|
6821
6836
|
|
|
6837
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-walk.js
|
|
6838
|
+
|
|
6839
|
+
|
|
6840
|
+
|
|
6841
|
+
function validateBudget(name, value) {
|
|
6842
|
+
if (value === undefined)
|
|
6843
|
+
return Number.POSITIVE_INFINITY;
|
|
6844
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
6845
|
+
throw new RangeError(`${name} must be a non-negative safe integer`);
|
|
6846
|
+
}
|
|
6847
|
+
return value;
|
|
6848
|
+
}
|
|
6849
|
+
function entryKind(entry) {
|
|
6850
|
+
if (entry.isSymbolicLink)
|
|
6851
|
+
return "symlink";
|
|
6852
|
+
if (entry.isDirectory)
|
|
6853
|
+
return "directory";
|
|
6854
|
+
if (entry.isFile)
|
|
6855
|
+
return "file";
|
|
6856
|
+
return "other";
|
|
6857
|
+
}
|
|
6858
|
+
function limitEntry(relativePath) {
|
|
6859
|
+
return { relativePath, kind: "truncated", size: 0 };
|
|
6860
|
+
}
|
|
6861
|
+
async function* walkRoot(root, relativePath, options) {
|
|
6862
|
+
const maxDepth = validateBudget("maxDepth", options.maxDepth);
|
|
6863
|
+
const maxEntries = validateBudget("maxEntries", options.maxEntries);
|
|
6864
|
+
const visitedDirectories = new Set();
|
|
6865
|
+
let examined = 0;
|
|
6866
|
+
const onLimit = (atPath) => {
|
|
6867
|
+
if ((options.limitBehavior ?? "truncate") === "throw") {
|
|
6868
|
+
throw new errors_FsSafeError("too-large", `root walk budget exceeded at ${atPath || "."}`);
|
|
6869
|
+
}
|
|
6870
|
+
return limitEntry(atPath);
|
|
6871
|
+
};
|
|
6872
|
+
async function* visit(directory, depth) {
|
|
6873
|
+
options.signal?.throwIfAborted();
|
|
6874
|
+
let entries;
|
|
6875
|
+
try {
|
|
6876
|
+
const resolvedDirectory = await resolveRootPath({
|
|
6877
|
+
absolutePath: external_node_path_.resolve(root.rootReal, directory),
|
|
6878
|
+
rootPath: root.rootReal,
|
|
6879
|
+
rootCanonicalPath: root.rootReal,
|
|
6880
|
+
boundaryLabel: "root walk",
|
|
6881
|
+
});
|
|
6882
|
+
if (!resolvedDirectory.exists || resolvedDirectory.kind !== "directory") {
|
|
6883
|
+
throw new errors_FsSafeError("not-file", `root walk path is not a directory: ${directory || "."}`);
|
|
6884
|
+
}
|
|
6885
|
+
if (visitedDirectories.has(resolvedDirectory.canonicalPath)) {
|
|
6886
|
+
return;
|
|
6887
|
+
}
|
|
6888
|
+
visitedDirectories.add(resolvedDirectory.canonicalPath);
|
|
6889
|
+
const listingDirectory = external_node_path_.relative(root.rootReal, resolvedDirectory.canonicalPath)
|
|
6890
|
+
.split(external_node_path_.sep)
|
|
6891
|
+
.join(external_node_path_.posix.sep);
|
|
6892
|
+
entries = await root.list(listingDirectory, { withFileTypes: true });
|
|
6893
|
+
}
|
|
6894
|
+
catch (error) {
|
|
6895
|
+
if ((options.onDirectoryError ?? "throw") === "throw")
|
|
6896
|
+
throw error;
|
|
6897
|
+
yield { relativePath: directory, kind: "directory-error", size: 0, error };
|
|
6898
|
+
return;
|
|
6899
|
+
}
|
|
6900
|
+
for (const entry of entries) {
|
|
6901
|
+
options.signal?.throwIfAborted();
|
|
6902
|
+
const child = directory
|
|
6903
|
+
? external_node_path_.posix.join(directory.split(external_node_path_.sep).join(external_node_path_.posix.sep), entry.name)
|
|
6904
|
+
: entry.name;
|
|
6905
|
+
if (examined >= maxEntries) {
|
|
6906
|
+
yield onLimit(child);
|
|
6907
|
+
return;
|
|
6908
|
+
}
|
|
6909
|
+
examined += 1;
|
|
6910
|
+
let kind = entryKind(entry);
|
|
6911
|
+
let size = entry.size;
|
|
6912
|
+
if (kind === "symlink") {
|
|
6913
|
+
if (options.symlinkPolicy === "skip") {
|
|
6914
|
+
continue;
|
|
6915
|
+
}
|
|
6916
|
+
const resolved = await resolveRootPath({
|
|
6917
|
+
absolutePath: external_node_path_.resolve(root.rootReal, child),
|
|
6918
|
+
rootPath: root.rootReal,
|
|
6919
|
+
rootCanonicalPath: root.rootReal,
|
|
6920
|
+
boundaryLabel: "root walk",
|
|
6921
|
+
});
|
|
6922
|
+
if (!resolved.exists) {
|
|
6923
|
+
continue;
|
|
6924
|
+
}
|
|
6925
|
+
kind = resolved.kind === "directory" ? "directory" : resolved.kind === "file" ? "file" : "other";
|
|
6926
|
+
size = entry.size;
|
|
6927
|
+
}
|
|
6928
|
+
const walkEntry = { relativePath: child, kind, size };
|
|
6929
|
+
const filterResult = options.entryFilter?.(walkEntry) ?? "include";
|
|
6930
|
+
if (!["include", "skip", "skip-subtree"].includes(filterResult)) {
|
|
6931
|
+
throw new TypeError(`invalid root walk entryFilter result: ${String(filterResult)}`);
|
|
6932
|
+
}
|
|
6933
|
+
if (filterResult === "include") {
|
|
6934
|
+
yield walkEntry;
|
|
6935
|
+
}
|
|
6936
|
+
if (kind !== "directory") {
|
|
6937
|
+
continue;
|
|
6938
|
+
}
|
|
6939
|
+
if (filterResult === "skip-subtree") {
|
|
6940
|
+
continue;
|
|
6941
|
+
}
|
|
6942
|
+
if (depth >= maxDepth) {
|
|
6943
|
+
yield onLimit(child);
|
|
6944
|
+
return;
|
|
6945
|
+
}
|
|
6946
|
+
yield* visit(child, depth + 1);
|
|
6947
|
+
}
|
|
6948
|
+
}
|
|
6949
|
+
yield* visit(relativePath, 0);
|
|
6950
|
+
}
|
|
6951
|
+
|
|
6822
6952
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/temp-cleanup.js
|
|
6823
6953
|
|
|
6954
|
+
|
|
6824
6955
|
const tempCleanupEntries = new Map();
|
|
6825
6956
|
let cleanupRegistered = false;
|
|
6957
|
+
function pathStillMatchesReceipt(entry) {
|
|
6958
|
+
if (!entry.identity) {
|
|
6959
|
+
return false;
|
|
6960
|
+
}
|
|
6961
|
+
try {
|
|
6962
|
+
return file_identity_sameFileIdentity(external_node_fs_.lstatSync(entry.path), entry.identity);
|
|
6963
|
+
}
|
|
6964
|
+
catch (error) {
|
|
6965
|
+
return error.code === "ENOENT";
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6826
6968
|
function cleanupRegisteredTempPathsSync() {
|
|
6827
6969
|
for (const entry of tempCleanupEntries.values()) {
|
|
6828
6970
|
try {
|
|
6829
|
-
|
|
6971
|
+
if (pathStillMatchesReceipt(entry)) {
|
|
6972
|
+
external_node_fs_.rmSync(entry.path, { force: true, recursive: entry.recursive });
|
|
6973
|
+
}
|
|
6830
6974
|
}
|
|
6831
6975
|
catch {
|
|
6832
6976
|
// Process-exit cleanup is best-effort.
|
|
@@ -6839,13 +6983,27 @@ function registerTempPathForExit(tempPath, options) {
|
|
|
6839
6983
|
cleanupRegistered = true;
|
|
6840
6984
|
process.once("exit", cleanupRegisteredTempPathsSync);
|
|
6841
6985
|
}
|
|
6842
|
-
|
|
6986
|
+
const entry = {
|
|
6843
6987
|
path: tempPath,
|
|
6844
6988
|
recursive: options?.recursive === true,
|
|
6845
|
-
|
|
6846
|
-
|
|
6989
|
+
identity: options?.identity,
|
|
6990
|
+
};
|
|
6991
|
+
if (!entry.identity) {
|
|
6992
|
+
try {
|
|
6993
|
+
entry.identity = external_node_fs_.lstatSync(tempPath);
|
|
6994
|
+
}
|
|
6995
|
+
catch {
|
|
6996
|
+
// Callers that register before creation set the identity after opening.
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
6999
|
+
tempCleanupEntries.set(tempPath, entry);
|
|
7000
|
+
const unregister = (() => {
|
|
6847
7001
|
tempCleanupEntries.delete(tempPath);
|
|
7002
|
+
});
|
|
7003
|
+
unregister.setIdentity = (identity) => {
|
|
7004
|
+
entry.identity = identity;
|
|
6848
7005
|
};
|
|
7006
|
+
return unregister;
|
|
6849
7007
|
}
|
|
6850
7008
|
function __cleanupRegisteredTempPathsForTest() {
|
|
6851
7009
|
cleanupRegisteredTempPathsSync();
|
|
@@ -6856,7 +7014,9 @@ function __cleanupRegisteredTempPathForTest(tempPath) {
|
|
|
6856
7014
|
return;
|
|
6857
7015
|
}
|
|
6858
7016
|
try {
|
|
6859
|
-
|
|
7017
|
+
if (pathStillMatchesReceipt(entry)) {
|
|
7018
|
+
fsSync.rmSync(entry.path, { force: true, recursive: entry.recursive });
|
|
7019
|
+
}
|
|
6860
7020
|
}
|
|
6861
7021
|
finally {
|
|
6862
7022
|
tempCleanupEntries.delete(tempPath);
|
|
@@ -6907,9 +7067,6 @@ async function serializePathWrite(key, run) {
|
|
|
6907
7067
|
|
|
6908
7068
|
|
|
6909
7069
|
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
7070
|
|
|
6914
7071
|
|
|
6915
7072
|
|
|
@@ -6940,6 +7097,7 @@ const DEFAULT_ROOT_MAX_BYTES = 16 * 1024 * 1024;
|
|
|
6940
7097
|
function openResult(params) {
|
|
6941
7098
|
return {
|
|
6942
7099
|
handle: params.handle,
|
|
7100
|
+
containment: "best-effort",
|
|
6943
7101
|
realPath: params.realPath,
|
|
6944
7102
|
stat: params.stat,
|
|
6945
7103
|
[Symbol.asyncDispose]: () => params.handle.close().catch(() => undefined),
|
|
@@ -7058,7 +7216,7 @@ class RootHandle {
|
|
|
7058
7216
|
};
|
|
7059
7217
|
}
|
|
7060
7218
|
async resolve(relativePath) {
|
|
7061
|
-
return (await resolvePathInRoot(this.context, relativePath)).resolved;
|
|
7219
|
+
return (await resolvePathInRoot(this.context, relativePath, { allowFinalSymlink: true })).resolved;
|
|
7062
7220
|
}
|
|
7063
7221
|
async open(relativePath, options = {}) {
|
|
7064
7222
|
return await openFileInRoot(this.context, {
|
|
@@ -7197,58 +7355,34 @@ class RootHandle {
|
|
|
7197
7355
|
}
|
|
7198
7356
|
async stat(relativePath) {
|
|
7199
7357
|
assertValidRootRelativePath(relativePath);
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
}
|
|
7203
|
-
catch (error) {
|
|
7204
|
-
if (canFallbackFromPythonError(error)) {
|
|
7205
|
-
return await statPathFallback(this.context, relativePath);
|
|
7206
|
-
}
|
|
7207
|
-
throw error;
|
|
7208
|
-
}
|
|
7358
|
+
validatePinnedOperationPayload({ relativePath });
|
|
7359
|
+
return await statPathFallback(this.context, relativePath);
|
|
7209
7360
|
}
|
|
7210
7361
|
async list(relativePath, options = {}) {
|
|
7211
7362
|
assertValidRootRelativePath(relativePath);
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
? await helperReaddir(this.rootReal, relativePath, true)
|
|
7215
|
-
: await helperReaddir(this.rootReal, relativePath, false);
|
|
7216
|
-
}
|
|
7217
|
-
catch (error) {
|
|
7218
|
-
if (canFallbackFromPythonError(error)) {
|
|
7219
|
-
return await listPathFallback(this.context, relativePath, options.withFileTypes === true);
|
|
7220
|
-
}
|
|
7221
|
-
throw error;
|
|
7222
|
-
}
|
|
7363
|
+
validatePinnedOperationPayload({ relativePath });
|
|
7364
|
+
return await listPathFallback(this.context, relativePath, options.withFileTypes === true);
|
|
7223
7365
|
}
|
|
7224
7366
|
async move(fromRelative, toRelative, options = {}) {
|
|
7225
7367
|
assertValidRootRelativePath(fromRelative);
|
|
7226
7368
|
assertValidRootRelativePath(toRelative);
|
|
7369
|
+
validatePinnedOperationPayload({ from: fromRelative, to: toRelative });
|
|
7227
7370
|
const denyMutations = mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations);
|
|
7228
7371
|
await assertMoveMutationAllowed(this.context, {
|
|
7229
7372
|
fromRelative,
|
|
7230
7373
|
toRelative,
|
|
7231
7374
|
denyMutations,
|
|
7232
7375
|
});
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
fromRelative,
|
|
7244
|
-
denyMutations,
|
|
7245
|
-
overwrite: options.overwrite ?? false,
|
|
7246
|
-
toRelative,
|
|
7247
|
-
});
|
|
7248
|
-
return;
|
|
7249
|
-
}
|
|
7250
|
-
throw error;
|
|
7251
|
-
}
|
|
7376
|
+
await movePathFallback(this.context, {
|
|
7377
|
+
fromRelative,
|
|
7378
|
+
denyMutations,
|
|
7379
|
+
overwrite: options.overwrite ?? false,
|
|
7380
|
+
toRelative,
|
|
7381
|
+
});
|
|
7382
|
+
}
|
|
7383
|
+
walk(relativePath, options) {
|
|
7384
|
+
assertValidRootRelativePath(relativePath);
|
|
7385
|
+
return walkRoot(this, relativePath, options);
|
|
7252
7386
|
}
|
|
7253
7387
|
}
|
|
7254
7388
|
function readDefaults(defaults) {
|
|
@@ -7263,7 +7397,9 @@ async function root_impl_root(rootDir, defaults = {}) {
|
|
|
7263
7397
|
return new RootHandle(await resolveRootContext(rootDir), defaults);
|
|
7264
7398
|
}
|
|
7265
7399
|
async function openFileInRoot(root, params) {
|
|
7266
|
-
const { rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath
|
|
7400
|
+
const { rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath, {
|
|
7401
|
+
allowFinalSymlink: true,
|
|
7402
|
+
});
|
|
7267
7403
|
let opened;
|
|
7268
7404
|
try {
|
|
7269
7405
|
opened = await openVerifiedLocalFile(resolved, {
|
|
@@ -7334,6 +7470,15 @@ function buildAtomicWriteTempPath(targetPath) {
|
|
|
7334
7470
|
function rootWriteQueueKey(root, relativePath) {
|
|
7335
7471
|
return `${root.rootReal}\0${relativePath}`;
|
|
7336
7472
|
}
|
|
7473
|
+
async function prepareRootWriteTarget(rootReal, targetPath) {
|
|
7474
|
+
const parentPath = await mkdirPathComponentsWithGuards({
|
|
7475
|
+
rootReal,
|
|
7476
|
+
targetPath: external_node_path_.dirname(targetPath),
|
|
7477
|
+
});
|
|
7478
|
+
// Continue through the guarded walk's real parent instead of re-entering
|
|
7479
|
+
// the original path through a symlinked component.
|
|
7480
|
+
return external_node_path_.join(parentPath, external_node_path_.basename(targetPath));
|
|
7481
|
+
}
|
|
7337
7482
|
async function writeTempFileForAtomicReplace(params) {
|
|
7338
7483
|
const tempHandle = await promises_.open(params.tempPath, OPEN_WRITE_CREATE_FLAGS, params.mode);
|
|
7339
7484
|
try {
|
|
@@ -7364,7 +7509,7 @@ async function verifyAtomicWriteResult(params) {
|
|
|
7364
7509
|
}
|
|
7365
7510
|
}
|
|
7366
7511
|
async function openWritableFileInRoot(root, params) {
|
|
7367
|
-
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath);
|
|
7512
|
+
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath, { aliasErrorCode: "path-alias" });
|
|
7368
7513
|
await assertMutationNotDenied(resolved, params.denyMutations);
|
|
7369
7514
|
try {
|
|
7370
7515
|
await assertNoPathAliasEscape({
|
|
@@ -7376,15 +7521,11 @@ async function openWritableFileInRoot(root, params) {
|
|
|
7376
7521
|
catch (err) {
|
|
7377
7522
|
throw new errors_FsSafeError("path-alias", "path alias escape blocked", { cause: err });
|
|
7378
7523
|
}
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
await
|
|
7382
|
-
await promises_.mkdir(external_node_path_.dirname(resolved), { recursive: true });
|
|
7383
|
-
});
|
|
7384
|
-
}
|
|
7385
|
-
let ioPath = resolved;
|
|
7524
|
+
let ioPath = params.mkdir === false
|
|
7525
|
+
? resolved
|
|
7526
|
+
: await prepareRootWriteTarget(rootReal, resolved);
|
|
7386
7527
|
try {
|
|
7387
|
-
const resolvedRealPath = await promises_.realpath(
|
|
7528
|
+
const resolvedRealPath = await promises_.realpath(ioPath);
|
|
7388
7529
|
if (!path_isPathInside(rootWithSep, resolvedRealPath)) {
|
|
7389
7530
|
throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
|
|
7390
7531
|
}
|
|
@@ -7469,6 +7610,7 @@ async function openWritableFileInRoot(root, params) {
|
|
|
7469
7610
|
}
|
|
7470
7611
|
return {
|
|
7471
7612
|
handle,
|
|
7613
|
+
containment: "best-effort",
|
|
7472
7614
|
createdForWrite,
|
|
7473
7615
|
realPath,
|
|
7474
7616
|
stat,
|
|
@@ -7524,44 +7666,22 @@ async function appendFileInRoot(root, params) {
|
|
|
7524
7666
|
}
|
|
7525
7667
|
}
|
|
7526
7668
|
async function removePathInRoot(root, params) {
|
|
7669
|
+
validatePinnedOperationPayload({ relativePath: params.relativePath });
|
|
7527
7670
|
const resolved = await resolvePinnedRemovePathInRoot(root, params.relativePath, params.denyMutations);
|
|
7528
|
-
if (process.platform === "win32") {
|
|
7529
|
-
await removePathFallback(resolved);
|
|
7530
|
-
return;
|
|
7531
|
-
}
|
|
7532
7671
|
try {
|
|
7533
|
-
await
|
|
7534
|
-
operation: "remove",
|
|
7535
|
-
rootPath: resolved.rootReal,
|
|
7536
|
-
relativePath: resolved.relativePosix,
|
|
7537
|
-
});
|
|
7672
|
+
await removePathFallback(resolved);
|
|
7538
7673
|
}
|
|
7539
7674
|
catch (error) {
|
|
7540
|
-
if (isPinnedPathHelperSpawnError(error)) {
|
|
7541
|
-
await removePathFallback(resolved);
|
|
7542
|
-
return;
|
|
7543
|
-
}
|
|
7544
7675
|
throw normalizePinnedPathError(error);
|
|
7545
7676
|
}
|
|
7546
7677
|
}
|
|
7547
7678
|
async function mkdirPathInRoot(root, params) {
|
|
7679
|
+
validatePinnedOperationPayload({ relativePath: params.relativePath });
|
|
7548
7680
|
const resolved = await resolvePinnedPathInRoot(root, params);
|
|
7549
|
-
if (process.platform === "win32") {
|
|
7550
|
-
await mkdirPathFallback(resolved);
|
|
7551
|
-
return;
|
|
7552
|
-
}
|
|
7553
7681
|
try {
|
|
7554
|
-
await
|
|
7555
|
-
operation: "mkdirp",
|
|
7556
|
-
rootPath: resolved.rootReal,
|
|
7557
|
-
relativePath: resolved.relativePosix,
|
|
7558
|
-
});
|
|
7682
|
+
await mkdirPathFallback(resolved);
|
|
7559
7683
|
}
|
|
7560
7684
|
catch (error) {
|
|
7561
|
-
if (isPinnedPathHelperSpawnError(error)) {
|
|
7562
|
-
await mkdirPathFallback(resolved);
|
|
7563
|
-
return;
|
|
7564
|
-
}
|
|
7565
7685
|
throw normalizePinnedPathError(error);
|
|
7566
7686
|
}
|
|
7567
7687
|
}
|
|
@@ -7627,49 +7747,25 @@ async function copyFileInRoot(root, params) {
|
|
|
7627
7747
|
throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${source.stat.size})`);
|
|
7628
7748
|
}
|
|
7629
7749
|
try {
|
|
7630
|
-
if (process.platform === "win32") {
|
|
7631
|
-
await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
|
|
7632
|
-
await copyFileFallback(root, params, source);
|
|
7633
|
-
});
|
|
7634
|
-
return;
|
|
7635
|
-
}
|
|
7636
7750
|
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
|
|
7637
7751
|
await serializePathWrite(pinned.targetPath, async () => {
|
|
7638
|
-
|
|
7752
|
+
await assertCopySourceCurrent(source);
|
|
7753
|
+
const identity = await runPinnedWriteHelper({
|
|
7754
|
+
rootPath: pinned.rootReal,
|
|
7755
|
+
relativeParentPath: pinned.relativeParentPath,
|
|
7756
|
+
basename: pinned.basename,
|
|
7757
|
+
mkdir: params.mkdir !== false,
|
|
7758
|
+
mode: pinned.mode,
|
|
7759
|
+
overwrite: true,
|
|
7760
|
+
maxBytes: params.maxBytes,
|
|
7761
|
+
input: { kind: "stream", stream: source.handle.createReadStream() },
|
|
7762
|
+
});
|
|
7639
7763
|
try {
|
|
7640
|
-
|
|
7641
|
-
await copyFileFallback(root, params, source);
|
|
7642
|
-
return;
|
|
7643
|
-
}
|
|
7644
|
-
identity = await runPinnedCopyHelper({
|
|
7645
|
-
rootPath: pinned.rootReal,
|
|
7646
|
-
relativeParentPath: pinned.relativeParentPath,
|
|
7647
|
-
basename: pinned.basename,
|
|
7648
|
-
mkdir: params.mkdir !== false,
|
|
7649
|
-
mode: pinned.mode,
|
|
7650
|
-
overwrite: true,
|
|
7651
|
-
maxBytes: params.maxBytes,
|
|
7652
|
-
sourcePath: source.realPath,
|
|
7653
|
-
sourceIdentity: { dev: source.stat.dev, ino: source.stat.ino },
|
|
7654
|
-
});
|
|
7764
|
+
await assertCopySourcePathCurrent(source);
|
|
7655
7765
|
}
|
|
7656
7766
|
catch (error) {
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
return;
|
|
7660
|
-
}
|
|
7661
|
-
throw normalizePinnedWriteError(error);
|
|
7662
|
-
}
|
|
7663
|
-
try {
|
|
7664
|
-
await verifyAtomicWriteResult({
|
|
7665
|
-
root,
|
|
7666
|
-
targetPath: pinned.targetPath,
|
|
7667
|
-
expectedIdentity: identity,
|
|
7668
|
-
});
|
|
7669
|
-
}
|
|
7670
|
-
catch (err) {
|
|
7671
|
-
emitWriteBoundaryWarning(`post-copy verification failed: ${String(err)}`);
|
|
7672
|
-
throw err;
|
|
7767
|
+
await removeCopyTargetIfUnchanged(pinned.targetPath, identity).catch(() => undefined);
|
|
7768
|
+
throw error;
|
|
7673
7769
|
}
|
|
7674
7770
|
});
|
|
7675
7771
|
}
|
|
@@ -7677,8 +7773,33 @@ async function copyFileInRoot(root, params) {
|
|
|
7677
7773
|
await source.handle.close().catch(() => { });
|
|
7678
7774
|
}
|
|
7679
7775
|
}
|
|
7776
|
+
async function assertCopySourceCurrent(source) {
|
|
7777
|
+
const opened = await source.handle.stat();
|
|
7778
|
+
if (!file_identity_sameFileIdentity(opened, source.stat)) {
|
|
7779
|
+
throw new errors_FsSafeError("path-mismatch", "copy source descriptor changed");
|
|
7780
|
+
}
|
|
7781
|
+
await assertCopySourcePathCurrent(source);
|
|
7782
|
+
}
|
|
7783
|
+
async function assertCopySourcePathCurrent(source) {
|
|
7784
|
+
const current = await promises_.lstat(source.realPath);
|
|
7785
|
+
if (current.isSymbolicLink() || !current.isFile() || !file_identity_sameFileIdentity(current, source.stat)) {
|
|
7786
|
+
throw new errors_FsSafeError("path-mismatch", "copy source path changed");
|
|
7787
|
+
}
|
|
7788
|
+
}
|
|
7789
|
+
async function removeCopyTargetIfUnchanged(targetPath, identity) {
|
|
7790
|
+
const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(targetPath));
|
|
7791
|
+
const current = await promises_.lstat(targetPath);
|
|
7792
|
+
if (current.isSymbolicLink() || !file_identity_sameFileIdentity(current, identity)) {
|
|
7793
|
+
return;
|
|
7794
|
+
}
|
|
7795
|
+
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
7796
|
+
await promises_.rm(targetPath);
|
|
7797
|
+
});
|
|
7798
|
+
}
|
|
7680
7799
|
async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
|
|
7681
|
-
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, relativePath
|
|
7800
|
+
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, relativePath, {
|
|
7801
|
+
aliasErrorCode: "path-alias",
|
|
7802
|
+
});
|
|
7682
7803
|
await assertMutationNotDenied(resolved, denyMutations);
|
|
7683
7804
|
try {
|
|
7684
7805
|
await assertNoPathAliasEscape({
|
|
@@ -7780,8 +7901,11 @@ async function resolvePinnedRootPathInRoot(root, params) {
|
|
|
7780
7901
|
const rootReal = root.rootReal;
|
|
7781
7902
|
let resolved;
|
|
7782
7903
|
try {
|
|
7904
|
+
const expandedPath = await expandRelativePathWithHome(params.relativePath);
|
|
7783
7905
|
resolved = await resolveRootPath({
|
|
7784
|
-
absolutePath: external_node_path_.
|
|
7906
|
+
absolutePath: external_node_path_.isAbsolute(expandedPath)
|
|
7907
|
+
? expandedPath
|
|
7908
|
+
: `${ensureTrailingSep(rootReal)}${expandedPath}`,
|
|
7785
7909
|
rootPath: rootReal,
|
|
7786
7910
|
rootCanonicalPath: rootReal,
|
|
7787
7911
|
boundaryLabel: "root",
|
|
@@ -7852,35 +7976,51 @@ async function listPathFallback(root, relativePath, withFileTypes) {
|
|
|
7852
7976
|
}
|
|
7853
7977
|
}
|
|
7854
7978
|
async function assertMoveMutationAllowed(root, params) {
|
|
7855
|
-
const source = await resolvePathInRoot(root, params.fromRelative
|
|
7979
|
+
const source = await resolvePathInRoot(root, params.fromRelative, {
|
|
7980
|
+
aliasErrorCode: "path-alias",
|
|
7981
|
+
allowFinalSymlink: true,
|
|
7982
|
+
});
|
|
7856
7983
|
await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
|
|
7857
|
-
const target = await resolvePathInRoot(root, params.toRelative
|
|
7984
|
+
const target = await resolvePathInRoot(root, params.toRelative, {
|
|
7985
|
+
aliasErrorCode: "path-alias",
|
|
7986
|
+
allowFinalSymlink: true,
|
|
7987
|
+
});
|
|
7858
7988
|
await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
|
|
7859
7989
|
}
|
|
7860
7990
|
async function movePathFallback(root, params) {
|
|
7861
|
-
const source = await resolvePathInRoot(root, params.fromRelative
|
|
7991
|
+
const source = await resolvePathInRoot(root, params.fromRelative, {
|
|
7992
|
+
aliasErrorCode: "path-alias",
|
|
7993
|
+
allowFinalSymlink: true,
|
|
7994
|
+
});
|
|
7862
7995
|
await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
|
|
7863
7996
|
await resolvePinnedRootPathInRoot(root, {
|
|
7864
7997
|
relativePath: params.fromRelative,
|
|
7865
7998
|
policy: PATH_ALIAS_POLICIES.strict,
|
|
7866
7999
|
});
|
|
7867
|
-
const target = await resolvePathInRoot(root, params.toRelative
|
|
8000
|
+
const target = await resolvePathInRoot(root, params.toRelative, {
|
|
8001
|
+
aliasErrorCode: "path-alias",
|
|
8002
|
+
allowFinalSymlink: true,
|
|
8003
|
+
});
|
|
7868
8004
|
await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
|
|
7869
8005
|
await resolvePinnedRootPathInRoot(root, {
|
|
7870
8006
|
relativePath: params.toRelative,
|
|
7871
8007
|
policy: PATH_ALIAS_POLICIES.unlinkTarget,
|
|
7872
8008
|
});
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
8009
|
+
const targetStat = await promises_.lstat(target.resolved).catch(() => undefined);
|
|
8010
|
+
const replacesFinalSymlink = process.platform !== "win32" && params.overwrite && targetStat?.isSymbolicLink() === true;
|
|
8011
|
+
if (!replacesFinalSymlink) {
|
|
8012
|
+
try {
|
|
8013
|
+
await assertNoPathAliasEscape({
|
|
8014
|
+
absolutePath: target.resolved,
|
|
8015
|
+
rootPath: target.rootReal,
|
|
8016
|
+
boundaryLabel: "root",
|
|
8017
|
+
});
|
|
8018
|
+
}
|
|
8019
|
+
catch (error) {
|
|
8020
|
+
throw new errors_FsSafeError("path-alias", "path alias escape blocked", {
|
|
8021
|
+
cause: error instanceof Error ? error : undefined,
|
|
8022
|
+
});
|
|
8023
|
+
}
|
|
7884
8024
|
}
|
|
7885
8025
|
let sourceStat;
|
|
7886
8026
|
try {
|
|
@@ -7967,6 +8107,7 @@ async function writeFileFallback(root, params) {
|
|
|
7967
8107
|
encoding: params.encoding,
|
|
7968
8108
|
mode: mode || 0o600,
|
|
7969
8109
|
});
|
|
8110
|
+
unregisterTempPath.setIdentity(writtenStat);
|
|
7970
8111
|
const commitTempPath = tempPath;
|
|
7971
8112
|
await withAsyncDirectoryGuards([destinationGuard], async () => {
|
|
7972
8113
|
await promises_.rename(commitTempPath, destinationPath);
|
|
@@ -7994,7 +8135,9 @@ async function writeFileFallback(root, params) {
|
|
|
7994
8135
|
}
|
|
7995
8136
|
}
|
|
7996
8137
|
async function writeMissingFileFallback(root, params) {
|
|
7997
|
-
const { rootReal, resolved } = await resolvePathInRoot(root, params.relativePath
|
|
8138
|
+
const { rootReal, resolved } = await resolvePathInRoot(root, params.relativePath, {
|
|
8139
|
+
aliasErrorCode: "path-alias",
|
|
8140
|
+
});
|
|
7998
8141
|
await assertMutationNotDenied(resolved, params.denyMutations);
|
|
7999
8142
|
try {
|
|
8000
8143
|
await assertNoPathAliasEscape({
|
|
@@ -8006,14 +8149,14 @@ async function writeMissingFileFallback(root, params) {
|
|
|
8006
8149
|
catch (err) {
|
|
8007
8150
|
throw new errors_FsSafeError("path-alias", "path alias escape blocked", { cause: err });
|
|
8008
8151
|
}
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(
|
|
8152
|
+
const targetPath = params.mkdir === false
|
|
8153
|
+
? resolved
|
|
8154
|
+
: await prepareRootWriteTarget(rootReal, resolved);
|
|
8155
|
+
const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(targetPath));
|
|
8013
8156
|
let created = false;
|
|
8014
8157
|
try {
|
|
8015
8158
|
const { handle, writtenStat } = await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
8016
|
-
const handle = await promises_.open(
|
|
8159
|
+
const handle = await promises_.open(targetPath, OPEN_WRITE_CREATE_FLAGS, params.mode ?? 0o600);
|
|
8017
8160
|
created = true;
|
|
8018
8161
|
try {
|
|
8019
8162
|
if (typeof params.data === "string") {
|
|
@@ -8037,7 +8180,7 @@ async function writeMissingFileFallback(root, params) {
|
|
|
8037
8180
|
await handle.close();
|
|
8038
8181
|
await verifyAtomicWriteResult({
|
|
8039
8182
|
root,
|
|
8040
|
-
targetPath
|
|
8183
|
+
targetPath,
|
|
8041
8184
|
expectedIdentity: writtenStat,
|
|
8042
8185
|
});
|
|
8043
8186
|
created = false;
|
|
@@ -8052,87 +8195,102 @@ async function writeMissingFileFallback(root, params) {
|
|
|
8052
8195
|
}
|
|
8053
8196
|
finally {
|
|
8054
8197
|
if (created) {
|
|
8055
|
-
await promises_.rm(
|
|
8198
|
+
await promises_.rm(targetPath, { force: true }).catch(() => undefined);
|
|
8056
8199
|
}
|
|
8057
8200
|
}
|
|
8058
8201
|
}
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
});
|
|
8075
|
-
const destinationPath = target.realPath;
|
|
8076
|
-
const mode = params.mode ?? (target.stat.mode & 0o777);
|
|
8077
|
-
await target.handle.close().catch(() => { });
|
|
8078
|
-
targetClosedByUs = true;
|
|
8079
|
-
const destinationGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(destinationPath));
|
|
8080
|
-
tempPath = buildAtomicWriteTempPath(destinationPath);
|
|
8081
|
-
unregisterTempPath = registerTempPathForExit(tempPath);
|
|
8082
|
-
tempHandle = await promises_.open(tempPath, OPEN_WRITE_CREATE_FLAGS, mode || 0o600);
|
|
8083
|
-
const sourceStream = createBoundedReadStream(source, params.maxBytes);
|
|
8084
|
-
const targetStream = tempHandle.createWriteStream();
|
|
8085
|
-
sourceStream.once("close", () => {
|
|
8086
|
-
sourceClosedByStream = true;
|
|
8087
|
-
});
|
|
8088
|
-
targetStream.once("close", () => {
|
|
8089
|
-
tempClosedByStream = true;
|
|
8090
|
-
});
|
|
8091
|
-
await (0,external_node_stream_promises_.pipeline)(sourceStream, targetStream);
|
|
8092
|
-
const writtenStat = await promises_.stat(tempPath);
|
|
8093
|
-
if (!tempClosedByStream) {
|
|
8094
|
-
await tempHandle.close().catch(() => { });
|
|
8095
|
-
tempClosedByStream = true;
|
|
8096
|
-
}
|
|
8097
|
-
tempHandle = null;
|
|
8098
|
-
const commitTempPath = tempPath;
|
|
8099
|
-
await withAsyncDirectoryGuards([destinationGuard], async () => {
|
|
8100
|
-
await promises_.rename(commitTempPath, destinationPath);
|
|
8101
|
-
});
|
|
8102
|
-
tempPath = null;
|
|
8103
|
-
unregisterTempPath();
|
|
8104
|
-
unregisterTempPath = null;
|
|
8105
|
-
try {
|
|
8106
|
-
await verifyAtomicWriteResult({
|
|
8107
|
-
root,
|
|
8108
|
-
targetPath: destinationPath,
|
|
8109
|
-
expectedIdentity: writtenStat,
|
|
8110
|
-
});
|
|
8202
|
+
|
|
8203
|
+
;// CONCATENATED MODULE: ../core/src/gateways/read-file-no-follow.ts
|
|
8204
|
+
/**
|
|
8205
|
+
* Atomic file read with symlink and size protection.
|
|
8206
|
+
*/
|
|
8207
|
+
|
|
8208
|
+
function isEloopError(error) {
|
|
8209
|
+
return error instanceof Error && "code" in error && error.code === "ELOOP";
|
|
8210
|
+
}
|
|
8211
|
+
async function openWithoutFollowingSymlinks(filePath, safePath) {
|
|
8212
|
+
const hasNoFollow = typeof external_node_fs_.constants.O_NOFOLLOW === "number" && external_node_fs_.constants.O_NOFOLLOW !== 0;
|
|
8213
|
+
if (!hasNoFollow) {
|
|
8214
|
+
const stats = await (0,promises_.lstat)(filePath);
|
|
8215
|
+
if (stats.isSymbolicLink()) {
|
|
8216
|
+
throw new Error(`Symlinks are not allowed: ${safePath}`);
|
|
8111
8217
|
}
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8218
|
+
return (0,promises_.open)(filePath, external_node_fs_.constants.O_RDONLY);
|
|
8219
|
+
}
|
|
8220
|
+
try {
|
|
8221
|
+
return await (0,promises_.open)(filePath, external_node_fs_.constants.O_RDONLY | external_node_fs_.constants.O_NOFOLLOW);
|
|
8222
|
+
} catch (error) {
|
|
8223
|
+
if (isEloopError(error)) {
|
|
8224
|
+
throw new Error(`Symlinks are not allowed: ${safePath}`);
|
|
8115
8225
|
}
|
|
8226
|
+
throw error;
|
|
8116
8227
|
}
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8228
|
+
}
|
|
8229
|
+
/**
|
|
8230
|
+
* Reads a file atomically with symlink and size protection.
|
|
8231
|
+
*
|
|
8232
|
+
* Uses `O_NOFOLLOW` (where available) to atomically reject symlinks at
|
|
8233
|
+
* the kernel level, eliminating the TOCTOU window between `lstat()` and
|
|
8234
|
+
* `readFile()`. Falls back to `lstat()` on platforms without `O_NOFOLLOW`.
|
|
8235
|
+
* Validates file size via `fstat()` on the opened file descriptor so the
|
|
8236
|
+
* size check and the read operate on the same inode.
|
|
8237
|
+
*/ async function readFileNoFollow(filePath, maxBytes) {
|
|
8238
|
+
const safePath = JSON.stringify(filePath);
|
|
8239
|
+
const fh = await openWithoutFollowingSymlinks(filePath, safePath);
|
|
8240
|
+
try {
|
|
8241
|
+
const fdStats = await fh.stat();
|
|
8242
|
+
if (fdStats.size > maxBytes) {
|
|
8243
|
+
throw new Error(`File ${safePath} exceeds size limit (${fdStats.size} bytes > ${maxBytes} bytes)`);
|
|
8120
8244
|
}
|
|
8121
|
-
|
|
8245
|
+
return await fh.readFile("utf-8");
|
|
8246
|
+
} finally{
|
|
8247
|
+
await fh.close();
|
|
8122
8248
|
}
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8249
|
+
}
|
|
8250
|
+
|
|
8251
|
+
;// CONCATENATED MODULE: ../core/src/gateways/symlink-path-guard.ts
|
|
8252
|
+
/**
|
|
8253
|
+
* Path-segment symlink rejection helpers for root-bounded FS ops.
|
|
8254
|
+
*/
|
|
8255
|
+
|
|
8256
|
+
|
|
8257
|
+
function isEnoentError(error) {
|
|
8258
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
8259
|
+
}
|
|
8260
|
+
function symlinkNotAllowedError(relativePath) {
|
|
8261
|
+
return new Error(`Symlinks are not allowed: path contains symbolic link: ${JSON.stringify(relativePath)}`, {
|
|
8262
|
+
cause: new errors_FsSafeError("symlink", "symlink not allowed")
|
|
8263
|
+
});
|
|
8264
|
+
}
|
|
8265
|
+
async function lstatIfExists(path) {
|
|
8266
|
+
try {
|
|
8267
|
+
return await (0,promises_.lstat)(path);
|
|
8268
|
+
} catch (error) {
|
|
8269
|
+
if (isEnoentError(error)) {
|
|
8270
|
+
return undefined;
|
|
8126
8271
|
}
|
|
8127
|
-
|
|
8128
|
-
|
|
8272
|
+
throw error;
|
|
8273
|
+
}
|
|
8274
|
+
}
|
|
8275
|
+
/**
|
|
8276
|
+
* Walk path segments under rootPath and reject any symbolic-link component.
|
|
8277
|
+
* Stops at the first missing segment (ENOENT).
|
|
8278
|
+
*/ async function rejectSymlinkSegments(rootPath, relativeFromRoot, onSymlink) {
|
|
8279
|
+
if (!relativeFromRoot) {
|
|
8280
|
+
return;
|
|
8281
|
+
}
|
|
8282
|
+
let currentPath = rootPath;
|
|
8283
|
+
for (const segment of relativeFromRoot.split(external_node_path_.sep)){
|
|
8284
|
+
if (!segment || segment === ".") {
|
|
8285
|
+
continue;
|
|
8129
8286
|
}
|
|
8130
|
-
|
|
8131
|
-
|
|
8287
|
+
currentPath = (0,external_node_path_.join)(currentPath, segment);
|
|
8288
|
+
const stats = await lstatIfExists(currentPath);
|
|
8289
|
+
if (stats === undefined) {
|
|
8290
|
+
return;
|
|
8132
8291
|
}
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
await target.handle.close().catch(() => { });
|
|
8292
|
+
if (stats.isSymbolicLink()) {
|
|
8293
|
+
throw onSymlink(currentPath);
|
|
8136
8294
|
}
|
|
8137
8295
|
}
|
|
8138
8296
|
}
|
|
@@ -8144,6 +8302,8 @@ async function copyFileFallback(root, params, source) {
|
|
|
8144
8302
|
|
|
8145
8303
|
|
|
8146
8304
|
|
|
8305
|
+
|
|
8306
|
+
|
|
8147
8307
|
/**
|
|
8148
8308
|
* Checks if a file or directory exists.
|
|
8149
8309
|
*/ async function fileExists(path) {
|
|
@@ -8155,28 +8315,34 @@ async function copyFileFallback(root, params, source) {
|
|
|
8155
8315
|
}
|
|
8156
8316
|
}
|
|
8157
8317
|
function isPathWithinRoot(rootPath, candidatePath) {
|
|
8158
|
-
|
|
8318
|
+
if (candidatePath === rootPath) {
|
|
8319
|
+
return true;
|
|
8320
|
+
}
|
|
8321
|
+
// When root is `/` (or `C:\`), appending sep would yield `//` / `C:\\` and
|
|
8322
|
+
// incorrectly reject every in-root path. Use root as the prefix when it
|
|
8323
|
+
// already ends with the separator.
|
|
8324
|
+
const prefix = rootPath.endsWith(external_node_path_.sep) ? rootPath : `${rootPath}${external_node_path_.sep}`;
|
|
8325
|
+
return candidatePath.startsWith(prefix);
|
|
8159
8326
|
}
|
|
8160
8327
|
function mapFsSafeError(error, relativePath) {
|
|
8161
|
-
if (error instanceof errors_FsSafeError) {
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
}
|
|
8328
|
+
if (!(error instanceof errors_FsSafeError)) {
|
|
8329
|
+
throw error;
|
|
8330
|
+
}
|
|
8331
|
+
const safePath = JSON.stringify(relativePath);
|
|
8332
|
+
if (error.code === "outside-workspace" || error.code === "invalid-path") {
|
|
8333
|
+
throw new Error(`Resolved path is outside target directory: ${safePath}`, {
|
|
8334
|
+
cause: error
|
|
8335
|
+
});
|
|
8336
|
+
}
|
|
8337
|
+
if (error.code === "path-alias" || error.code === "symlink") {
|
|
8338
|
+
throw new Error(`Symlinks are not allowed: path contains symbolic link: ${safePath}`, {
|
|
8339
|
+
cause: error
|
|
8340
|
+
});
|
|
8341
|
+
}
|
|
8342
|
+
if (error.code === "too-large") {
|
|
8343
|
+
throw new Error(`File ${safePath} exceeds size limit: ${error.message}`, {
|
|
8344
|
+
cause: error
|
|
8345
|
+
});
|
|
8180
8346
|
}
|
|
8181
8347
|
throw error;
|
|
8182
8348
|
}
|
|
@@ -8187,8 +8353,20 @@ async function createSafeRoot(targetDir, maxBytes) {
|
|
|
8187
8353
|
symlinks: "reject"
|
|
8188
8354
|
});
|
|
8189
8355
|
}
|
|
8356
|
+
/**
|
|
8357
|
+
* fs-safe 0.5 exists/stat/list canonicalize through in-root symlinks; open/read
|
|
8358
|
+
* still honor symlinks:"reject". Enforce reject on every root-bounded op.
|
|
8359
|
+
*/ async function assertNoSymlinksWithinRoot(targetDir, relativePath) {
|
|
8360
|
+
if (!relativePath) {
|
|
8361
|
+
return;
|
|
8362
|
+
}
|
|
8363
|
+
const absolutePath = await file_system_utils_resolvePathWithinRoot(targetDir, relativePath);
|
|
8364
|
+
const rootPath = await (0,promises_.realpath)(targetDir);
|
|
8365
|
+
await rejectSymlinkSegments(rootPath, (0,external_node_path_.relative)(rootPath, absolutePath), ()=>symlinkNotAllowedError(relativePath));
|
|
8366
|
+
}
|
|
8190
8367
|
async function file_system_utils_readTextWithinRoot(targetDir, relativePath, maxBytes) {
|
|
8191
8368
|
try {
|
|
8369
|
+
await assertNoSymlinksWithinRoot(targetDir, relativePath);
|
|
8192
8370
|
const safeRoot = await createSafeRoot(targetDir, maxBytes);
|
|
8193
8371
|
return await safeRoot.readText(relativePath, {
|
|
8194
8372
|
maxBytes
|
|
@@ -8199,6 +8377,7 @@ async function file_system_utils_readTextWithinRoot(targetDir, relativePath, max
|
|
|
8199
8377
|
}
|
|
8200
8378
|
async function file_system_utils_listDirectoryWithinRoot(targetDir, relativePath) {
|
|
8201
8379
|
try {
|
|
8380
|
+
await assertNoSymlinksWithinRoot(targetDir, relativePath);
|
|
8202
8381
|
const safeRoot = await createSafeRoot(targetDir);
|
|
8203
8382
|
const entries = await safeRoot.list(relativePath, {
|
|
8204
8383
|
withFileTypes: true
|
|
@@ -8218,6 +8397,7 @@ function toSafeDirEntry(entry) {
|
|
|
8218
8397
|
}
|
|
8219
8398
|
async function file_system_utils_pathExistsWithinRoot(targetDir, relativePath) {
|
|
8220
8399
|
try {
|
|
8400
|
+
await assertNoSymlinksWithinRoot(targetDir, relativePath);
|
|
8221
8401
|
const safeRoot = await createSafeRoot(targetDir);
|
|
8222
8402
|
return await safeRoot.exists(relativePath);
|
|
8223
8403
|
} catch (error) {
|
|
@@ -8234,6 +8414,7 @@ async function file_system_utils_pathExistsWithinRoot(targetDir, relativePath) {
|
|
|
8234
8414
|
}
|
|
8235
8415
|
async function statWithinRoot(targetDir, relativePath) {
|
|
8236
8416
|
try {
|
|
8417
|
+
await assertNoSymlinksWithinRoot(targetDir, relativePath);
|
|
8237
8418
|
const safeRoot = await createSafeRoot(targetDir);
|
|
8238
8419
|
return await safeRoot.stat(relativePath);
|
|
8239
8420
|
} catch (error) {
|
|
@@ -8260,26 +8441,7 @@ async function statWithinRoot(targetDir, relativePath) {
|
|
|
8260
8441
|
if (!isPathWithinRoot(rootPath, absolutePath)) {
|
|
8261
8442
|
throw new Error(`Resolved path is outside target directory: ${absolutePath}`);
|
|
8262
8443
|
}
|
|
8263
|
-
|
|
8264
|
-
if (!relativePath || relativePath === ".") {
|
|
8265
|
-
return;
|
|
8266
|
-
}
|
|
8267
|
-
const segments = relativePath.split(external_node_path_.sep);
|
|
8268
|
-
let currentPath = rootPath;
|
|
8269
|
-
for (const segment of segments){
|
|
8270
|
-
currentPath = (0,external_node_path_.join)(currentPath, segment);
|
|
8271
|
-
try {
|
|
8272
|
-
const stats = await (0,promises_.lstat)(currentPath);
|
|
8273
|
-
if (stats.isSymbolicLink()) {
|
|
8274
|
-
throw new Error(`Path contains symbolic link: ${currentPath}`);
|
|
8275
|
-
}
|
|
8276
|
-
} catch (error) {
|
|
8277
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
8278
|
-
return;
|
|
8279
|
-
}
|
|
8280
|
-
throw error;
|
|
8281
|
-
}
|
|
8282
|
-
}
|
|
8444
|
+
await rejectSymlinkSegments(rootPath, (0,external_node_path_.relative)(rootPath, absolutePath), (segmentPath)=>new Error(`Path contains symbolic link: ${segmentPath}`));
|
|
8283
8445
|
}
|
|
8284
8446
|
/**
|
|
8285
8447
|
* Resolves a relative path under targetDir and validates it does not pass through symlinks.
|
|
@@ -8296,44 +8458,6 @@ async function statWithinRoot(targetDir, relativePath) {
|
|
|
8296
8458
|
throw new Error(`${context} exceeds size limit (${fileStats.size} bytes > ${maxBytes} bytes)`);
|
|
8297
8459
|
}
|
|
8298
8460
|
}
|
|
8299
|
-
/**
|
|
8300
|
-
* Reads a file atomically with symlink and size protection.
|
|
8301
|
-
*
|
|
8302
|
-
* Uses `O_NOFOLLOW` (where available) to atomically reject symlinks at
|
|
8303
|
-
* the kernel level, eliminating the TOCTOU window between `lstat()` and
|
|
8304
|
-
* `readFile()`. Falls back to `lstat()` on platforms without `O_NOFOLLOW`.
|
|
8305
|
-
* Validates file size via `fstat()` on the opened file descriptor so the
|
|
8306
|
-
* size check and the read operate on the same inode.
|
|
8307
|
-
*/ async function readFileNoFollow(filePath, maxBytes) {
|
|
8308
|
-
const hasNoFollow = typeof external_node_fs_.constants.O_NOFOLLOW === "number" && external_node_fs_.constants.O_NOFOLLOW !== 0;
|
|
8309
|
-
const safePath = JSON.stringify(filePath);
|
|
8310
|
-
let fh;
|
|
8311
|
-
if (hasNoFollow) {
|
|
8312
|
-
try {
|
|
8313
|
-
fh = await (0,promises_.open)(filePath, external_node_fs_.constants.O_RDONLY | external_node_fs_.constants.O_NOFOLLOW);
|
|
8314
|
-
} catch (error) {
|
|
8315
|
-
if (error instanceof Error && "code" in error && error.code === "ELOOP") {
|
|
8316
|
-
throw new Error(`Symlinks are not allowed: ${safePath}`);
|
|
8317
|
-
}
|
|
8318
|
-
throw error;
|
|
8319
|
-
}
|
|
8320
|
-
} else {
|
|
8321
|
-
const stats = await (0,promises_.lstat)(filePath);
|
|
8322
|
-
if (stats.isSymbolicLink()) {
|
|
8323
|
-
throw new Error(`Symlinks are not allowed: ${safePath}`);
|
|
8324
|
-
}
|
|
8325
|
-
fh = await (0,promises_.open)(filePath, external_node_fs_.constants.O_RDONLY);
|
|
8326
|
-
}
|
|
8327
|
-
try {
|
|
8328
|
-
const fdStats = await fh.stat();
|
|
8329
|
-
if (fdStats.size > maxBytes) {
|
|
8330
|
-
throw new Error(`File ${safePath} exceeds size limit (${fdStats.size} bytes > ${maxBytes} bytes)`);
|
|
8331
|
-
}
|
|
8332
|
-
return await fh.readFile("utf-8");
|
|
8333
|
-
} finally{
|
|
8334
|
-
await fh.close();
|
|
8335
|
-
}
|
|
8336
|
-
}
|
|
8337
8461
|
|
|
8338
8462
|
;// CONCATENATED MODULE: ../core/src/gateways/init-hooks-config-gateway.ts
|
|
8339
8463
|
|
|
@@ -20709,8 +20833,6 @@ function _parseInput(input) {
|
|
|
20709
20833
|
}
|
|
20710
20834
|
|
|
20711
20835
|
|
|
20712
|
-
// EXTERNAL MODULE: external "node:module"
|
|
20713
|
-
var external_node_module_ = __webpack_require__(8995);
|
|
20714
20836
|
// EXTERNAL MODULE: ../../node_modules/jiti/dist/jiti.cjs
|
|
20715
20837
|
var jiti = __webpack_require__(3273);
|
|
20716
20838
|
;// CONCATENATED MODULE: ../../node_modules/jiti/lib/jiti.mjs
|
|
@@ -22395,6 +22517,8 @@ async function readVersionFileContent(targetDir, relativePath) {
|
|
|
22395
22517
|
return new FileSystemEnvironmentGateway(cwd);
|
|
22396
22518
|
}
|
|
22397
22519
|
|
|
22520
|
+
// EXTERNAL MODULE: external "node:child_process"
|
|
22521
|
+
var external_node_child_process_ = __webpack_require__(1421);
|
|
22398
22522
|
;// CONCATENATED MODULE: ../../node_modules/universal-user-agent/index.js
|
|
22399
22523
|
function getUserAgent() {
|
|
22400
22524
|
if (typeof navigator === "object" && "userAgent" in navigator) {
|
|
@@ -27525,6 +27649,312 @@ const PackageJsonSchema = schemas_object({
|
|
|
27525
27649
|
return new FileSystemSkillFileGateway();
|
|
27526
27650
|
}
|
|
27527
27651
|
|
|
27652
|
+
;// CONCATENATED MODULE: ../core/src/entities/agentic-location-catalog.ts
|
|
27653
|
+
/**
|
|
27654
|
+
* Canonical catalog of agentic construct locations shared by lint and doctor.
|
|
27655
|
+
* Pure data only — no filesystem I/O.
|
|
27656
|
+
*/ const agentic_location_catalog_AGENTIC_LOCATION_CATALOG = [
|
|
27657
|
+
// Skill lint roots
|
|
27658
|
+
{
|
|
27659
|
+
id: "skill-github",
|
|
27660
|
+
path: ".github/skills",
|
|
27661
|
+
matchKind: "directory-prefix",
|
|
27662
|
+
primaryConstruct: "skill",
|
|
27663
|
+
lintTarget: "skills",
|
|
27664
|
+
harnessHints: [
|
|
27665
|
+
"copilot"
|
|
27666
|
+
]
|
|
27667
|
+
},
|
|
27668
|
+
{
|
|
27669
|
+
id: "skill-claude",
|
|
27670
|
+
path: ".claude/skills",
|
|
27671
|
+
matchKind: "directory-prefix",
|
|
27672
|
+
primaryConstruct: "skill",
|
|
27673
|
+
lintTarget: "skills",
|
|
27674
|
+
harnessHints: [
|
|
27675
|
+
"claude"
|
|
27676
|
+
]
|
|
27677
|
+
},
|
|
27678
|
+
{
|
|
27679
|
+
id: "skill-agents",
|
|
27680
|
+
path: ".agents/skills",
|
|
27681
|
+
matchKind: "directory-prefix",
|
|
27682
|
+
primaryConstruct: "skill",
|
|
27683
|
+
lintTarget: "skills",
|
|
27684
|
+
harnessHints: [
|
|
27685
|
+
"shared"
|
|
27686
|
+
]
|
|
27687
|
+
},
|
|
27688
|
+
// Doctor-only skill locations
|
|
27689
|
+
{
|
|
27690
|
+
id: "skill-pi",
|
|
27691
|
+
path: ".pi/skills",
|
|
27692
|
+
matchKind: "directory-prefix",
|
|
27693
|
+
primaryConstruct: "skill",
|
|
27694
|
+
lintTarget: "none",
|
|
27695
|
+
harnessHints: [
|
|
27696
|
+
"pi"
|
|
27697
|
+
]
|
|
27698
|
+
},
|
|
27699
|
+
{
|
|
27700
|
+
id: "skill-pi-prompts",
|
|
27701
|
+
path: ".pi/prompts",
|
|
27702
|
+
matchKind: "directory-prefix",
|
|
27703
|
+
primaryConstruct: "skill",
|
|
27704
|
+
lintTarget: "none",
|
|
27705
|
+
harnessHints: [
|
|
27706
|
+
"pi"
|
|
27707
|
+
]
|
|
27708
|
+
},
|
|
27709
|
+
// Agent lint root (dual-role: agent + instruction)
|
|
27710
|
+
{
|
|
27711
|
+
id: "agent-github",
|
|
27712
|
+
path: ".github/agents",
|
|
27713
|
+
matchKind: "directory-prefix",
|
|
27714
|
+
primaryConstruct: "agent",
|
|
27715
|
+
secondaryConstructs: [
|
|
27716
|
+
"instruction"
|
|
27717
|
+
],
|
|
27718
|
+
lintTarget: "agents",
|
|
27719
|
+
harnessHints: [
|
|
27720
|
+
"copilot"
|
|
27721
|
+
],
|
|
27722
|
+
instructionFormat: "copilot-agent"
|
|
27723
|
+
},
|
|
27724
|
+
// Hook lint exact configs
|
|
27725
|
+
{
|
|
27726
|
+
id: "hook-copilot-agent-shell",
|
|
27727
|
+
path: ".github/hooks/agent-shell/hooks.json",
|
|
27728
|
+
matchKind: "exact",
|
|
27729
|
+
primaryConstruct: "hook",
|
|
27730
|
+
lintTarget: "hooks",
|
|
27731
|
+
harnessHints: [
|
|
27732
|
+
"copilot"
|
|
27733
|
+
],
|
|
27734
|
+
hookPlatform: "copilot"
|
|
27735
|
+
},
|
|
27736
|
+
{
|
|
27737
|
+
id: "hook-claude-settings",
|
|
27738
|
+
path: ".claude/settings.json",
|
|
27739
|
+
matchKind: "exact",
|
|
27740
|
+
primaryConstruct: "hook",
|
|
27741
|
+
lintTarget: "hooks",
|
|
27742
|
+
harnessHints: [
|
|
27743
|
+
"claude"
|
|
27744
|
+
],
|
|
27745
|
+
hookPlatform: "claude"
|
|
27746
|
+
},
|
|
27747
|
+
{
|
|
27748
|
+
id: "hook-claude-settings-local",
|
|
27749
|
+
path: ".claude/settings.local.json",
|
|
27750
|
+
matchKind: "exact",
|
|
27751
|
+
primaryConstruct: "hook",
|
|
27752
|
+
lintTarget: "hooks",
|
|
27753
|
+
harnessHints: [
|
|
27754
|
+
"claude"
|
|
27755
|
+
],
|
|
27756
|
+
hookPlatform: "claude"
|
|
27757
|
+
},
|
|
27758
|
+
// Doctor-only hook directory prefixes
|
|
27759
|
+
{
|
|
27760
|
+
id: "hook-github-dir",
|
|
27761
|
+
path: ".github/hooks",
|
|
27762
|
+
matchKind: "directory-prefix",
|
|
27763
|
+
primaryConstruct: "hook",
|
|
27764
|
+
lintTarget: "none",
|
|
27765
|
+
harnessHints: [
|
|
27766
|
+
"copilot"
|
|
27767
|
+
]
|
|
27768
|
+
},
|
|
27769
|
+
{
|
|
27770
|
+
id: "hook-claude-dir",
|
|
27771
|
+
path: ".claude/hooks",
|
|
27772
|
+
matchKind: "directory-prefix",
|
|
27773
|
+
primaryConstruct: "hook",
|
|
27774
|
+
lintTarget: "none",
|
|
27775
|
+
harnessHints: [
|
|
27776
|
+
"claude"
|
|
27777
|
+
]
|
|
27778
|
+
},
|
|
27779
|
+
// Instruction lint locations
|
|
27780
|
+
{
|
|
27781
|
+
id: "instruction-copilot-root",
|
|
27782
|
+
path: ".github/copilot-instructions.md",
|
|
27783
|
+
matchKind: "exact",
|
|
27784
|
+
primaryConstruct: "instruction",
|
|
27785
|
+
lintTarget: "instructions",
|
|
27786
|
+
harnessHints: [
|
|
27787
|
+
"copilot"
|
|
27788
|
+
],
|
|
27789
|
+
instructionFormat: "copilot-instructions"
|
|
27790
|
+
},
|
|
27791
|
+
{
|
|
27792
|
+
id: "instruction-copilot-scoped",
|
|
27793
|
+
path: ".github/instructions",
|
|
27794
|
+
matchKind: "directory-prefix",
|
|
27795
|
+
primaryConstruct: "instruction",
|
|
27796
|
+
lintTarget: "instructions",
|
|
27797
|
+
harnessHints: [
|
|
27798
|
+
"copilot"
|
|
27799
|
+
],
|
|
27800
|
+
instructionFormat: "copilot-scoped"
|
|
27801
|
+
},
|
|
27802
|
+
{
|
|
27803
|
+
id: "instruction-agents-md",
|
|
27804
|
+
path: "AGENTS.md",
|
|
27805
|
+
matchKind: "exact",
|
|
27806
|
+
primaryConstruct: "instruction",
|
|
27807
|
+
lintTarget: "instructions",
|
|
27808
|
+
harnessHints: [
|
|
27809
|
+
"shared"
|
|
27810
|
+
],
|
|
27811
|
+
instructionFormat: "agents-md"
|
|
27812
|
+
},
|
|
27813
|
+
{
|
|
27814
|
+
id: "instruction-claude-md",
|
|
27815
|
+
path: "CLAUDE.md",
|
|
27816
|
+
matchKind: "exact",
|
|
27817
|
+
primaryConstruct: "instruction",
|
|
27818
|
+
lintTarget: "instructions",
|
|
27819
|
+
harnessHints: [
|
|
27820
|
+
"claude"
|
|
27821
|
+
],
|
|
27822
|
+
instructionFormat: "claude-md"
|
|
27823
|
+
},
|
|
27824
|
+
// Doctor-only construct locations
|
|
27825
|
+
{
|
|
27826
|
+
id: "subagent-claude",
|
|
27827
|
+
path: ".claude/agents",
|
|
27828
|
+
matchKind: "directory-prefix",
|
|
27829
|
+
primaryConstruct: "subagent",
|
|
27830
|
+
lintTarget: "none",
|
|
27831
|
+
harnessHints: [
|
|
27832
|
+
"claude"
|
|
27833
|
+
]
|
|
27834
|
+
},
|
|
27835
|
+
{
|
|
27836
|
+
id: "agent-claude-commands",
|
|
27837
|
+
path: ".claude/commands",
|
|
27838
|
+
matchKind: "directory-prefix",
|
|
27839
|
+
primaryConstruct: "agent",
|
|
27840
|
+
lintTarget: "none",
|
|
27841
|
+
harnessHints: [
|
|
27842
|
+
"claude"
|
|
27843
|
+
]
|
|
27844
|
+
},
|
|
27845
|
+
{
|
|
27846
|
+
id: "plugin-codex",
|
|
27847
|
+
path: ".codex-plugin",
|
|
27848
|
+
matchKind: "directory-prefix",
|
|
27849
|
+
primaryConstruct: "plugin",
|
|
27850
|
+
lintTarget: "none",
|
|
27851
|
+
harnessHints: [
|
|
27852
|
+
"codex"
|
|
27853
|
+
]
|
|
27854
|
+
}
|
|
27855
|
+
];
|
|
27856
|
+
|
|
27857
|
+
;// CONCATENATED MODULE: ../core/src/lib/agentic-location-matchers.ts
|
|
27858
|
+
/**
|
|
27859
|
+
* Pure path→construct matching helpers over the agentic location catalog.
|
|
27860
|
+
* No filesystem I/O.
|
|
27861
|
+
*/
|
|
27862
|
+
/**
|
|
27863
|
+
* Normalizes a repo-relative path for catalog matching.
|
|
27864
|
+
* Collapses `.` / `..` segments. Returns "" when the path is absolute or
|
|
27865
|
+
* escapes the repository root — callers treat "" as non-matching.
|
|
27866
|
+
*/ function normalizeRepoRelativePath(path) {
|
|
27867
|
+
const normalized = path.replaceAll("\\", "/");
|
|
27868
|
+
// POSIX absolute, UNC-style leading slash after backslash conversion,
|
|
27869
|
+
// and Windows drive-letter absolute (C:/..., d:\...).
|
|
27870
|
+
if (normalized.startsWith("/") || /^[A-Za-z]:(\/|$)/.test(normalized)) {
|
|
27871
|
+
return "";
|
|
27872
|
+
}
|
|
27873
|
+
const resolved = [];
|
|
27874
|
+
for (const segment of normalized.split("/")){
|
|
27875
|
+
if (segment === "" || segment === ".") {
|
|
27876
|
+
continue;
|
|
27877
|
+
}
|
|
27878
|
+
if (segment === "..") {
|
|
27879
|
+
if (resolved.length === 0) {
|
|
27880
|
+
return "";
|
|
27881
|
+
}
|
|
27882
|
+
resolved.pop();
|
|
27883
|
+
continue;
|
|
27884
|
+
}
|
|
27885
|
+
resolved.push(segment);
|
|
27886
|
+
}
|
|
27887
|
+
return resolved.join("/");
|
|
27888
|
+
}
|
|
27889
|
+
function matchesLocationEntry(path, entry) {
|
|
27890
|
+
const normalized = normalizeRepoRelativePath(path);
|
|
27891
|
+
if (entry.matchKind === "exact") {
|
|
27892
|
+
return normalized === entry.path;
|
|
27893
|
+
}
|
|
27894
|
+
return normalized === entry.path || normalized.startsWith(`${entry.path}/`);
|
|
27895
|
+
}
|
|
27896
|
+
function entriesMatchingPath(path, catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
27897
|
+
return catalog.filter((entry)=>matchesLocationEntry(path, entry));
|
|
27898
|
+
}
|
|
27899
|
+
function specificityScore(entry) {
|
|
27900
|
+
// Exact matches outrank any prefix; longer prefixes outrank shorter ones.
|
|
27901
|
+
const kindBonus = entry.matchKind === "exact" ? 1_000_000 : 0;
|
|
27902
|
+
return kindBonus + entry.path.length;
|
|
27903
|
+
}
|
|
27904
|
+
function bestMatchingEntry(path, catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
27905
|
+
const matches = entriesMatchingPath(path, catalog);
|
|
27906
|
+
if (matches.length === 0) {
|
|
27907
|
+
return null;
|
|
27908
|
+
}
|
|
27909
|
+
let best = matches[0];
|
|
27910
|
+
let bestScore = specificityScore(best);
|
|
27911
|
+
for(let i = 1; i < matches.length; i++){
|
|
27912
|
+
const candidate = matches[i];
|
|
27913
|
+
const score = specificityScore(candidate);
|
|
27914
|
+
if (score > bestScore) {
|
|
27915
|
+
best = candidate;
|
|
27916
|
+
bestScore = score;
|
|
27917
|
+
}
|
|
27918
|
+
}
|
|
27919
|
+
return best;
|
|
27920
|
+
}
|
|
27921
|
+
function primaryConstructTypeForPath(path, catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
27922
|
+
return bestMatchingEntry(path, catalog)?.primaryConstruct ?? null;
|
|
27923
|
+
}
|
|
27924
|
+
function constructTypesForPath(path, catalog = AGENTIC_LOCATION_CATALOG) {
|
|
27925
|
+
const best = bestMatchingEntry(path, catalog);
|
|
27926
|
+
if (!best) {
|
|
27927
|
+
return [];
|
|
27928
|
+
}
|
|
27929
|
+
if (best.secondaryConstructs && best.secondaryConstructs.length > 0) {
|
|
27930
|
+
return [
|
|
27931
|
+
best.primaryConstruct,
|
|
27932
|
+
...best.secondaryConstructs
|
|
27933
|
+
];
|
|
27934
|
+
}
|
|
27935
|
+
return [
|
|
27936
|
+
best.primaryConstruct
|
|
27937
|
+
];
|
|
27938
|
+
}
|
|
27939
|
+
function lintTargetEntries(target, catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
27940
|
+
return catalog.filter((entry)=>entry.lintTarget === target);
|
|
27941
|
+
}
|
|
27942
|
+
function skillDirectoryRoots(catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
27943
|
+
return lintTargetEntries("skills", catalog).filter((entry)=>entry.matchKind === "directory-prefix").map((entry)=>entry.path);
|
|
27944
|
+
}
|
|
27945
|
+
function agentDirectoryRoots(catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
27946
|
+
return lintTargetEntries("agents", catalog).filter((entry)=>entry.matchKind === "directory-prefix").map((entry)=>entry.path);
|
|
27947
|
+
}
|
|
27948
|
+
function hookConfigPaths(catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
27949
|
+
return lintTargetEntries("hooks", catalog).filter((entry)=>entry.matchKind === "exact" && entry.hookPlatform != null).map((entry)=>({
|
|
27950
|
+
relativePath: entry.path,
|
|
27951
|
+
platform: entry.hookPlatform
|
|
27952
|
+
}));
|
|
27953
|
+
}
|
|
27954
|
+
function instructionDiscoveryEntries(catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
27955
|
+
return catalog.filter((entry)=>entry.instructionFormat != null);
|
|
27956
|
+
}
|
|
27957
|
+
|
|
27528
27958
|
;// CONCATENATED MODULE: ../core/src/gateways/skill-lint-gateway.ts
|
|
27529
27959
|
/**
|
|
27530
27960
|
* Gateway for skill lint file system operations.
|
|
@@ -27533,14 +27963,9 @@ const PackageJsonSchema = schemas_object({
|
|
|
27533
27963
|
|
|
27534
27964
|
|
|
27535
27965
|
|
|
27966
|
+
|
|
27536
27967
|
/** Maximum skill file size: 1 MB */ const MAX_SKILL_FILE_BYTES = 1_048_576;
|
|
27537
|
-
/**
|
|
27538
|
-
* Skill directory locations to search for SKILL.md files.
|
|
27539
|
-
*/ const SKILL_DIRECTORIES = [
|
|
27540
|
-
(0,external_node_path_.join)(".github", "skills"),
|
|
27541
|
-
(0,external_node_path_.join)(".claude", "skills"),
|
|
27542
|
-
(0,external_node_path_.join)(".agents", "skills")
|
|
27543
|
-
];
|
|
27968
|
+
/** Catalog skill roots converted to OS-native relative paths. */ const SKILL_DIRECTORIES = skillDirectoryRoots().map((root)=>(0,external_node_path_.join)(...root.split("/")));
|
|
27544
27969
|
/** Maximum lock file size: 256 KB */ const MAX_LOCK_FILE_BYTES = 262_144;
|
|
27545
27970
|
/** Relative path to the skills lock file. */ const SKILLS_LOCK_PATH = "skills-lock.json";
|
|
27546
27971
|
/** Zod schema for skills-lock.json structure */ const SkillsLockSchema = schemas_object({
|
|
@@ -29677,7 +30102,10 @@ const HARNESS_FOOTPRINTS = {
|
|
|
29677
30102
|
],
|
|
29678
30103
|
primaryIndicators: [
|
|
29679
30104
|
".github/copilot-instructions.md",
|
|
29680
|
-
".github/instructions/"
|
|
30105
|
+
".github/instructions/",
|
|
30106
|
+
".github/skills/",
|
|
30107
|
+
".github/agents/",
|
|
30108
|
+
".github/hooks/"
|
|
29681
30109
|
],
|
|
29682
30110
|
crossRefMechanisms: [
|
|
29683
30111
|
{
|
|
@@ -29996,6 +30424,7 @@ async function enumerateMcpServers(repoRoot) {
|
|
|
29996
30424
|
|
|
29997
30425
|
|
|
29998
30426
|
|
|
30427
|
+
|
|
29999
30428
|
const MAX_FILE_BYTES = 1_048_576;
|
|
30000
30429
|
const SCANNABLE_EXTENSIONS = new Set([
|
|
30001
30430
|
".md",
|
|
@@ -30023,22 +30452,7 @@ function isMarkdownFile(name) {
|
|
|
30023
30452
|
return name.endsWith(".md") || name.endsWith(".mdc");
|
|
30024
30453
|
}
|
|
30025
30454
|
function determineConstructType(relPath) {
|
|
30026
|
-
|
|
30027
|
-
return "skill";
|
|
30028
|
-
}
|
|
30029
|
-
if (relPath.startsWith(".codex-plugin/")) {
|
|
30030
|
-
return "plugin";
|
|
30031
|
-
}
|
|
30032
|
-
if (relPath.startsWith(".claude/hooks/")) {
|
|
30033
|
-
return "hook";
|
|
30034
|
-
}
|
|
30035
|
-
if (relPath.startsWith(".claude/agents/")) {
|
|
30036
|
-
return "subagent";
|
|
30037
|
-
}
|
|
30038
|
-
if (relPath.startsWith(".claude/commands/")) {
|
|
30039
|
-
return "agent";
|
|
30040
|
-
}
|
|
30041
|
-
return "instruction";
|
|
30455
|
+
return primaryConstructTypeForPath(relPath) ?? "instruction";
|
|
30042
30456
|
}
|
|
30043
30457
|
function determineHarness(repoRelativePath) {
|
|
30044
30458
|
const matching = ALL_HARNESS_NAMES.filter((harness)=>matchesPrimaryIndicator(repoRelativePath, HARNESS_FOOTPRINTS[harness].primaryIndicators));
|
|
@@ -32435,12 +32849,21 @@ function createInitHooksCommand(useCase) {
|
|
|
32435
32849
|
*/
|
|
32436
32850
|
|
|
32437
32851
|
|
|
32852
|
+
|
|
32438
32853
|
/** Maximum agent file size: 1 MB */ const MAX_AGENT_FILE_BYTES = 1_048_576;
|
|
32854
|
+
/** Catalog agent roots converted to OS-native relative paths. */ const AGENT_DIRECTORIES = agentDirectoryRoots().map((root)=>(0,external_node_path_.join)(...root.split("/")));
|
|
32439
32855
|
/**
|
|
32440
32856
|
* File system implementation of the agent lint gateway.
|
|
32441
32857
|
*/ class FileSystemAgentLintGateway {
|
|
32442
32858
|
async discoverAgents(targetDir) {
|
|
32443
|
-
const
|
|
32859
|
+
const agents = [];
|
|
32860
|
+
for (const agentsDir of AGENT_DIRECTORIES){
|
|
32861
|
+
const discovered = await this.discoverAgentsInDir(targetDir, agentsDir);
|
|
32862
|
+
agents.push(...discovered);
|
|
32863
|
+
}
|
|
32864
|
+
return agents;
|
|
32865
|
+
}
|
|
32866
|
+
async discoverAgentsInDir(targetDir, agentsDir) {
|
|
32444
32867
|
try {
|
|
32445
32868
|
if (!await file_system_utils_pathExistsWithinRoot(targetDir, agentsDir)) {
|
|
32446
32869
|
return [];
|
|
@@ -32506,25 +32929,14 @@ function createInitHooksCommand(useCase) {
|
|
|
32506
32929
|
* Discovers hook config files for GitHub Copilot and Claude Code.
|
|
32507
32930
|
*/
|
|
32508
32931
|
|
|
32932
|
+
|
|
32509
32933
|
/** Maximum hook config file size: 1 MB */ const MAX_CONFIG_FILE_BYTES = 1_048_576;
|
|
32510
32934
|
/** Matches the Copilot hook key `"preToolUse":` to detect hook section presence */ const COPILOT_HOOK_PATTERN = /"preToolUse"\s*:/;
|
|
32511
32935
|
/** Matches the Claude hook key `"PreToolUse":` to detect hook section presence */ const CLAUDE_HOOK_PATTERN = /"PreToolUse"\s*:/;
|
|
32512
|
-
/**
|
|
32513
|
-
|
|
32514
|
-
|
|
32515
|
-
|
|
32516
|
-
relativePath: (0,external_node_path_.join)(".github", "hooks", "agent-shell", "hooks.json"),
|
|
32517
|
-
platform: "copilot"
|
|
32518
|
-
},
|
|
32519
|
-
{
|
|
32520
|
-
relativePath: (0,external_node_path_.join)(".claude", "settings.json"),
|
|
32521
|
-
platform: "claude"
|
|
32522
|
-
},
|
|
32523
|
-
{
|
|
32524
|
-
relativePath: (0,external_node_path_.join)(".claude", "settings.local.json"),
|
|
32525
|
-
platform: "claude"
|
|
32526
|
-
}
|
|
32527
|
-
];
|
|
32936
|
+
/** Catalog hook config paths converted to OS-native relative paths. */ const HOOK_CONFIG_PATHS = hookConfigPaths().map((entry)=>({
|
|
32937
|
+
relativePath: (0,external_node_path_.join)(...entry.relativePath.split("/")),
|
|
32938
|
+
platform: entry.platform
|
|
32939
|
+
}));
|
|
32528
32940
|
/**
|
|
32529
32941
|
* File system implementation of the hook config lint gateway.
|
|
32530
32942
|
*/ class FileSystemHookConfigGateway {
|
|
@@ -32593,40 +33005,28 @@ function createInitHooksCommand(useCase) {
|
|
|
32593
33005
|
* Supports GitHub Copilot, Claude Code, and community agent instruction formats.
|
|
32594
33006
|
*/
|
|
32595
33007
|
|
|
33008
|
+
|
|
32596
33009
|
/**
|
|
32597
33010
|
* File system implementation of the instruction file discovery gateway.
|
|
32598
33011
|
*/ class FileSystemInstructionFileDiscoveryGateway {
|
|
32599
33012
|
async discoverInstructionFiles(targetDir) {
|
|
32600
33013
|
const files = [];
|
|
32601
|
-
|
|
32602
|
-
|
|
32603
|
-
|
|
32604
|
-
|
|
32605
|
-
|
|
32606
|
-
|
|
32607
|
-
|
|
32608
|
-
|
|
32609
|
-
|
|
32610
|
-
|
|
32611
|
-
|
|
32612
|
-
|
|
32613
|
-
|
|
32614
|
-
|
|
32615
|
-
|
|
32616
|
-
|
|
32617
|
-
if (await this.safePathExists(targetDir, agentsMd)) {
|
|
32618
|
-
files.push({
|
|
32619
|
-
filePath: (0,external_node_path_.join)(targetDir, agentsMd),
|
|
32620
|
-
format: "agents-md"
|
|
32621
|
-
});
|
|
32622
|
-
}
|
|
32623
|
-
// CLAUDE.md at repo root
|
|
32624
|
-
const claudeMd = "CLAUDE.md";
|
|
32625
|
-
if (await this.safePathExists(targetDir, claudeMd)) {
|
|
32626
|
-
files.push({
|
|
32627
|
-
filePath: (0,external_node_path_.join)(targetDir, claudeMd),
|
|
32628
|
-
format: "claude-md"
|
|
32629
|
-
});
|
|
33014
|
+
for (const entry of instructionDiscoveryEntries()){
|
|
33015
|
+
const format = entry.instructionFormat;
|
|
33016
|
+
if (format == null) {
|
|
33017
|
+
continue;
|
|
33018
|
+
}
|
|
33019
|
+
const relativePath = (0,external_node_path_.join)(...entry.path.split("/"));
|
|
33020
|
+
if (entry.matchKind === "exact") {
|
|
33021
|
+
if (await this.safePathExists(targetDir, relativePath)) {
|
|
33022
|
+
files.push({
|
|
33023
|
+
filePath: (0,external_node_path_.join)(targetDir, relativePath),
|
|
33024
|
+
format
|
|
33025
|
+
});
|
|
33026
|
+
}
|
|
33027
|
+
continue;
|
|
33028
|
+
}
|
|
33029
|
+
await this.discoverMdFilesInDir(targetDir, relativePath, format, files);
|
|
32630
33030
|
}
|
|
32631
33031
|
return files;
|
|
32632
33032
|
}
|
|
@@ -62612,4 +63012,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
|
|
|
62612
63012
|
// module factories are used so entry inlining is disabled
|
|
62613
63013
|
// startup
|
|
62614
63014
|
// Load entry module and return exports
|
|
62615
|
-
var __webpack_exports__ = __webpack_require__(
|
|
63015
|
+
var __webpack_exports__ = __webpack_require__(7015);
|