@lousy-agents/mcp 5.14.8 → 5.14.10

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 +237 -123
  2. package/package.json +1 -1
@@ -6559,7 +6559,7 @@ function escapeJsonPtr(str) {
6559
6559
 
6560
6560
 
6561
6561
  },
6562
- 2415(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6562
+ 3523(__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);
@@ -26726,6 +26726,9 @@ function path_isPathInside(root, target) {
26726
26726
  const firstSegment = relative.split(external_node_path_.posix.sep)[0];
26727
26727
  return relative === "" || (firstSegment !== ".." && !external_node_path_.isAbsolute(relative));
26728
26728
  }
26729
+ function isPathRelativeEscape(relativePath) {
26730
+ return relativePath === ".." || relativePath.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relativePath);
26731
+ }
26729
26732
  function resolveSafeBaseDir(rootDir) {
26730
26733
  const resolved = path.resolve(rootDir);
26731
26734
  return resolved.endsWith(path.sep) ? resolved : `${resolved}${path.sep}`;
@@ -26791,7 +26794,7 @@ function splitSafeRelativePath(relativePath) {
26791
26794
  function resolveSafeRelativePath(rootDir, relativePath) {
26792
26795
  const root = path.resolve(rootDir);
26793
26796
  const target = path.resolve(root, ...splitSafeRelativePath(relativePath));
26794
- if (target !== root && !target.startsWith(root + path.sep)) {
26797
+ if (!path_isPathInside(root, target)) {
26795
26798
  throw new FsSafeError("outside-workspace", "relative path escapes root");
26796
26799
  }
26797
26800
  return target;
@@ -26874,17 +26877,17 @@ function directory_guard_createNearestExistingSyncDirectoryGuard(rootReal, targe
26874
26877
 
26875
26878
 
26876
26879
 
26880
+
26877
26881
  function isSameOrChildPath(candidate, parent) {
26878
- return candidate === parent || candidate.startsWith(`${parent}${external_node_path_.sep}`);
26879
- }
26880
- function isPathEscape(relativePath) {
26881
- return relativePath === ".." || relativePath.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relativePath);
26882
+ const parentPrefix = parent.endsWith(external_node_path_.sep) ? parent : `${parent}${external_node_path_.sep}`;
26883
+ return candidate === parent || candidate.startsWith(parentPrefix);
26882
26884
  }
26883
26885
  async function mkdirPathComponentsWithGuards(params) {
26884
26886
  const root = external_node_path_.resolve(params.rootReal);
26887
+ const rootCanonical = external_node_path_.resolve(await promises_.realpath(root));
26885
26888
  const target = external_node_path_.resolve(params.targetPath);
26886
26889
  const relative = external_node_path_.relative(root, target);
26887
- if (isPathEscape(relative)) {
26890
+ if (isPathRelativeEscape(relative)) {
26888
26891
  throw new errors_FsSafeError("outside-workspace", "directory is outside workspace root");
26889
26892
  }
26890
26893
  let current = root;
@@ -26907,7 +26910,7 @@ async function mkdirPathComponentsWithGuards(params) {
26907
26910
  }
26908
26911
  // Node's recursive mkdir follows symlinks in missing components. Build one
26909
26912
  // segment at a time and realpath-check each segment before descending.
26910
- if (!isSameOrChildPath(external_node_path_.resolve(await promises_.realpath(next)), root)) {
26913
+ if (!isSameOrChildPath(external_node_path_.resolve(await promises_.realpath(next)), rootCanonical)) {
26911
26914
  throw new errors_FsSafeError("outside-workspace", "directory escaped workspace root");
26912
26915
  }
26913
26916
  await directory_guard_createAsyncDirectoryGuard(next);
@@ -27042,26 +27045,20 @@ var external_node_child_process_ = __webpack_require__(1421);
27042
27045
 
27043
27046
 
27044
27047
  const PINNED_PYTHON_WORKER_SOURCE = String.raw `
27045
- import base64
27046
- import errno
27047
- import json
27048
- import os
27049
- import secrets
27050
- import stat
27051
- import sys
27052
-
27048
+ import base64, errno, json, os, secrets, stat, sys
27053
27049
  DIR_FLAGS = os.O_RDONLY
27054
27050
  if hasattr(os, "O_DIRECTORY"):
27055
27051
  DIR_FLAGS |= os.O_DIRECTORY
27056
27052
  if hasattr(os, "O_NOFOLLOW"):
27057
27053
  DIR_FLAGS |= os.O_NOFOLLOW
27058
27054
  READ_FLAGS = os.O_RDONLY
27055
+ if hasattr(os, "O_NONBLOCK"):
27056
+ READ_FLAGS |= os.O_NONBLOCK
27059
27057
  if hasattr(os, "O_NOFOLLOW"):
27060
27058
  READ_FLAGS |= os.O_NOFOLLOW
27061
27059
  WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
27062
27060
  if hasattr(os, "O_NOFOLLOW"):
27063
27061
  WRITE_FLAGS |= os.O_NOFOLLOW
27064
-
27065
27062
  def split_relative(value):
27066
27063
  if value in ("", "."):
27067
27064
  return []
@@ -27074,10 +27071,8 @@ def split_relative(value):
27074
27071
  if part == "..":
27075
27072
  raise OSError(errno.EPERM, "path traversal is not allowed")
27076
27073
  return parts
27077
-
27078
27074
  def open_dir(path_value, dir_fd=None):
27079
27075
  return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
27080
-
27081
27076
  def walk_dir(root_fd, segments, mkdir_enabled=False):
27082
27077
  current_fd = os.dup(root_fd)
27083
27078
  try:
@@ -27095,14 +27090,12 @@ def walk_dir(root_fd, segments, mkdir_enabled=False):
27095
27090
  except Exception:
27096
27091
  os.close(current_fd)
27097
27092
  raise
27098
-
27099
27093
  def parent_and_basename(root_fd, relative):
27100
27094
  segments = split_relative(relative)
27101
27095
  if not segments:
27102
27096
  raise OSError(errno.EPERM, "operation requires a non-root path")
27103
27097
  parent_fd = walk_dir(root_fd, segments[:-1])
27104
27098
  return parent_fd, segments[-1]
27105
-
27106
27099
  def encode_stat(st):
27107
27100
  mode = st.st_mode
27108
27101
  return {
@@ -27118,14 +27111,108 @@ def encode_stat(st):
27118
27111
  "size": st.st_size,
27119
27112
  "uid": st.st_uid,
27120
27113
  }
27121
-
27122
27114
  def reject_unsafe_endpoint(st):
27123
27115
  mode = st.st_mode
27124
27116
  if stat.S_ISLNK(mode):
27125
27117
  raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
27126
27118
  if stat.S_ISREG(mode) and st.st_nlink > 1:
27127
27119
  raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
27128
-
27120
+ def copy_bytes(source_fd, dest_fd):
27121
+ while True:
27122
+ chunk = os.read(source_fd, 65536)
27123
+ if not chunk:
27124
+ break
27125
+ view = memoryview(chunk)
27126
+ while view:
27127
+ written = os.write(dest_fd, view)
27128
+ if written <= 0:
27129
+ raise OSError(errno.EIO, "short write")
27130
+ view = view[written:]
27131
+ def write_all(fd, data):
27132
+ view = memoryview(data)
27133
+ while view:
27134
+ written = os.write(fd, view)
27135
+ if written <= 0:
27136
+ raise OSError(errno.EIO, "short write")
27137
+ view = view[written:]
27138
+ def link_unsupported(exc):
27139
+ unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
27140
+ return getattr(exc, "errno", None) in unsupported
27141
+ def link_no_replace(name, new_name, source_fd, target_fd):
27142
+ linked = False
27143
+ try:
27144
+ os.link(name, new_name, src_dir_fd=source_fd, dst_dir_fd=target_fd, follow_symlinks=False)
27145
+ linked = True
27146
+ os.unlink(name, dir_fd=source_fd)
27147
+ except Exception:
27148
+ if linked:
27149
+ try: os.unlink(new_name, dir_fd=target_fd)
27150
+ except FileNotFoundError: pass
27151
+ raise
27152
+ os.fsync(source_fd)
27153
+ if source_fd != target_fd:
27154
+ os.fsync(target_fd)
27155
+ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
27156
+ source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
27157
+ dest_fd = None; success = False; dest_stat = None
27158
+ try:
27159
+ if expected is not None:
27160
+ source_stat = os.fstat(source_fd)
27161
+ if source_stat.st_dev != expected.st_dev or source_stat.st_ino != expected.st_ino:
27162
+ raise RuntimeError("fs-safe-source-mismatch")
27163
+ dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
27164
+ copy_bytes(source_fd, dest_fd)
27165
+ os.fsync(dest_fd)
27166
+ dest_stat = os.fstat(dest_fd)
27167
+ success = True
27168
+ finally:
27169
+ os.close(source_fd)
27170
+ if dest_fd is not None:
27171
+ os.close(dest_fd)
27172
+ if dest_fd is not None and not success:
27173
+ try: os.unlink(basename, dir_fd=target_parent_fd)
27174
+ except FileNotFoundError: pass
27175
+ if unlink_source:
27176
+ try:
27177
+ os.unlink(source_name, dir_fd=source_parent_fd)
27178
+ except Exception:
27179
+ try: os.unlink(basename, dir_fd=target_parent_fd)
27180
+ except FileNotFoundError: pass
27181
+ raise
27182
+ return dest_stat
27183
+ def same_identity(left, right):
27184
+ return left.st_dev == right.st_dev and left.st_ino == right.st_ino
27185
+ def verify_temp_name(parent_fd, temp_name, expected_stat):
27186
+ current_stat = os.lstat(temp_name, dir_fd=parent_fd)
27187
+ if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
27188
+ raise RuntimeError("fs-safe-temp-mismatch")
27189
+ def verify_committed_temp(parent_fd, basename, expected_stat):
27190
+ final_stat = os.lstat(basename, dir_fd=parent_fd)
27191
+ if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
27192
+ return final_stat
27193
+ try: os.unlink(basename, dir_fd=parent_fd)
27194
+ except FileNotFoundError: pass
27195
+ raise RuntimeError("fs-safe-temp-mismatch")
27196
+ def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
27197
+ verify_temp_name(parent_fd, temp_name, expected_stat)
27198
+ if overwrite:
27199
+ os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
27200
+ return verify_committed_temp(parent_fd, basename, expected_stat)
27201
+ else:
27202
+ try:
27203
+ os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
27204
+ final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
27205
+ os.unlink(temp_name, dir_fd=parent_fd)
27206
+ return final_stat
27207
+ except OSError as exc:
27208
+ if not link_unsupported(exc):
27209
+ raise
27210
+ return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
27211
+ def assert_expected_root(root_fd, payload):
27212
+ if "rootDev" in payload or "rootIno" in payload:
27213
+ root_stat = os.fstat(root_fd)
27214
+ if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
27215
+ raise RuntimeError("fs-safe-root-mismatch")
27129
27216
  def stat_path(root_fd, payload):
27130
27217
  relative = payload.get("relativePath", "")
27131
27218
  segments = split_relative(relative)
@@ -27139,7 +27226,6 @@ def stat_path(root_fd, payload):
27139
27226
  return encode_stat(st)
27140
27227
  finally:
27141
27228
  os.close(parent_fd)
27142
-
27143
27229
  def readdir_path(root_fd, payload):
27144
27230
  dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
27145
27231
  try:
@@ -27155,12 +27241,9 @@ def readdir_path(root_fd, payload):
27155
27241
  return entries
27156
27242
  finally:
27157
27243
  os.close(dir_fd)
27158
-
27159
27244
  def mkdirp_path(root_fd, payload):
27160
27245
  dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
27161
- os.close(dir_fd)
27162
- return None
27163
-
27246
+ os.close(dir_fd); return None
27164
27247
  def remove_tree(parent_fd, basename):
27165
27248
  st = os.lstat(basename, dir_fd=parent_fd)
27166
27249
  if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
@@ -27173,7 +27256,6 @@ def remove_tree(parent_fd, basename):
27173
27256
  os.rmdir(basename, dir_fd=parent_fd)
27174
27257
  else:
27175
27258
  os.unlink(basename, dir_fd=parent_fd)
27176
-
27177
27259
  def remove_path(root_fd, payload):
27178
27260
  parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
27179
27261
  try:
@@ -27200,7 +27282,18 @@ def rename_path(root_fd, payload):
27200
27282
  try:
27201
27283
  from_stat = os.lstat(from_base, dir_fd=from_parent_fd)
27202
27284
  reject_unsafe_endpoint(from_stat)
27203
- if not payload.get("overwrite", True):
27285
+ overwrite = payload.get("overwrite", True)
27286
+ if not overwrite and stat.S_ISREG(from_stat.st_mode):
27287
+ try:
27288
+ link_no_replace(from_base, to_base, from_parent_fd, to_parent_fd)
27289
+ except OSError as exc:
27290
+ if not link_unsupported(exc):
27291
+ raise
27292
+ copy_file_no_replace(from_parent_fd, from_base, to_parent_fd, to_base, stat.S_IMODE(from_stat.st_mode), from_stat, True)
27293
+ return None
27294
+ if not overwrite and stat.S_ISDIR(from_stat.st_mode):
27295
+ raise RuntimeError("fs-safe-directory-noreplace-unsupported")
27296
+ if not overwrite:
27204
27297
  try:
27205
27298
  os.lstat(to_base, dir_fd=to_parent_fd)
27206
27299
  raise FileExistsError(errno.EEXIST, "destination exists", to_base)
@@ -27245,19 +27338,15 @@ def write_path(root_fd, payload):
27245
27338
  except FileNotFoundError:
27246
27339
  pass
27247
27340
  temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
27248
- view = memoryview(data)
27249
- while view:
27250
- written = os.write(temp_fd, view)
27251
- if written <= 0:
27252
- raise OSError(errno.EIO, "short write")
27253
- view = view[written:]
27341
+ os.fchmod(temp_fd, mode)
27342
+ write_all(temp_fd, data)
27254
27343
  os.fsync(temp_fd)
27344
+ temp_stat = os.fstat(temp_fd)
27255
27345
  os.close(temp_fd)
27256
27346
  temp_fd = None
27257
- os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
27347
+ result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
27258
27348
  temp_name = None
27259
27349
  os.fsync(parent_fd)
27260
- result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
27261
27350
  return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
27262
27351
  finally:
27263
27352
  if temp_fd is not None:
@@ -27287,13 +27376,8 @@ def copy_path(root_fd, payload):
27287
27376
  if max_bytes >= 0 and source_stat.st_size > max_bytes:
27288
27377
  raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
27289
27378
  parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
27290
- if not overwrite:
27291
- try:
27292
- os.lstat(basename, dir_fd=parent_fd)
27293
- raise FileExistsError(errno.EEXIST, "destination exists", basename)
27294
- except FileNotFoundError:
27295
- pass
27296
27379
  temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
27380
+ os.fchmod(temp_fd, mode)
27297
27381
  written_bytes = 0
27298
27382
  while True:
27299
27383
  chunk = os.read(source_fd, 65536)
@@ -27309,12 +27393,12 @@ def copy_path(root_fd, payload):
27309
27393
  raise OSError(errno.EIO, "short write")
27310
27394
  view = view[written:]
27311
27395
  os.fsync(temp_fd)
27396
+ temp_stat = os.fstat(temp_fd)
27312
27397
  os.close(temp_fd)
27313
27398
  temp_fd = None
27314
- os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
27399
+ result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
27315
27400
  temp_name = None
27316
27401
  os.fsync(parent_fd)
27317
- result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
27318
27402
  return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
27319
27403
  finally:
27320
27404
  os.close(source_fd)
@@ -27331,6 +27415,7 @@ def copy_path(root_fd, payload):
27331
27415
  def run_operation(operation, root_path, payload):
27332
27416
  root_fd = open_dir(root_path)
27333
27417
  try:
27418
+ assert_expected_root(root_fd, payload)
27334
27419
  if operation == "stat":
27335
27420
  return stat_path(root_fd, payload)
27336
27421
  if operation == "readdir":
@@ -27435,6 +27520,15 @@ function mapWorkerError(response) {
27435
27520
  if (message.includes("fs-safe-source-mismatch")) {
27436
27521
  return new errors_FsSafeError("path-mismatch", "source path changed during copy");
27437
27522
  }
27523
+ if (message.includes("fs-safe-temp-mismatch")) {
27524
+ return new errors_FsSafeError("path-mismatch", "temp path changed during write");
27525
+ }
27526
+ if (message.includes("fs-safe-root-mismatch")) {
27527
+ return new errors_FsSafeError("path-mismatch", "root path changed during operation");
27528
+ }
27529
+ if (message.includes("fs-safe-directory-noreplace-unsupported")) {
27530
+ return new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
27531
+ }
27438
27532
  if (code === "FileNotFoundError" || errno === 2) {
27439
27533
  return new errors_FsSafeError("not-found", "file not found");
27440
27534
  }
@@ -27452,19 +27546,19 @@ function mapWorkerError(response) {
27452
27546
  }
27453
27547
  return new errors_FsSafeError("helper-failed", message);
27454
27548
  }
27455
- function rejectPending(error) {
27456
- if (!worker) {
27549
+ function rejectPending(error, targetWorker = worker) {
27550
+ if (!targetWorker || worker !== targetWorker) {
27457
27551
  return;
27458
27552
  }
27459
- setWorkerRef(worker, false);
27460
- for (const pending of worker.pending.values()) {
27553
+ setWorkerRef(targetWorker, false);
27554
+ for (const pending of targetWorker.pending.values()) {
27461
27555
  pending.reject(error);
27462
27556
  }
27463
- worker.pending.clear();
27557
+ targetWorker.pending.clear();
27464
27558
  worker = null;
27465
27559
  }
27466
- function handleWorkerLine(line) {
27467
- if (!worker || !line.trim()) {
27560
+ function handleWorkerLine(currentWorker, line) {
27561
+ if (worker !== currentWorker || !line.trim()) {
27468
27562
  return;
27469
27563
  }
27470
27564
  let decoded;
@@ -27472,11 +27566,11 @@ function handleWorkerLine(line) {
27472
27566
  decoded = JSON.parse(line);
27473
27567
  }
27474
27568
  catch {
27475
- rejectPending(new errors_FsSafeError("helper-failed", `pinned helper returned invalid JSON: ${line}`));
27569
+ rejectPending(new errors_FsSafeError("helper-failed", `pinned helper returned invalid JSON: ${line}`), currentWorker);
27476
27570
  return;
27477
27571
  }
27478
27572
  if (typeof decoded !== "object" || decoded === null || !("id" in decoded)) {
27479
- rejectPending(new errors_FsSafeError("helper-failed", "pinned helper returned invalid response"));
27573
+ rejectPending(new errors_FsSafeError("helper-failed", "pinned helper returned invalid response"), currentWorker);
27480
27574
  return;
27481
27575
  }
27482
27576
  const response = decoded;
@@ -27484,13 +27578,13 @@ function handleWorkerLine(line) {
27484
27578
  if (id === undefined) {
27485
27579
  return;
27486
27580
  }
27487
- const pending = worker.pending.get(id);
27581
+ const pending = currentWorker.pending.get(id);
27488
27582
  if (!pending) {
27489
27583
  return;
27490
27584
  }
27491
- worker.pending.delete(id);
27492
- if (worker.pending.size === 0) {
27493
- setWorkerRef(worker, false);
27585
+ currentWorker.pending.delete(id);
27586
+ if (currentWorker.pending.size === 0) {
27587
+ setWorkerRef(currentWorker, false);
27494
27588
  }
27495
27589
  if (response.ok === true) {
27496
27590
  pending.resolve(response.result);
@@ -27506,28 +27600,28 @@ function getWorker() {
27506
27600
  const child = (0,external_node_child_process_.spawn)(resolvePython(), ["-u", "-c", PINNED_PYTHON_WORKER_SOURCE], {
27507
27601
  stdio: ["pipe", "pipe", "pipe"],
27508
27602
  });
27509
- worker = { child, pending: new Map(), stderr: "", stdoutBuffer: "" };
27603
+ const currentWorker = { child, pending: new Map(), stderr: "", stdoutBuffer: "" };
27604
+ worker = currentWorker;
27510
27605
  child.stdout.setEncoding("utf8");
27511
27606
  child.stderr.setEncoding("utf8");
27512
27607
  child.stdout.on("data", (chunk) => {
27513
- const current = worker;
27514
- if (!current) {
27608
+ if (worker !== currentWorker) {
27515
27609
  return;
27516
27610
  }
27517
- current.stdoutBuffer += chunk;
27611
+ currentWorker.stdoutBuffer += chunk;
27518
27612
  for (;;) {
27519
- const newline = current.stdoutBuffer.indexOf("\n");
27613
+ const newline = currentWorker.stdoutBuffer.indexOf("\n");
27520
27614
  if (newline < 0) {
27521
27615
  break;
27522
27616
  }
27523
- const line = current.stdoutBuffer.slice(0, newline);
27524
- current.stdoutBuffer = current.stdoutBuffer.slice(newline + 1);
27525
- handleWorkerLine(line);
27617
+ const line = currentWorker.stdoutBuffer.slice(0, newline);
27618
+ currentWorker.stdoutBuffer = currentWorker.stdoutBuffer.slice(newline + 1);
27619
+ handleWorkerLine(currentWorker, line);
27526
27620
  }
27527
27621
  });
27528
27622
  child.stderr.on("data", (chunk) => {
27529
- if (worker) {
27530
- worker.stderr = `${worker.stderr}${chunk}`.slice(-4096);
27623
+ if (worker === currentWorker) {
27624
+ currentWorker.stderr = `${currentWorker.stderr}${chunk}`.slice(-4096);
27531
27625
  }
27532
27626
  });
27533
27627
  child.once("error", (error) => {
@@ -27536,17 +27630,17 @@ function getWorker() {
27536
27630
  : error instanceof Error
27537
27631
  ? error
27538
27632
  : new Error(String(error));
27539
- rejectPending(mapped);
27633
+ rejectPending(mapped, currentWorker);
27540
27634
  });
27541
27635
  child.once("close", (code, signal) => {
27542
- const stderr = worker?.stderr.trim();
27543
- rejectPending(new errors_FsSafeError("helper-failed", stderr || `pinned helper exited with code ${code ?? "null"} (${signal ?? "?"})`));
27636
+ const stderr = currentWorker.stderr.trim();
27637
+ rejectPending(new errors_FsSafeError("helper-failed", stderr || `pinned helper exited with code ${code ?? "null"} (${signal ?? "?"})`), currentWorker);
27544
27638
  });
27545
27639
  process.once("exit", () => {
27546
27640
  child.kill("SIGTERM");
27547
27641
  });
27548
- setWorkerRef(worker, false);
27549
- return worker;
27642
+ setWorkerRef(currentWorker, false);
27643
+ return currentWorker;
27550
27644
  }
27551
27645
  function setRefable(value, ref) {
27552
27646
  if (!value) {
@@ -27612,9 +27706,7 @@ function validatePinnedOperationPayload(payload) {
27612
27706
  }
27613
27707
  }
27614
27708
  function isPinnedHelperUnavailable(error) {
27615
- return error instanceof Error &&
27616
- "code" in error &&
27617
- error.code === "helper-unavailable";
27709
+ return error instanceof Error && "code" in error && error.code === "helper-unavailable";
27618
27710
  }
27619
27711
  function validatePinnedRelativePath(relativePath) {
27620
27712
  if (relativePath.length === 0 || relativePath === ".") {
@@ -27712,29 +27804,21 @@ function assertWithinMaxBytes(bytes, maxBytes) {
27712
27804
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
27713
27805
  }
27714
27806
  }
27715
- function pinned_write_createMaxBytesTransform(maxBytes) {
27716
- if (maxBytes === undefined) {
27717
- return undefined;
27718
- }
27807
+ async function writeStreamToHandle(stream, handle, maxBytes) {
27719
27808
  let bytes = 0;
27720
- return new external_node_stream_.Transform({
27721
- transform(chunk, _encoding, callback) {
27722
- bytes += chunk.byteLength;
27723
- if (bytes > maxBytes) {
27724
- callback(new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`));
27725
- return;
27809
+ for await (const chunk of stream) {
27810
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
27811
+ bytes += buffer.byteLength;
27812
+ assertWithinMaxBytes(bytes, maxBytes);
27813
+ let offset = 0;
27814
+ while (offset < buffer.byteLength) {
27815
+ const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
27816
+ if (bytesWritten <= 0) {
27817
+ throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
27726
27818
  }
27727
- callback(null, chunk);
27728
- },
27729
- });
27730
- }
27731
- async function pipelineWithMaxBytes(stream, destination, maxBytes) {
27732
- const limiter = pinned_write_createMaxBytesTransform(maxBytes);
27733
- if (limiter) {
27734
- await (0,external_node_stream_promises_.pipeline)(stream, limiter, destination);
27735
- return;
27819
+ offset += bytesWritten;
27820
+ }
27736
27821
  }
27737
- await (0,external_node_stream_promises_.pipeline)(stream, destination);
27738
27822
  }
27739
27823
  async function inputToBase64(input, maxBytes) {
27740
27824
  if (input.kind === "buffer") {
@@ -27759,7 +27843,7 @@ async function runPinnedWriteHelper(params) {
27759
27843
  relativeParentPath: params.relativeParentPath,
27760
27844
  });
27761
27845
  if (getFsSafePythonConfig().mode === "off") {
27762
- return await runPinnedWriteFallback(params);
27846
+ return await runPinnedWriteFallbackOrThrow(params);
27763
27847
  }
27764
27848
  if (params.input.kind === "stream") {
27765
27849
  try {
@@ -27767,7 +27851,7 @@ async function runPinnedWriteHelper(params) {
27767
27851
  }
27768
27852
  catch (error) {
27769
27853
  if (canFallbackFromPythonError(error)) {
27770
- return await runPinnedWriteFallback(params);
27854
+ return await runPinnedWriteFallbackOrThrow(params, error);
27771
27855
  }
27772
27856
  throw error;
27773
27857
  }
@@ -27780,6 +27864,7 @@ async function runPinnedWriteHelper(params) {
27780
27864
  mode: params.mode || 0o600,
27781
27865
  overwrite: params.overwrite !== false,
27782
27866
  relativeParentPath: params.relativeParentPath,
27867
+ ...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
27783
27868
  };
27784
27869
  try {
27785
27870
  return await runPinnedPythonOperation({
@@ -27790,7 +27875,7 @@ async function runPinnedWriteHelper(params) {
27790
27875
  }
27791
27876
  catch (error) {
27792
27877
  if (canFallbackFromPythonError(error)) {
27793
- return await runPinnedWriteFallback(params);
27878
+ return await runPinnedWriteFallbackOrThrow(params, error);
27794
27879
  }
27795
27880
  throw error;
27796
27881
  }
@@ -27810,22 +27895,29 @@ async function runPinnedCopyHelper(params) {
27810
27895
  mode: params.mode || 0o600,
27811
27896
  overwrite: params.overwrite !== false,
27812
27897
  relativeParentPath: params.relativeParentPath,
27898
+ ...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
27813
27899
  sourceDev: params.sourceIdentity.dev,
27814
27900
  sourceIno: params.sourceIdentity.ino,
27815
27901
  sourcePath: params.sourcePath,
27816
27902
  },
27817
27903
  });
27818
27904
  }
27905
+ async function runPinnedWriteFallbackOrThrow(params, cause) {
27906
+ if (process.platform !== "win32") {
27907
+ throw new errors_FsSafeError("helper-unavailable", "Python helper is required for pinned writes on this platform", { cause });
27908
+ }
27909
+ return await runPinnedWriteFallback(params);
27910
+ }
27819
27911
  async function runPinnedWriteFallback(params) {
27820
27912
  const parentPath = params.relativeParentPath
27821
27913
  ? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
27822
27914
  : params.rootPath;
27823
- const parentGuard = await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
27824
27915
  if (params.mkdir) {
27825
- await withAsyncDirectoryGuards([parentGuard], async () => {
27826
- await promises_.mkdir(parentPath, { recursive: true });
27827
- });
27916
+ await mkdirPathComponentsWithGuards({ rootReal: params.rootPath, targetPath: parentPath });
27828
27917
  }
27918
+ const parentGuard = params.mkdir
27919
+ ? await directory_guard_createAsyncDirectoryGuard(parentPath)
27920
+ : await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
27829
27921
  const targetPath = external_node_path_.join(parentPath, params.basename);
27830
27922
  if (params.overwrite === false) {
27831
27923
  let handle = await withAsyncDirectoryGuards([parentGuard], async () => await promises_.open(targetPath, external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL, params.mode), {
@@ -27847,7 +27939,7 @@ async function runPinnedWriteFallback(params) {
27847
27939
  }
27848
27940
  }
27849
27941
  else {
27850
- await pipelineWithMaxBytes(params.input.stream, handle.createWriteStream(), params.maxBytes);
27942
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
27851
27943
  }
27852
27944
  const stat = await handle.stat();
27853
27945
  created = false;
@@ -27868,7 +27960,9 @@ async function runPinnedWriteFallback(params) {
27868
27960
  ? external_node_fs_.constants.O_NOFOLLOW
27869
27961
  : 0);
27870
27962
  let handle;
27871
- let handleClosedByStream = false;
27963
+ let tempStat;
27964
+ let targetStat;
27965
+ let renamed = false;
27872
27966
  try {
27873
27967
  handle = await promises_.open(tempPath, tempFlags, params.mode);
27874
27968
  if (params.input.kind === "buffer") {
@@ -27881,29 +27975,36 @@ async function runPinnedWriteFallback(params) {
27881
27975
  }
27882
27976
  }
27883
27977
  else {
27884
- const writable = handle.createWriteStream();
27885
- writable.once("close", () => {
27886
- handleClosedByStream = true;
27887
- });
27888
- await pipelineWithMaxBytes(params.input.stream, writable, params.maxBytes);
27978
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
27889
27979
  }
27890
- if (!handleClosedByStream) {
27891
- await handle.close().catch(() => undefined);
27892
- handle = undefined;
27980
+ tempStat = await handle.stat();
27981
+ const tempPathStat = await promises_.lstat(tempPath);
27982
+ if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
27983
+ throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
27893
27984
  }
27985
+ const expectedTempStat = tempStat;
27986
+ await handle.close().catch(() => undefined);
27987
+ handle = undefined;
27894
27988
  await withAsyncDirectoryGuards([parentGuard], async () => {
27895
27989
  await promises_.rename(tempPath, targetPath);
27990
+ renamed = true;
27991
+ targetStat = await promises_.lstat(targetPath);
27992
+ if (targetStat.isSymbolicLink() || !file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
27993
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
27994
+ }
27896
27995
  });
27897
27996
  }
27898
27997
  catch (error) {
27899
- if (handle && !handleClosedByStream) {
27900
- await handle.close().catch(() => undefined);
27998
+ await handle?.close().catch(() => undefined);
27999
+ if (!renamed) {
28000
+ await promises_.rm(tempPath, { force: true }).catch(() => undefined);
27901
28001
  }
27902
- await promises_.rm(tempPath, { force: true }).catch(() => undefined);
27903
28002
  throw error;
27904
28003
  }
27905
- const stat = await promises_.stat(targetPath);
27906
- return { dev: stat.dev, ino: stat.ino };
28004
+ if (!targetStat) {
28005
+ throw new errors_FsSafeError("path-mismatch", "fallback target was not verified");
28006
+ }
28007
+ return { dev: targetStat.dev, ino: targetStat.ino };
27907
28008
  }
27908
28009
 
27909
28010
  // EXTERNAL MODULE: external "node:os"
@@ -28442,7 +28543,7 @@ function relativeInsideRoot(rootPath, targetPath) {
28442
28543
  if (!relative || relative === ".") {
28443
28544
  return "";
28444
28545
  }
28445
- if (relative.startsWith("..") || external_node_path_.isAbsolute(relative)) {
28546
+ if (isPathRelativeEscape(relative)) {
28446
28547
  return "";
28447
28548
  }
28448
28549
  return relative;
@@ -28793,6 +28894,15 @@ function __setFsSafeTestHooksForTest(hooks) {
28793
28894
  test_hooks_fsSafeTestHooks = hooks;
28794
28895
  }
28795
28896
 
28897
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/json-stringify.js
28898
+ function stringifyJsonDocument(value, replacer, space) {
28899
+ const text = JSON.stringify(value, replacer, space);
28900
+ if (typeof text !== "string") {
28901
+ throw new TypeError("value is not representable as a JSON document");
28902
+ }
28903
+ return text;
28904
+ }
28905
+
28796
28906
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/temp-cleanup.js
28797
28907
 
28798
28908
  const tempCleanupEntries = new Map();
@@ -28880,6 +28990,7 @@ async function serializePathWrite(key, run) {
28880
28990
 
28881
28991
 
28882
28992
 
28993
+
28883
28994
 
28884
28995
 
28885
28996
  function logWarn(message) {
@@ -29120,12 +29231,12 @@ class RootHandle {
29120
29231
  }
29121
29232
  async writeJson(relativePath, data, options = {}) {
29122
29233
  const { replacer, space, trailingNewline = true, ...writeOptions } = options;
29123
- const json = JSON.stringify(data, replacer, space);
29234
+ const json = stringifyJsonDocument(data, replacer, space);
29124
29235
  await this.write(relativePath, trailingNewline ? `${json}\n` : json, writeOptions);
29125
29236
  }
29126
29237
  async createJson(relativePath, data, options = {}) {
29127
29238
  const { replacer, space, trailingNewline = true, ...writeOptions } = options;
29128
- const json = JSON.stringify(data, replacer, space);
29239
+ const json = stringifyJsonDocument(data, replacer, space);
29129
29240
  await this.create(relativePath, trailingNewline ? `${json}\n` : json, writeOptions);
29130
29241
  }
29131
29242
  async copyIn(relativePath, sourcePath, options = {}) {
@@ -29910,6 +30021,9 @@ async function movePathFallback(root, params) {
29910
30021
  if (sourceStat.isFile() && sourceStat.nlink > 1) {
29911
30022
  throw new errors_FsSafeError("hardlink", "hardlinked path not allowed");
29912
30023
  }
30024
+ if (!params.overwrite && sourceStat.isDirectory()) {
30025
+ throw new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
30026
+ }
29913
30027
  if (!params.overwrite) {
29914
30028
  try {
29915
30029
  await promises_.lstat(target.resolved);
@@ -68063,4 +68177,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
68063
68177
  // module factories are used so entry inlining is disabled
68064
68178
  // startup
68065
68179
  // Load entry module and return exports
68066
- var __webpack_exports__ = __webpack_require__(2415);
68180
+ var __webpack_exports__ = __webpack_require__(3523);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/mcp",
3
- "version": "5.14.8",
3
+ "version": "5.14.10",
4
4
  "description": "MCP server for lousy-agents - provides AI coding assistant tools via the Model Context Protocol",
5
5
  "type": "module",
6
6
  "repository": {