@lousy-agents/mcp 5.18.0 → 5.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/mcp-server.js +1669 -1526
  2. package/package.json +3 -2
@@ -6559,7 +6559,7 @@ function escapeJsonPtr(str) {
6559
6559
 
6560
6560
 
6561
6561
  },
6562
- 8454(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6562
+ 7489(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6563
6563
  // NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
6564
6564
  var constructs_namespaceObject = {};
6565
6565
  __webpack_require__.r(constructs_namespaceObject);
@@ -26549,8 +26549,6 @@ var external_node_path_ = __webpack_require__(6760);
26549
26549
  * List of known action names for convenience
26550
26550
  */ const KNOWN_ACTIONS = Object.keys(DEFAULT_ACTION_VERSIONS);
26551
26551
 
26552
- // EXTERNAL MODULE: external "node:fs"
26553
- var external_node_fs_ = __webpack_require__(3024);
26554
26552
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/errors.js
26555
26553
  const OPERATIONAL_CODES = new Set([
26556
26554
  "helper-failed",
@@ -26565,42 +26563,20 @@ function categorizeFsSafeError(code) {
26565
26563
  class errors_FsSafeError extends Error {
26566
26564
  code;
26567
26565
  category;
26566
+ details;
26568
26567
  constructor(code, message, options = {}) {
26569
26568
  super(message, options);
26570
26569
  this.name = "FsSafeError";
26571
26570
  this.code = code;
26572
26571
  this.category = categorizeFsSafeError(code);
26572
+ this.details = options.details;
26573
26573
  }
26574
26574
  }
26575
26575
 
26576
26576
  // EXTERNAL MODULE: external "node:crypto"
26577
26577
  var external_node_crypto_ = __webpack_require__(7598);
26578
- // EXTERNAL MODULE: external "node:stream/promises"
26579
- var external_node_stream_promises_ = __webpack_require__(6466);
26580
- // EXTERNAL MODULE: external "node:stream"
26581
- var external_node_stream_ = __webpack_require__(7075);
26582
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read-stream.js
26583
-
26584
-
26585
- function createMaxBytesTransform(maxBytes) {
26586
- let bytes = 0;
26587
- return new external_node_stream_.Transform({
26588
- transform(chunk, _encoding, callback) {
26589
- const buffer = chunk instanceof Buffer ? chunk : Buffer.from(chunk);
26590
- bytes += buffer.byteLength;
26591
- if (bytes > maxBytes) {
26592
- callback(new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`));
26593
- return;
26594
- }
26595
- callback(null, buffer);
26596
- },
26597
- });
26598
- }
26599
- function createBoundedReadStream(opened, maxBytes) {
26600
- const stream = opened.handle.createReadStream();
26601
- return maxBytes === undefined ? stream : stream.pipe(createMaxBytesTransform(maxBytes));
26602
- }
26603
-
26578
+ // EXTERNAL MODULE: external "node:fs"
26579
+ var external_node_fs_ = __webpack_require__(3024);
26604
26580
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/file-identity.js
26605
26581
 
26606
26582
  function isZero(value) {
@@ -26625,86 +26601,11 @@ function file_identity_sameFileIdentity(left, right, platform = process.platform
26625
26601
  return platform === "win32" && (isZero(left.dev) || isZero(right.dev));
26626
26602
  }
26627
26603
 
26628
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/string-coerce.js
26629
- function readStringValue(value) {
26630
- return typeof value === "string" ? value : undefined;
26631
- }
26632
- function normalizeNullableString(value) {
26633
- if (typeof value !== "string") {
26634
- return null;
26635
- }
26636
- const trimmed = value.trim();
26637
- return trimmed ? trimmed : null;
26638
- }
26639
- function normalizeOptionalString(value) {
26640
- return normalizeNullableString(value) ?? undefined;
26641
- }
26642
- function normalizeStringifiedOptionalString(value) {
26643
- if (typeof value === "string") {
26644
- return normalizeOptionalString(value);
26645
- }
26646
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
26647
- return normalizeOptionalString(String(value));
26648
- }
26649
- return undefined;
26650
- }
26651
- function normalizeOptionalLowercaseString(value) {
26652
- return normalizeOptionalString(value)?.toLowerCase();
26653
- }
26654
- function normalizeLowercaseStringOrEmpty(value) {
26655
- return normalizeOptionalLowercaseString(value) ?? "";
26656
- }
26657
- function normalizeFastMode(raw) {
26658
- if (typeof raw === "boolean") {
26659
- return raw;
26660
- }
26661
- if (!raw) {
26662
- return undefined;
26663
- }
26664
- const key = normalizeLowercaseStringOrEmpty(raw);
26665
- if (["off", "false", "no", "0", "disable", "disabled", "normal"].includes(key)) {
26666
- return false;
26667
- }
26668
- if (["on", "true", "yes", "1", "enable", "enabled", "fast"].includes(key)) {
26669
- return true;
26670
- }
26671
- return undefined;
26672
- }
26673
- function lowercasePreservingWhitespace(value) {
26674
- return value.toLowerCase();
26675
- }
26676
- function localeLowercasePreservingWhitespace(value) {
26677
- return value.toLocaleLowerCase();
26678
- }
26679
- function resolvePrimaryStringValue(value) {
26680
- if (typeof value === "string") {
26681
- return normalizeOptionalString(value);
26682
- }
26683
- if (!value || typeof value !== "object") {
26684
- return undefined;
26685
- }
26686
- return normalizeOptionalString(value.primary);
26687
- }
26688
- function normalizeOptionalThreadValue(value) {
26689
- if (typeof value === "number") {
26690
- return Number.isFinite(value) ? Math.trunc(value) : undefined;
26691
- }
26692
- return normalizeOptionalString(value);
26693
- }
26694
- function normalizeOptionalStringifiedId(value) {
26695
- const normalized = normalizeOptionalThreadValue(value);
26696
- return normalized == null ? undefined : String(normalized);
26697
- }
26698
- function hasNonEmptyString(value) {
26699
- return normalizeOptionalString(value) !== undefined;
26700
- }
26701
-
26702
26604
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path.js
26703
26605
 
26704
26606
 
26705
26607
 
26706
26608
 
26707
-
26708
26609
  const NOT_FOUND_CODES = new Set(["ENOENT", "ENOTDIR"]);
26709
26610
  const SYMLINK_OPEN_CODES = new Set(["ELOOP", "EINVAL", "ENOTSUP"]);
26710
26611
  const POSIX_SEPARATOR_CHAR_CODE = 0x2f;
@@ -26716,7 +26617,9 @@ function normalizeWindowsPathForComparison(input) {
26716
26617
  normalized = `\\\\${normalized.slice(4)}`;
26717
26618
  }
26718
26619
  }
26719
- return normalizeLowercaseStringOrEmpty(normalized.replaceAll("/", "\\"));
26620
+ // Lowercase only: this value feeds Windows containment math, so surrounding
26621
+ // whitespace is part of the path and must not be trimmed away.
26622
+ return normalized.replaceAll("/", "\\").toLowerCase();
26720
26623
  }
26721
26624
  function isNodeError(value) {
26722
26625
  return Boolean(value && typeof value === "object" && "code" in value);
@@ -27215,6 +27118,27 @@ function isSameOrChildPath(candidate, parent) {
27215
27118
  const parentPrefix = parent.endsWith(external_node_path_.sep) ? parent : `${parent}${external_node_path_.sep}`;
27216
27119
  return candidate === parent || candidate.startsWith(parentPrefix);
27217
27120
  }
27121
+ async function realpathOrThrowNotFile(target) {
27122
+ try {
27123
+ return external_node_path_.resolve(await promises_.realpath(target));
27124
+ }
27125
+ catch (error) {
27126
+ if (path_isNotFoundPathError(error)) {
27127
+ // A dangling symlink (or a component removed between lstat and
27128
+ // realpath) is not a usable directory component.
27129
+ throw new errors_FsSafeError("not-file", "directory component must be a directory", {
27130
+ cause: error instanceof Error ? error : undefined,
27131
+ });
27132
+ }
27133
+ throw error;
27134
+ }
27135
+ }
27136
+ /**
27137
+ * Creates each missing path component from `rootReal` down to `targetPath`,
27138
+ * guarding every step. Returns the real (symlink-resolved) path of the final
27139
+ * component so callers can guard/use that path directly instead of
27140
+ * re-deriving it from the original, possibly-symlinked, lexical path.
27141
+ */
27218
27142
  async function mkdirPathComponentsWithGuards(params) {
27219
27143
  const root = external_node_path_.resolve(params.rootReal);
27220
27144
  const rootCanonical = external_node_path_.resolve(await promises_.realpath(root));
@@ -27238,18 +27162,39 @@ async function mkdirPathComponentsWithGuards(params) {
27238
27162
  }
27239
27163
  }
27240
27164
  const stat = await promises_.lstat(next);
27241
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
27165
+ if (!stat.isSymbolicLink() && !stat.isDirectory()) {
27242
27166
  throw new errors_FsSafeError("not-file", "directory component must be a directory");
27243
27167
  }
27244
27168
  // Node's recursive mkdir follows symlinks in missing components. Build one
27245
27169
  // segment at a time and realpath-check each segment before descending.
27246
- if (!isSameOrChildPath(external_node_path_.resolve(await promises_.realpath(next)), rootCanonical)) {
27170
+ const nextReal = await realpathOrThrowNotFile(next);
27171
+ if (!isSameOrChildPath(nextReal, rootCanonical)) {
27247
27172
  throw new errors_FsSafeError("outside-workspace", "directory escaped workspace root");
27248
27173
  }
27174
+ if (stat.isSymbolicLink()) {
27175
+ // An existing path component may legitimately be a symlink to a real
27176
+ // directory inside the root (e.g. a skill-bank layout). We already
27177
+ // verified above that it resolves inside the root, so treat the
27178
+ // resolved real path as the directory for the rest of this walk
27179
+ // instead of rejecting it outright. Guard checks from here on operate
27180
+ // on the real (non-symlink) path, preserving TOCTOU protection for
27181
+ // every subsequent segment. Callers that need the final directory
27182
+ // (e.g. to guard it themselves after this function returns) must use
27183
+ // the returned resolved path, not their own lexical parent path.
27184
+ const targetStat = await promises_.stat(nextReal);
27185
+ if (!targetStat.isDirectory()) {
27186
+ throw new errors_FsSafeError("not-file", "directory component must be a directory");
27187
+ }
27188
+ await directory_guard_createAsyncDirectoryGuard(nextReal);
27189
+ await assertAsyncDirectoryGuard(parentGuard);
27190
+ current = nextReal;
27191
+ continue;
27192
+ }
27249
27193
  await directory_guard_createAsyncDirectoryGuard(next);
27250
27194
  await assertAsyncDirectoryGuard(parentGuard);
27251
27195
  current = next;
27252
27196
  }
27197
+ return current;
27253
27198
  }
27254
27199
 
27255
27200
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/guarded-mutation.js
@@ -27515,8 +27460,20 @@ async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
27515
27460
  return null;
27516
27461
  }
27517
27462
 
27518
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-python-config.js
27463
+ // EXTERNAL MODULE: external "node:module"
27464
+ var external_node_module_ = __webpack_require__(8995);
27465
+ // EXTERNAL MODULE: external "node:url"
27466
+ var external_node_url_ = __webpack_require__(3136);
27467
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native-config.js
27519
27468
  let overrideConfig = {};
27469
+ let legacyWarningEmitted = false;
27470
+ const legacyModeEnvKeys = ["FS_SAFE_PYTHON_MODE", "OPENCLAW_FS_SAFE_PYTHON_MODE"];
27471
+ const legacyPathEnvKeys = [
27472
+ "FS_SAFE_PYTHON",
27473
+ "OPENCLAW_FS_SAFE_PYTHON",
27474
+ "OPENCLAW_PINNED_PYTHON",
27475
+ "OPENCLAW_PINNED_WRITE_PYTHON",
27476
+ ];
27520
27477
  function parseMode(value) {
27521
27478
  if (!value) {
27522
27479
  return undefined;
@@ -27533,682 +27490,495 @@ function parseMode(value) {
27533
27490
  }
27534
27491
  return undefined;
27535
27492
  }
27536
- function configureFsSafePython(config) {
27493
+ function configureFsSafeNative(config) {
27537
27494
  overrideConfig = { ...overrideConfig, ...config };
27538
27495
  }
27539
- function getFsSafePythonConfig() {
27496
+ function warnLegacyPythonConfiguration(source, mappedMode) {
27497
+ if (legacyWarningEmitted) {
27498
+ return;
27499
+ }
27500
+ legacyWarningEmitted = true;
27501
+ process.emitWarning(`${source} is a deprecated 0.4 compatibility bridge; mapped to native mode ` +
27502
+ `"${mappedMode}". Use configureFsSafeNative({ mode: "${mappedMode}" }) or ` +
27503
+ `FS_SAFE_NATIVE_MODE=${mappedMode}. Python interpreter paths are no longer used.`, { code: "FS_SAFE_PYTHON_DEPRECATED", type: "DeprecationWarning" });
27504
+ }
27505
+ function readLegacyPythonMode() {
27506
+ const configuredKeys = [...legacyModeEnvKeys, ...legacyPathEnvKeys].filter((key) => process.env[key] !== undefined);
27507
+ if (configuredKeys.length === 0) {
27508
+ return undefined;
27509
+ }
27510
+ const rawMode = legacyModeEnvKeys.map((key) => process.env[key]).find((value) => value !== undefined);
27511
+ const mappedMode = parseMode(rawMode) ?? "auto";
27512
+ warnLegacyPythonConfiguration(`Legacy ${configuredKeys.join(", ")}`, mappedMode);
27513
+ return mappedMode;
27514
+ }
27515
+ /**
27516
+ * @deprecated Compatibility bridge for 0.4 upgrades. Use configureFsSafeNative.
27517
+ */
27518
+ function configureFsSafePython(config) {
27519
+ const mappedMode = config.mode ?? "auto";
27520
+ warnLegacyPythonConfiguration("configureFsSafePython()", mappedMode);
27521
+ if (config.mode !== undefined) {
27522
+ configureFsSafeNative({ mode: config.mode });
27523
+ }
27524
+ }
27525
+ function getFsSafeNativeConfig() {
27526
+ const legacyMode = readLegacyPythonMode();
27540
27527
  return {
27541
27528
  mode: overrideConfig.mode ??
27542
- parseMode(process.env.FS_SAFE_PYTHON_MODE) ??
27543
- parseMode(process.env.OPENCLAW_FS_SAFE_PYTHON_MODE) ??
27529
+ parseMode(process.env.FS_SAFE_NATIVE_MODE) ??
27530
+ parseMode(process.env.OPENCLAW_FS_SAFE_NATIVE_MODE) ??
27531
+ legacyMode ??
27544
27532
  "auto",
27545
- pythonPath: overrideConfig.pythonPath ??
27546
- process.env.FS_SAFE_PYTHON ??
27547
- process.env.OPENCLAW_FS_SAFE_PYTHON ??
27548
- process.env.OPENCLAW_PINNED_PYTHON ??
27549
- process.env.OPENCLAW_PINNED_WRITE_PYTHON,
27550
27533
  };
27551
27534
  }
27552
- function canFallbackFromPythonError(error) {
27553
- const code = error instanceof Error && "code" in error ? error.code : undefined;
27554
- return (getFsSafePythonConfig().mode !== "require" &&
27555
- (code === "helper-unavailable" || code === "unsupported-platform"));
27535
+ function __resetFsSafeNativeConfigForTest() {
27536
+ overrideConfig = {};
27537
+ legacyWarningEmitted = false;
27556
27538
  }
27557
27539
 
27558
- // EXTERNAL MODULE: external "node:child_process"
27559
- var external_node_child_process_ = __webpack_require__(1421);
27560
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-python.js
27561
-
27562
-
27563
-
27564
-
27565
- const PINNED_PYTHON_WORKER_SOURCE = String.raw `
27566
- import base64, errno, json, os, secrets, stat, sys
27567
- DIR_FLAGS = os.O_RDONLY
27568
- if hasattr(os, "O_DIRECTORY"): DIR_FLAGS |= os.O_DIRECTORY
27569
- if hasattr(os, "O_NOFOLLOW"): DIR_FLAGS |= os.O_NOFOLLOW
27570
- READ_FLAGS = os.O_RDONLY
27571
- if hasattr(os, "O_NONBLOCK"): READ_FLAGS |= os.O_NONBLOCK
27572
- if hasattr(os, "O_NOFOLLOW"): READ_FLAGS |= os.O_NOFOLLOW
27573
- WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
27574
- if hasattr(os, "O_NOFOLLOW"):
27575
- WRITE_FLAGS |= os.O_NOFOLLOW
27576
- def split_relative(value):
27577
- if value in ("", "."):
27578
- return []
27579
- if "\x00" in value or value.startswith("/") or value.startswith("//"):
27580
- raise OSError(errno.EPERM, "invalid relative path")
27581
- if value.startswith("..\\"):
27582
- raise OSError(errno.EPERM, "path traversal is not allowed")
27583
- parts = [part for part in value.split("/") if part and part != "."]
27584
- for part in parts:
27585
- if part == "..":
27586
- raise OSError(errno.EPERM, "path traversal is not allowed")
27587
- return parts
27588
- def open_dir(path_value, dir_fd=None):
27589
- return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
27590
- def walk_dir(root_fd, segments, mkdir_enabled=False):
27591
- current_fd = os.dup(root_fd)
27592
- try:
27593
- for segment in segments:
27594
- try:
27595
- next_fd = open_dir(segment, dir_fd=current_fd)
27596
- except FileNotFoundError:
27597
- if not mkdir_enabled:
27598
- raise
27599
- os.mkdir(segment, 0o777, dir_fd=current_fd)
27600
- next_fd = open_dir(segment, dir_fd=current_fd)
27601
- os.close(current_fd)
27602
- current_fd = next_fd
27603
- return current_fd
27604
- except Exception:
27605
- os.close(current_fd)
27606
- raise
27607
- def parent_and_basename(root_fd, relative):
27608
- segments = split_relative(relative)
27609
- if not segments:
27610
- raise OSError(errno.EPERM, "operation requires a non-root path")
27611
- parent_fd = walk_dir(root_fd, segments[:-1])
27612
- return parent_fd, segments[-1]
27613
- def encode_stat(st):
27614
- mode = st.st_mode
27615
- return {
27616
- "dev": st.st_dev,
27617
- "gid": st.st_gid,
27618
- "ino": st.st_ino,
27619
- "isDirectory": stat.S_ISDIR(mode),
27620
- "isFile": stat.S_ISREG(mode),
27621
- "isSymbolicLink": stat.S_ISLNK(mode),
27622
- "mode": mode,
27623
- "mtimeMs": st.st_mtime * 1000,
27624
- "nlink": st.st_nlink,
27625
- "size": st.st_size,
27626
- "uid": st.st_uid,
27627
- }
27628
- def reject_unsafe_endpoint(st):
27629
- mode = st.st_mode
27630
- if stat.S_ISLNK(mode):
27631
- raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
27632
- if stat.S_ISREG(mode) and st.st_nlink > 1:
27633
- raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
27634
- def copy_bytes(source_fd, dest_fd):
27635
- while True:
27636
- chunk = os.read(source_fd, 65536)
27637
- if not chunk:
27638
- break
27639
- view = memoryview(chunk)
27640
- while view:
27641
- written = os.write(dest_fd, view)
27642
- if written <= 0:
27643
- raise OSError(errno.EIO, "short write")
27644
- view = view[written:]
27645
- def write_all(fd, data):
27646
- view = memoryview(data)
27647
- while view:
27648
- written = os.write(fd, view)
27649
- if written <= 0:
27650
- raise OSError(errno.EIO, "short write")
27651
- view = view[written:]
27652
- def fsync_best_effort(fd):
27653
- try: os.fsync(fd)
27654
- except OSError as error:
27655
- if error.errno != errno.EPERM: raise
27656
- def link_unsupported(exc):
27657
- unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
27658
- return getattr(exc, "errno", None) in unsupported
27659
- def link_no_replace(name, new_name, source_fd, target_fd):
27660
- linked = False
27661
- try:
27662
- os.link(name, new_name, src_dir_fd=source_fd, dst_dir_fd=target_fd, follow_symlinks=False)
27663
- linked = True
27664
- os.unlink(name, dir_fd=source_fd)
27665
- except Exception:
27666
- if linked:
27667
- try: os.unlink(new_name, dir_fd=target_fd)
27668
- except FileNotFoundError: pass
27669
- raise
27670
- os.fsync(source_fd)
27671
- if source_fd != target_fd:
27672
- os.fsync(target_fd)
27673
- def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
27674
- source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
27675
- dest_fd = None; success = False; dest_stat = None
27676
- try:
27677
- if expected is not None:
27678
- source_stat = os.fstat(source_fd)
27679
- if source_stat.st_dev != expected.st_dev or source_stat.st_ino != expected.st_ino:
27680
- raise RuntimeError("fs-safe-source-mismatch")
27681
- dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
27682
- copy_bytes(source_fd, dest_fd)
27683
- os.fsync(dest_fd)
27684
- dest_stat = os.fstat(dest_fd)
27685
- success = True
27686
- finally:
27687
- os.close(source_fd)
27688
- if dest_fd is not None:
27689
- os.close(dest_fd)
27690
- if dest_fd is not None and not success:
27691
- try: os.unlink(basename, dir_fd=target_parent_fd)
27692
- except FileNotFoundError: pass
27693
- if unlink_source:
27694
- try:
27695
- os.unlink(source_name, dir_fd=source_parent_fd)
27696
- except Exception:
27697
- try: os.unlink(basename, dir_fd=target_parent_fd)
27698
- except FileNotFoundError: pass
27699
- raise
27700
- return dest_stat
27701
- def same_identity(left, right):
27702
- return left.st_dev == right.st_dev and left.st_ino == right.st_ino
27703
- def verify_temp_name(parent_fd, temp_name, expected_stat):
27704
- current_stat = os.lstat(temp_name, dir_fd=parent_fd)
27705
- if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
27706
- raise RuntimeError("fs-safe-temp-mismatch")
27707
- def verify_committed_temp(parent_fd, basename, expected_stat):
27708
- final_stat = os.lstat(basename, dir_fd=parent_fd)
27709
- if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
27710
- return final_stat
27711
- try: os.unlink(basename, dir_fd=parent_fd)
27712
- except FileNotFoundError: pass
27713
- raise RuntimeError("fs-safe-temp-mismatch")
27714
- def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
27715
- verify_temp_name(parent_fd, temp_name, expected_stat)
27716
- if overwrite:
27717
- os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
27718
- return verify_committed_temp(parent_fd, basename, expected_stat)
27719
- else:
27720
- try:
27721
- os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
27722
- final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
27723
- os.unlink(temp_name, dir_fd=parent_fd)
27724
- return final_stat
27725
- except OSError as exc:
27726
- if not link_unsupported(exc):
27727
- raise
27728
- return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
27729
- def assert_expected_root(root_fd, payload):
27730
- if "rootDev" in payload or "rootIno" in payload:
27731
- root_stat = os.fstat(root_fd)
27732
- if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
27733
- raise RuntimeError("fs-safe-root-mismatch")
27734
- def stat_path(root_fd, payload):
27735
- relative = payload.get("relativePath", "")
27736
- segments = split_relative(relative)
27737
- if not segments:
27738
- return encode_stat(os.fstat(root_fd))
27739
- parent_fd, basename = parent_and_basename(root_fd, relative)
27740
- try:
27741
- st = os.lstat(basename, dir_fd=parent_fd)
27742
- if payload.get("rejectSymlink", True) and stat.S_ISLNK(st.st_mode):
27743
- raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
27744
- return encode_stat(st)
27745
- finally:
27746
- os.close(parent_fd)
27747
- def readdir_path(root_fd, payload):
27748
- dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
27749
- try:
27750
- names = sorted(os.listdir(dir_fd))
27751
- if not payload.get("withFileTypes", False):
27752
- return names
27753
- entries = []
27754
- for name in names:
27755
- st = os.lstat(name, dir_fd=dir_fd)
27756
- entry = encode_stat(st)
27757
- entry["name"] = name
27758
- entries.append(entry)
27759
- return entries
27760
- finally:
27761
- os.close(dir_fd)
27762
- def mkdirp_path(root_fd, payload):
27763
- dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
27764
- os.close(dir_fd); return None
27765
- def remove_tree(parent_fd, basename):
27766
- st = os.lstat(basename, dir_fd=parent_fd)
27767
- if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
27768
- dir_fd = open_dir(basename, dir_fd=parent_fd)
27769
- try:
27770
- for child in os.listdir(dir_fd):
27771
- remove_tree(dir_fd, child)
27772
- finally:
27773
- os.close(dir_fd)
27774
- os.rmdir(basename, dir_fd=parent_fd)
27775
- else:
27776
- os.unlink(basename, dir_fd=parent_fd)
27777
- def remove_path(root_fd, payload):
27778
- parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
27779
- try:
27780
- try:
27781
- st = os.lstat(basename, dir_fd=parent_fd)
27782
- except FileNotFoundError:
27783
- if payload.get("force", True):
27784
- return None
27785
- raise
27786
- if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
27787
- if payload.get("recursive", False):
27788
- remove_tree(parent_fd, basename)
27789
- else:
27790
- os.rmdir(basename, dir_fd=parent_fd)
27791
- else:
27792
- os.unlink(basename, dir_fd=parent_fd)
27793
- return None
27794
- finally:
27795
- os.close(parent_fd)
27796
-
27797
- def rename_path(root_fd, payload):
27798
- from_parent_fd, from_base = parent_and_basename(root_fd, payload["from"])
27799
- to_parent_fd, to_base = parent_and_basename(root_fd, payload["to"])
27800
- try:
27801
- from_stat = os.lstat(from_base, dir_fd=from_parent_fd)
27802
- reject_unsafe_endpoint(from_stat)
27803
- overwrite = payload.get("overwrite", True)
27804
- if not overwrite and stat.S_ISREG(from_stat.st_mode):
27805
- try:
27806
- link_no_replace(from_base, to_base, from_parent_fd, to_parent_fd)
27807
- except OSError as exc:
27808
- if not link_unsupported(exc):
27809
- raise
27810
- copy_file_no_replace(from_parent_fd, from_base, to_parent_fd, to_base, stat.S_IMODE(from_stat.st_mode), from_stat, True)
27811
- return None
27812
- if not overwrite and stat.S_ISDIR(from_stat.st_mode):
27813
- raise RuntimeError("fs-safe-directory-noreplace-unsupported")
27814
- if not overwrite:
27815
- try:
27816
- os.lstat(to_base, dir_fd=to_parent_fd)
27817
- raise FileExistsError(errno.EEXIST, "destination exists", to_base)
27818
- except FileNotFoundError:
27819
- pass
27820
- os.rename(from_base, to_base, src_dir_fd=from_parent_fd, dst_dir_fd=to_parent_fd)
27821
- os.fsync(from_parent_fd)
27822
- if from_parent_fd != to_parent_fd:
27823
- os.fsync(to_parent_fd)
27824
- return None
27825
- finally:
27826
- os.close(from_parent_fd)
27827
- os.close(to_parent_fd)
27828
-
27829
- def create_temp_file(parent_fd, basename, mode):
27830
- prefix = "." + basename + "."
27831
- for _ in range(128):
27832
- candidate = prefix + secrets.token_hex(6) + ".tmp"
27833
- try:
27834
- fd = os.open(candidate, WRITE_FLAGS, mode, dir_fd=parent_fd)
27835
- return candidate, fd
27836
- except FileExistsError:
27837
- continue
27838
- raise RuntimeError("failed to allocate pinned temp file")
27839
-
27840
- def write_path(root_fd, payload):
27841
- parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
27842
- temp_fd = None
27843
- temp_name = None
27844
- basename = payload["basename"]
27845
- mode = int(payload.get("mode", 0o600))
27846
- overwrite = bool(payload.get("overwrite", True))
27847
- max_bytes = int(payload.get("maxBytes", -1))
27848
- data = base64.b64decode(payload.get("base64", ""))
27849
- try:
27850
- if max_bytes >= 0 and len(data) > max_bytes:
27851
- raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, len(data)))
27852
- if not overwrite:
27853
- try:
27854
- os.lstat(basename, dir_fd=parent_fd)
27855
- raise FileExistsError(errno.EEXIST, "destination exists", basename)
27856
- except FileNotFoundError:
27857
- pass
27858
- temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
27859
- os.fchmod(temp_fd, mode)
27860
- write_all(temp_fd, data)
27861
- fsync_best_effort(temp_fd)
27862
- temp_stat = os.fstat(temp_fd)
27863
- os.close(temp_fd)
27864
- temp_fd = None
27865
- result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
27866
- temp_name = None
27867
- fsync_best_effort(parent_fd)
27868
- return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
27869
- finally:
27870
- if temp_fd is not None:
27871
- os.close(temp_fd)
27872
- if temp_name is not None:
27873
- try:
27874
- os.unlink(temp_name, dir_fd=parent_fd)
27875
- except FileNotFoundError:
27876
- pass
27877
- os.close(parent_fd)
27878
-
27879
- def copy_path(root_fd, payload):
27880
- source_fd = os.open(payload["sourcePath"], READ_FLAGS)
27881
- parent_fd = None
27882
- temp_fd = None
27883
- temp_name = None
27884
- try:
27885
- source_stat = os.fstat(source_fd)
27886
- if not stat.S_ISREG(source_stat.st_mode):
27887
- raise RuntimeError("fs-safe-not-file")
27888
- if source_stat.st_dev != int(payload["sourceDev"]) or source_stat.st_ino != int(payload["sourceIno"]):
27889
- raise RuntimeError("fs-safe-source-mismatch")
27890
- basename = payload["basename"]
27891
- mode = int(payload.get("mode", 0o600))
27892
- overwrite = bool(payload.get("overwrite", True))
27893
- max_bytes = int(payload.get("maxBytes", -1))
27894
- if max_bytes >= 0 and source_stat.st_size > max_bytes:
27895
- raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
27896
- parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
27897
- temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
27898
- os.fchmod(temp_fd, mode)
27899
- written_bytes = 0
27900
- while True:
27901
- chunk = os.read(source_fd, 65536)
27902
- if not chunk:
27903
- break
27904
- written_bytes += len(chunk)
27905
- if max_bytes >= 0 and written_bytes > max_bytes:
27906
- raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, written_bytes))
27907
- view = memoryview(chunk)
27908
- while view:
27909
- written = os.write(temp_fd, view)
27910
- if written <= 0:
27911
- raise OSError(errno.EIO, "short write")
27912
- view = view[written:]
27913
- os.fsync(temp_fd)
27914
- temp_stat = os.fstat(temp_fd)
27915
- os.close(temp_fd)
27916
- temp_fd = None
27917
- result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
27918
- temp_name = None
27919
- os.fsync(parent_fd)
27920
- return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
27921
- finally:
27922
- os.close(source_fd)
27923
- if temp_fd is not None:
27924
- os.close(temp_fd)
27925
- if temp_name is not None and parent_fd is not None:
27926
- try:
27927
- os.unlink(temp_name, dir_fd=parent_fd)
27928
- except FileNotFoundError:
27929
- pass
27930
- if parent_fd is not None:
27931
- os.close(parent_fd)
27932
-
27933
- def run_operation(operation, root_path, payload):
27934
- root_fd = open_dir(root_path)
27935
- try:
27936
- assert_expected_root(root_fd, payload)
27937
- if operation == "stat":
27938
- return stat_path(root_fd, payload)
27939
- if operation == "readdir":
27940
- return readdir_path(root_fd, payload)
27941
- if operation == "mkdirp":
27942
- return mkdirp_path(root_fd, payload)
27943
- if operation == "remove":
27944
- return remove_path(root_fd, payload)
27945
- if operation == "rename":
27946
- return rename_path(root_fd, payload)
27947
- if operation == "write":
27948
- return write_path(root_fd, payload)
27949
- if operation == "copy":
27950
- return copy_path(root_fd, payload)
27951
- raise RuntimeError("unknown operation: " + operation)
27952
- finally:
27953
- os.close(root_fd)
27954
-
27955
- for line in sys.stdin:
27956
- try:
27957
- request = json.loads(line)
27958
- result = run_operation(request["operation"], request["rootPath"], request.get("payload") or {})
27959
- response = {"id": request["id"], "ok": True, "result": result}
27960
- except Exception as exc:
27961
- response = {
27962
- "id": request.get("id") if isinstance(locals().get("request"), dict) else None,
27963
- "ok": False,
27964
- "code": exc.__class__.__name__,
27965
- "errno": getattr(exc, "errno", None),
27966
- "message": str(exc),
27967
- }
27968
- print(json.dumps(response, separators=(",", ":")), flush=True)
27969
- `;
27970
- let nextRequestId = 1;
27971
- let worker = null;
27972
- function __resetPinnedPythonWorkerForTest() {
27973
- const currentWorker = worker;
27974
- worker = null;
27975
- if (!currentWorker) {
27976
- return;
27977
- }
27978
- currentWorker.pending.clear();
27979
- currentWorker.child.kill("SIGTERM");
27540
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native.js
27541
+
27542
+
27543
+
27544
+
27545
+
27546
+
27547
+ const native_require = (0,external_node_module_.createRequire)(import.meta.url);
27548
+ const bundledNativeDirectory = (0,external_node_url_.fileURLToPath)(new URL("../dist/native/", import.meta.url));
27549
+ let native_binding;
27550
+ let loadError;
27551
+ let attempted = false;
27552
+ let loadBinding = loadBundledBinding;
27553
+ function isMuslFilename(file) {
27554
+ return file.includes("libc.musl-") || file.includes("ld-musl-");
27980
27555
  }
27981
- const PYTHON_CANDIDATE_DEFAULTS = [
27982
- "/usr/bin/python3",
27983
- "/opt/homebrew/bin/python3",
27984
- "/usr/local/bin/python3",
27985
- ];
27986
- function canExecute(binPath) {
27556
+ function isMuslFromReport() {
27987
27557
  try {
27988
- external_node_fs_.accessSync(binPath, external_node_fs_.constants.X_OK);
27989
- return true;
27558
+ if (!process.report || typeof process.report.getReport !== "function")
27559
+ return undefined;
27560
+ const report = process.report.getReport();
27561
+ if (report.header?.glibcVersionRuntime)
27562
+ return false;
27563
+ if (report.sharedObjects?.some(isMuslFilename))
27564
+ return true;
27990
27565
  }
27991
27566
  catch {
27992
- return false;
27567
+ // Continue with filesystem and ELF inspection.
27993
27568
  }
27569
+ return undefined;
27994
27570
  }
27995
- function resolvePython() {
27996
- const configured = getFsSafePythonConfig().pythonPath;
27997
- if (configured) {
27998
- return configured;
27999
- }
28000
- for (const candidate of PYTHON_CANDIDATE_DEFAULTS) {
28001
- if (canExecute(candidate)) {
28002
- return candidate;
27571
+ function isMuslFromFilesystem() {
27572
+ for (const directory of ["/lib", "/usr/lib"]) {
27573
+ try {
27574
+ if ((0,external_node_fs_.readdirSync)(directory).some(isMuslFilename))
27575
+ return true;
27576
+ }
27577
+ catch {
27578
+ // A missing or unreadable conventional library directory is inconclusive.
28003
27579
  }
28004
27580
  }
28005
- return "python3";
27581
+ return undefined;
28006
27582
  }
28007
- function assertPinnedHelperSupported() {
28008
- if (process.platform === "win32") {
28009
- throw new errors_FsSafeError("unsupported-platform", "fd-relative pinned filesystem operations are not available on Windows");
27583
+ function readUInt(buffer, offset, bytes, littleEndian) {
27584
+ if (offset < 0 || offset + bytes > buffer.length)
27585
+ return undefined;
27586
+ if (bytes === 2)
27587
+ return littleEndian ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
27588
+ if (bytes === 4)
27589
+ return littleEndian ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
27590
+ const value = littleEndian ? buffer.readBigUInt64LE(offset) : buffer.readBigUInt64BE(offset);
27591
+ return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : undefined;
27592
+ }
27593
+ function isMuslFromElfInterpreter() {
27594
+ let fd;
27595
+ try {
27596
+ fd = (0,external_node_fs_.openSync)(process.execPath, "r");
27597
+ const header = Buffer.alloc(64);
27598
+ if ((0,external_node_fs_.readSync)(fd, header, 0, header.length, 0) < 52)
27599
+ return undefined;
27600
+ if (!header.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46])))
27601
+ return undefined;
27602
+ const elfClass = header[4];
27603
+ const dataEncoding = header[5];
27604
+ if ((elfClass !== 1 && elfClass !== 2) || (dataEncoding !== 1 && dataEncoding !== 2)) {
27605
+ return undefined;
27606
+ }
27607
+ const littleEndian = dataEncoding === 1;
27608
+ const is64Bit = elfClass === 2;
27609
+ const tableOffset = readUInt(header, is64Bit ? 32 : 28, is64Bit ? 8 : 4, littleEndian);
27610
+ const entrySize = readUInt(header, is64Bit ? 54 : 42, 2, littleEndian);
27611
+ const entryCount = readUInt(header, is64Bit ? 56 : 44, 2, littleEndian);
27612
+ if (tableOffset === undefined ||
27613
+ entrySize === undefined ||
27614
+ entryCount === undefined ||
27615
+ entrySize < (is64Bit ? 56 : 32) ||
27616
+ entryCount > 1024) {
27617
+ return undefined;
27618
+ }
27619
+ const programHeader = Buffer.alloc(entrySize);
27620
+ for (let index = 0; index < entryCount; index += 1) {
27621
+ const offset = tableOffset + index * entrySize;
27622
+ if ((0,external_node_fs_.readSync)(fd, programHeader, 0, entrySize, offset) !== entrySize)
27623
+ return undefined;
27624
+ if (readUInt(programHeader, 0, 4, littleEndian) !== 3)
27625
+ continue;
27626
+ const interpreterOffset = readUInt(programHeader, is64Bit ? 8 : 4, is64Bit ? 8 : 4, littleEndian);
27627
+ const interpreterSize = readUInt(programHeader, is64Bit ? 32 : 16, is64Bit ? 8 : 4, littleEndian);
27628
+ if (interpreterOffset === undefined ||
27629
+ interpreterSize === undefined ||
27630
+ interpreterSize < 1 ||
27631
+ interpreterSize > 4096) {
27632
+ return undefined;
27633
+ }
27634
+ const interpreter = Buffer.alloc(interpreterSize);
27635
+ if ((0,external_node_fs_.readSync)(fd, interpreter, 0, interpreterSize, interpreterOffset) !== interpreterSize) {
27636
+ return undefined;
27637
+ }
27638
+ return isMuslFilename(interpreter.toString("utf8"));
27639
+ }
28010
27640
  }
28011
- if (getFsSafePythonConfig().mode === "off") {
28012
- throw new errors_FsSafeError("helper-unavailable", "Python helper is disabled");
27641
+ catch {
27642
+ return undefined;
27643
+ }
27644
+ finally {
27645
+ if (fd !== undefined) {
27646
+ try {
27647
+ (0,external_node_fs_.closeSync)(fd);
27648
+ }
27649
+ catch {
27650
+ // Best-effort detection must never turn close failure into import failure.
27651
+ }
27652
+ }
28013
27653
  }
27654
+ return undefined;
28014
27655
  }
28015
- function isSpawnUnavailable(error) {
28016
- if (!(error instanceof Error)) {
27656
+ function isMusl() {
27657
+ if (process.platform !== "linux")
28017
27658
  return false;
27659
+ for (const detector of [isMuslFromReport, isMuslFromFilesystem, isMuslFromElfInterpreter]) {
27660
+ const result = detector();
27661
+ if (result !== undefined)
27662
+ return result;
28018
27663
  }
28019
- const maybeErrno = error;
28020
- return (typeof maybeErrno.syscall === "string" &&
28021
- maybeErrno.syscall.startsWith("spawn") &&
28022
- ["EACCES", "ENOENT", "ENOEXEC"].includes(maybeErrno.code ?? ""));
27664
+ // Unknown Linux libc: try the glibc binary and let its require fail normally.
27665
+ return false;
28023
27666
  }
28024
- function mapWorkerError(response) {
28025
- const code = typeof response.code === "string" ? response.code : "";
28026
- const errno = typeof response.errno === "number" ? response.errno : undefined;
28027
- const message = typeof response.message === "string" && response.message
28028
- ? response.message
28029
- : "pinned helper failed";
28030
- const tooLarge = message.match(/fs-safe-too-large:(\d+):(\d+)/);
28031
- if (tooLarge) {
28032
- const [, limit, got] = tooLarge;
28033
- return new errors_FsSafeError("too-large", `file exceeds limit of ${limit} bytes (got at least ${got})`);
28034
- }
28035
- if (message.includes("fs-safe-not-file")) {
28036
- return new errors_FsSafeError("not-file", "not a file");
28037
- }
28038
- if (message.includes("fs-safe-source-mismatch")) {
28039
- return new errors_FsSafeError("path-mismatch", "source path changed during copy");
28040
- }
28041
- if (message.includes("fs-safe-temp-mismatch")) {
28042
- return new errors_FsSafeError("path-mismatch", "temp path changed during write");
28043
- }
28044
- if (message.includes("fs-safe-root-mismatch")) {
28045
- return new errors_FsSafeError("path-mismatch", "root path changed during operation");
28046
- }
28047
- if (message.includes("fs-safe-directory-noreplace-unsupported")) {
28048
- return new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
27667
+ function targetFor(platform, arch, musl) {
27668
+ if (platform === "win32" && arch === "x64")
27669
+ return "win32-x64-msvc";
27670
+ if (platform === "darwin" && arch === "x64")
27671
+ return "darwin-x64";
27672
+ if (platform === "darwin" && arch === "arm64")
27673
+ return "darwin-arm64";
27674
+ if (platform === "linux" && (arch === "x64" || arch === "arm64")) {
27675
+ return `linux-${arch}-${musl ? "musl" : "gnu"}`;
28049
27676
  }
28050
- if (code === "FileNotFoundError" || errno === 2) {
28051
- return new errors_FsSafeError("not-found", "file not found");
28052
- }
28053
- if (code === "FileExistsError" || errno === 17) {
28054
- return new errors_FsSafeError("already-exists", message);
28055
- }
28056
- if (errno === 39) {
28057
- return new errors_FsSafeError("not-empty", "directory is not empty");
27677
+ return undefined;
27678
+ }
27679
+ function bundledTarget() {
27680
+ return targetFor(process.platform, process.arch, isMusl());
27681
+ }
27682
+ function loadBundledBinding() {
27683
+ const target = bundledTarget();
27684
+ if (!target) {
27685
+ throw new Error(`Unsupported OS or architecture: ${process.platform}-${process.arch}`);
28058
27686
  }
28059
- if (errno === 1 || errno === 13 || errno === 21) {
28060
- return new errors_FsSafeError("not-removable", "path is not removable under root");
27687
+ return native_require((0,external_node_path_.join)(bundledNativeDirectory, target, "fs-safe-native.node"));
27688
+ }
27689
+ function __loadBundledNativeForTest() {
27690
+ return loadBundledBinding();
27691
+ }
27692
+ function __nativeLoaderDetectorsForTest() {
27693
+ return {
27694
+ report: isMuslFromReport(),
27695
+ filesystem: isMuslFromFilesystem(),
27696
+ elfInterpreter: isMuslFromElfInterpreter(),
27697
+ };
27698
+ }
27699
+ function __nativeTargetForTest(platform, arch, musl = false) {
27700
+ return targetFor(platform, arch, musl);
27701
+ }
27702
+ function getNativeBinding() {
27703
+ const { mode } = getFsSafeNativeConfig();
27704
+ if (mode === "off")
27705
+ return undefined;
27706
+ if (!attempted) {
27707
+ attempted = true;
27708
+ try {
27709
+ native_binding = loadBinding();
27710
+ }
27711
+ catch (error) {
27712
+ loadError = error;
27713
+ }
28061
27714
  }
28062
- if (code === "NotADirectoryError" || code === "OSError" || errno === 20 || errno === 40) {
28063
- return new errors_FsSafeError("path-alias", message);
27715
+ if (native_binding)
27716
+ return native_binding;
27717
+ if (mode === "require") {
27718
+ throw new errors_FsSafeError("helper-unavailable", "native fs-safe helper is unavailable", {
27719
+ cause: loadError,
27720
+ });
28064
27721
  }
28065
- return new errors_FsSafeError("helper-failed", message);
27722
+ return undefined;
28066
27723
  }
28067
- function rejectPending(error, targetWorker = worker) {
28068
- if (!targetWorker || worker !== targetWorker) {
28069
- return;
27724
+ function requireNativeBinding() {
27725
+ const native = getNativeBinding();
27726
+ if (!native) {
27727
+ throw new FsSafeError("helper-unavailable", "native fs-safe helper is unavailable", {
27728
+ cause: loadError,
27729
+ });
28070
27730
  }
28071
- setWorkerRef(targetWorker, false);
28072
- for (const pending of targetWorker.pending.values()) {
28073
- pending.reject(error);
27731
+ return native;
27732
+ }
27733
+ function __setNativeLoaderForTest(loader) {
27734
+ native_binding = undefined;
27735
+ loadError = undefined;
27736
+ attempted = false;
27737
+ loadBinding = loader;
27738
+ }
27739
+ function __resetNativeLoaderForTest() {
27740
+ native_binding = undefined;
27741
+ loadError = undefined;
27742
+ attempted = false;
27743
+ loadBinding = loadBundledBinding;
27744
+ }
27745
+
27746
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native-operations.js
27747
+
27748
+
27749
+
27750
+
27751
+
27752
+ function nativeOpenFlags(flags) {
27753
+ const closeOnExec = external_node_fs_.constants.O_CLOEXEC;
27754
+ return (flags |
27755
+ (closeOnExec ?? 0) |
27756
+ (typeof external_node_fs_.constants.O_NOFOLLOW === "number" ? external_node_fs_.constants.O_NOFOLLOW : 0));
27757
+ }
27758
+ function writeAll(fd, data) {
27759
+ let offset = 0;
27760
+ while (offset < data.byteLength) {
27761
+ const written = external_node_fs_.writeSync(fd, data, offset, data.byteLength - offset);
27762
+ if (written <= 0) {
27763
+ throw Object.assign(new Error("native file write made no progress"), { code: "EIO" });
27764
+ }
27765
+ offset += written;
28074
27766
  }
28075
- targetWorker.pending.clear();
28076
- worker = null;
28077
27767
  }
28078
- function handleWorkerLine(currentWorker, line) {
28079
- if (worker !== currentWorker || !line.trim()) {
27768
+ function wrapNativeFd(fd, containment) {
27769
+ let open = true;
27770
+ return {
27771
+ fd,
27772
+ containment,
27773
+ async close() {
27774
+ if (open) {
27775
+ open = false;
27776
+ external_node_fs_.closeSync(fd);
27777
+ }
27778
+ },
27779
+ async stat() {
27780
+ return external_node_fs_.fstatSync(fd);
27781
+ },
27782
+ async writeFile(data, encoding) {
27783
+ writeAll(fd, Buffer.isBuffer(data) ? data : Buffer.from(data, encoding ?? "utf8"));
27784
+ },
27785
+ };
27786
+ }
27787
+ function removeNativeCreatedFileIfStillPinned(params) {
27788
+ if (!params.created) {
28080
27789
  return;
28081
27790
  }
28082
- let decoded;
28083
27791
  try {
28084
- decoded = JSON.parse(line);
27792
+ const parentPathStat = external_node_fs_.lstatSync(params.parentPath);
27793
+ const parentFdStat = params.binding.fstatIdentity(params.parentFd);
27794
+ const targetPath = external_node_path_.join(params.parentPath, params.basename);
27795
+ const target = external_node_fs_.lstatSync(targetPath);
27796
+ if (!parentPathStat.isSymbolicLink() &&
27797
+ parentPathStat.dev === parentFdStat.dev &&
27798
+ parentPathStat.ino === parentFdStat.ino &&
27799
+ !target.isSymbolicLink() &&
27800
+ file_identity_sameFileIdentity(target, params.created)) {
27801
+ external_node_fs_.rmSync(targetPath);
27802
+ }
28085
27803
  }
28086
27804
  catch {
28087
- rejectPending(new errors_FsSafeError("helper-failed", `pinned helper returned invalid JSON: ${line}`), currentWorker);
28088
- return;
27805
+ // Failed cleanup leaves a fail-closed artifact instead of deleting an
27806
+ // unverified pathname.
28089
27807
  }
28090
- if (typeof decoded !== "object" || decoded === null || !("id" in decoded)) {
28091
- rejectPending(new errors_FsSafeError("helper-failed", "pinned helper returned invalid response"), currentWorker);
28092
- return;
27808
+ }
27809
+ async function createNativeExclusiveFile(targetPath, mode) {
27810
+ const binding = getNativeBinding();
27811
+ if (!binding) {
27812
+ return undefined;
28093
27813
  }
28094
- const response = decoded;
28095
- const id = typeof response.id === "number" ? response.id : undefined;
28096
- if (id === undefined) {
28097
- return;
27814
+ const parentPath = external_node_path_.dirname(targetPath);
27815
+ const basename = external_node_path_.basename(targetPath);
27816
+ const parent = await promises_.open(parentPath, external_node_fs_.constants.O_RDONLY |
27817
+ (typeof external_node_fs_.constants.O_DIRECTORY === "number" ? external_node_fs_.constants.O_DIRECTORY : 0));
27818
+ let fd;
27819
+ let created;
27820
+ try {
27821
+ 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));
27822
+ fd = opened.fd;
27823
+ external_node_fs_.fchmodSync(fd, mode);
27824
+ created = external_node_fs_.fstatSync(fd);
27825
+ return wrapNativeFd(fd, opened.containment);
28098
27826
  }
28099
- const pending = currentWorker.pending.get(id);
28100
- if (!pending) {
28101
- return;
27827
+ catch (error) {
27828
+ if (fd !== undefined) {
27829
+ try {
27830
+ external_node_fs_.closeSync(fd);
27831
+ }
27832
+ catch {
27833
+ // Preserve the original error.
27834
+ }
27835
+ removeNativeCreatedFileIfStillPinned({
27836
+ binding,
27837
+ parentPath,
27838
+ parentFd: parent.fd,
27839
+ basename,
27840
+ created,
27841
+ });
27842
+ }
27843
+ throw error;
28102
27844
  }
28103
- currentWorker.pending.delete(id);
28104
- if (currentWorker.pending.size === 0) {
28105
- setWorkerRef(currentWorker, false);
27845
+ finally {
27846
+ await parent.close().catch(() => undefined);
28106
27847
  }
28107
- if (response.ok === true) {
28108
- pending.resolve(response.result);
28109
- return;
27848
+ }
27849
+ function syncNativeFileBestEffort(fd) {
27850
+ try {
27851
+ external_node_fs_.fsyncSync(fd);
27852
+ }
27853
+ catch (error) {
27854
+ if (error.code !== "EPERM") {
27855
+ throw error;
27856
+ }
27857
+ }
27858
+ }
27859
+ function writeNativeFd(fd, data) {
27860
+ writeAll(fd, data);
27861
+ }
27862
+
27863
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native-pinned-write.js
27864
+
27865
+
27866
+
27867
+
27868
+
27869
+
27870
+ function assertWithinMaxBytes(bytes, maxBytes) {
27871
+ if (maxBytes !== undefined && bytes > maxBytes) {
27872
+ throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
28110
27873
  }
28111
- pending.reject(mapWorkerError(decoded));
28112
27874
  }
28113
- function getWorker() {
28114
- assertPinnedHelperSupported();
28115
- if (worker) {
28116
- return worker;
27875
+ async function inputToBuffer(input, maxBytes) {
27876
+ if (input.kind === "buffer") {
27877
+ const data = typeof input.data === "string"
27878
+ ? Buffer.from(input.data, input.encoding ?? "utf8")
27879
+ : Buffer.from(input.data);
27880
+ assertWithinMaxBytes(data.byteLength, maxBytes);
27881
+ return data;
28117
27882
  }
28118
- const child = (0,external_node_child_process_.spawn)(resolvePython(), ["-u", "-c", PINNED_PYTHON_WORKER_SOURCE], {
28119
- stdio: ["pipe", "pipe", "pipe"],
28120
- });
28121
- const currentWorker = { child, pending: new Map(), stderr: "", stdoutBuffer: "" };
28122
- worker = currentWorker;
28123
- child.stdout.setEncoding("utf8");
28124
- child.stderr.setEncoding("utf8");
28125
- child.stdout.on("data", (chunk) => {
28126
- if (worker !== currentWorker) {
28127
- return;
27883
+ const chunks = [];
27884
+ let bytes = 0;
27885
+ for await (const chunk of input.stream) {
27886
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
27887
+ bytes += buffer.byteLength;
27888
+ assertWithinMaxBytes(bytes, maxBytes);
27889
+ chunks.push(buffer);
27890
+ }
27891
+ return Buffer.concat(chunks, bytes);
27892
+ }
27893
+ function native_pinned_write_nativeOpenFlags(flags) {
27894
+ const closeOnExec = external_node_fs_.constants.O_CLOEXEC;
27895
+ return (flags |
27896
+ (closeOnExec ?? 0) |
27897
+ (typeof external_node_fs_.constants.O_NOFOLLOW === "number" ? external_node_fs_.constants.O_NOFOLLOW : 0));
27898
+ }
27899
+ function sameNativeIdentity(left, right) {
27900
+ return left.dev === right.dev && left.ino === right.ino;
27901
+ }
27902
+ async function runPinnedWriteNative(binding, params) {
27903
+ const data = await inputToBuffer(params.input, params.maxBytes);
27904
+ const root = await promises_.open(params.rootPath, external_node_fs_.constants.O_RDONLY |
27905
+ (typeof external_node_fs_.constants.O_DIRECTORY === "number" ? external_node_fs_.constants.O_DIRECTORY : 0));
27906
+ let parentFd;
27907
+ let tempFd;
27908
+ let targetFd;
27909
+ let tempIdentity;
27910
+ let parentPath = params.rootPath;
27911
+ const tempName = `.${params.basename}.${(0,external_node_crypto_.randomUUID)()}.native.tmp`;
27912
+ let renamed = false;
27913
+ try {
27914
+ const rootIdentity = binding.fstatIdentity(root.fd);
27915
+ if (params.rootIdentity && !sameNativeIdentity(params.rootIdentity, rootIdentity)) {
27916
+ throw new errors_FsSafeError("path-mismatch", "root path changed during native write");
27917
+ }
27918
+ if (params.mkdir) {
27919
+ binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
27920
+ }
27921
+ const parentFlags = external_node_fs_.constants.O_RDONLY |
27922
+ (typeof external_node_fs_.constants.O_DIRECTORY === "number" ? external_node_fs_.constants.O_DIRECTORY : 0);
27923
+ parentFd = binding.openBeneath(root.fd, params.relativeParentPath, parentFlags).fd;
27924
+ parentPath = await promises_.realpath(params.relativeParentPath
27925
+ ? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
27926
+ : params.rootPath);
27927
+ const parentPathStat = await promises_.lstat(parentPath);
27928
+ const parentIdentity = binding.fstatIdentity(parentFd);
27929
+ if (parentPathStat.isSymbolicLink() ||
27930
+ !sameNativeIdentity(parentPathStat, parentIdentity)) {
27931
+ throw new errors_FsSafeError("path-mismatch", "native write parent changed during resolution");
28128
27932
  }
28129
- currentWorker.stdoutBuffer += chunk;
28130
- for (;;) {
28131
- const newline = currentWorker.stdoutBuffer.indexOf("\n");
28132
- if (newline < 0) {
28133
- break;
27933
+ try {
27934
+ await promises_.lstat(external_node_path_.join(parentPath, params.basename));
27935
+ throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
27936
+ }
27937
+ catch (error) {
27938
+ if (error.code !== "ENOENT") {
27939
+ throw error;
28134
27940
  }
28135
- const line = currentWorker.stdoutBuffer.slice(0, newline);
28136
- currentWorker.stdoutBuffer = currentWorker.stdoutBuffer.slice(newline + 1);
28137
- handleWorkerLine(currentWorker, line);
28138
27941
  }
28139
- });
28140
- child.stderr.on("data", (chunk) => {
28141
- if (worker === currentWorker) {
28142
- currentWorker.stderr = `${currentWorker.stderr}${chunk}`.slice(-4096);
27942
+ 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;
27943
+ external_node_fs_.fchmodSync(tempFd, params.mode || 0o600);
27944
+ writeNativeFd(tempFd, data);
27945
+ syncNativeFileBestEffort(tempFd);
27946
+ tempIdentity = external_node_fs_.fstatSync(tempFd);
27947
+ binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
27948
+ renamed = true;
27949
+ targetFd = binding.openBeneath(parentFd, params.basename, native_pinned_write_nativeOpenFlags(external_node_fs_.constants.O_RDONLY)).fd;
27950
+ const targetIdentity = binding.fstatIdentity(targetFd);
27951
+ if (!targetIdentity.isFile || !sameNativeIdentity(tempIdentity, targetIdentity)) {
27952
+ throw new errors_FsSafeError("path-mismatch", "native write target changed after rename");
28143
27953
  }
28144
- });
28145
- child.once("error", (error) => {
28146
- const mapped = isSpawnUnavailable(error)
28147
- ? new errors_FsSafeError("helper-unavailable", "Python helper is unavailable", { cause: error })
28148
- : error instanceof Error
28149
- ? error
28150
- : new Error(String(error));
28151
- rejectPending(mapped, currentWorker);
28152
- });
28153
- child.once("close", (code, signal) => {
28154
- const stderr = currentWorker.stderr.trim();
28155
- rejectPending(new errors_FsSafeError("helper-failed", stderr || `pinned helper exited with code ${code ?? "null"} (${signal ?? "?"})`), currentWorker);
28156
- });
28157
- process.once("exit", () => {
28158
- child.kill("SIGTERM");
28159
- });
28160
- setWorkerRef(currentWorker, false);
28161
- return currentWorker;
28162
- }
28163
- function setRefable(value, ref) {
28164
- if (!value) {
28165
- return;
27954
+ syncNativeFileBestEffort(parentFd);
27955
+ return { dev: targetIdentity.dev, ino: targetIdentity.ino };
28166
27956
  }
28167
- const method = ref ? "ref" : "unref";
28168
- const refable = value;
28169
- refable[method]?.();
28170
- }
28171
- function setWorkerRef(currentWorker, ref) {
28172
- setRefable(currentWorker.child, ref);
28173
- setRefable(currentWorker.child.stdin, ref);
28174
- setRefable(currentWorker.child.stdout, ref);
28175
- setRefable(currentWorker.child.stderr, ref);
28176
- }
28177
- async function runPinnedPythonOperation(params) {
28178
- const requestId = nextRequestId++;
28179
- const currentWorker = getWorker();
28180
- if (typeof currentWorker.child.stdin?.write !== "function") {
28181
- throw new errors_FsSafeError("helper-unavailable", "Python helper stdin is unavailable");
28182
- }
28183
- setWorkerRef(currentWorker, true);
28184
- return await new Promise((resolve, reject) => {
28185
- currentWorker.pending.set(requestId, {
28186
- reject,
28187
- resolve: (value) => resolve(value),
28188
- });
28189
- const request = JSON.stringify({
28190
- id: requestId,
28191
- operation: params.operation,
28192
- rootPath: params.rootPath,
28193
- payload: params.payload,
28194
- });
28195
- currentWorker.child.stdin.write(`${request}\n`, (error) => {
28196
- if (error) {
28197
- currentWorker.pending.delete(requestId);
28198
- if (currentWorker.pending.size === 0) {
28199
- setWorkerRef(currentWorker, false);
28200
- }
28201
- reject(error);
28202
- }
28203
- });
28204
- });
28205
- }
28206
- function assertPinnedPythonOperationAvailable() {
28207
- const currentWorker = getWorker();
28208
- if (typeof currentWorker.child.stdin?.write !== "function") {
28209
- throw new errors_FsSafeError("helper-unavailable", "Python helper stdin is unavailable");
27957
+ finally {
27958
+ if (targetFd !== undefined) {
27959
+ external_node_fs_.closeSync(targetFd);
27960
+ }
27961
+ if (tempFd !== undefined) {
27962
+ external_node_fs_.closeSync(tempFd);
27963
+ }
27964
+ if (!renamed && parentFd !== undefined) {
27965
+ removeNativeCreatedFileIfStillPinned({
27966
+ binding,
27967
+ parentPath,
27968
+ parentFd,
27969
+ basename: tempName,
27970
+ created: tempIdentity,
27971
+ });
27972
+ }
27973
+ if (parentFd !== undefined) {
27974
+ external_node_fs_.closeSync(parentFd);
27975
+ }
27976
+ await root.close().catch(() => undefined);
28210
27977
  }
28211
27978
  }
27979
+
27980
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-operation.js
27981
+
28212
27982
  function validatePinnedOperationPayload(payload) {
28213
27983
  if (typeof payload.relativePath === "string") {
28214
27984
  validatePinnedRelativePath(payload.relativePath);
@@ -28223,9 +27993,6 @@ function validatePinnedOperationPayload(payload) {
28223
27993
  validatePinnedRelativePath(payload.to);
28224
27994
  }
28225
27995
  }
28226
- function isPinnedHelperUnavailable(error) {
28227
- return error instanceof Error && "code" in error && error.code === "helper-unavailable";
28228
- }
28229
27996
  function validatePinnedRelativePath(relativePath) {
28230
27997
  if (relativePath.length === 0 || relativePath === ".") {
28231
27998
  return;
@@ -28247,47 +28014,91 @@ function validatePinnedRelativePath(relativePath) {
28247
28014
  }
28248
28015
  }
28249
28016
 
28250
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-helper.js
28017
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read.js
28251
28018
 
28252
28019
 
28253
- async function runPinnedHelper(operation, rootDir, payload) {
28254
- validatePinnedOperationPayload(payload);
28255
- return await runPinnedPythonOperation({
28256
- operation,
28257
- rootPath: rootDir,
28258
- payload,
28259
- });
28260
- }
28261
- async function helperStat(rootDir, relativePath) {
28262
- return await runPinnedHelper("stat", rootDir, { relativePath });
28020
+ const READ_CHUNK_BYTES = 64 * 1024;
28021
+ function assertMaxBytes(maxBytes) {
28022
+ if (maxBytes === Number.POSITIVE_INFINITY) {
28023
+ return;
28024
+ }
28025
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
28026
+ throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
28027
+ }
28263
28028
  }
28264
- async function helperReaddir(rootDir, relativePath, withFileTypes) {
28265
- return await runPinnedHelper("readdir", rootDir, {
28266
- relativePath,
28267
- withFileTypes,
28268
- });
28029
+ function createScratchBuffer(maxBytes) {
28030
+ const initialReadBytes = Number.isFinite(maxBytes)
28031
+ ? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
28032
+ : READ_CHUNK_BYTES;
28033
+ return Buffer.allocUnsafe(Math.max(1, initialReadBytes));
28269
28034
  }
28270
-
28271
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-path.js
28272
-
28273
-
28274
-
28275
- function isPinnedPathHelperSpawnError(error) {
28276
- return canFallbackFromPythonError(error);
28035
+ function nextReadLength(total, maxBytes, capacity) {
28036
+ return Number.isFinite(maxBytes)
28037
+ ? Math.min(capacity, maxBytes - total + 1)
28038
+ : capacity;
28277
28039
  }
28278
- async function runPinnedPathHelper(params) {
28279
- try {
28280
- await runPinnedHelper(params.operation, params.rootPath, {
28281
- relativePath: params.relativePath,
28282
- });
28040
+ function appendChunk(params) {
28041
+ const total = params.total + params.bytesRead;
28042
+ if (total > params.maxBytes) {
28043
+ throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got at least ${total})`);
28283
28044
  }
28284
- catch (error) {
28285
- if (error instanceof errors_FsSafeError) {
28286
- throw error;
28045
+ params.chunks.push(Buffer.from(params.scratch.subarray(0, params.bytesRead)));
28046
+ return total;
28047
+ }
28048
+ async function readBoundedAsync(maxBytes, readChunk) {
28049
+ assertMaxBytes(maxBytes);
28050
+ const chunks = [];
28051
+ const scratch = createScratchBuffer(maxBytes);
28052
+ let total = 0;
28053
+ while (true) {
28054
+ const length = nextReadLength(total, maxBytes, scratch.length);
28055
+ const bytesRead = await readChunk(scratch, length);
28056
+ if (bytesRead === 0) {
28057
+ return Buffer.concat(chunks, total);
28287
28058
  }
28288
- throw new errors_FsSafeError("helper-failed", "pinned path helper failed", {
28289
- cause: error instanceof Error ? error : undefined,
28059
+ total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
28060
+ }
28061
+ }
28062
+ /**
28063
+ * Reads from the handle's current offset without closing it. A bounded read
28064
+ * consumes at most maxBytes + 1 bytes so growth after an earlier stat cannot
28065
+ * force an unbounded allocation.
28066
+ */
28067
+ async function readFileHandleBounded(handle, maxBytes) {
28068
+ return await readBoundedAsync(maxBytes, async (scratch, length) => {
28069
+ return (await handle.read(scratch, 0, length, null)).bytesRead;
28070
+ });
28071
+ }
28072
+ function readDescriptorChunk(fd, scratch, length) {
28073
+ return new Promise((resolve, reject) => {
28074
+ fs.read(fd, scratch, 0, length, null, (error, bytesRead) => {
28075
+ if (error) {
28076
+ reject(error);
28077
+ return;
28078
+ }
28079
+ resolve(bytesRead);
28290
28080
  });
28081
+ });
28082
+ }
28083
+ /** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
28084
+ async function readFileDescriptorBounded(fd, maxBytes) {
28085
+ return await readBoundedAsync(maxBytes, async (scratch, length) => {
28086
+ return await readDescriptorChunk(fd, scratch, length);
28087
+ });
28088
+ }
28089
+ /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
28090
+ function readFileDescriptorBoundedSync(fd, maxBytes) {
28091
+ assertMaxBytes(maxBytes);
28092
+ const chunks = [];
28093
+ const scratch = createScratchBuffer(maxBytes);
28094
+ let total = 0;
28095
+ while (true) {
28096
+ const length = nextReadLength(total, maxBytes, scratch.length);
28097
+ const bytesRead = fs.readSync(fd, scratch, 0, length, null);
28098
+ if (bytesRead === 0) {
28099
+ return Buffer.concat(chunks, total);
28100
+ }
28101
+ total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
28291
28102
  }
28292
28103
  }
28293
28104
 
@@ -28295,6 +28106,11 @@ async function runPinnedPathHelper(params) {
28295
28106
 
28296
28107
 
28297
28108
 
28109
+
28110
+
28111
+
28112
+
28113
+ const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
28298
28114
  const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
28299
28115
  const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
28300
28116
  const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
@@ -28318,28 +28134,98 @@ function serializeSidecarLockPayload(payload) {
28318
28134
  ownershipToken,
28319
28135
  };
28320
28136
  }
28321
- async function readSidecarLockSnapshot(lockPath) {
28137
+ function relativeSidecarLockPath(lockRoot, lockPath) {
28138
+ const resolved = external_node_path_.resolve(lockPath);
28139
+ const lexicalRelative = external_node_path_.relative(lockRoot.rootDir, resolved);
28140
+ const relative = lexicalRelative !== ".." &&
28141
+ !lexicalRelative.startsWith(`..${external_node_path_.sep}`) &&
28142
+ !external_node_path_.isAbsolute(lexicalRelative)
28143
+ ? lexicalRelative
28144
+ : external_node_path_.relative(lockRoot.rootReal, resolved);
28145
+ if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relative)) {
28146
+ throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
28147
+ }
28148
+ return relative.split(external_node_path_.sep).join(external_node_path_.posix.sep);
28149
+ }
28150
+ function parseSidecarLockPayload(raw, parser) {
28151
+ if (parser) {
28152
+ return parser(raw);
28153
+ }
28154
+ try {
28155
+ const parsed = JSON.parse(raw);
28156
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
28157
+ }
28158
+ catch {
28159
+ return null;
28160
+ }
28161
+ }
28162
+ async function readSidecarLockSnapshot(lockPath, options = {}) {
28322
28163
  try {
28164
+ if (options.lockRoot) {
28165
+ const opened = await options.lockRoot.open(relativeSidecarLockPath(options.lockRoot, lockPath));
28166
+ try {
28167
+ const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
28168
+ return {
28169
+ raw,
28170
+ payload: parseSidecarLockPayload(raw, options.parsePayload),
28171
+ stat: opened.stat,
28172
+ };
28173
+ }
28174
+ finally {
28175
+ await opened.handle.close().catch(() => undefined);
28176
+ }
28177
+ }
28323
28178
  const stat = await promises_.lstat(lockPath);
28324
28179
  const raw = await promises_.readFile(lockPath, "utf8");
28325
- try {
28326
- const parsed = JSON.parse(raw);
28327
- const payload = parsed && typeof parsed === "object" && !Array.isArray(parsed)
28328
- ? parsed
28329
- : null;
28330
- return { raw, payload, stat };
28331
- }
28332
- catch {
28333
- return { raw, payload: null, stat };
28334
- }
28180
+ return { raw, payload: parseSidecarLockPayload(raw, options.parsePayload), stat };
28335
28181
  }
28336
28182
  catch (err) {
28337
- if (err.code === "ENOENT") {
28183
+ if (err.code === "ENOENT" ||
28184
+ (err instanceof errors_FsSafeError && err.code === "not-found")) {
28338
28185
  return null;
28339
28186
  }
28340
28187
  throw err;
28341
28188
  }
28342
28189
  }
28190
+ function readSidecarLockSnapshotSync(lockPath, parsePayload) {
28191
+ let fd;
28192
+ try {
28193
+ const before = fsSync.lstatSync(lockPath);
28194
+ if (!before.isFile() || before.isSymbolicLink())
28195
+ return null;
28196
+ const noFollow = process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
28197
+ ? fsSync.constants.O_NOFOLLOW
28198
+ : 0;
28199
+ fd = fsSync.openSync(lockPath, fsSync.constants.O_RDONLY | noFollow);
28200
+ const opened = fsSync.fstatSync(fd);
28201
+ const raw = fsSync.readFileSync(fd, "utf8");
28202
+ const after = fsSync.lstatSync(lockPath);
28203
+ if (!sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
28204
+ return null;
28205
+ return {
28206
+ raw,
28207
+ payload: parseSidecarLockPayload(raw, parsePayload),
28208
+ stat: after,
28209
+ ownershipToken: readSidecarLockOwnershipToken(raw),
28210
+ };
28211
+ }
28212
+ catch (error) {
28213
+ if (error.code === "ENOENT")
28214
+ return null;
28215
+ throw error;
28216
+ }
28217
+ finally {
28218
+ if (fd !== undefined)
28219
+ fsSync.closeSync(fd);
28220
+ }
28221
+ }
28222
+ function removeSidecarLockIfUnchangedSync(lockPath, observed) {
28223
+ const current = readSidecarLockSnapshotSync(lockPath);
28224
+ if (!current || !sidecarLockSnapshotMatches(current, observed))
28225
+ return false;
28226
+ fsSync.rmSync(lockPath);
28227
+ return true;
28228
+ }
28343
28229
  function sidecarLockSnapshotMatches(current, observed) {
28344
28230
  if (observed.ownershipToken !== undefined) {
28345
28231
  return (current.stat?.isFile() === true &&
@@ -28357,16 +28243,21 @@ function sidecarLockSnapshotMatches(current, observed) {
28357
28243
  }
28358
28244
  return observed.stat !== undefined && current.stat !== undefined;
28359
28245
  }
28360
- async function removeSidecarLockIfUnchanged(lockPath, observed) {
28361
- const current = await readSidecarLockSnapshot(lockPath);
28246
+ async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
28247
+ const current = await readSidecarLockSnapshot(lockPath, options);
28362
28248
  if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
28363
28249
  return false;
28364
28250
  }
28365
- await promises_.rm(lockPath, { force: true }).catch(() => undefined);
28251
+ if (options.lockRoot) {
28252
+ await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath)).catch(() => undefined);
28253
+ }
28254
+ else {
28255
+ await promises_.rm(lockPath, { force: true }).catch(() => undefined);
28256
+ }
28366
28257
  return true;
28367
28258
  }
28368
- async function sidecarLockSnapshotStillPresent(lockPath, observed) {
28369
- const current = await readSidecarLockSnapshot(lockPath);
28259
+ async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
28260
+ const current = await readSidecarLockSnapshot(lockPath, options);
28370
28261
  return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
28371
28262
  }
28372
28263
  async function sidecarReclaimGuardExists(pathname) {
@@ -28402,7 +28293,8 @@ async function removeStaleSidecarLockIfAllowed(params) {
28402
28293
  if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
28403
28294
  return "not-approved";
28404
28295
  }
28405
- if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot))) {
28296
+ const ioOptions = { lockRoot: params.lockRoot, parsePayload: params.parsePayload };
28297
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
28406
28298
  return "changed";
28407
28299
  }
28408
28300
  if (!(await params.shouldRemoveStaleLock({
@@ -28413,11 +28305,16 @@ async function removeStaleSidecarLockIfAllowed(params) {
28413
28305
  }))) {
28414
28306
  return "not-approved";
28415
28307
  }
28416
- if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot))) {
28308
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
28417
28309
  return "changed";
28418
28310
  }
28419
28311
  try {
28420
- await promises_.rm(params.lockPath);
28312
+ if (params.lockRoot) {
28313
+ await params.lockRoot.remove(relativeSidecarLockPath(params.lockRoot, params.lockPath));
28314
+ }
28315
+ else {
28316
+ await promises_.rm(params.lockPath);
28317
+ }
28421
28318
  return "removed";
28422
28319
  }
28423
28320
  catch (err) {
@@ -28428,12 +28325,77 @@ async function removeStaleSidecarLockIfAllowed(params) {
28428
28325
  }
28429
28326
  }
28430
28327
 
28328
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
28329
+
28330
+ function createSidecarLockHandle(params) {
28331
+ let released = false;
28332
+ const release = async () => {
28333
+ if (released)
28334
+ return;
28335
+ released = true;
28336
+ await params.release();
28337
+ };
28338
+ return {
28339
+ lockPath: params.lockPath,
28340
+ normalizedTargetPath: params.normalizedTargetPath,
28341
+ verifyStillHeld: params.verifyStillHeld,
28342
+ release,
28343
+ [Symbol.asyncDispose]: release,
28344
+ };
28345
+ }
28346
+ function createHeldSidecarLockHandle(params) {
28347
+ return createSidecarLockHandle({
28348
+ lockPath: params.held.lockPath,
28349
+ normalizedTargetPath: params.normalizedTargetPath,
28350
+ verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
28351
+ lockRoot: params.held.lockRoot,
28352
+ parsePayload: params.held.parsePayload,
28353
+ }),
28354
+ release: params.release,
28355
+ });
28356
+ }
28357
+
28358
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
28359
+
28360
+ function computeSidecarLockDelayMs(retry, attempt) {
28361
+ const minTimeout = retry.minTimeout ?? 50;
28362
+ const maxTimeout = retry.maxTimeout ?? 1000;
28363
+ const factor = retry.factor ?? 1;
28364
+ const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
28365
+ const jitter = retry.randomize ? 1 + Math.random() : 1;
28366
+ return Math.min(maxTimeout, Math.round(base * jitter));
28367
+ }
28368
+ function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
28369
+ const createdAt = payload &&
28370
+ typeof payload === "object" &&
28371
+ "createdAt" in payload &&
28372
+ typeof payload.createdAt === "string"
28373
+ ? payload.createdAt
28374
+ : "";
28375
+ const createdAtMs = Date.parse(createdAt);
28376
+ return Number.isFinite(createdAtMs) && nowMs - createdAtMs > staleMs;
28377
+ }
28378
+ async function defaultSidecarLockShouldReclaim(params) {
28379
+ if (sidecarLockPayloadIsStale(params.payload, params.staleMs, params.nowMs))
28380
+ return true;
28381
+ try {
28382
+ return params.nowMs - (await promises_.stat(params.lockPath)).mtimeMs > params.staleMs;
28383
+ }
28384
+ catch {
28385
+ return true;
28386
+ }
28387
+ }
28388
+
28431
28389
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
28432
28390
 
28433
28391
 
28434
28392
 
28435
28393
 
28436
28394
 
28395
+
28396
+
28397
+
28398
+
28437
28399
  const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
28438
28400
  function getGlobalManagers() {
28439
28401
  const globalWithState = globalThis;
@@ -28459,6 +28421,9 @@ function resolveManagerState(key) {
28459
28421
  // Backfill state created by fs-safe versions that predate reclaim guards.
28460
28422
  state.reclaimCleanupRegistered ??= false;
28461
28423
  state.reclaimGuards ??= new Set();
28424
+ for (const held of state.held.values()) {
28425
+ held.refCount ??= 1;
28426
+ }
28462
28427
  }
28463
28428
  return state;
28464
28429
  }
@@ -28514,28 +28479,6 @@ async function resolveNormalizedTargetPath(targetPath) {
28514
28479
  return resolved;
28515
28480
  }
28516
28481
  }
28517
- function computeDelayMs(retry, attempt) {
28518
- const minTimeout = retry.minTimeout ?? 50;
28519
- const maxTimeout = retry.maxTimeout ?? 1000;
28520
- const factor = retry.factor ?? 1;
28521
- const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
28522
- const jitter = retry.randomize ? 1 + Math.random() : 1;
28523
- return Math.min(maxTimeout, Math.round(base * jitter));
28524
- }
28525
- async function defaultShouldReclaim(params) {
28526
- const createdAt = typeof params.payload?.createdAt === "string" ? params.payload.createdAt : "";
28527
- const createdAtMs = Date.parse(createdAt);
28528
- if (Number.isFinite(createdAtMs) && params.nowMs - createdAtMs > params.staleMs) {
28529
- return true;
28530
- }
28531
- try {
28532
- const stat = await promises_.stat(params.lockPath);
28533
- return params.nowMs - stat.mtimeMs > params.staleMs;
28534
- }
28535
- catch {
28536
- return true;
28537
- }
28538
- }
28539
28482
  function releaseAllReclaimGuardsSync(state) {
28540
28483
  for (const reclaimGuardPath of state.reclaimGuards) {
28541
28484
  try {
@@ -28551,7 +28494,7 @@ function releaseAllLocksSync(state) {
28551
28494
  for (const [normalizedTargetPath, held] of state.held) {
28552
28495
  void held.handle.close().catch(() => undefined);
28553
28496
  try {
28554
- if (snapshotMatchesSync(held.lockPath, held.snapshot)) {
28497
+ if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
28555
28498
  external_node_fs_.rmSync(held.lockPath, { force: true });
28556
28499
  }
28557
28500
  }
@@ -28562,17 +28505,17 @@ function releaseAllLocksSync(state) {
28562
28505
  }
28563
28506
  releaseAllReclaimGuardsSync(state);
28564
28507
  }
28565
- async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
28508
+ async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
28566
28509
  const current = state.held.get(normalizedTargetPath);
28567
28510
  if (current !== held) {
28568
28511
  return false;
28569
28512
  }
28570
- if (opts.force) {
28571
- held.count = 0;
28513
+ if (options.force) {
28514
+ held.refCount = 0;
28572
28515
  }
28573
28516
  else {
28574
- held.count -= 1;
28575
- if (held.count > 0) {
28517
+ held.refCount -= 1;
28518
+ if (held.refCount > 0) {
28576
28519
  return false;
28577
28520
  }
28578
28521
  }
@@ -28581,9 +28524,16 @@ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
28581
28524
  return true;
28582
28525
  }
28583
28526
  state.held.delete(normalizedTargetPath);
28527
+ if (held.compromiseTimer) {
28528
+ clearInterval(held.compromiseTimer);
28529
+ held.compromiseTimer = undefined;
28530
+ }
28584
28531
  held.releasePromise = (async () => {
28585
28532
  await held.handle.close().catch(() => undefined);
28586
- await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot);
28533
+ await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
28534
+ lockRoot: held.lockRoot,
28535
+ parsePayload: held.parsePayload,
28536
+ });
28587
28537
  })();
28588
28538
  try {
28589
28539
  await held.releasePromise;
@@ -28593,6 +28543,13 @@ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
28593
28543
  held.releasePromise = undefined;
28594
28544
  }
28595
28545
  }
28546
+ function handleForHeldLock(state, normalizedTargetPath, held) {
28547
+ return createHeldSidecarLockHandle({
28548
+ normalizedTargetPath,
28549
+ held,
28550
+ release: async () => await releaseHeldLock(state, normalizedTargetPath, held),
28551
+ });
28552
+ }
28596
28553
  function createSidecarLockManager(key) {
28597
28554
  const state = resolveManagerState(key);
28598
28555
  function ensureExitCleanupRegistered() {
@@ -28612,15 +28569,12 @@ function createSidecarLockManager(key) {
28612
28569
  const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
28613
28570
  const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
28614
28571
  const held = state.held.get(normalizedTargetPath);
28615
- if (held && options.allowReentrant) {
28616
- held.count += 1;
28617
- const release = () => releaseHeldLock(state, normalizedTargetPath, held).then(() => undefined);
28618
- return {
28619
- lockPath,
28620
- normalizedTargetPath,
28621
- release,
28622
- [Symbol.asyncDispose]: release,
28623
- };
28572
+ if (held &&
28573
+ options.reentrantOwner !== undefined &&
28574
+ held.reentrantOwner !== undefined &&
28575
+ options.reentrantOwner === held.reentrantOwner) {
28576
+ held.refCount += 1;
28577
+ return handleForHeldLock(state, normalizedTargetPath, held);
28624
28578
  }
28625
28579
  const startedAt = Date.now();
28626
28580
  const retry = options.retry ?? {};
@@ -28643,7 +28597,7 @@ function createSidecarLockManager(key) {
28643
28597
  const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
28644
28598
  ? Number.POSITIVE_INFINITY
28645
28599
  : Math.max(0, options.timeoutMs - elapsed);
28646
- const delay = Math.min(computeDelayMs(retry, attempt), remaining);
28600
+ const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
28647
28601
  attempt += 1;
28648
28602
  await new Promise((resolve) => setTimeout(resolve, delay));
28649
28603
  };
@@ -28655,18 +28609,36 @@ function createSidecarLockManager(key) {
28655
28609
  }
28656
28610
  let handle = null;
28657
28611
  try {
28658
- handle = await promises_.open(lockPath, "wx");
28659
28612
  const payload = await options.payload();
28660
28613
  const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
28661
- await handle.writeFile(raw, "utf8");
28614
+ if (options.lockRoot) {
28615
+ const relativeLockPath = relativeSidecarLockPath(options.lockRoot, lockPath);
28616
+ try {
28617
+ await options.lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
28618
+ }
28619
+ catch (error) {
28620
+ if (error instanceof errors_FsSafeError && error.code === "already-exists") {
28621
+ throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
28622
+ }
28623
+ throw error;
28624
+ }
28625
+ handle = (await options.lockRoot.open(relativeLockPath)).handle;
28626
+ }
28627
+ else {
28628
+ handle = (await createNativeExclusiveFile(lockPath, 0o600)) ?? await promises_.open(lockPath, "wx");
28629
+ await handle.writeFile(raw, "utf8");
28630
+ }
28662
28631
  const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
28663
28632
  const createdHeld = {
28664
- count: 1,
28633
+ refCount: 1,
28634
+ reentrantOwner: options.reentrantOwner,
28665
28635
  handle,
28666
28636
  lockPath,
28667
28637
  snapshot,
28668
28638
  acquiredAt: Date.now(),
28669
28639
  metadata: options.metadata ?? {},
28640
+ lockRoot: options.lockRoot,
28641
+ parsePayload: options.parsePayload,
28670
28642
  };
28671
28643
  state.held.set(normalizedTargetPath, createdHeld);
28672
28644
  if (ownsReclaimGuard) {
@@ -28679,13 +28651,21 @@ function createSidecarLockManager(key) {
28679
28651
  throw err;
28680
28652
  }
28681
28653
  }
28682
- const release = () => releaseHeldLock(state, normalizedTargetPath, createdHeld).then(() => undefined);
28683
- return {
28684
- lockPath,
28685
- normalizedTargetPath,
28686
- release,
28687
- [Symbol.asyncDispose]: release,
28688
- };
28654
+ const returnedHandle = handleForHeldLock(state, normalizedTargetPath, createdHeld);
28655
+ const interval = options.compromiseCheckIntervalMs;
28656
+ if (options.onCompromised && interval !== undefined && interval > 0) {
28657
+ createdHeld.compromiseTimer = setInterval(() => {
28658
+ void returnedHandle.verifyStillHeld().then((stillHeld) => {
28659
+ if (!stillHeld && createdHeld.compromiseTimer) {
28660
+ clearInterval(createdHeld.compromiseTimer);
28661
+ createdHeld.compromiseTimer = undefined;
28662
+ options.onCompromised?.({ lockPath, normalizedTargetPath });
28663
+ }
28664
+ });
28665
+ }, interval);
28666
+ createdHeld.compromiseTimer.unref();
28667
+ }
28668
+ return returnedHandle;
28689
28669
  }
28690
28670
  catch (err) {
28691
28671
  if (handle) {
@@ -28702,11 +28682,16 @@ function createSidecarLockManager(key) {
28702
28682
  }
28703
28683
  // If payload serialization/write fails, the file may be empty or
28704
28684
  // partial JSON, so remove while our exclusive handle is still open.
28705
- await promises_.rm(lockPath, { force: true }).catch(() => undefined);
28685
+ if (!options.lockRoot) {
28686
+ await promises_.rm(lockPath, { force: true }).catch(() => undefined);
28687
+ }
28706
28688
  await handle.close().catch(() => undefined);
28707
28689
  // Windows can refuse removing an open file; retry after close but
28708
28690
  // only if the path still points at the file identity we created.
28709
- await removeSidecarLockIfUnchanged(lockPath, failedSnapshot);
28691
+ await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
28692
+ lockRoot: options.lockRoot,
28693
+ parsePayload: options.parsePayload,
28694
+ });
28710
28695
  }
28711
28696
  if (err.code !== "EEXIST") {
28712
28697
  throw err;
@@ -28717,11 +28702,18 @@ function createSidecarLockManager(key) {
28717
28702
  continue;
28718
28703
  }
28719
28704
  const nowMs = Date.now();
28720
- const snapshot = await readSidecarLockSnapshot(lockPath);
28705
+ const snapshot = await readSidecarLockSnapshot(lockPath, {
28706
+ lockRoot: options.lockRoot,
28707
+ parsePayload: options.parsePayload,
28708
+ });
28721
28709
  if (!snapshot) {
28722
28710
  continue;
28723
28711
  }
28724
- const shouldReclaim = options.shouldReclaim ?? defaultShouldReclaim;
28712
+ if (state.held.has(normalizedTargetPath)) {
28713
+ await waitForRetry();
28714
+ continue;
28715
+ }
28716
+ const shouldReclaim = options.shouldReclaim ?? defaultSidecarLockShouldReclaim;
28725
28717
  if (await shouldReclaim({
28726
28718
  lockPath,
28727
28719
  normalizedTargetPath,
@@ -28730,7 +28722,10 @@ function createSidecarLockManager(key) {
28730
28722
  nowMs,
28731
28723
  heldByThisProcess: state.held.has(normalizedTargetPath),
28732
28724
  })) {
28733
- if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot))) {
28725
+ if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
28726
+ lockRoot: options.lockRoot,
28727
+ parsePayload: options.parsePayload,
28728
+ }))) {
28734
28729
  continue;
28735
28730
  }
28736
28731
  const staleRecovery = options.staleRecovery ?? "fail-closed";
@@ -28745,6 +28740,8 @@ function createSidecarLockManager(key) {
28745
28740
  normalizedTargetPath,
28746
28741
  snapshot,
28747
28742
  shouldRemoveStaleLock: options.shouldRemoveStaleLock,
28743
+ lockRoot: options.lockRoot,
28744
+ parsePayload: options.parsePayload,
28748
28745
  });
28749
28746
  if (removal === "removed" || removal === "changed") {
28750
28747
  continue;
@@ -28832,6 +28829,7 @@ function __setFsSafeTestHooksForTest(hooks) {
28832
28829
 
28833
28830
 
28834
28831
 
28832
+
28835
28833
  function pinned_write_byteLength(input, encoding) {
28836
28834
  return typeof input === "string"
28837
28835
  ? Buffer.byteLength(input, encoding ?? "utf8")
@@ -28846,7 +28844,7 @@ function assertSafeBasename(basename) {
28846
28844
  throw new errors_FsSafeError("invalid-path", "invalid target path");
28847
28845
  }
28848
28846
  }
28849
- function assertWithinMaxBytes(bytes, maxBytes) {
28847
+ function pinned_write_assertWithinMaxBytes(bytes, maxBytes) {
28850
28848
  if (maxBytes !== undefined && bytes > maxBytes) {
28851
28849
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
28852
28850
  }
@@ -28866,7 +28864,7 @@ async function writeStreamToHandle(stream, handle, maxBytes) {
28866
28864
  for await (const chunk of stream) {
28867
28865
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
28868
28866
  bytes += buffer.byteLength;
28869
- assertWithinMaxBytes(bytes, maxBytes);
28867
+ pinned_write_assertWithinMaxBytes(bytes, maxBytes);
28870
28868
  let offset = 0;
28871
28869
  while (offset < buffer.byteLength) {
28872
28870
  const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
@@ -28877,74 +28875,21 @@ async function writeStreamToHandle(stream, handle, maxBytes) {
28877
28875
  }
28878
28876
  }
28879
28877
  }
28880
- async function inputToBase64(input, maxBytes) {
28881
- if (input.kind === "buffer") {
28882
- assertWithinMaxBytes(pinned_write_byteLength(input.data, input.encoding), maxBytes);
28883
- return (typeof input.data === "string"
28884
- ? Buffer.from(input.data, input.encoding ?? "utf8")
28885
- : input.data).toString("base64");
28886
- }
28887
- const chunks = [];
28888
- let bytes = 0;
28889
- for await (const chunk of input.stream) {
28890
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
28891
- bytes += buffer.byteLength;
28892
- assertWithinMaxBytes(bytes, maxBytes);
28893
- chunks.push(buffer);
28894
- }
28895
- return Buffer.concat(chunks, bytes).toString("base64");
28896
- }
28897
28878
  async function runPinnedWriteHelper(params) {
28898
28879
  assertSafeBasename(params.basename);
28899
28880
  validatePinnedOperationPayload({
28900
28881
  relativeParentPath: params.relativeParentPath,
28901
28882
  });
28902
- // The Python helper deliberately enforces the strict post-rename inode
28903
- // contract. The explicit compatibility policy therefore uses the guarded
28904
- // Node fallback, where content verification can replace that one check.
28883
+ // The explicit compatibility policy uses the guarded Node fallback, where
28884
+ // content verification can replace the strict post-rename inode check.
28905
28885
  if (params.onRenameIdentityMismatch === "verify-content") {
28906
28886
  return await runPinnedWriteFallback(params);
28907
28887
  }
28908
- if (getFsSafePythonConfig().mode === "off") {
28909
- return await runPinnedWriteFallback(params);
28910
- }
28911
- if (params.input.kind === "stream") {
28912
- try {
28913
- assertPinnedPythonOperationAvailable();
28914
- }
28915
- catch (error) {
28916
- if (canFallbackFromPythonError(error)) {
28917
- return await runPinnedWriteFallback(params);
28918
- }
28919
- throw error;
28920
- }
28921
- }
28922
- const input = params.input.kind === "stream"
28923
- ? { kind: "buffer", data: Buffer.from(await inputToBase64(params.input, params.maxBytes), "base64") }
28924
- : params.input;
28925
- const payload = {
28926
- base64: await inputToBase64(input, params.maxBytes),
28927
- basename: params.basename,
28928
- maxBytes: params.maxBytes ?? -1,
28929
- mkdir: params.mkdir,
28930
- mode: params.mode || 0o600,
28931
- overwrite: params.overwrite !== false,
28932
- relativeParentPath: params.relativeParentPath,
28933
- ...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
28934
- };
28935
- try {
28936
- return await runPinnedPythonOperation({
28937
- operation: "write",
28938
- rootPath: params.rootPath,
28939
- payload,
28940
- });
28941
- }
28942
- catch (error) {
28943
- if (canFallbackFromPythonError(error)) {
28944
- return await runPinnedWriteFallback({ ...params, input });
28945
- }
28946
- throw error;
28888
+ const native = getNativeBinding();
28889
+ if (native && params.overwrite === false) {
28890
+ return await runPinnedWriteNative(native, params);
28947
28891
  }
28892
+ return await runPinnedWriteFallback(params);
28948
28893
  }
28949
28894
  async function runPinnedWriteWithRenamePolicy(params) {
28950
28895
  const { targetPath, renameIdentity, ...writeParams } = params;
@@ -28967,34 +28912,19 @@ async function runPinnedWriteWithRenamePolicy(params) {
28967
28912
  onRenameIdentityMismatch: "verify-content",
28968
28913
  }));
28969
28914
  }
28970
- async function runPinnedCopyHelper(params) {
28971
- assertSafeBasename(params.basename);
28972
- validatePinnedOperationPayload({
28973
- relativeParentPath: params.relativeParentPath,
28974
- });
28975
- return await runPinnedPythonOperation({
28976
- operation: "copy",
28977
- rootPath: params.rootPath,
28978
- payload: {
28979
- basename: params.basename,
28980
- maxBytes: params.maxBytes ?? -1,
28981
- mkdir: params.mkdir,
28982
- mode: params.mode || 0o600,
28983
- overwrite: params.overwrite !== false,
28984
- relativeParentPath: params.relativeParentPath,
28985
- ...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
28986
- sourceDev: params.sourceIdentity.dev,
28987
- sourceIno: params.sourceIdentity.ino,
28988
- sourcePath: params.sourcePath,
28989
- },
28990
- });
28991
- }
28992
28915
  async function runPinnedWriteFallback(params) {
28993
- const parentPath = params.relativeParentPath
28916
+ let parentPath = params.relativeParentPath
28994
28917
  ? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
28995
28918
  : params.rootPath;
28996
28919
  if (params.mkdir) {
28997
- await mkdirPathComponentsWithGuards({ rootReal: params.rootPath, targetPath: parentPath });
28920
+ // mkdirPathComponentsWithGuards may resolve the final component through
28921
+ // an in-root symlink (e.g. a skill-bank layout). Use its returned real
28922
+ // path for the subsequent guard and target path so we don't re-check the
28923
+ // original, possibly-symlinked, lexical path and reject it outright.
28924
+ parentPath = await mkdirPathComponentsWithGuards({
28925
+ rootReal: params.rootPath,
28926
+ targetPath: parentPath,
28927
+ });
28998
28928
  }
28999
28929
  const parentGuard = params.mkdir
29000
28930
  ? await directory_guard_createAsyncDirectoryGuard(parentPath)
@@ -29010,8 +28940,9 @@ async function runPinnedWriteFallback(params) {
29010
28940
  });
29011
28941
  let created = true;
29012
28942
  try {
28943
+ await handle.chmod(params.mode);
29013
28944
  if (params.input.kind === "buffer") {
29014
- assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
28945
+ pinned_write_assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
29015
28946
  if (typeof params.input.data === "string") {
29016
28947
  await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
29017
28948
  }
@@ -29049,8 +28980,9 @@ async function runPinnedWriteFallback(params) {
29049
28980
  let renamed = false;
29050
28981
  try {
29051
28982
  handle = await promises_.open(tempPath, tempFlags, params.mode);
28983
+ await handle.chmod(params.mode);
29052
28984
  if (params.input.kind === "buffer") {
29053
- assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
28985
+ pinned_write_assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
29054
28986
  if (typeof params.input.data === "string") {
29055
28987
  await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
29056
28988
  }
@@ -29133,12 +29065,85 @@ async function runPinnedWriteFallback(params) {
29133
29065
 
29134
29066
  // EXTERNAL MODULE: external "node:os"
29135
29067
  var external_node_os_ = __webpack_require__(8161);
29068
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-existing.js
29069
+
29070
+
29071
+
29072
+
29073
+ function isFilesystemRoot(candidate) {
29074
+ return external_node_path_.parse(candidate).root === candidate;
29075
+ }
29076
+ async function root_path_existing_pathExists(targetPath) {
29077
+ try {
29078
+ await promises_.lstat(targetPath);
29079
+ return true;
29080
+ }
29081
+ catch (error) {
29082
+ if (path_isNotFoundPathError(error)) {
29083
+ return false;
29084
+ }
29085
+ throw error;
29086
+ }
29087
+ }
29088
+ async function root_path_existing_resolvePathViaExistingAncestor(targetPath) {
29089
+ const normalized = external_node_path_.resolve(targetPath);
29090
+ let cursor = normalized;
29091
+ const missingSuffix = [];
29092
+ while (!isFilesystemRoot(cursor) && !(await root_path_existing_pathExists(cursor))) {
29093
+ missingSuffix.unshift(external_node_path_.basename(cursor));
29094
+ const parent = external_node_path_.dirname(cursor);
29095
+ if (parent === cursor) {
29096
+ break;
29097
+ }
29098
+ cursor = parent;
29099
+ }
29100
+ if (!(await root_path_existing_pathExists(cursor))) {
29101
+ return normalized;
29102
+ }
29103
+ try {
29104
+ const resolvedAncestor = external_node_path_.resolve(await promises_.realpath(cursor));
29105
+ return missingSuffix.length === 0
29106
+ ? resolvedAncestor
29107
+ : external_node_path_.resolve(resolvedAncestor, ...missingSuffix);
29108
+ }
29109
+ catch {
29110
+ return normalized;
29111
+ }
29112
+ }
29113
+ function root_path_existing_resolvePathViaExistingAncestorSync(targetPath) {
29114
+ const normalized = path.resolve(targetPath);
29115
+ let cursor = normalized;
29116
+ const missingSuffix = [];
29117
+ while (!isFilesystemRoot(cursor) && !fs.existsSync(cursor)) {
29118
+ missingSuffix.unshift(path.basename(cursor));
29119
+ const parent = path.dirname(cursor);
29120
+ if (parent === cursor) {
29121
+ break;
29122
+ }
29123
+ cursor = parent;
29124
+ }
29125
+ if (!fs.existsSync(cursor)) {
29126
+ return normalized;
29127
+ }
29128
+ try {
29129
+ const resolvedAncestor = path.resolve(fs.realpathSync(cursor));
29130
+ return missingSuffix.length === 0
29131
+ ? resolvedAncestor
29132
+ : path.resolve(resolvedAncestor, ...missingSuffix);
29133
+ }
29134
+ catch {
29135
+ return normalized;
29136
+ }
29137
+ }
29138
+
29136
29139
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
29137
29140
 
29138
29141
 
29139
29142
 
29140
29143
 
29141
29144
 
29145
+
29146
+
29142
29147
  const ROOT_PATH_ALIAS_POLICIES = {
29143
29148
  strict: Object.freeze({
29144
29149
  allowFinalSymlinkForUnlink: false,
@@ -29154,7 +29159,7 @@ async function resolveRootPath(params) {
29154
29159
  const absolutePath = external_node_path_.resolve(params.absolutePath);
29155
29160
  const rootCanonicalPath = params.rootCanonicalPath
29156
29161
  ? external_node_path_.resolve(params.rootCanonicalPath)
29157
- : await root_path_resolvePathViaExistingAncestor(rootPath);
29162
+ : await root_path_existing_resolvePathViaExistingAncestor(rootPath);
29158
29163
  const context = createBoundaryResolutionContext({
29159
29164
  resolveParams: params,
29160
29165
  rootPath,
@@ -29216,15 +29221,40 @@ function isPromiseLike(value) {
29216
29221
  typeof value.then === "function");
29217
29222
  }
29218
29223
  function createLexicalTraversalState(params) {
29219
- const relative = external_node_path_.relative(params.rootPath, params.absolutePath);
29224
+ const rawAbsolutePath = params.params.absolutePath;
29225
+ const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
29226
+ const relative = rawRelativePath ?? external_node_path_.relative(params.rootPath, params.absolutePath);
29220
29227
  return {
29221
- segments: relative.split(external_node_path_.sep).filter(Boolean),
29228
+ segments: splitTraversalSegments(relative),
29222
29229
  allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
29223
29230
  canonicalCursor: params.rootCanonicalPath,
29224
29231
  lexicalCursor: params.rootPath,
29225
29232
  preserveFinalSymlink: false,
29226
29233
  };
29227
29234
  }
29235
+ function splitTraversalSegments(value) {
29236
+ return value
29237
+ .split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
29238
+ .filter((segment) => Boolean(segment) && segment !== ".");
29239
+ }
29240
+ function rawPathRelativeToRoot(rootPath, candidatePath) {
29241
+ if (!external_node_path_.isAbsolute(candidatePath)) {
29242
+ return undefined;
29243
+ }
29244
+ const root = external_node_path_.resolve(rootPath);
29245
+ const candidate = process.platform === "win32"
29246
+ ? candidatePath.replaceAll("/", external_node_path_.sep)
29247
+ : candidatePath;
29248
+ if (candidate === root) {
29249
+ return "";
29250
+ }
29251
+ const rootWithSep = root.endsWith(external_node_path_.sep) ? root : `${root}${external_node_path_.sep}`;
29252
+ const candidatePrefix = candidate.slice(0, rootWithSep.length);
29253
+ const prefixMatches = process.platform === "win32"
29254
+ ? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
29255
+ : candidatePrefix === rootWithSep;
29256
+ return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
29257
+ }
29228
29258
  function assertLexicalCursorInsideBoundary(params) {
29229
29259
  assertInsideBoundary({
29230
29260
  boundaryLabel: params.params.boundaryLabel,
@@ -29235,13 +29265,15 @@ function assertLexicalCursorInsideBoundary(params) {
29235
29265
  }
29236
29266
  function applyMissingSuffixToCanonicalCursor(params) {
29237
29267
  const missingSuffix = params.state.segments.slice(params.missingFromIndex);
29238
- params.state.canonicalCursor = external_node_path_.resolve(params.state.canonicalCursor, ...missingSuffix);
29239
- assertLexicalCursorInsideBoundary({
29240
- params: params.params,
29241
- rootCanonicalPath: params.rootCanonicalPath,
29242
- candidatePath: params.state.canonicalCursor,
29243
- absolutePath: params.absolutePath,
29244
- });
29268
+ for (const segment of missingSuffix) {
29269
+ advanceCanonicalCursorForSegment({
29270
+ state: params.state,
29271
+ segment,
29272
+ rootCanonicalPath: params.rootCanonicalPath,
29273
+ params: params.params,
29274
+ absolutePath: params.absolutePath,
29275
+ });
29276
+ }
29245
29277
  }
29246
29278
  function advanceCanonicalCursorForSegment(params) {
29247
29279
  params.state.canonicalCursor = external_node_path_.resolve(params.state.canonicalCursor, params.segment);
@@ -29357,11 +29389,20 @@ function resolveAndApplySymlinkHop(params) {
29357
29389
  boundaryLabel: params.boundaryLabel,
29358
29390
  });
29359
29391
  }
29392
+ function applyParentTraversalStep(params) {
29393
+ params.state.lexicalCursor = external_node_path_.resolve(params.state.lexicalCursor, "..");
29394
+ advanceCanonicalCursorForSegment({
29395
+ state: params.state,
29396
+ segment: "..",
29397
+ rootCanonicalPath: params.rootCanonicalPath,
29398
+ params: params.resolveParams,
29399
+ absolutePath: params.absolutePath,
29400
+ });
29401
+ }
29360
29402
  function* iterateLexicalTraversal(state) {
29361
29403
  for (let idx = 0; idx < state.segments.length; idx += 1) {
29362
29404
  const segment = state.segments[idx] ?? "";
29363
29405
  const isLast = idx === state.segments.length - 1;
29364
- state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
29365
29406
  yield { idx, segment, isLast };
29366
29407
  }
29367
29408
  }
@@ -29374,6 +29415,14 @@ async function resolveRootPathLexicalAsync(params) {
29374
29415
  absolutePath: params.absolutePath,
29375
29416
  };
29376
29417
  for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
29418
+ if (segment === "..") {
29419
+ applyParentTraversalStep({
29420
+ ...sharedStepParams,
29421
+ resolveParams: params.params,
29422
+ });
29423
+ continue;
29424
+ }
29425
+ state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
29377
29426
  const stat = await readLexicalStat({
29378
29427
  ...sharedStepParams,
29379
29428
  missingFromIndex: idx,
@@ -29401,7 +29450,7 @@ async function resolveRootPathLexicalAsync(params) {
29401
29450
  resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
29402
29451
  });
29403
29452
  }
29404
- const kind = await getPathKind(params.absolutePath, state.preserveFinalSymlink);
29453
+ const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
29405
29454
  return finalizeLexicalResolution({
29406
29455
  ...params,
29407
29456
  state,
@@ -29413,6 +29462,15 @@ function resolveRootPathLexicalSync(params) {
29413
29462
  for (let idx = 0; idx < state.segments.length; idx += 1) {
29414
29463
  const segment = state.segments[idx] ?? "";
29415
29464
  const isLast = idx === state.segments.length - 1;
29465
+ if (segment === "..") {
29466
+ applyParentTraversalStep({
29467
+ state,
29468
+ rootCanonicalPath: params.rootCanonicalPath,
29469
+ resolveParams: params.params,
29470
+ absolutePath: params.absolutePath,
29471
+ });
29472
+ continue;
29473
+ }
29416
29474
  state.lexicalCursor = path.join(state.lexicalCursor, segment);
29417
29475
  const maybeStat = readLexicalStat({
29418
29476
  state,
@@ -29454,7 +29512,7 @@ function resolveRootPathLexicalSync(params) {
29454
29512
  throw new Error("Unexpected async symlink resolution");
29455
29513
  }
29456
29514
  }
29457
- const kind = getPathKindSync(params.absolutePath, state.preserveFinalSymlink);
29515
+ const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
29458
29516
  return finalizeLexicalResolution({
29459
29517
  ...params,
29460
29518
  state,
@@ -29523,7 +29581,7 @@ async function resolveOutsideLexicalCanonicalPathAsync(params) {
29523
29581
  if (path_isPathInside(params.rootPath, params.absolutePath)) {
29524
29582
  return undefined;
29525
29583
  }
29526
- return await root_path_resolvePathViaExistingAncestor(params.absolutePath);
29584
+ return await root_path_existing_resolvePathViaExistingAncestor(params.absolutePath);
29527
29585
  }
29528
29586
  function resolveOutsideLexicalCanonicalPathSync(params) {
29529
29587
  if (isPathInside(params.rootPath, params.absolutePath)) {
@@ -29570,60 +29628,6 @@ function buildResolvedRootPath(params) {
29570
29628
  kind: params.kind.kind,
29571
29629
  };
29572
29630
  }
29573
- async function root_path_resolvePathViaExistingAncestor(targetPath) {
29574
- const normalized = external_node_path_.resolve(targetPath);
29575
- let cursor = normalized;
29576
- const missingSuffix = [];
29577
- while (!isFilesystemRoot(cursor) && !(await root_path_pathExists(cursor))) {
29578
- missingSuffix.unshift(external_node_path_.basename(cursor));
29579
- const parent = external_node_path_.dirname(cursor);
29580
- if (parent === cursor) {
29581
- break;
29582
- }
29583
- cursor = parent;
29584
- }
29585
- if (!(await root_path_pathExists(cursor))) {
29586
- return normalized;
29587
- }
29588
- try {
29589
- const resolvedAncestor = external_node_path_.resolve(await promises_.realpath(cursor));
29590
- if (missingSuffix.length === 0) {
29591
- return resolvedAncestor;
29592
- }
29593
- return external_node_path_.resolve(resolvedAncestor, ...missingSuffix);
29594
- }
29595
- catch {
29596
- return normalized;
29597
- }
29598
- }
29599
- function resolvePathViaExistingAncestorSync(targetPath) {
29600
- const normalized = path.resolve(targetPath);
29601
- let cursor = normalized;
29602
- const missingSuffix = [];
29603
- while (!isFilesystemRoot(cursor) && !fs.existsSync(cursor)) {
29604
- missingSuffix.unshift(path.basename(cursor));
29605
- const parent = path.dirname(cursor);
29606
- if (parent === cursor) {
29607
- break;
29608
- }
29609
- cursor = parent;
29610
- }
29611
- if (!fs.existsSync(cursor)) {
29612
- return normalized;
29613
- }
29614
- try {
29615
- // Keep sync behavior aligned with async (`fsp.realpath`) to avoid
29616
- // platform-specific canonical alias drift (notably on Windows).
29617
- const resolvedAncestor = path.resolve(fs.realpathSync(cursor));
29618
- if (missingSuffix.length === 0) {
29619
- return resolvedAncestor;
29620
- }
29621
- return path.resolve(resolvedAncestor, ...missingSuffix);
29622
- }
29623
- catch {
29624
- return normalized;
29625
- }
29626
- }
29627
29631
  async function getPathKind(absolutePath, preserveFinalSymlink) {
29628
29632
  try {
29629
29633
  const stat = preserveFinalSymlink
@@ -29691,21 +29695,6 @@ function shortPath(value) {
29691
29695
  }
29692
29696
  return value;
29693
29697
  }
29694
- function isFilesystemRoot(candidate) {
29695
- return external_node_path_.parse(candidate).root === candidate;
29696
- }
29697
- async function root_path_pathExists(targetPath) {
29698
- try {
29699
- await promises_.lstat(targetPath);
29700
- return true;
29701
- }
29702
- catch (error) {
29703
- if (path_isNotFoundPathError(error)) {
29704
- return false;
29705
- }
29706
- throw error;
29707
- }
29708
- }
29709
29698
  async function resolveSymlinkHopPath(symlinkPath) {
29710
29699
  try {
29711
29700
  return external_node_path_.resolve(await promises_.realpath(symlinkPath));
@@ -29716,7 +29705,7 @@ async function resolveSymlinkHopPath(symlinkPath) {
29716
29705
  }
29717
29706
  const linkTarget = await promises_.readlink(symlinkPath);
29718
29707
  const linkAbsolute = external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget);
29719
- return root_path_resolvePathViaExistingAncestor(linkAbsolute);
29708
+ return root_path_existing_resolvePathViaExistingAncestor(linkAbsolute);
29720
29709
  }
29721
29710
  }
29722
29711
  function resolveSymlinkHopPathSync(symlinkPath) {
@@ -29751,7 +29740,7 @@ async function assertNoPathAliasEscape(params) {
29751
29740
  return;
29752
29741
  }
29753
29742
  await assertNoHardlinkedFinalPath({
29754
- filePath: resolved.absolutePath,
29743
+ filePath: resolved.canonicalPath,
29755
29744
  root: resolved.rootPath,
29756
29745
  boundaryLabel: params.boundaryLabel,
29757
29746
  allowFinalHardlinkForUnlink: params.policy?.allowFinalHardlinkForUnlink,
@@ -29785,8 +29774,80 @@ function path_policy_shortPath(value) {
29785
29774
  return value;
29786
29775
  }
29787
29776
 
29788
- // EXTERNAL MODULE: external "node:url"
29789
- var external_node_url_ = __webpack_require__(3136);
29777
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/string-coerce.js
29778
+ function readStringValue(value) {
29779
+ return typeof value === "string" ? value : undefined;
29780
+ }
29781
+ function normalizeNullableString(value) {
29782
+ if (typeof value !== "string") {
29783
+ return null;
29784
+ }
29785
+ const trimmed = value.trim();
29786
+ return trimmed ? trimmed : null;
29787
+ }
29788
+ function normalizeOptionalString(value) {
29789
+ return normalizeNullableString(value) ?? undefined;
29790
+ }
29791
+ function normalizeStringifiedOptionalString(value) {
29792
+ if (typeof value === "string") {
29793
+ return normalizeOptionalString(value);
29794
+ }
29795
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
29796
+ return normalizeOptionalString(String(value));
29797
+ }
29798
+ return undefined;
29799
+ }
29800
+ function normalizeOptionalLowercaseString(value) {
29801
+ return normalizeOptionalString(value)?.toLowerCase();
29802
+ }
29803
+ function normalizeLowercaseStringOrEmpty(value) {
29804
+ return normalizeOptionalLowercaseString(value) ?? "";
29805
+ }
29806
+ function normalizeFastMode(raw) {
29807
+ if (typeof raw === "boolean") {
29808
+ return raw;
29809
+ }
29810
+ if (!raw) {
29811
+ return undefined;
29812
+ }
29813
+ const key = normalizeLowercaseStringOrEmpty(raw);
29814
+ if (["off", "false", "no", "0", "disable", "disabled", "normal"].includes(key)) {
29815
+ return false;
29816
+ }
29817
+ if (["on", "true", "yes", "1", "enable", "enabled", "fast"].includes(key)) {
29818
+ return true;
29819
+ }
29820
+ return undefined;
29821
+ }
29822
+ function lowercasePreservingWhitespace(value) {
29823
+ return value.toLowerCase();
29824
+ }
29825
+ function localeLowercasePreservingWhitespace(value) {
29826
+ return value.toLocaleLowerCase();
29827
+ }
29828
+ function resolvePrimaryStringValue(value) {
29829
+ if (typeof value === "string") {
29830
+ return normalizeOptionalString(value);
29831
+ }
29832
+ if (!value || typeof value !== "object") {
29833
+ return undefined;
29834
+ }
29835
+ return normalizeOptionalString(value.primary);
29836
+ }
29837
+ function normalizeOptionalThreadValue(value) {
29838
+ if (typeof value === "number") {
29839
+ return Number.isFinite(value) ? Math.trunc(value) : undefined;
29840
+ }
29841
+ return normalizeOptionalString(value);
29842
+ }
29843
+ function normalizeOptionalStringifiedId(value) {
29844
+ const normalized = normalizeOptionalThreadValue(value);
29845
+ return normalized == null ? undefined : String(normalized);
29846
+ }
29847
+ function hasNonEmptyString(value) {
29848
+ return normalizeOptionalString(value) !== undefined;
29849
+ }
29850
+
29790
29851
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/local-file-access.js
29791
29852
 
29792
29853
 
@@ -29918,6 +29979,28 @@ const WINDOWS_RESERVED_DEVICE_NAMES = new Set([
29918
29979
  "LPT²",
29919
29980
  "LPT³",
29920
29981
  ]);
29982
+ const WINDOWS_SEPARATOR_CHAR_CODE = 0x5c;
29983
+ const WINDOWS_IGNORED_SPACE_CHAR_CODE = 0x20;
29984
+ const WINDOWS_IGNORED_DOT_CHAR_CODE = 0x2e;
29985
+ function trimTrailingWindowsSeparators(value) {
29986
+ let end = value.length;
29987
+ while (end > 0 && value.charCodeAt(end - 1) === WINDOWS_SEPARATOR_CHAR_CODE) {
29988
+ end -= 1;
29989
+ }
29990
+ return end === value.length ? value : value.slice(0, end);
29991
+ }
29992
+ function trimTrailingWindowsIgnoredChars(value) {
29993
+ let end = value.length;
29994
+ while (end > 0) {
29995
+ const charCode = value.charCodeAt(end - 1);
29996
+ if (charCode !== WINDOWS_IGNORED_SPACE_CHAR_CODE &&
29997
+ charCode !== WINDOWS_IGNORED_DOT_CHAR_CODE) {
29998
+ break;
29999
+ }
30000
+ end -= 1;
30001
+ }
30002
+ return end === value.length ? value : value.slice(0, end);
30003
+ }
29921
30004
  function candidateReadPaths(filePath) {
29922
30005
  if (!filePath.startsWith("file://")) {
29923
30006
  return [filePath];
@@ -29946,10 +30029,10 @@ function matchPosixDeviceReadPath(filePath, cwd) {
29946
30029
  return undefined;
29947
30030
  }
29948
30031
  function normalizeWindowsDeviceBaseName(filePath) {
29949
- const normalized = filePath.replace(/\//g, "\\").replace(/[\\]+$/g, "");
30032
+ const normalized = trimTrailingWindowsSeparators(filePath.replace(/\//g, "\\"));
29950
30033
  const lastSegment = normalized.split("\\").filter(Boolean).at(-1) ?? normalized;
29951
30034
  const withoutStream = lastSegment.split(":")[0] ?? lastSegment;
29952
- const withoutTrailingIgnoredChars = withoutStream.replace(/[ .]+$/g, "");
30035
+ const withoutTrailingIgnoredChars = trimTrailingWindowsIgnoredChars(withoutStream);
29953
30036
  return (withoutTrailingIgnoredChars.split(".")[0] ?? withoutTrailingIgnoredChars).toUpperCase();
29954
30037
  }
29955
30038
  function matchWindowsDeviceReadPath(filePath) {
@@ -29984,94 +30067,6 @@ function assertNoUnsafeDeviceReadPath(filePath, options) {
29984
30067
  }
29985
30068
  }
29986
30069
 
29987
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read.js
29988
-
29989
-
29990
- const READ_CHUNK_BYTES = 64 * 1024;
29991
- function assertMaxBytes(maxBytes) {
29992
- if (maxBytes === Number.POSITIVE_INFINITY) {
29993
- return;
29994
- }
29995
- if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
29996
- throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
29997
- }
29998
- }
29999
- function createScratchBuffer(maxBytes) {
30000
- const initialReadBytes = Number.isFinite(maxBytes)
30001
- ? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
30002
- : READ_CHUNK_BYTES;
30003
- return Buffer.allocUnsafe(Math.max(1, initialReadBytes));
30004
- }
30005
- function nextReadLength(total, maxBytes, capacity) {
30006
- return Number.isFinite(maxBytes)
30007
- ? Math.min(capacity, maxBytes - total + 1)
30008
- : capacity;
30009
- }
30010
- function appendChunk(params) {
30011
- const total = params.total + params.bytesRead;
30012
- if (total > params.maxBytes) {
30013
- throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got at least ${total})`);
30014
- }
30015
- params.chunks.push(Buffer.from(params.scratch.subarray(0, params.bytesRead)));
30016
- return total;
30017
- }
30018
- async function readBoundedAsync(maxBytes, readChunk) {
30019
- assertMaxBytes(maxBytes);
30020
- const chunks = [];
30021
- const scratch = createScratchBuffer(maxBytes);
30022
- let total = 0;
30023
- while (true) {
30024
- const length = nextReadLength(total, maxBytes, scratch.length);
30025
- const bytesRead = await readChunk(scratch, length);
30026
- if (bytesRead === 0) {
30027
- return Buffer.concat(chunks, total);
30028
- }
30029
- total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
30030
- }
30031
- }
30032
- /**
30033
- * Reads from the handle's current offset without closing it. A bounded read
30034
- * consumes at most maxBytes + 1 bytes so growth after an earlier stat cannot
30035
- * force an unbounded allocation.
30036
- */
30037
- async function readFileHandleBounded(handle, maxBytes) {
30038
- return await readBoundedAsync(maxBytes, async (scratch, length) => {
30039
- return (await handle.read(scratch, 0, length, null)).bytesRead;
30040
- });
30041
- }
30042
- function readDescriptorChunk(fd, scratch, length) {
30043
- return new Promise((resolve, reject) => {
30044
- fs.read(fd, scratch, 0, length, null, (error, bytesRead) => {
30045
- if (error) {
30046
- reject(error);
30047
- return;
30048
- }
30049
- resolve(bytesRead);
30050
- });
30051
- });
30052
- }
30053
- /** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
30054
- async function readFileDescriptorBounded(fd, maxBytes) {
30055
- return await readBoundedAsync(maxBytes, async (scratch, length) => {
30056
- return await readDescriptorChunk(fd, scratch, length);
30057
- });
30058
- }
30059
- /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
30060
- function readFileDescriptorBoundedSync(fd, maxBytes) {
30061
- assertMaxBytes(maxBytes);
30062
- const chunks = [];
30063
- const scratch = createScratchBuffer(maxBytes);
30064
- let total = 0;
30065
- while (true) {
30066
- const length = nextReadLength(total, maxBytes, scratch.length);
30067
- const bytesRead = fs.readSync(fd, scratch, 0, length, null);
30068
- if (bytesRead === 0) {
30069
- return Buffer.concat(chunks, total);
30070
- }
30071
- total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
30072
- }
30073
- }
30074
-
30075
30070
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/read-opened-file.js
30076
30071
 
30077
30072
 
@@ -30084,6 +30079,7 @@ async function read_opened_file_readOpenedFileSafely(params) {
30084
30079
  : await readFileHandleBounded(params.opened.handle, params.maxBytes);
30085
30080
  return {
30086
30081
  buffer,
30082
+ containment: params.opened.containment,
30087
30083
  realPath: params.opened.realPath,
30088
30084
  stat: params.opened.stat,
30089
30085
  };
@@ -30227,6 +30223,7 @@ function resolveOsHomeRelativePath(input, opts) {
30227
30223
 
30228
30224
 
30229
30225
 
30226
+
30230
30227
  const ensureTrailingSep = (value) => value.endsWith(external_node_path_.sep) ? value : value + external_node_path_.sep;
30231
30228
  function assertValidRootRelativePath(relativePath) {
30232
30229
  path_assertNoNulPathInput(relativePath, "relative path contains a NUL byte");
@@ -30271,13 +30268,31 @@ async function resolveRootContext(rootDir) {
30271
30268
  rootWithSep: ensureTrailingSep(rootReal),
30272
30269
  };
30273
30270
  }
30274
- async function resolvePathInRoot(root, relativePath) {
30271
+ async function resolvePathInRoot(root, relativePath, options) {
30275
30272
  assertValidRootRelativePath(relativePath);
30276
30273
  const expanded = await expandRelativePathWithHome(relativePath);
30277
30274
  const resolved = external_node_path_.resolve(root.rootWithSep, expanded);
30278
30275
  if (!path_isPathInside(root.rootWithSep, resolved)) {
30279
30276
  throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
30280
30277
  }
30278
+ const rawAbsolutePath = external_node_path_.isAbsolute(expanded)
30279
+ ? expanded
30280
+ : `${root.rootWithSep}${expanded}`;
30281
+ try {
30282
+ await resolveRootPath({
30283
+ absolutePath: rawAbsolutePath,
30284
+ rootPath: root.rootReal,
30285
+ rootCanonicalPath: root.rootReal,
30286
+ boundaryLabel: "root",
30287
+ policy: options?.allowFinalSymlink ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
30288
+ });
30289
+ }
30290
+ catch (error) {
30291
+ const code = options?.aliasErrorCode ?? "outside-workspace";
30292
+ throw new errors_FsSafeError(code, code === "path-alias" ? "path alias escape blocked" : "file is outside workspace root", {
30293
+ cause: error instanceof Error ? error : undefined,
30294
+ });
30295
+ }
30281
30296
  return { rootReal: root.rootReal, rootWithSep: root.rootWithSep, resolved };
30282
30297
  }
30283
30298
  async function resolvePathWithinRoot(params) {
@@ -30316,14 +30331,143 @@ function stringifyJsonDocument(value, replacer, space) {
30316
30331
  return text;
30317
30332
  }
30318
30333
 
30334
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-walk.js
30335
+
30336
+
30337
+
30338
+ function validateBudget(name, value) {
30339
+ if (value === undefined)
30340
+ return Number.POSITIVE_INFINITY;
30341
+ if (!Number.isSafeInteger(value) || value < 0) {
30342
+ throw new RangeError(`${name} must be a non-negative safe integer`);
30343
+ }
30344
+ return value;
30345
+ }
30346
+ function entryKind(entry) {
30347
+ if (entry.isSymbolicLink)
30348
+ return "symlink";
30349
+ if (entry.isDirectory)
30350
+ return "directory";
30351
+ if (entry.isFile)
30352
+ return "file";
30353
+ return "other";
30354
+ }
30355
+ function limitEntry(relativePath) {
30356
+ return { relativePath, kind: "truncated", size: 0 };
30357
+ }
30358
+ async function* walkRoot(root, relativePath, options) {
30359
+ const maxDepth = validateBudget("maxDepth", options.maxDepth);
30360
+ const maxEntries = validateBudget("maxEntries", options.maxEntries);
30361
+ const visitedDirectories = new Set();
30362
+ let examined = 0;
30363
+ const onLimit = (atPath) => {
30364
+ if ((options.limitBehavior ?? "truncate") === "throw") {
30365
+ throw new errors_FsSafeError("too-large", `root walk budget exceeded at ${atPath || "."}`);
30366
+ }
30367
+ return limitEntry(atPath);
30368
+ };
30369
+ async function* visit(directory, depth) {
30370
+ options.signal?.throwIfAborted();
30371
+ let entries;
30372
+ try {
30373
+ const resolvedDirectory = await resolveRootPath({
30374
+ absolutePath: external_node_path_.resolve(root.rootReal, directory),
30375
+ rootPath: root.rootReal,
30376
+ rootCanonicalPath: root.rootReal,
30377
+ boundaryLabel: "root walk",
30378
+ });
30379
+ if (!resolvedDirectory.exists || resolvedDirectory.kind !== "directory") {
30380
+ throw new errors_FsSafeError("not-file", `root walk path is not a directory: ${directory || "."}`);
30381
+ }
30382
+ if (visitedDirectories.has(resolvedDirectory.canonicalPath)) {
30383
+ return;
30384
+ }
30385
+ visitedDirectories.add(resolvedDirectory.canonicalPath);
30386
+ const listingDirectory = external_node_path_.relative(root.rootReal, resolvedDirectory.canonicalPath)
30387
+ .split(external_node_path_.sep)
30388
+ .join(external_node_path_.posix.sep);
30389
+ entries = await root.list(listingDirectory, { withFileTypes: true });
30390
+ }
30391
+ catch (error) {
30392
+ if ((options.onDirectoryError ?? "throw") === "throw")
30393
+ throw error;
30394
+ yield { relativePath: directory, kind: "directory-error", size: 0, error };
30395
+ return;
30396
+ }
30397
+ for (const entry of entries) {
30398
+ options.signal?.throwIfAborted();
30399
+ const child = directory
30400
+ ? external_node_path_.posix.join(directory.split(external_node_path_.sep).join(external_node_path_.posix.sep), entry.name)
30401
+ : entry.name;
30402
+ if (examined >= maxEntries) {
30403
+ yield onLimit(child);
30404
+ return;
30405
+ }
30406
+ examined += 1;
30407
+ let kind = entryKind(entry);
30408
+ let size = entry.size;
30409
+ if (kind === "symlink") {
30410
+ if (options.symlinkPolicy === "skip") {
30411
+ continue;
30412
+ }
30413
+ const resolved = await resolveRootPath({
30414
+ absolutePath: external_node_path_.resolve(root.rootReal, child),
30415
+ rootPath: root.rootReal,
30416
+ rootCanonicalPath: root.rootReal,
30417
+ boundaryLabel: "root walk",
30418
+ });
30419
+ if (!resolved.exists) {
30420
+ continue;
30421
+ }
30422
+ kind = resolved.kind === "directory" ? "directory" : resolved.kind === "file" ? "file" : "other";
30423
+ size = entry.size;
30424
+ }
30425
+ const walkEntry = { relativePath: child, kind, size };
30426
+ const filterResult = options.entryFilter?.(walkEntry) ?? "include";
30427
+ if (!["include", "skip", "skip-subtree"].includes(filterResult)) {
30428
+ throw new TypeError(`invalid root walk entryFilter result: ${String(filterResult)}`);
30429
+ }
30430
+ if (filterResult === "include") {
30431
+ yield walkEntry;
30432
+ }
30433
+ if (kind !== "directory") {
30434
+ continue;
30435
+ }
30436
+ if (filterResult === "skip-subtree") {
30437
+ continue;
30438
+ }
30439
+ if (depth >= maxDepth) {
30440
+ yield onLimit(child);
30441
+ return;
30442
+ }
30443
+ yield* visit(child, depth + 1);
30444
+ }
30445
+ }
30446
+ yield* visit(relativePath, 0);
30447
+ }
30448
+
30319
30449
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/temp-cleanup.js
30320
30450
 
30451
+
30321
30452
  const tempCleanupEntries = new Map();
30322
30453
  let cleanupRegistered = false;
30454
+ function pathStillMatchesReceipt(entry) {
30455
+ if (!entry.identity) {
30456
+ return false;
30457
+ }
30458
+ try {
30459
+ return file_identity_sameFileIdentity(external_node_fs_.lstatSync(entry.path), entry.identity);
30460
+ }
30461
+ catch (error) {
30462
+ return error.code === "ENOENT";
30463
+ }
30464
+ }
30323
30465
  function cleanupRegisteredTempPathsSync() {
30324
30466
  for (const entry of tempCleanupEntries.values()) {
30325
30467
  try {
30326
- external_node_fs_.rmSync(entry.path, { force: true, recursive: entry.recursive });
30468
+ if (pathStillMatchesReceipt(entry)) {
30469
+ external_node_fs_.rmSync(entry.path, { force: true, recursive: entry.recursive });
30470
+ }
30327
30471
  }
30328
30472
  catch {
30329
30473
  // Process-exit cleanup is best-effort.
@@ -30336,13 +30480,27 @@ function registerTempPathForExit(tempPath, options) {
30336
30480
  cleanupRegistered = true;
30337
30481
  process.once("exit", cleanupRegisteredTempPathsSync);
30338
30482
  }
30339
- tempCleanupEntries.set(tempPath, {
30483
+ const entry = {
30340
30484
  path: tempPath,
30341
30485
  recursive: options?.recursive === true,
30342
- });
30343
- return () => {
30486
+ identity: options?.identity,
30487
+ };
30488
+ if (!entry.identity) {
30489
+ try {
30490
+ entry.identity = external_node_fs_.lstatSync(tempPath);
30491
+ }
30492
+ catch {
30493
+ // Callers that register before creation set the identity after opening.
30494
+ }
30495
+ }
30496
+ tempCleanupEntries.set(tempPath, entry);
30497
+ const unregister = (() => {
30344
30498
  tempCleanupEntries.delete(tempPath);
30499
+ });
30500
+ unregister.setIdentity = (identity) => {
30501
+ entry.identity = identity;
30345
30502
  };
30503
+ return unregister;
30346
30504
  }
30347
30505
  function __cleanupRegisteredTempPathsForTest() {
30348
30506
  cleanupRegisteredTempPathsSync();
@@ -30353,7 +30511,9 @@ function __cleanupRegisteredTempPathForTest(tempPath) {
30353
30511
  return;
30354
30512
  }
30355
30513
  try {
30356
- fsSync.rmSync(entry.path, { force: true, recursive: entry.recursive });
30514
+ if (pathStillMatchesReceipt(entry)) {
30515
+ fsSync.rmSync(entry.path, { force: true, recursive: entry.recursive });
30516
+ }
30357
30517
  }
30358
30518
  finally {
30359
30519
  tempCleanupEntries.delete(tempPath);
@@ -30404,9 +30564,6 @@ async function serializePathWrite(key, run) {
30404
30564
 
30405
30565
 
30406
30566
 
30407
-
30408
-
30409
-
30410
30567
 
30411
30568
 
30412
30569
 
@@ -30437,6 +30594,7 @@ const DEFAULT_ROOT_MAX_BYTES = 16 * 1024 * 1024;
30437
30594
  function openResult(params) {
30438
30595
  return {
30439
30596
  handle: params.handle,
30597
+ containment: "best-effort",
30440
30598
  realPath: params.realPath,
30441
30599
  stat: params.stat,
30442
30600
  [Symbol.asyncDispose]: () => params.handle.close().catch(() => undefined),
@@ -30555,7 +30713,7 @@ class RootHandle {
30555
30713
  };
30556
30714
  }
30557
30715
  async resolve(relativePath) {
30558
- return (await resolvePathInRoot(this.context, relativePath)).resolved;
30716
+ return (await resolvePathInRoot(this.context, relativePath, { allowFinalSymlink: true })).resolved;
30559
30717
  }
30560
30718
  async open(relativePath, options = {}) {
30561
30719
  return await openFileInRoot(this.context, {
@@ -30694,58 +30852,34 @@ class RootHandle {
30694
30852
  }
30695
30853
  async stat(relativePath) {
30696
30854
  assertValidRootRelativePath(relativePath);
30697
- try {
30698
- return await helperStat(this.rootReal, relativePath);
30699
- }
30700
- catch (error) {
30701
- if (canFallbackFromPythonError(error)) {
30702
- return await statPathFallback(this.context, relativePath);
30703
- }
30704
- throw error;
30705
- }
30855
+ validatePinnedOperationPayload({ relativePath });
30856
+ return await statPathFallback(this.context, relativePath);
30706
30857
  }
30707
30858
  async list(relativePath, options = {}) {
30708
30859
  assertValidRootRelativePath(relativePath);
30709
- try {
30710
- return options.withFileTypes === true
30711
- ? await helperReaddir(this.rootReal, relativePath, true)
30712
- : await helperReaddir(this.rootReal, relativePath, false);
30713
- }
30714
- catch (error) {
30715
- if (canFallbackFromPythonError(error)) {
30716
- return await listPathFallback(this.context, relativePath, options.withFileTypes === true);
30717
- }
30718
- throw error;
30719
- }
30860
+ validatePinnedOperationPayload({ relativePath });
30861
+ return await listPathFallback(this.context, relativePath, options.withFileTypes === true);
30720
30862
  }
30721
30863
  async move(fromRelative, toRelative, options = {}) {
30722
30864
  assertValidRootRelativePath(fromRelative);
30723
30865
  assertValidRootRelativePath(toRelative);
30866
+ validatePinnedOperationPayload({ from: fromRelative, to: toRelative });
30724
30867
  const denyMutations = mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations);
30725
30868
  await assertMoveMutationAllowed(this.context, {
30726
30869
  fromRelative,
30727
30870
  toRelative,
30728
30871
  denyMutations,
30729
30872
  });
30730
- try {
30731
- await runPinnedHelper("rename", this.rootReal, {
30732
- from: fromRelative,
30733
- overwrite: options.overwrite ?? false,
30734
- to: toRelative,
30735
- });
30736
- }
30737
- catch (error) {
30738
- if (canFallbackFromPythonError(error)) {
30739
- await movePathFallback(this.context, {
30740
- fromRelative,
30741
- denyMutations,
30742
- overwrite: options.overwrite ?? false,
30743
- toRelative,
30744
- });
30745
- return;
30746
- }
30747
- throw error;
30748
- }
30873
+ await movePathFallback(this.context, {
30874
+ fromRelative,
30875
+ denyMutations,
30876
+ overwrite: options.overwrite ?? false,
30877
+ toRelative,
30878
+ });
30879
+ }
30880
+ walk(relativePath, options) {
30881
+ assertValidRootRelativePath(relativePath);
30882
+ return walkRoot(this, relativePath, options);
30749
30883
  }
30750
30884
  }
30751
30885
  function readDefaults(defaults) {
@@ -30760,7 +30894,9 @@ async function root_impl_root(rootDir, defaults = {}) {
30760
30894
  return new RootHandle(await resolveRootContext(rootDir), defaults);
30761
30895
  }
30762
30896
  async function openFileInRoot(root, params) {
30763
- const { rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath);
30897
+ const { rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath, {
30898
+ allowFinalSymlink: true,
30899
+ });
30764
30900
  let opened;
30765
30901
  try {
30766
30902
  opened = await openVerifiedLocalFile(resolved, {
@@ -30831,6 +30967,15 @@ function buildAtomicWriteTempPath(targetPath) {
30831
30967
  function rootWriteQueueKey(root, relativePath) {
30832
30968
  return `${root.rootReal}\0${relativePath}`;
30833
30969
  }
30970
+ async function prepareRootWriteTarget(rootReal, targetPath) {
30971
+ const parentPath = await mkdirPathComponentsWithGuards({
30972
+ rootReal,
30973
+ targetPath: external_node_path_.dirname(targetPath),
30974
+ });
30975
+ // Continue through the guarded walk's real parent instead of re-entering
30976
+ // the original path through a symlinked component.
30977
+ return external_node_path_.join(parentPath, external_node_path_.basename(targetPath));
30978
+ }
30834
30979
  async function writeTempFileForAtomicReplace(params) {
30835
30980
  const tempHandle = await promises_.open(params.tempPath, OPEN_WRITE_CREATE_FLAGS, params.mode);
30836
30981
  try {
@@ -30861,7 +31006,7 @@ async function verifyAtomicWriteResult(params) {
30861
31006
  }
30862
31007
  }
30863
31008
  async function openWritableFileInRoot(root, params) {
30864
- const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath);
31009
+ const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath, { aliasErrorCode: "path-alias" });
30865
31010
  await assertMutationNotDenied(resolved, params.denyMutations);
30866
31011
  try {
30867
31012
  await assertNoPathAliasEscape({
@@ -30873,15 +31018,11 @@ async function openWritableFileInRoot(root, params) {
30873
31018
  catch (err) {
30874
31019
  throw new errors_FsSafeError("path-alias", "path alias escape blocked", { cause: err });
30875
31020
  }
30876
- if (params.mkdir !== false) {
30877
- const parentGuard = await directory_guard_createNearestExistingDirectoryGuard(rootReal, external_node_path_.dirname(resolved));
30878
- await withAsyncDirectoryGuards([parentGuard], async () => {
30879
- await promises_.mkdir(external_node_path_.dirname(resolved), { recursive: true });
30880
- });
30881
- }
30882
- let ioPath = resolved;
31021
+ let ioPath = params.mkdir === false
31022
+ ? resolved
31023
+ : await prepareRootWriteTarget(rootReal, resolved);
30883
31024
  try {
30884
- const resolvedRealPath = await promises_.realpath(resolved);
31025
+ const resolvedRealPath = await promises_.realpath(ioPath);
30885
31026
  if (!path_isPathInside(rootWithSep, resolvedRealPath)) {
30886
31027
  throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
30887
31028
  }
@@ -30966,6 +31107,7 @@ async function openWritableFileInRoot(root, params) {
30966
31107
  }
30967
31108
  return {
30968
31109
  handle,
31110
+ containment: "best-effort",
30969
31111
  createdForWrite,
30970
31112
  realPath,
30971
31113
  stat,
@@ -31021,44 +31163,22 @@ async function appendFileInRoot(root, params) {
31021
31163
  }
31022
31164
  }
31023
31165
  async function removePathInRoot(root, params) {
31166
+ validatePinnedOperationPayload({ relativePath: params.relativePath });
31024
31167
  const resolved = await resolvePinnedRemovePathInRoot(root, params.relativePath, params.denyMutations);
31025
- if (process.platform === "win32") {
31026
- await removePathFallback(resolved);
31027
- return;
31028
- }
31029
31168
  try {
31030
- await runPinnedPathHelper({
31031
- operation: "remove",
31032
- rootPath: resolved.rootReal,
31033
- relativePath: resolved.relativePosix,
31034
- });
31169
+ await removePathFallback(resolved);
31035
31170
  }
31036
31171
  catch (error) {
31037
- if (isPinnedPathHelperSpawnError(error)) {
31038
- await removePathFallback(resolved);
31039
- return;
31040
- }
31041
31172
  throw normalizePinnedPathError(error);
31042
31173
  }
31043
31174
  }
31044
31175
  async function mkdirPathInRoot(root, params) {
31176
+ validatePinnedOperationPayload({ relativePath: params.relativePath });
31045
31177
  const resolved = await resolvePinnedPathInRoot(root, params);
31046
- if (process.platform === "win32") {
31047
- await mkdirPathFallback(resolved);
31048
- return;
31049
- }
31050
31178
  try {
31051
- await runPinnedPathHelper({
31052
- operation: "mkdirp",
31053
- rootPath: resolved.rootReal,
31054
- relativePath: resolved.relativePosix,
31055
- });
31179
+ await mkdirPathFallback(resolved);
31056
31180
  }
31057
31181
  catch (error) {
31058
- if (isPinnedPathHelperSpawnError(error)) {
31059
- await mkdirPathFallback(resolved);
31060
- return;
31061
- }
31062
31182
  throw normalizePinnedPathError(error);
31063
31183
  }
31064
31184
  }
@@ -31124,49 +31244,25 @@ async function copyFileInRoot(root, params) {
31124
31244
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${source.stat.size})`);
31125
31245
  }
31126
31246
  try {
31127
- if (process.platform === "win32") {
31128
- await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
31129
- await copyFileFallback(root, params, source);
31130
- });
31131
- return;
31132
- }
31133
31247
  const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
31134
31248
  await serializePathWrite(pinned.targetPath, async () => {
31135
- let identity;
31249
+ await assertCopySourceCurrent(source);
31250
+ const identity = await runPinnedWriteHelper({
31251
+ rootPath: pinned.rootReal,
31252
+ relativeParentPath: pinned.relativeParentPath,
31253
+ basename: pinned.basename,
31254
+ mkdir: params.mkdir !== false,
31255
+ mode: pinned.mode,
31256
+ overwrite: true,
31257
+ maxBytes: params.maxBytes,
31258
+ input: { kind: "stream", stream: source.handle.createReadStream() },
31259
+ });
31136
31260
  try {
31137
- if (getFsSafePythonConfig().mode === "off") {
31138
- await copyFileFallback(root, params, source);
31139
- return;
31140
- }
31141
- identity = await runPinnedCopyHelper({
31142
- rootPath: pinned.rootReal,
31143
- relativeParentPath: pinned.relativeParentPath,
31144
- basename: pinned.basename,
31145
- mkdir: params.mkdir !== false,
31146
- mode: pinned.mode,
31147
- overwrite: true,
31148
- maxBytes: params.maxBytes,
31149
- sourcePath: source.realPath,
31150
- sourceIdentity: { dev: source.stat.dev, ino: source.stat.ino },
31151
- });
31261
+ await assertCopySourcePathCurrent(source);
31152
31262
  }
31153
31263
  catch (error) {
31154
- if (canFallbackFromPythonError(error)) {
31155
- await copyFileFallback(root, params, source);
31156
- return;
31157
- }
31158
- throw normalizePinnedWriteError(error);
31159
- }
31160
- try {
31161
- await verifyAtomicWriteResult({
31162
- root,
31163
- targetPath: pinned.targetPath,
31164
- expectedIdentity: identity,
31165
- });
31166
- }
31167
- catch (err) {
31168
- emitWriteBoundaryWarning(`post-copy verification failed: ${String(err)}`);
31169
- throw err;
31264
+ await removeCopyTargetIfUnchanged(pinned.targetPath, identity).catch(() => undefined);
31265
+ throw error;
31170
31266
  }
31171
31267
  });
31172
31268
  }
@@ -31174,8 +31270,33 @@ async function copyFileInRoot(root, params) {
31174
31270
  await source.handle.close().catch(() => { });
31175
31271
  }
31176
31272
  }
31273
+ async function assertCopySourceCurrent(source) {
31274
+ const opened = await source.handle.stat();
31275
+ if (!file_identity_sameFileIdentity(opened, source.stat)) {
31276
+ throw new errors_FsSafeError("path-mismatch", "copy source descriptor changed");
31277
+ }
31278
+ await assertCopySourcePathCurrent(source);
31279
+ }
31280
+ async function assertCopySourcePathCurrent(source) {
31281
+ const current = await promises_.lstat(source.realPath);
31282
+ if (current.isSymbolicLink() || !current.isFile() || !file_identity_sameFileIdentity(current, source.stat)) {
31283
+ throw new errors_FsSafeError("path-mismatch", "copy source path changed");
31284
+ }
31285
+ }
31286
+ async function removeCopyTargetIfUnchanged(targetPath, identity) {
31287
+ const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(targetPath));
31288
+ const current = await promises_.lstat(targetPath);
31289
+ if (current.isSymbolicLink() || !file_identity_sameFileIdentity(current, identity)) {
31290
+ return;
31291
+ }
31292
+ await withAsyncDirectoryGuards([parentGuard], async () => {
31293
+ await promises_.rm(targetPath);
31294
+ });
31295
+ }
31177
31296
  async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
31178
- const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, relativePath);
31297
+ const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, relativePath, {
31298
+ aliasErrorCode: "path-alias",
31299
+ });
31179
31300
  await assertMutationNotDenied(resolved, denyMutations);
31180
31301
  try {
31181
31302
  await assertNoPathAliasEscape({
@@ -31277,8 +31398,11 @@ async function resolvePinnedRootPathInRoot(root, params) {
31277
31398
  const rootReal = root.rootReal;
31278
31399
  let resolved;
31279
31400
  try {
31401
+ const expandedPath = await expandRelativePathWithHome(params.relativePath);
31280
31402
  resolved = await resolveRootPath({
31281
- absolutePath: external_node_path_.resolve(rootReal, await expandRelativePathWithHome(params.relativePath)),
31403
+ absolutePath: external_node_path_.isAbsolute(expandedPath)
31404
+ ? expandedPath
31405
+ : `${ensureTrailingSep(rootReal)}${expandedPath}`,
31282
31406
  rootPath: rootReal,
31283
31407
  rootCanonicalPath: rootReal,
31284
31408
  boundaryLabel: "root",
@@ -31349,35 +31473,51 @@ async function listPathFallback(root, relativePath, withFileTypes) {
31349
31473
  }
31350
31474
  }
31351
31475
  async function assertMoveMutationAllowed(root, params) {
31352
- const source = await resolvePathInRoot(root, params.fromRelative);
31476
+ const source = await resolvePathInRoot(root, params.fromRelative, {
31477
+ aliasErrorCode: "path-alias",
31478
+ allowFinalSymlink: true,
31479
+ });
31353
31480
  await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
31354
- const target = await resolvePathInRoot(root, params.toRelative);
31481
+ const target = await resolvePathInRoot(root, params.toRelative, {
31482
+ aliasErrorCode: "path-alias",
31483
+ allowFinalSymlink: true,
31484
+ });
31355
31485
  await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
31356
31486
  }
31357
31487
  async function movePathFallback(root, params) {
31358
- const source = await resolvePathInRoot(root, params.fromRelative);
31488
+ const source = await resolvePathInRoot(root, params.fromRelative, {
31489
+ aliasErrorCode: "path-alias",
31490
+ allowFinalSymlink: true,
31491
+ });
31359
31492
  await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
31360
31493
  await resolvePinnedRootPathInRoot(root, {
31361
31494
  relativePath: params.fromRelative,
31362
31495
  policy: PATH_ALIAS_POLICIES.strict,
31363
31496
  });
31364
- const target = await resolvePathInRoot(root, params.toRelative);
31497
+ const target = await resolvePathInRoot(root, params.toRelative, {
31498
+ aliasErrorCode: "path-alias",
31499
+ allowFinalSymlink: true,
31500
+ });
31365
31501
  await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
31366
31502
  await resolvePinnedRootPathInRoot(root, {
31367
31503
  relativePath: params.toRelative,
31368
31504
  policy: PATH_ALIAS_POLICIES.unlinkTarget,
31369
31505
  });
31370
- try {
31371
- await assertNoPathAliasEscape({
31372
- absolutePath: target.resolved,
31373
- rootPath: target.rootReal,
31374
- boundaryLabel: "root",
31375
- });
31376
- }
31377
- catch (error) {
31378
- throw new errors_FsSafeError("path-alias", "path alias escape blocked", {
31379
- cause: error instanceof Error ? error : undefined,
31380
- });
31506
+ const targetStat = await promises_.lstat(target.resolved).catch(() => undefined);
31507
+ const replacesFinalSymlink = process.platform !== "win32" && params.overwrite && targetStat?.isSymbolicLink() === true;
31508
+ if (!replacesFinalSymlink) {
31509
+ try {
31510
+ await assertNoPathAliasEscape({
31511
+ absolutePath: target.resolved,
31512
+ rootPath: target.rootReal,
31513
+ boundaryLabel: "root",
31514
+ });
31515
+ }
31516
+ catch (error) {
31517
+ throw new errors_FsSafeError("path-alias", "path alias escape blocked", {
31518
+ cause: error instanceof Error ? error : undefined,
31519
+ });
31520
+ }
31381
31521
  }
31382
31522
  let sourceStat;
31383
31523
  try {
@@ -31464,6 +31604,7 @@ async function writeFileFallback(root, params) {
31464
31604
  encoding: params.encoding,
31465
31605
  mode: mode || 0o600,
31466
31606
  });
31607
+ unregisterTempPath.setIdentity(writtenStat);
31467
31608
  const commitTempPath = tempPath;
31468
31609
  await withAsyncDirectoryGuards([destinationGuard], async () => {
31469
31610
  await promises_.rename(commitTempPath, destinationPath);
@@ -31491,7 +31632,9 @@ async function writeFileFallback(root, params) {
31491
31632
  }
31492
31633
  }
31493
31634
  async function writeMissingFileFallback(root, params) {
31494
- const { rootReal, resolved } = await resolvePathInRoot(root, params.relativePath);
31635
+ const { rootReal, resolved } = await resolvePathInRoot(root, params.relativePath, {
31636
+ aliasErrorCode: "path-alias",
31637
+ });
31495
31638
  await assertMutationNotDenied(resolved, params.denyMutations);
31496
31639
  try {
31497
31640
  await assertNoPathAliasEscape({
@@ -31503,14 +31646,14 @@ async function writeMissingFileFallback(root, params) {
31503
31646
  catch (err) {
31504
31647
  throw new errors_FsSafeError("path-alias", "path alias escape blocked", { cause: err });
31505
31648
  }
31506
- if (params.mkdir !== false) {
31507
- await promises_.mkdir(external_node_path_.dirname(resolved), { recursive: true });
31508
- }
31509
- const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(resolved));
31649
+ const targetPath = params.mkdir === false
31650
+ ? resolved
31651
+ : await prepareRootWriteTarget(rootReal, resolved);
31652
+ const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(targetPath));
31510
31653
  let created = false;
31511
31654
  try {
31512
31655
  const { handle, writtenStat } = await withAsyncDirectoryGuards([parentGuard], async () => {
31513
- const handle = await promises_.open(resolved, OPEN_WRITE_CREATE_FLAGS, params.mode ?? 0o600);
31656
+ const handle = await promises_.open(targetPath, OPEN_WRITE_CREATE_FLAGS, params.mode ?? 0o600);
31514
31657
  created = true;
31515
31658
  try {
31516
31659
  if (typeof params.data === "string") {
@@ -31534,7 +31677,7 @@ async function writeMissingFileFallback(root, params) {
31534
31677
  await handle.close();
31535
31678
  await verifyAtomicWriteResult({
31536
31679
  root,
31537
- targetPath: resolved,
31680
+ targetPath,
31538
31681
  expectedIdentity: writtenStat,
31539
31682
  });
31540
31683
  created = false;
@@ -31549,87 +31692,102 @@ async function writeMissingFileFallback(root, params) {
31549
31692
  }
31550
31693
  finally {
31551
31694
  if (created) {
31552
- await promises_.rm(resolved, { force: true }).catch(() => undefined);
31695
+ await promises_.rm(targetPath, { force: true }).catch(() => undefined);
31553
31696
  }
31554
31697
  }
31555
31698
  }
31556
- async function copyFileFallback(root, params, source) {
31557
- let target = null;
31558
- let sourceClosedByStream = false;
31559
- let targetClosedByUs = false;
31560
- let tempHandle = null;
31561
- let tempPath = null;
31562
- let unregisterTempPath = null;
31563
- let tempClosedByStream = false;
31564
- try {
31565
- target = await openWritableFileInRoot(root, {
31566
- relativePath: params.relativePath,
31567
- mkdir: params.mkdir,
31568
- mode: params.mode,
31569
- denyMutations: params.denyMutations,
31570
- truncateExisting: false,
31571
- });
31572
- const destinationPath = target.realPath;
31573
- const mode = params.mode ?? (target.stat.mode & 0o777);
31574
- await target.handle.close().catch(() => { });
31575
- targetClosedByUs = true;
31576
- const destinationGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_.dirname(destinationPath));
31577
- tempPath = buildAtomicWriteTempPath(destinationPath);
31578
- unregisterTempPath = registerTempPathForExit(tempPath);
31579
- tempHandle = await promises_.open(tempPath, OPEN_WRITE_CREATE_FLAGS, mode || 0o600);
31580
- const sourceStream = createBoundedReadStream(source, params.maxBytes);
31581
- const targetStream = tempHandle.createWriteStream();
31582
- sourceStream.once("close", () => {
31583
- sourceClosedByStream = true;
31584
- });
31585
- targetStream.once("close", () => {
31586
- tempClosedByStream = true;
31587
- });
31588
- await (0,external_node_stream_promises_.pipeline)(sourceStream, targetStream);
31589
- const writtenStat = await promises_.stat(tempPath);
31590
- if (!tempClosedByStream) {
31591
- await tempHandle.close().catch(() => { });
31592
- tempClosedByStream = true;
31593
- }
31594
- tempHandle = null;
31595
- const commitTempPath = tempPath;
31596
- await withAsyncDirectoryGuards([destinationGuard], async () => {
31597
- await promises_.rename(commitTempPath, destinationPath);
31598
- });
31599
- tempPath = null;
31600
- unregisterTempPath();
31601
- unregisterTempPath = null;
31602
- try {
31603
- await verifyAtomicWriteResult({
31604
- root,
31605
- targetPath: destinationPath,
31606
- expectedIdentity: writtenStat,
31607
- });
31699
+
31700
+ ;// CONCATENATED MODULE: ../core/src/gateways/read-file-no-follow.ts
31701
+ /**
31702
+ * Atomic file read with symlink and size protection.
31703
+ */
31704
+
31705
+ function isEloopError(error) {
31706
+ return error instanceof Error && "code" in error && error.code === "ELOOP";
31707
+ }
31708
+ async function openWithoutFollowingSymlinks(filePath, safePath) {
31709
+ const hasNoFollow = typeof external_node_fs_.constants.O_NOFOLLOW === "number" && external_node_fs_.constants.O_NOFOLLOW !== 0;
31710
+ if (!hasNoFollow) {
31711
+ const stats = await (0,promises_.lstat)(filePath);
31712
+ if (stats.isSymbolicLink()) {
31713
+ throw new Error(`Symlinks are not allowed: ${safePath}`);
31608
31714
  }
31609
- catch (err) {
31610
- emitWriteBoundaryWarning(`post-copy verification failed: ${String(err)}`);
31611
- throw err;
31715
+ return (0,promises_.open)(filePath, external_node_fs_.constants.O_RDONLY);
31716
+ }
31717
+ try {
31718
+ return await (0,promises_.open)(filePath, external_node_fs_.constants.O_RDONLY | external_node_fs_.constants.O_NOFOLLOW);
31719
+ } catch (error) {
31720
+ if (isEloopError(error)) {
31721
+ throw new Error(`Symlinks are not allowed: ${safePath}`);
31612
31722
  }
31723
+ throw error;
31613
31724
  }
31614
- catch (err) {
31615
- if (target?.createdForWrite) {
31616
- await promises_.rm(target.realPath, { force: true }).catch(() => { });
31725
+ }
31726
+ /**
31727
+ * Reads a file atomically with symlink and size protection.
31728
+ *
31729
+ * Uses `O_NOFOLLOW` (where available) to atomically reject symlinks at
31730
+ * the kernel level, eliminating the TOCTOU window between `lstat()` and
31731
+ * `readFile()`. Falls back to `lstat()` on platforms without `O_NOFOLLOW`.
31732
+ * Validates file size via `fstat()` on the opened file descriptor so the
31733
+ * size check and the read operate on the same inode.
31734
+ */ async function read_file_no_follow_readFileNoFollow(filePath, maxBytes) {
31735
+ const safePath = JSON.stringify(filePath);
31736
+ const fh = await openWithoutFollowingSymlinks(filePath, safePath);
31737
+ try {
31738
+ const fdStats = await fh.stat();
31739
+ if (fdStats.size > maxBytes) {
31740
+ throw new Error(`File ${safePath} exceeds size limit (${fdStats.size} bytes > ${maxBytes} bytes)`);
31617
31741
  }
31618
- throw err;
31742
+ return await fh.readFile("utf-8");
31743
+ } finally{
31744
+ await fh.close();
31619
31745
  }
31620
- finally {
31621
- if (!sourceClosedByStream) {
31622
- await source.handle.close().catch(() => { });
31746
+ }
31747
+
31748
+ ;// CONCATENATED MODULE: ../core/src/gateways/symlink-path-guard.ts
31749
+ /**
31750
+ * Path-segment symlink rejection helpers for root-bounded FS ops.
31751
+ */
31752
+
31753
+
31754
+ function isEnoentError(error) {
31755
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
31756
+ }
31757
+ function symlinkNotAllowedError(relativePath) {
31758
+ return new Error(`Symlinks are not allowed: path contains symbolic link: ${JSON.stringify(relativePath)}`, {
31759
+ cause: new errors_FsSafeError("symlink", "symlink not allowed")
31760
+ });
31761
+ }
31762
+ async function lstatIfExists(path) {
31763
+ try {
31764
+ return await (0,promises_.lstat)(path);
31765
+ } catch (error) {
31766
+ if (isEnoentError(error)) {
31767
+ return undefined;
31623
31768
  }
31624
- if (tempHandle && !tempClosedByStream) {
31625
- await tempHandle.close().catch(() => { });
31769
+ throw error;
31770
+ }
31771
+ }
31772
+ /**
31773
+ * Walk path segments under rootPath and reject any symbolic-link component.
31774
+ * Stops at the first missing segment (ENOENT).
31775
+ */ async function rejectSymlinkSegments(rootPath, relativeFromRoot, onSymlink) {
31776
+ if (!relativeFromRoot) {
31777
+ return;
31778
+ }
31779
+ let currentPath = rootPath;
31780
+ for (const segment of relativeFromRoot.split(external_node_path_.sep)){
31781
+ if (!segment || segment === ".") {
31782
+ continue;
31626
31783
  }
31627
- if (tempPath) {
31628
- await promises_.rm(tempPath, { force: true }).catch(() => { });
31784
+ currentPath = (0,external_node_path_.join)(currentPath, segment);
31785
+ const stats = await lstatIfExists(currentPath);
31786
+ if (stats === undefined) {
31787
+ return;
31629
31788
  }
31630
- unregisterTempPath?.();
31631
- if (target && !targetClosedByUs) {
31632
- await target.handle.close().catch(() => { });
31789
+ if (stats.isSymbolicLink()) {
31790
+ throw onSymlink(currentPath);
31633
31791
  }
31634
31792
  }
31635
31793
  }
@@ -31641,6 +31799,8 @@ async function copyFileFallback(root, params, source) {
31641
31799
 
31642
31800
 
31643
31801
 
31802
+
31803
+
31644
31804
  /**
31645
31805
  * Checks if a file or directory exists.
31646
31806
  */ async function file_system_utils_fileExists(path) {
@@ -31652,28 +31812,34 @@ async function copyFileFallback(root, params, source) {
31652
31812
  }
31653
31813
  }
31654
31814
  function isPathWithinRoot(rootPath, candidatePath) {
31655
- return candidatePath === rootPath || candidatePath.startsWith(`${rootPath}${external_node_path_.sep}`);
31815
+ if (candidatePath === rootPath) {
31816
+ return true;
31817
+ }
31818
+ // When root is `/` (or `C:\`), appending sep would yield `//` / `C:\\` and
31819
+ // incorrectly reject every in-root path. Use root as the prefix when it
31820
+ // already ends with the separator.
31821
+ const prefix = rootPath.endsWith(external_node_path_.sep) ? rootPath : `${rootPath}${external_node_path_.sep}`;
31822
+ return candidatePath.startsWith(prefix);
31656
31823
  }
31657
31824
  function mapFsSafeError(error, relativePath) {
31658
- if (error instanceof errors_FsSafeError) {
31659
- switch(error.code){
31660
- case "outside-workspace":
31661
- case "invalid-path":
31662
- throw new Error(`Resolved path is outside target directory: ${relativePath}`, {
31663
- cause: error
31664
- });
31665
- case "path-alias":
31666
- case "symlink":
31667
- throw new Error(`Symlinks are not allowed: path contains symbolic link: ${relativePath}`, {
31668
- cause: error
31669
- });
31670
- case "too-large":
31671
- throw new Error(`File ${relativePath} exceeds size limit: ${error.message}`, {
31672
- cause: error
31673
- });
31674
- default:
31675
- throw error;
31676
- }
31825
+ if (!(error instanceof errors_FsSafeError)) {
31826
+ throw error;
31827
+ }
31828
+ const safePath = JSON.stringify(relativePath);
31829
+ if (error.code === "outside-workspace" || error.code === "invalid-path") {
31830
+ throw new Error(`Resolved path is outside target directory: ${safePath}`, {
31831
+ cause: error
31832
+ });
31833
+ }
31834
+ if (error.code === "path-alias" || error.code === "symlink") {
31835
+ throw new Error(`Symlinks are not allowed: path contains symbolic link: ${safePath}`, {
31836
+ cause: error
31837
+ });
31838
+ }
31839
+ if (error.code === "too-large") {
31840
+ throw new Error(`File ${safePath} exceeds size limit: ${error.message}`, {
31841
+ cause: error
31842
+ });
31677
31843
  }
31678
31844
  throw error;
31679
31845
  }
@@ -31684,8 +31850,20 @@ async function createSafeRoot(targetDir, maxBytes) {
31684
31850
  symlinks: "reject"
31685
31851
  });
31686
31852
  }
31853
+ /**
31854
+ * fs-safe 0.5 exists/stat/list canonicalize through in-root symlinks; open/read
31855
+ * still honor symlinks:"reject". Enforce reject on every root-bounded op.
31856
+ */ async function assertNoSymlinksWithinRoot(targetDir, relativePath) {
31857
+ if (!relativePath) {
31858
+ return;
31859
+ }
31860
+ const absolutePath = await file_system_utils_resolvePathWithinRoot(targetDir, relativePath);
31861
+ const rootPath = await (0,promises_.realpath)(targetDir);
31862
+ await rejectSymlinkSegments(rootPath, (0,external_node_path_.relative)(rootPath, absolutePath), ()=>symlinkNotAllowedError(relativePath));
31863
+ }
31687
31864
  async function file_system_utils_readTextWithinRoot(targetDir, relativePath, maxBytes) {
31688
31865
  try {
31866
+ await assertNoSymlinksWithinRoot(targetDir, relativePath);
31689
31867
  const safeRoot = await createSafeRoot(targetDir, maxBytes);
31690
31868
  return await safeRoot.readText(relativePath, {
31691
31869
  maxBytes
@@ -31696,6 +31874,7 @@ async function file_system_utils_readTextWithinRoot(targetDir, relativePath, max
31696
31874
  }
31697
31875
  async function file_system_utils_listDirectoryWithinRoot(targetDir, relativePath) {
31698
31876
  try {
31877
+ await assertNoSymlinksWithinRoot(targetDir, relativePath);
31699
31878
  const safeRoot = await createSafeRoot(targetDir);
31700
31879
  const entries = await safeRoot.list(relativePath, {
31701
31880
  withFileTypes: true
@@ -31715,6 +31894,7 @@ function toSafeDirEntry(entry) {
31715
31894
  }
31716
31895
  async function file_system_utils_pathExistsWithinRoot(targetDir, relativePath) {
31717
31896
  try {
31897
+ await assertNoSymlinksWithinRoot(targetDir, relativePath);
31718
31898
  const safeRoot = await createSafeRoot(targetDir);
31719
31899
  return await safeRoot.exists(relativePath);
31720
31900
  } catch (error) {
@@ -31731,6 +31911,7 @@ async function file_system_utils_pathExistsWithinRoot(targetDir, relativePath) {
31731
31911
  }
31732
31912
  async function file_system_utils_statWithinRoot(targetDir, relativePath) {
31733
31913
  try {
31914
+ await assertNoSymlinksWithinRoot(targetDir, relativePath);
31734
31915
  const safeRoot = await createSafeRoot(targetDir);
31735
31916
  return await safeRoot.stat(relativePath);
31736
31917
  } catch (error) {
@@ -31757,26 +31938,7 @@ async function file_system_utils_statWithinRoot(targetDir, relativePath) {
31757
31938
  if (!isPathWithinRoot(rootPath, absolutePath)) {
31758
31939
  throw new Error(`Resolved path is outside target directory: ${absolutePath}`);
31759
31940
  }
31760
- const relativePath = (0,external_node_path_.relative)(rootPath, absolutePath);
31761
- if (!relativePath || relativePath === ".") {
31762
- return;
31763
- }
31764
- const segments = relativePath.split(external_node_path_.sep);
31765
- let currentPath = rootPath;
31766
- for (const segment of segments){
31767
- currentPath = (0,external_node_path_.join)(currentPath, segment);
31768
- try {
31769
- const stats = await (0,promises_.lstat)(currentPath);
31770
- if (stats.isSymbolicLink()) {
31771
- throw new Error(`Path contains symbolic link: ${currentPath}`);
31772
- }
31773
- } catch (error) {
31774
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
31775
- return;
31776
- }
31777
- throw error;
31778
- }
31779
- }
31941
+ await rejectSymlinkSegments(rootPath, (0,external_node_path_.relative)(rootPath, absolutePath), (segmentPath)=>new Error(`Path contains symbolic link: ${segmentPath}`));
31780
31942
  }
31781
31943
  /**
31782
31944
  * Resolves a relative path under targetDir and validates it does not pass through symlinks.
@@ -31793,44 +31955,6 @@ async function file_system_utils_statWithinRoot(targetDir, relativePath) {
31793
31955
  throw new Error(`${context} exceeds size limit (${fileStats.size} bytes > ${maxBytes} bytes)`);
31794
31956
  }
31795
31957
  }
31796
- /**
31797
- * Reads a file atomically with symlink and size protection.
31798
- *
31799
- * Uses `O_NOFOLLOW` (where available) to atomically reject symlinks at
31800
- * the kernel level, eliminating the TOCTOU window between `lstat()` and
31801
- * `readFile()`. Falls back to `lstat()` on platforms without `O_NOFOLLOW`.
31802
- * Validates file size via `fstat()` on the opened file descriptor so the
31803
- * size check and the read operate on the same inode.
31804
- */ async function file_system_utils_readFileNoFollow(filePath, maxBytes) {
31805
- const hasNoFollow = typeof external_node_fs_.constants.O_NOFOLLOW === "number" && external_node_fs_.constants.O_NOFOLLOW !== 0;
31806
- const safePath = JSON.stringify(filePath);
31807
- let fh;
31808
- if (hasNoFollow) {
31809
- try {
31810
- fh = await (0,promises_.open)(filePath, external_node_fs_.constants.O_RDONLY | external_node_fs_.constants.O_NOFOLLOW);
31811
- } catch (error) {
31812
- if (error instanceof Error && "code" in error && error.code === "ELOOP") {
31813
- throw new Error(`Symlinks are not allowed: ${safePath}`);
31814
- }
31815
- throw error;
31816
- }
31817
- } else {
31818
- const stats = await (0,promises_.lstat)(filePath);
31819
- if (stats.isSymbolicLink()) {
31820
- throw new Error(`Symlinks are not allowed: ${safePath}`);
31821
- }
31822
- fh = await (0,promises_.open)(filePath, external_node_fs_.constants.O_RDONLY);
31823
- }
31824
- try {
31825
- const fdStats = await fh.stat();
31826
- if (fdStats.size > maxBytes) {
31827
- throw new Error(`File ${safePath} exceeds size limit (${fdStats.size} bytes > ${maxBytes} bytes)`);
31828
- }
31829
- return await fh.readFile("utf-8");
31830
- } finally{
31831
- await fh.close();
31832
- }
31833
- }
31834
31958
 
31835
31959
  ;// CONCATENATED MODULE: ../core/src/gateways/agent-file-gateway.ts
31836
31960
  /**
@@ -33199,8 +33323,6 @@ function _parseInput(input) {
33199
33323
  }
33200
33324
 
33201
33325
 
33202
- // EXTERNAL MODULE: external "node:module"
33203
- var external_node_module_ = __webpack_require__(8995);
33204
33326
  // EXTERNAL MODULE: ../../node_modules/jiti/dist/jiti.cjs
33205
33327
  var jiti = __webpack_require__(3273);
33206
33328
  ;// CONCATENATED MODULE: ../../node_modules/jiti/lib/jiti.mjs
@@ -34845,8 +34967,9 @@ async function readVersionFileContent(targetDir, relativePath) {
34845
34967
  continue;
34846
34968
  }
34847
34969
  if (await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.manifestFile)) {
34848
- const lockfilePath = pmConfig.lockfile ? await file_system_utils_resolveSafePath(targetDir, pmConfig.lockfile) : undefined;
34849
- const hasLockfile = lockfilePath ? await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.lockfile) : false;
34970
+ const { lockfile } = pmConfig;
34971
+ const lockfilePath = lockfile ? await file_system_utils_resolveSafePath(targetDir, lockfile) : undefined;
34972
+ const hasLockfile = lockfilePath && lockfile ? await file_system_utils_pathExistsWithinRoot(targetDir, lockfile) : false;
34850
34973
  if (pmConfig.requiresLockfile && !hasLockfile) {
34851
34974
  continue;
34852
34975
  }
@@ -34863,8 +34986,9 @@ async function readVersionFileContent(targetDir, relativePath) {
34863
34986
  const packageManagers = [];
34864
34987
  for (const pmConfig of otherPackageManagers){
34865
34988
  if (await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.manifestFile)) {
34866
- const lockfilePath = pmConfig.lockfile ? await file_system_utils_resolveSafePath(targetDir, pmConfig.lockfile) : undefined;
34867
- const hasLockfile = lockfilePath ? await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.lockfile) : false;
34989
+ const { lockfile } = pmConfig;
34990
+ const lockfilePath = lockfile ? await file_system_utils_resolveSafePath(targetDir, lockfile) : undefined;
34991
+ const hasLockfile = lockfilePath && lockfile ? await file_system_utils_pathExistsWithinRoot(targetDir, lockfile) : false;
34868
34992
  if (pmConfig.requiresLockfile && !hasLockfile) {
34869
34993
  continue;
34870
34994
  }
@@ -34885,6 +35009,8 @@ async function readVersionFileContent(targetDir, relativePath) {
34885
35009
  return new FileSystemEnvironmentGateway(cwd);
34886
35010
  }
34887
35011
 
35012
+ // EXTERNAL MODULE: external "node:child_process"
35013
+ var external_node_child_process_ = __webpack_require__(1421);
34888
35014
  ;// CONCATENATED MODULE: ../core/src/gateways/github-ruleset-gateway.ts
34889
35015
  /**
34890
35016
  * Gateway for interacting with GitHub repository rulesets via Octokit.
@@ -35024,11 +35150,18 @@ function defaultExec(command, args, options) {
35024
35150
  throw new Error("Not authenticated");
35025
35151
  }
35026
35152
  try {
35027
- await this.octokit.rest.repos.createRepoRuleset({
35153
+ const params = {
35028
35154
  owner,
35029
35155
  repo,
35030
- ...payload
35031
- });
35156
+ name: payload.name,
35157
+ enforcement: payload.enforcement,
35158
+ target: payload.target,
35159
+ // biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
35160
+ bypass_actors: payload.bypass_actors,
35161
+ conditions: payload.conditions,
35162
+ rules: payload.rules
35163
+ };
35164
+ await this.octokit.rest.repos.createRepoRuleset(params);
35032
35165
  } catch (error) {
35033
35166
  const details = formatOctokitError(error);
35034
35167
  throw new Error(`Failed to create ruleset for ${owner}/${repo}: ${details}`);
@@ -37364,7 +37497,7 @@ const PackageJsonSchema = schemas_object({
37364
37497
  const packageJsonPath = (0,external_node_path_.join)(targetDir, "package.json");
37365
37498
  let content;
37366
37499
  try {
37367
- content = await file_system_utils_readFileNoFollow(packageJsonPath, MAX_PACKAGE_JSON_BYTES);
37500
+ content = await read_file_no_follow_readFileNoFollow(packageJsonPath, MAX_PACKAGE_JSON_BYTES);
37368
37501
  } catch (error) {
37369
37502
  const code = error instanceof Error && "code" in error ? error.code : undefined;
37370
37503
  if (code !== "ENOENT") {
@@ -57350,120 +57483,6 @@ function lib_isUint8Array(value) {
57350
57483
  */
57351
57484
  const remark = unified().use(remarkParse).use(remarkStringify).freeze()
57352
57485
 
57353
- ;// CONCATENATED MODULE: ../core/src/gateways/markdown-ast-gateway.ts
57354
- /**
57355
- * Gateway for parsing Markdown files into AST using remark.
57356
- * Extracts structural features like headings, code blocks, and paragraphs.
57357
- */
57358
-
57359
-
57360
- /** Maximum instruction file size: 1 MB */ const markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES = 1_048_576;
57361
- /**
57362
- * Remark-based implementation of the Markdown AST gateway.
57363
- */ class RemarkMarkdownAstGateway {
57364
- async parseFile(filePath) {
57365
- const content = await file_system_utils_readFileNoFollow(filePath, markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES);
57366
- return this.parseContent(content);
57367
- }
57368
- parseContent(content) {
57369
- const processor = remark();
57370
- const ast = processor.parse(content);
57371
- const headings = [];
57372
- const codeBlocks = [];
57373
- const inlineCodes = [];
57374
- lib_visit(ast, "heading", (node)=>{
57375
- const text = this.extractHeadingText(node);
57376
- headings.push({
57377
- text,
57378
- depth: node.depth,
57379
- position: {
57380
- line: node.position?.start.line ?? 0
57381
- }
57382
- });
57383
- });
57384
- // Track code blocks with their node index in root children
57385
- for(let i = 0; i < ast.children.length; i++){
57386
- const child = ast.children[i];
57387
- if (child.type === "code") {
57388
- const codeNode = child;
57389
- codeBlocks.push({
57390
- value: codeNode.value,
57391
- lang: codeNode.lang ?? undefined,
57392
- position: {
57393
- line: codeNode.position?.start.line ?? 0
57394
- },
57395
- nodeIndex: i
57396
- });
57397
- }
57398
- }
57399
- lib_visit(ast, "inlineCode", (node)=>{
57400
- inlineCodes.push({
57401
- value: node.value,
57402
- position: {
57403
- line: node.position?.start.line ?? 0
57404
- }
57405
- });
57406
- });
57407
- return {
57408
- headings,
57409
- codeBlocks,
57410
- inlineCodes,
57411
- ast
57412
- };
57413
- }
57414
- findConditionalKeywordsInProximity(structure, codeBlockNodeIndex, proximityWindow, keywords) {
57415
- const ast = structure.ast;
57416
- const endIndex = Math.min(ast.children.length, codeBlockNodeIndex + 1 + proximityWindow);
57417
- for(let i = codeBlockNodeIndex + 1; i < endIndex; i++){
57418
- const sibling = ast.children[i];
57419
- const text = extractTextFromNode(sibling);
57420
- const lowerText = text.toLowerCase();
57421
- for (const keyword of keywords){
57422
- if (containsWordBoundary(lowerText, keyword.toLowerCase())) {
57423
- return true;
57424
- }
57425
- }
57426
- }
57427
- return false;
57428
- }
57429
- extractHeadingText(node) {
57430
- const parts = [];
57431
- lib_visit(node, "text", (textNode)=>{
57432
- parts.push(textNode.value);
57433
- });
57434
- return parts.join("");
57435
- }
57436
- }
57437
- /**
57438
- * Checks if text contains a keyword at a word boundary (safe string matching, no regex).
57439
- */ function containsWordBoundary(text, keyword) {
57440
- const index = text.indexOf(keyword);
57441
- if (index === -1) {
57442
- return false;
57443
- }
57444
- const charBefore = index > 0 ? text[index - 1] : " ";
57445
- const charAfter = index + keyword.length < text.length ? text[index + keyword.length] : " ";
57446
- const isWordBoundaryBefore = !/[a-z0-9]/i.test(charBefore);
57447
- const isWordBoundaryAfter = !/[a-z0-9]/i.test(charAfter);
57448
- return isWordBoundaryBefore && isWordBoundaryAfter;
57449
- }
57450
- /**
57451
- * Extracts plain text content from any AST node recursively.
57452
- */ function extractTextFromNode(node) {
57453
- if (node.value && typeof node.value === "string") {
57454
- return node.value;
57455
- }
57456
- if (Array.isArray(node.children)) {
57457
- return node.children.map((child)=>extractTextFromNode(child)).join(" ");
57458
- }
57459
- return "";
57460
- }
57461
- /**
57462
- * Creates and returns the default Markdown AST gateway.
57463
- */ function createMarkdownAstGateway() {
57464
- return new RemarkMarkdownAstGateway();
57465
- }
57466
-
57467
57486
  ;// CONCATENATED MODULE: ../core/src/entities/instruction-quality.ts
57468
57487
  /**
57469
57488
  * Core domain entities for instruction quality analysis.
@@ -57515,6 +57534,9 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
57515
57534
  * Orchestrates discovery, AST parsing, and scoring of feedback loop documentation.
57516
57535
  */
57517
57536
 
57537
+ /** Type guard narrowing an `unknown` AST child to {@link AstNodeLike}. */ function isAstNodeLike(node) {
57538
+ return typeof node === "object" && node !== null && "type" in node && typeof node.type === "string";
57539
+ }
57518
57540
  /** Maximum number of raw heading pattern entries accepted before deduplication. */ const MAX_RAW_HEADING_PATTERNS = 1000;
57519
57541
  /** Maximum number of heading patterns accepted in a single execute() call. */ const MAX_HEADING_PATTERNS = 50;
57520
57542
  /** Maximum character length for a single heading pattern. */ const analyze_instruction_quality_MAX_PATTERN_LENGTH = 200;
@@ -57870,6 +57892,9 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
57870
57892
  return parts.join(" ");
57871
57893
  }
57872
57894
  collectText(node, parts) {
57895
+ if (!isAstNodeLike(node)) {
57896
+ return;
57897
+ }
57873
57898
  if (node.type === "text" && typeof node.value === "string") {
57874
57899
  parts.push(node.value);
57875
57900
  }
@@ -58016,6 +58041,124 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
58016
58041
  }
58017
58042
  }
58018
58043
 
58044
+ ;// CONCATENATED MODULE: ../core/src/gateways/markdown-ast-gateway.ts
58045
+ /**
58046
+ * Gateway for parsing Markdown files into AST using remark.
58047
+ * Extracts structural features like headings, code blocks, and paragraphs.
58048
+ */
58049
+
58050
+
58051
+
58052
+ /** Maximum instruction file size: 1 MB */ const markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES = 1_048_576;
58053
+ /**
58054
+ * Remark-based implementation of the Markdown AST gateway.
58055
+ */ class RemarkMarkdownAstGateway {
58056
+ async parseFile(filePath) {
58057
+ const content = await read_file_no_follow_readFileNoFollow(filePath, markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES);
58058
+ return this.parseContent(content);
58059
+ }
58060
+ parseContent(content) {
58061
+ const processor = remark();
58062
+ const ast = processor.parse(content);
58063
+ const headings = [];
58064
+ const codeBlocks = [];
58065
+ const inlineCodes = [];
58066
+ lib_visit(ast, "heading", (node)=>{
58067
+ const text = this.extractHeadingText(node);
58068
+ headings.push({
58069
+ text,
58070
+ depth: node.depth,
58071
+ position: {
58072
+ line: node.position?.start.line ?? 0
58073
+ }
58074
+ });
58075
+ });
58076
+ // Track code blocks with their node index in root children
58077
+ for(let i = 0; i < ast.children.length; i++){
58078
+ const child = ast.children[i];
58079
+ if (child.type === "code") {
58080
+ const codeNode = child;
58081
+ codeBlocks.push({
58082
+ value: codeNode.value,
58083
+ lang: codeNode.lang ?? undefined,
58084
+ position: {
58085
+ line: codeNode.position?.start.line ?? 0
58086
+ },
58087
+ nodeIndex: i
58088
+ });
58089
+ }
58090
+ }
58091
+ lib_visit(ast, "inlineCode", (node)=>{
58092
+ inlineCodes.push({
58093
+ value: node.value,
58094
+ position: {
58095
+ line: node.position?.start.line ?? 0
58096
+ }
58097
+ });
58098
+ });
58099
+ return {
58100
+ headings,
58101
+ codeBlocks,
58102
+ inlineCodes,
58103
+ ast
58104
+ };
58105
+ }
58106
+ findConditionalKeywordsInProximity(structure, codeBlockNodeIndex, proximityWindow, keywords) {
58107
+ const ast = structure.ast;
58108
+ const endIndex = Math.min(ast.children.length, codeBlockNodeIndex + 1 + proximityWindow);
58109
+ for(let i = codeBlockNodeIndex + 1; i < endIndex; i++){
58110
+ const sibling = ast.children[i];
58111
+ const text = extractTextFromNode(sibling);
58112
+ const lowerText = text.toLowerCase();
58113
+ for (const keyword of keywords){
58114
+ if (containsWordBoundary(lowerText, keyword.toLowerCase())) {
58115
+ return true;
58116
+ }
58117
+ }
58118
+ }
58119
+ return false;
58120
+ }
58121
+ extractHeadingText(node) {
58122
+ const parts = [];
58123
+ lib_visit(node, "text", (textNode)=>{
58124
+ parts.push(textNode.value);
58125
+ });
58126
+ return parts.join("");
58127
+ }
58128
+ }
58129
+ /**
58130
+ * Checks if text contains a keyword at a word boundary (safe string matching, no regex).
58131
+ */ function containsWordBoundary(text, keyword) {
58132
+ const index = text.indexOf(keyword);
58133
+ if (index === -1) {
58134
+ return false;
58135
+ }
58136
+ const charBefore = index > 0 ? text[index - 1] : " ";
58137
+ const charAfter = index + keyword.length < text.length ? text[index + keyword.length] : " ";
58138
+ const isWordBoundaryBefore = !/[a-z0-9]/i.test(charBefore);
58139
+ const isWordBoundaryAfter = !/[a-z0-9]/i.test(charAfter);
58140
+ return isWordBoundaryBefore && isWordBoundaryAfter;
58141
+ }
58142
+ /**
58143
+ * Extracts plain text content from any AST node recursively.
58144
+ */ function extractTextFromNode(node) {
58145
+ if (!isAstNodeLike(node)) {
58146
+ return "";
58147
+ }
58148
+ if (node.value && typeof node.value === "string") {
58149
+ return node.value;
58150
+ }
58151
+ if (Array.isArray(node.children)) {
58152
+ return node.children.map((child)=>extractTextFromNode(child)).join(" ");
58153
+ }
58154
+ return "";
58155
+ }
58156
+ /**
58157
+ * Creates and returns the default Markdown AST gateway.
58158
+ */ function createMarkdownAstGateway() {
58159
+ return new RemarkMarkdownAstGateway();
58160
+ }
58161
+
58019
58162
  ;// CONCATENATED MODULE: ./src/tools/analyze-instruction-quality.ts
58020
58163
  /**
58021
58164
  * MCP tool handler for analyzing instruction quality.
@@ -69893,4 +70036,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
69893
70036
  // module factories are used so entry inlining is disabled
69894
70037
  // startup
69895
70038
  // Load entry module and return exports
69896
- var __webpack_exports__ = __webpack_require__(8454);
70039
+ var __webpack_exports__ = __webpack_require__(7489);