@lousy-agents/agent-shell 5.14.7 → 5.14.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +143 -48
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __rspack_createRequire } from "node:module";
3
3
  const __rspack_createRequire_require = __rspack_createRequire(import.meta.url);
4
4
  var __webpack_modules__ = ({
5
- 391(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
5
+ 145(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6
6
 
7
7
  ;// CONCATENATED MODULE: external "node:child_process"
8
8
  const external_node_child_process_namespaceObject = __rspack_createRequire_require("node:child_process");
@@ -340,7 +340,7 @@ function splitSafeRelativePath(relativePath) {
340
340
  function resolveSafeRelativePath(rootDir, relativePath) {
341
341
  const root = path.resolve(rootDir);
342
342
  const target = path.resolve(root, ...splitSafeRelativePath(relativePath));
343
- if (target !== root && !target.startsWith(root + path.sep)) {
343
+ if (!path_isPathInside(root, target)) {
344
344
  throw new FsSafeError("outside-workspace", "relative path escapes root");
345
345
  }
346
346
  return target;
@@ -673,6 +673,85 @@ def reject_unsafe_endpoint(st):
673
673
  if stat.S_ISREG(mode) and st.st_nlink > 1:
674
674
  raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
675
675
 
676
+ def copy_bytes(source_fd, dest_fd):
677
+ while True:
678
+ chunk = os.read(source_fd, 65536)
679
+ if not chunk:
680
+ break
681
+ view = memoryview(chunk)
682
+ while view:
683
+ written = os.write(dest_fd, view)
684
+ if written <= 0:
685
+ raise OSError(errno.EIO, "short write")
686
+ view = view[written:]
687
+
688
+ def write_all(fd, data):
689
+ view = memoryview(data)
690
+ while view:
691
+ written = os.write(fd, view)
692
+ if written <= 0:
693
+ raise OSError(errno.EIO, "short write")
694
+ view = view[written:]
695
+
696
+ def link_unsupported(exc):
697
+ unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
698
+ return getattr(exc, "errno", None) in unsupported
699
+
700
+ def link_no_replace(name, new_name, source_fd, target_fd):
701
+ linked = False
702
+ try:
703
+ os.link(name, new_name, src_dir_fd=source_fd, dst_dir_fd=target_fd, follow_symlinks=False)
704
+ linked = True
705
+ os.unlink(name, dir_fd=source_fd)
706
+ except Exception:
707
+ if linked:
708
+ try: os.unlink(new_name, dir_fd=target_fd)
709
+ except FileNotFoundError: pass
710
+ raise
711
+ os.fsync(source_fd)
712
+ if source_fd != target_fd:
713
+ os.fsync(target_fd)
714
+
715
+ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
716
+ source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
717
+ dest_fd = None
718
+ success = False
719
+ try:
720
+ if expected is not None:
721
+ source_stat = os.fstat(source_fd)
722
+ if source_stat.st_dev != expected.st_dev or source_stat.st_ino != expected.st_ino:
723
+ raise RuntimeError("fs-safe-source-mismatch")
724
+ dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
725
+ copy_bytes(source_fd, dest_fd)
726
+ os.fsync(dest_fd)
727
+ success = True
728
+ finally:
729
+ os.close(source_fd)
730
+ if dest_fd is not None:
731
+ os.close(dest_fd)
732
+ if dest_fd is not None and not success:
733
+ try: os.unlink(basename, dir_fd=target_parent_fd)
734
+ except FileNotFoundError: pass
735
+ if unlink_source:
736
+ try:
737
+ os.unlink(source_name, dir_fd=source_parent_fd)
738
+ except Exception:
739
+ try: os.unlink(basename, dir_fd=target_parent_fd)
740
+ except FileNotFoundError: pass
741
+ raise
742
+
743
+ def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode):
744
+ if overwrite:
745
+ os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
746
+ else:
747
+ try:
748
+ os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
749
+ os.unlink(temp_name, dir_fd=parent_fd)
750
+ except OSError as exc:
751
+ if not link_unsupported(exc):
752
+ raise
753
+ copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, unlink_source=True)
754
+
676
755
  def stat_path(root_fd, payload):
677
756
  relative = payload.get("relativePath", "")
678
757
  segments = split_relative(relative)
@@ -747,7 +826,18 @@ def rename_path(root_fd, payload):
747
826
  try:
748
827
  from_stat = os.lstat(from_base, dir_fd=from_parent_fd)
749
828
  reject_unsafe_endpoint(from_stat)
750
- if not payload.get("overwrite", True):
829
+ overwrite = payload.get("overwrite", True)
830
+ if not overwrite and stat.S_ISREG(from_stat.st_mode):
831
+ try:
832
+ link_no_replace(from_base, to_base, from_parent_fd, to_parent_fd)
833
+ except OSError as exc:
834
+ if not link_unsupported(exc):
835
+ raise
836
+ copy_file_no_replace(from_parent_fd, from_base, to_parent_fd, to_base, stat.S_IMODE(from_stat.st_mode), from_stat, True)
837
+ return None
838
+ if not overwrite and stat.S_ISDIR(from_stat.st_mode):
839
+ raise RuntimeError("fs-safe-directory-noreplace-unsupported")
840
+ if not overwrite:
751
841
  try:
752
842
  os.lstat(to_base, dir_fd=to_parent_fd)
753
843
  raise FileExistsError(errno.EEXIST, "destination exists", to_base)
@@ -792,16 +882,11 @@ def write_path(root_fd, payload):
792
882
  except FileNotFoundError:
793
883
  pass
794
884
  temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
795
- view = memoryview(data)
796
- while view:
797
- written = os.write(temp_fd, view)
798
- if written <= 0:
799
- raise OSError(errno.EIO, "short write")
800
- view = view[written:]
885
+ write_all(temp_fd, data)
801
886
  os.fsync(temp_fd)
802
887
  os.close(temp_fd)
803
888
  temp_fd = None
804
- os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
889
+ commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
805
890
  temp_name = None
806
891
  os.fsync(parent_fd)
807
892
  result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
@@ -834,12 +919,6 @@ def copy_path(root_fd, payload):
834
919
  if max_bytes >= 0 and source_stat.st_size > max_bytes:
835
920
  raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
836
921
  parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
837
- if not overwrite:
838
- try:
839
- os.lstat(basename, dir_fd=parent_fd)
840
- raise FileExistsError(errno.EEXIST, "destination exists", basename)
841
- except FileNotFoundError:
842
- pass
843
922
  temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
844
923
  written_bytes = 0
845
924
  while True:
@@ -858,7 +937,7 @@ def copy_path(root_fd, payload):
858
937
  os.fsync(temp_fd)
859
938
  os.close(temp_fd)
860
939
  temp_fd = None
861
- os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
940
+ commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
862
941
  temp_name = None
863
942
  os.fsync(parent_fd)
864
943
  result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
@@ -982,6 +1061,9 @@ function mapWorkerError(response) {
982
1061
  if (message.includes("fs-safe-source-mismatch")) {
983
1062
  return new errors_FsSafeError("path-mismatch", "source path changed during copy");
984
1063
  }
1064
+ if (message.includes("fs-safe-directory-noreplace-unsupported")) {
1065
+ return new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
1066
+ }
985
1067
  if (code === "FileNotFoundError" || errno === 2) {
986
1068
  return new errors_FsSafeError("not-found", "file not found");
987
1069
  }
@@ -999,19 +1081,19 @@ function mapWorkerError(response) {
999
1081
  }
1000
1082
  return new errors_FsSafeError("helper-failed", message);
1001
1083
  }
1002
- function rejectPending(error) {
1003
- if (!worker) {
1084
+ function rejectPending(error, targetWorker = worker) {
1085
+ if (!targetWorker || worker !== targetWorker) {
1004
1086
  return;
1005
1087
  }
1006
- setWorkerRef(worker, false);
1007
- for (const pending of worker.pending.values()) {
1088
+ setWorkerRef(targetWorker, false);
1089
+ for (const pending of targetWorker.pending.values()) {
1008
1090
  pending.reject(error);
1009
1091
  }
1010
- worker.pending.clear();
1092
+ targetWorker.pending.clear();
1011
1093
  worker = null;
1012
1094
  }
1013
- function handleWorkerLine(line) {
1014
- if (!worker || !line.trim()) {
1095
+ function handleWorkerLine(currentWorker, line) {
1096
+ if (worker !== currentWorker || !line.trim()) {
1015
1097
  return;
1016
1098
  }
1017
1099
  let decoded;
@@ -1019,11 +1101,11 @@ function handleWorkerLine(line) {
1019
1101
  decoded = JSON.parse(line);
1020
1102
  }
1021
1103
  catch {
1022
- rejectPending(new errors_FsSafeError("helper-failed", `pinned helper returned invalid JSON: ${line}`));
1104
+ rejectPending(new errors_FsSafeError("helper-failed", `pinned helper returned invalid JSON: ${line}`), currentWorker);
1023
1105
  return;
1024
1106
  }
1025
1107
  if (typeof decoded !== "object" || decoded === null || !("id" in decoded)) {
1026
- rejectPending(new errors_FsSafeError("helper-failed", "pinned helper returned invalid response"));
1108
+ rejectPending(new errors_FsSafeError("helper-failed", "pinned helper returned invalid response"), currentWorker);
1027
1109
  return;
1028
1110
  }
1029
1111
  const response = decoded;
@@ -1031,13 +1113,13 @@ function handleWorkerLine(line) {
1031
1113
  if (id === undefined) {
1032
1114
  return;
1033
1115
  }
1034
- const pending = worker.pending.get(id);
1116
+ const pending = currentWorker.pending.get(id);
1035
1117
  if (!pending) {
1036
1118
  return;
1037
1119
  }
1038
- worker.pending.delete(id);
1039
- if (worker.pending.size === 0) {
1040
- setWorkerRef(worker, false);
1120
+ currentWorker.pending.delete(id);
1121
+ if (currentWorker.pending.size === 0) {
1122
+ setWorkerRef(currentWorker, false);
1041
1123
  }
1042
1124
  if (response.ok === true) {
1043
1125
  pending.resolve(response.result);
@@ -1053,28 +1135,28 @@ function getWorker() {
1053
1135
  const child = (0,external_node_child_process_namespaceObject.spawn)(resolvePython(), ["-u", "-c", PINNED_PYTHON_WORKER_SOURCE], {
1054
1136
  stdio: ["pipe", "pipe", "pipe"],
1055
1137
  });
1056
- worker = { child, pending: new Map(), stderr: "", stdoutBuffer: "" };
1138
+ const currentWorker = { child, pending: new Map(), stderr: "", stdoutBuffer: "" };
1139
+ worker = currentWorker;
1057
1140
  child.stdout.setEncoding("utf8");
1058
1141
  child.stderr.setEncoding("utf8");
1059
1142
  child.stdout.on("data", (chunk) => {
1060
- const current = worker;
1061
- if (!current) {
1143
+ if (worker !== currentWorker) {
1062
1144
  return;
1063
1145
  }
1064
- current.stdoutBuffer += chunk;
1146
+ currentWorker.stdoutBuffer += chunk;
1065
1147
  for (;;) {
1066
- const newline = current.stdoutBuffer.indexOf("\n");
1148
+ const newline = currentWorker.stdoutBuffer.indexOf("\n");
1067
1149
  if (newline < 0) {
1068
1150
  break;
1069
1151
  }
1070
- const line = current.stdoutBuffer.slice(0, newline);
1071
- current.stdoutBuffer = current.stdoutBuffer.slice(newline + 1);
1072
- handleWorkerLine(line);
1152
+ const line = currentWorker.stdoutBuffer.slice(0, newline);
1153
+ currentWorker.stdoutBuffer = currentWorker.stdoutBuffer.slice(newline + 1);
1154
+ handleWorkerLine(currentWorker, line);
1073
1155
  }
1074
1156
  });
1075
1157
  child.stderr.on("data", (chunk) => {
1076
- if (worker) {
1077
- worker.stderr = `${worker.stderr}${chunk}`.slice(-4096);
1158
+ if (worker === currentWorker) {
1159
+ currentWorker.stderr = `${currentWorker.stderr}${chunk}`.slice(-4096);
1078
1160
  }
1079
1161
  });
1080
1162
  child.once("error", (error) => {
@@ -1083,17 +1165,17 @@ function getWorker() {
1083
1165
  : error instanceof Error
1084
1166
  ? error
1085
1167
  : new Error(String(error));
1086
- rejectPending(mapped);
1168
+ rejectPending(mapped, currentWorker);
1087
1169
  });
1088
1170
  child.once("close", (code, signal) => {
1089
- const stderr = worker?.stderr.trim();
1090
- rejectPending(new errors_FsSafeError("helper-failed", stderr || `pinned helper exited with code ${code ?? "null"} (${signal ?? "?"})`));
1171
+ const stderr = currentWorker.stderr.trim();
1172
+ rejectPending(new errors_FsSafeError("helper-failed", stderr || `pinned helper exited with code ${code ?? "null"} (${signal ?? "?"})`), currentWorker);
1091
1173
  });
1092
1174
  process.once("exit", () => {
1093
1175
  child.kill("SIGTERM");
1094
1176
  });
1095
- setWorkerRef(worker, false);
1096
- return worker;
1177
+ setWorkerRef(currentWorker, false);
1178
+ return currentWorker;
1097
1179
  }
1098
1180
  function setRefable(value, ref) {
1099
1181
  if (!value) {
@@ -2340,6 +2422,15 @@ function __setFsSafeTestHooksForTest(hooks) {
2340
2422
  test_hooks_fsSafeTestHooks = hooks;
2341
2423
  }
2342
2424
 
2425
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/json-stringify.js
2426
+ function stringifyJsonDocument(value, replacer, space) {
2427
+ const text = JSON.stringify(value, replacer, space);
2428
+ if (typeof text !== "string") {
2429
+ throw new TypeError("value is not representable as a JSON document");
2430
+ }
2431
+ return text;
2432
+ }
2433
+
2343
2434
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/temp-cleanup.js
2344
2435
 
2345
2436
  const tempCleanupEntries = new Map();
@@ -2427,6 +2518,7 @@ async function serializePathWrite(key, run) {
2427
2518
 
2428
2519
 
2429
2520
 
2521
+
2430
2522
 
2431
2523
 
2432
2524
  function logWarn(message) {
@@ -2667,12 +2759,12 @@ class RootHandle {
2667
2759
  }
2668
2760
  async writeJson(relativePath, data, options = {}) {
2669
2761
  const { replacer, space, trailingNewline = true, ...writeOptions } = options;
2670
- const json = JSON.stringify(data, replacer, space);
2762
+ const json = stringifyJsonDocument(data, replacer, space);
2671
2763
  await this.write(relativePath, trailingNewline ? `${json}\n` : json, writeOptions);
2672
2764
  }
2673
2765
  async createJson(relativePath, data, options = {}) {
2674
2766
  const { replacer, space, trailingNewline = true, ...writeOptions } = options;
2675
- const json = JSON.stringify(data, replacer, space);
2767
+ const json = stringifyJsonDocument(data, replacer, space);
2676
2768
  await this.create(relativePath, trailingNewline ? `${json}\n` : json, writeOptions);
2677
2769
  }
2678
2770
  async copyIn(relativePath, sourcePath, options = {}) {
@@ -3457,6 +3549,9 @@ async function movePathFallback(root, params) {
3457
3549
  if (sourceStat.isFile() && sourceStat.nlink > 1) {
3458
3550
  throw new errors_FsSafeError("hardlink", "hardlinked path not allowed");
3459
3551
  }
3552
+ if (!params.overwrite && sourceStat.isDirectory()) {
3553
+ throw new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
3554
+ }
3460
3555
  if (!params.overwrite) {
3461
3556
  try {
3462
3557
  await promises_namespaceObject.lstat(target.resolved);
@@ -14782,4 +14877,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
14782
14877
  // module factories are used so entry inlining is disabled
14783
14878
  // startup
14784
14879
  // Load entry module and return exports
14785
- var __webpack_exports__ = __webpack_require__(391);
14880
+ var __webpack_exports__ = __webpack_require__(145);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/agent-shell",
3
- "version": "5.14.7",
3
+ "version": "5.14.9",
4
4
  "description": "A flight recorder for npm script execution",
5
5
  "type": "module",
6
6
  "repository": {
@@ -36,7 +36,7 @@
36
36
  "build": "rspack build --config rspack.config.ts && chmod +x dist/index.js"
37
37
  },
38
38
  "dependencies": {
39
- "@openclaw/fs-safe": "0.2.4",
39
+ "@openclaw/fs-safe": "0.2.5",
40
40
  "zod": "4.4.3"
41
41
  },
42
42
  "peerDependencies": {