@lousy-agents/agent-shell 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.
- package/dist/index.js +237 -123
- 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
|
-
|
|
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");
|
|
@@ -275,6 +275,9 @@ function path_isPathInside(root, target) {
|
|
|
275
275
|
const firstSegment = relative.split(external_node_path_namespaceObject.posix.sep)[0];
|
|
276
276
|
return relative === "" || (firstSegment !== ".." && !external_node_path_namespaceObject.isAbsolute(relative));
|
|
277
277
|
}
|
|
278
|
+
function isPathRelativeEscape(relativePath) {
|
|
279
|
+
return relativePath === ".." || relativePath.startsWith(`..${external_node_path_namespaceObject.sep}`) || external_node_path_namespaceObject.isAbsolute(relativePath);
|
|
280
|
+
}
|
|
278
281
|
function resolveSafeBaseDir(rootDir) {
|
|
279
282
|
const resolved = path.resolve(rootDir);
|
|
280
283
|
return resolved.endsWith(path.sep) ? resolved : `${resolved}${path.sep}`;
|
|
@@ -340,7 +343,7 @@ function splitSafeRelativePath(relativePath) {
|
|
|
340
343
|
function resolveSafeRelativePath(rootDir, relativePath) {
|
|
341
344
|
const root = path.resolve(rootDir);
|
|
342
345
|
const target = path.resolve(root, ...splitSafeRelativePath(relativePath));
|
|
343
|
-
if (
|
|
346
|
+
if (!path_isPathInside(root, target)) {
|
|
344
347
|
throw new FsSafeError("outside-workspace", "relative path escapes root");
|
|
345
348
|
}
|
|
346
349
|
return target;
|
|
@@ -423,17 +426,17 @@ function directory_guard_createNearestExistingSyncDirectoryGuard(rootReal, targe
|
|
|
423
426
|
|
|
424
427
|
|
|
425
428
|
|
|
429
|
+
|
|
426
430
|
function isSameOrChildPath(candidate, parent) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
function isPathEscape(relativePath) {
|
|
430
|
-
return relativePath === ".." || relativePath.startsWith(`..${external_node_path_namespaceObject.sep}`) || external_node_path_namespaceObject.isAbsolute(relativePath);
|
|
431
|
+
const parentPrefix = parent.endsWith(external_node_path_namespaceObject.sep) ? parent : `${parent}${external_node_path_namespaceObject.sep}`;
|
|
432
|
+
return candidate === parent || candidate.startsWith(parentPrefix);
|
|
431
433
|
}
|
|
432
434
|
async function mkdirPathComponentsWithGuards(params) {
|
|
433
435
|
const root = external_node_path_namespaceObject.resolve(params.rootReal);
|
|
436
|
+
const rootCanonical = external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(root));
|
|
434
437
|
const target = external_node_path_namespaceObject.resolve(params.targetPath);
|
|
435
438
|
const relative = external_node_path_namespaceObject.relative(root, target);
|
|
436
|
-
if (
|
|
439
|
+
if (isPathRelativeEscape(relative)) {
|
|
437
440
|
throw new errors_FsSafeError("outside-workspace", "directory is outside workspace root");
|
|
438
441
|
}
|
|
439
442
|
let current = root;
|
|
@@ -456,7 +459,7 @@ async function mkdirPathComponentsWithGuards(params) {
|
|
|
456
459
|
}
|
|
457
460
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
458
461
|
// segment at a time and realpath-check each segment before descending.
|
|
459
|
-
if (!isSameOrChildPath(external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(next)),
|
|
462
|
+
if (!isSameOrChildPath(external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(next)), rootCanonical)) {
|
|
460
463
|
throw new errors_FsSafeError("outside-workspace", "directory escaped workspace root");
|
|
461
464
|
}
|
|
462
465
|
await directory_guard_createAsyncDirectoryGuard(next);
|
|
@@ -589,26 +592,20 @@ function canFallbackFromPythonError(error) {
|
|
|
589
592
|
|
|
590
593
|
|
|
591
594
|
const PINNED_PYTHON_WORKER_SOURCE = String.raw `
|
|
592
|
-
import base64
|
|
593
|
-
import errno
|
|
594
|
-
import json
|
|
595
|
-
import os
|
|
596
|
-
import secrets
|
|
597
|
-
import stat
|
|
598
|
-
import sys
|
|
599
|
-
|
|
595
|
+
import base64, errno, json, os, secrets, stat, sys
|
|
600
596
|
DIR_FLAGS = os.O_RDONLY
|
|
601
597
|
if hasattr(os, "O_DIRECTORY"):
|
|
602
598
|
DIR_FLAGS |= os.O_DIRECTORY
|
|
603
599
|
if hasattr(os, "O_NOFOLLOW"):
|
|
604
600
|
DIR_FLAGS |= os.O_NOFOLLOW
|
|
605
601
|
READ_FLAGS = os.O_RDONLY
|
|
602
|
+
if hasattr(os, "O_NONBLOCK"):
|
|
603
|
+
READ_FLAGS |= os.O_NONBLOCK
|
|
606
604
|
if hasattr(os, "O_NOFOLLOW"):
|
|
607
605
|
READ_FLAGS |= os.O_NOFOLLOW
|
|
608
606
|
WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
609
607
|
if hasattr(os, "O_NOFOLLOW"):
|
|
610
608
|
WRITE_FLAGS |= os.O_NOFOLLOW
|
|
611
|
-
|
|
612
609
|
def split_relative(value):
|
|
613
610
|
if value in ("", "."):
|
|
614
611
|
return []
|
|
@@ -621,10 +618,8 @@ def split_relative(value):
|
|
|
621
618
|
if part == "..":
|
|
622
619
|
raise OSError(errno.EPERM, "path traversal is not allowed")
|
|
623
620
|
return parts
|
|
624
|
-
|
|
625
621
|
def open_dir(path_value, dir_fd=None):
|
|
626
622
|
return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
|
|
627
|
-
|
|
628
623
|
def walk_dir(root_fd, segments, mkdir_enabled=False):
|
|
629
624
|
current_fd = os.dup(root_fd)
|
|
630
625
|
try:
|
|
@@ -642,14 +637,12 @@ def walk_dir(root_fd, segments, mkdir_enabled=False):
|
|
|
642
637
|
except Exception:
|
|
643
638
|
os.close(current_fd)
|
|
644
639
|
raise
|
|
645
|
-
|
|
646
640
|
def parent_and_basename(root_fd, relative):
|
|
647
641
|
segments = split_relative(relative)
|
|
648
642
|
if not segments:
|
|
649
643
|
raise OSError(errno.EPERM, "operation requires a non-root path")
|
|
650
644
|
parent_fd = walk_dir(root_fd, segments[:-1])
|
|
651
645
|
return parent_fd, segments[-1]
|
|
652
|
-
|
|
653
646
|
def encode_stat(st):
|
|
654
647
|
mode = st.st_mode
|
|
655
648
|
return {
|
|
@@ -665,14 +658,108 @@ def encode_stat(st):
|
|
|
665
658
|
"size": st.st_size,
|
|
666
659
|
"uid": st.st_uid,
|
|
667
660
|
}
|
|
668
|
-
|
|
669
661
|
def reject_unsafe_endpoint(st):
|
|
670
662
|
mode = st.st_mode
|
|
671
663
|
if stat.S_ISLNK(mode):
|
|
672
664
|
raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
|
|
673
665
|
if stat.S_ISREG(mode) and st.st_nlink > 1:
|
|
674
666
|
raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
|
|
675
|
-
|
|
667
|
+
def copy_bytes(source_fd, dest_fd):
|
|
668
|
+
while True:
|
|
669
|
+
chunk = os.read(source_fd, 65536)
|
|
670
|
+
if not chunk:
|
|
671
|
+
break
|
|
672
|
+
view = memoryview(chunk)
|
|
673
|
+
while view:
|
|
674
|
+
written = os.write(dest_fd, view)
|
|
675
|
+
if written <= 0:
|
|
676
|
+
raise OSError(errno.EIO, "short write")
|
|
677
|
+
view = view[written:]
|
|
678
|
+
def write_all(fd, data):
|
|
679
|
+
view = memoryview(data)
|
|
680
|
+
while view:
|
|
681
|
+
written = os.write(fd, view)
|
|
682
|
+
if written <= 0:
|
|
683
|
+
raise OSError(errno.EIO, "short write")
|
|
684
|
+
view = view[written:]
|
|
685
|
+
def link_unsupported(exc):
|
|
686
|
+
unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
|
|
687
|
+
return getattr(exc, "errno", None) in unsupported
|
|
688
|
+
def link_no_replace(name, new_name, source_fd, target_fd):
|
|
689
|
+
linked = False
|
|
690
|
+
try:
|
|
691
|
+
os.link(name, new_name, src_dir_fd=source_fd, dst_dir_fd=target_fd, follow_symlinks=False)
|
|
692
|
+
linked = True
|
|
693
|
+
os.unlink(name, dir_fd=source_fd)
|
|
694
|
+
except Exception:
|
|
695
|
+
if linked:
|
|
696
|
+
try: os.unlink(new_name, dir_fd=target_fd)
|
|
697
|
+
except FileNotFoundError: pass
|
|
698
|
+
raise
|
|
699
|
+
os.fsync(source_fd)
|
|
700
|
+
if source_fd != target_fd:
|
|
701
|
+
os.fsync(target_fd)
|
|
702
|
+
def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
|
|
703
|
+
source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
|
|
704
|
+
dest_fd = None; success = False; dest_stat = None
|
|
705
|
+
try:
|
|
706
|
+
if expected is not None:
|
|
707
|
+
source_stat = os.fstat(source_fd)
|
|
708
|
+
if source_stat.st_dev != expected.st_dev or source_stat.st_ino != expected.st_ino:
|
|
709
|
+
raise RuntimeError("fs-safe-source-mismatch")
|
|
710
|
+
dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
|
|
711
|
+
copy_bytes(source_fd, dest_fd)
|
|
712
|
+
os.fsync(dest_fd)
|
|
713
|
+
dest_stat = os.fstat(dest_fd)
|
|
714
|
+
success = True
|
|
715
|
+
finally:
|
|
716
|
+
os.close(source_fd)
|
|
717
|
+
if dest_fd is not None:
|
|
718
|
+
os.close(dest_fd)
|
|
719
|
+
if dest_fd is not None and not success:
|
|
720
|
+
try: os.unlink(basename, dir_fd=target_parent_fd)
|
|
721
|
+
except FileNotFoundError: pass
|
|
722
|
+
if unlink_source:
|
|
723
|
+
try:
|
|
724
|
+
os.unlink(source_name, dir_fd=source_parent_fd)
|
|
725
|
+
except Exception:
|
|
726
|
+
try: os.unlink(basename, dir_fd=target_parent_fd)
|
|
727
|
+
except FileNotFoundError: pass
|
|
728
|
+
raise
|
|
729
|
+
return dest_stat
|
|
730
|
+
def same_identity(left, right):
|
|
731
|
+
return left.st_dev == right.st_dev and left.st_ino == right.st_ino
|
|
732
|
+
def verify_temp_name(parent_fd, temp_name, expected_stat):
|
|
733
|
+
current_stat = os.lstat(temp_name, dir_fd=parent_fd)
|
|
734
|
+
if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
|
|
735
|
+
raise RuntimeError("fs-safe-temp-mismatch")
|
|
736
|
+
def verify_committed_temp(parent_fd, basename, expected_stat):
|
|
737
|
+
final_stat = os.lstat(basename, dir_fd=parent_fd)
|
|
738
|
+
if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
|
|
739
|
+
return final_stat
|
|
740
|
+
try: os.unlink(basename, dir_fd=parent_fd)
|
|
741
|
+
except FileNotFoundError: pass
|
|
742
|
+
raise RuntimeError("fs-safe-temp-mismatch")
|
|
743
|
+
def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
|
|
744
|
+
verify_temp_name(parent_fd, temp_name, expected_stat)
|
|
745
|
+
if overwrite:
|
|
746
|
+
os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
|
|
747
|
+
return verify_committed_temp(parent_fd, basename, expected_stat)
|
|
748
|
+
else:
|
|
749
|
+
try:
|
|
750
|
+
os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
|
|
751
|
+
final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
|
|
752
|
+
os.unlink(temp_name, dir_fd=parent_fd)
|
|
753
|
+
return final_stat
|
|
754
|
+
except OSError as exc:
|
|
755
|
+
if not link_unsupported(exc):
|
|
756
|
+
raise
|
|
757
|
+
return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
|
|
758
|
+
def assert_expected_root(root_fd, payload):
|
|
759
|
+
if "rootDev" in payload or "rootIno" in payload:
|
|
760
|
+
root_stat = os.fstat(root_fd)
|
|
761
|
+
if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
|
|
762
|
+
raise RuntimeError("fs-safe-root-mismatch")
|
|
676
763
|
def stat_path(root_fd, payload):
|
|
677
764
|
relative = payload.get("relativePath", "")
|
|
678
765
|
segments = split_relative(relative)
|
|
@@ -686,7 +773,6 @@ def stat_path(root_fd, payload):
|
|
|
686
773
|
return encode_stat(st)
|
|
687
774
|
finally:
|
|
688
775
|
os.close(parent_fd)
|
|
689
|
-
|
|
690
776
|
def readdir_path(root_fd, payload):
|
|
691
777
|
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
|
|
692
778
|
try:
|
|
@@ -702,12 +788,9 @@ def readdir_path(root_fd, payload):
|
|
|
702
788
|
return entries
|
|
703
789
|
finally:
|
|
704
790
|
os.close(dir_fd)
|
|
705
|
-
|
|
706
791
|
def mkdirp_path(root_fd, payload):
|
|
707
792
|
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
|
|
708
|
-
os.close(dir_fd)
|
|
709
|
-
return None
|
|
710
|
-
|
|
793
|
+
os.close(dir_fd); return None
|
|
711
794
|
def remove_tree(parent_fd, basename):
|
|
712
795
|
st = os.lstat(basename, dir_fd=parent_fd)
|
|
713
796
|
if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
|
|
@@ -720,7 +803,6 @@ def remove_tree(parent_fd, basename):
|
|
|
720
803
|
os.rmdir(basename, dir_fd=parent_fd)
|
|
721
804
|
else:
|
|
722
805
|
os.unlink(basename, dir_fd=parent_fd)
|
|
723
|
-
|
|
724
806
|
def remove_path(root_fd, payload):
|
|
725
807
|
parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
|
|
726
808
|
try:
|
|
@@ -747,7 +829,18 @@ def rename_path(root_fd, payload):
|
|
|
747
829
|
try:
|
|
748
830
|
from_stat = os.lstat(from_base, dir_fd=from_parent_fd)
|
|
749
831
|
reject_unsafe_endpoint(from_stat)
|
|
750
|
-
|
|
832
|
+
overwrite = payload.get("overwrite", True)
|
|
833
|
+
if not overwrite and stat.S_ISREG(from_stat.st_mode):
|
|
834
|
+
try:
|
|
835
|
+
link_no_replace(from_base, to_base, from_parent_fd, to_parent_fd)
|
|
836
|
+
except OSError as exc:
|
|
837
|
+
if not link_unsupported(exc):
|
|
838
|
+
raise
|
|
839
|
+
copy_file_no_replace(from_parent_fd, from_base, to_parent_fd, to_base, stat.S_IMODE(from_stat.st_mode), from_stat, True)
|
|
840
|
+
return None
|
|
841
|
+
if not overwrite and stat.S_ISDIR(from_stat.st_mode):
|
|
842
|
+
raise RuntimeError("fs-safe-directory-noreplace-unsupported")
|
|
843
|
+
if not overwrite:
|
|
751
844
|
try:
|
|
752
845
|
os.lstat(to_base, dir_fd=to_parent_fd)
|
|
753
846
|
raise FileExistsError(errno.EEXIST, "destination exists", to_base)
|
|
@@ -792,19 +885,15 @@ def write_path(root_fd, payload):
|
|
|
792
885
|
except FileNotFoundError:
|
|
793
886
|
pass
|
|
794
887
|
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
written = os.write(temp_fd, view)
|
|
798
|
-
if written <= 0:
|
|
799
|
-
raise OSError(errno.EIO, "short write")
|
|
800
|
-
view = view[written:]
|
|
888
|
+
os.fchmod(temp_fd, mode)
|
|
889
|
+
write_all(temp_fd, data)
|
|
801
890
|
os.fsync(temp_fd)
|
|
891
|
+
temp_stat = os.fstat(temp_fd)
|
|
802
892
|
os.close(temp_fd)
|
|
803
893
|
temp_fd = None
|
|
804
|
-
|
|
894
|
+
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
805
895
|
temp_name = None
|
|
806
896
|
os.fsync(parent_fd)
|
|
807
|
-
result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
|
|
808
897
|
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
809
898
|
finally:
|
|
810
899
|
if temp_fd is not None:
|
|
@@ -834,13 +923,8 @@ def copy_path(root_fd, payload):
|
|
|
834
923
|
if max_bytes >= 0 and source_stat.st_size > max_bytes:
|
|
835
924
|
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
|
|
836
925
|
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
926
|
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
927
|
+
os.fchmod(temp_fd, mode)
|
|
844
928
|
written_bytes = 0
|
|
845
929
|
while True:
|
|
846
930
|
chunk = os.read(source_fd, 65536)
|
|
@@ -856,12 +940,12 @@ def copy_path(root_fd, payload):
|
|
|
856
940
|
raise OSError(errno.EIO, "short write")
|
|
857
941
|
view = view[written:]
|
|
858
942
|
os.fsync(temp_fd)
|
|
943
|
+
temp_stat = os.fstat(temp_fd)
|
|
859
944
|
os.close(temp_fd)
|
|
860
945
|
temp_fd = None
|
|
861
|
-
|
|
946
|
+
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
862
947
|
temp_name = None
|
|
863
948
|
os.fsync(parent_fd)
|
|
864
|
-
result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
|
|
865
949
|
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
866
950
|
finally:
|
|
867
951
|
os.close(source_fd)
|
|
@@ -878,6 +962,7 @@ def copy_path(root_fd, payload):
|
|
|
878
962
|
def run_operation(operation, root_path, payload):
|
|
879
963
|
root_fd = open_dir(root_path)
|
|
880
964
|
try:
|
|
965
|
+
assert_expected_root(root_fd, payload)
|
|
881
966
|
if operation == "stat":
|
|
882
967
|
return stat_path(root_fd, payload)
|
|
883
968
|
if operation == "readdir":
|
|
@@ -982,6 +1067,15 @@ function mapWorkerError(response) {
|
|
|
982
1067
|
if (message.includes("fs-safe-source-mismatch")) {
|
|
983
1068
|
return new errors_FsSafeError("path-mismatch", "source path changed during copy");
|
|
984
1069
|
}
|
|
1070
|
+
if (message.includes("fs-safe-temp-mismatch")) {
|
|
1071
|
+
return new errors_FsSafeError("path-mismatch", "temp path changed during write");
|
|
1072
|
+
}
|
|
1073
|
+
if (message.includes("fs-safe-root-mismatch")) {
|
|
1074
|
+
return new errors_FsSafeError("path-mismatch", "root path changed during operation");
|
|
1075
|
+
}
|
|
1076
|
+
if (message.includes("fs-safe-directory-noreplace-unsupported")) {
|
|
1077
|
+
return new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
|
|
1078
|
+
}
|
|
985
1079
|
if (code === "FileNotFoundError" || errno === 2) {
|
|
986
1080
|
return new errors_FsSafeError("not-found", "file not found");
|
|
987
1081
|
}
|
|
@@ -999,19 +1093,19 @@ function mapWorkerError(response) {
|
|
|
999
1093
|
}
|
|
1000
1094
|
return new errors_FsSafeError("helper-failed", message);
|
|
1001
1095
|
}
|
|
1002
|
-
function rejectPending(error) {
|
|
1003
|
-
if (!worker) {
|
|
1096
|
+
function rejectPending(error, targetWorker = worker) {
|
|
1097
|
+
if (!targetWorker || worker !== targetWorker) {
|
|
1004
1098
|
return;
|
|
1005
1099
|
}
|
|
1006
|
-
setWorkerRef(
|
|
1007
|
-
for (const pending of
|
|
1100
|
+
setWorkerRef(targetWorker, false);
|
|
1101
|
+
for (const pending of targetWorker.pending.values()) {
|
|
1008
1102
|
pending.reject(error);
|
|
1009
1103
|
}
|
|
1010
|
-
|
|
1104
|
+
targetWorker.pending.clear();
|
|
1011
1105
|
worker = null;
|
|
1012
1106
|
}
|
|
1013
|
-
function handleWorkerLine(line) {
|
|
1014
|
-
if (
|
|
1107
|
+
function handleWorkerLine(currentWorker, line) {
|
|
1108
|
+
if (worker !== currentWorker || !line.trim()) {
|
|
1015
1109
|
return;
|
|
1016
1110
|
}
|
|
1017
1111
|
let decoded;
|
|
@@ -1019,11 +1113,11 @@ function handleWorkerLine(line) {
|
|
|
1019
1113
|
decoded = JSON.parse(line);
|
|
1020
1114
|
}
|
|
1021
1115
|
catch {
|
|
1022
|
-
rejectPending(new errors_FsSafeError("helper-failed", `pinned helper returned invalid JSON: ${line}`));
|
|
1116
|
+
rejectPending(new errors_FsSafeError("helper-failed", `pinned helper returned invalid JSON: ${line}`), currentWorker);
|
|
1023
1117
|
return;
|
|
1024
1118
|
}
|
|
1025
1119
|
if (typeof decoded !== "object" || decoded === null || !("id" in decoded)) {
|
|
1026
|
-
rejectPending(new errors_FsSafeError("helper-failed", "pinned helper returned invalid response"));
|
|
1120
|
+
rejectPending(new errors_FsSafeError("helper-failed", "pinned helper returned invalid response"), currentWorker);
|
|
1027
1121
|
return;
|
|
1028
1122
|
}
|
|
1029
1123
|
const response = decoded;
|
|
@@ -1031,13 +1125,13 @@ function handleWorkerLine(line) {
|
|
|
1031
1125
|
if (id === undefined) {
|
|
1032
1126
|
return;
|
|
1033
1127
|
}
|
|
1034
|
-
const pending =
|
|
1128
|
+
const pending = currentWorker.pending.get(id);
|
|
1035
1129
|
if (!pending) {
|
|
1036
1130
|
return;
|
|
1037
1131
|
}
|
|
1038
|
-
|
|
1039
|
-
if (
|
|
1040
|
-
setWorkerRef(
|
|
1132
|
+
currentWorker.pending.delete(id);
|
|
1133
|
+
if (currentWorker.pending.size === 0) {
|
|
1134
|
+
setWorkerRef(currentWorker, false);
|
|
1041
1135
|
}
|
|
1042
1136
|
if (response.ok === true) {
|
|
1043
1137
|
pending.resolve(response.result);
|
|
@@ -1053,28 +1147,28 @@ function getWorker() {
|
|
|
1053
1147
|
const child = (0,external_node_child_process_namespaceObject.spawn)(resolvePython(), ["-u", "-c", PINNED_PYTHON_WORKER_SOURCE], {
|
|
1054
1148
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1055
1149
|
});
|
|
1056
|
-
|
|
1150
|
+
const currentWorker = { child, pending: new Map(), stderr: "", stdoutBuffer: "" };
|
|
1151
|
+
worker = currentWorker;
|
|
1057
1152
|
child.stdout.setEncoding("utf8");
|
|
1058
1153
|
child.stderr.setEncoding("utf8");
|
|
1059
1154
|
child.stdout.on("data", (chunk) => {
|
|
1060
|
-
|
|
1061
|
-
if (!current) {
|
|
1155
|
+
if (worker !== currentWorker) {
|
|
1062
1156
|
return;
|
|
1063
1157
|
}
|
|
1064
|
-
|
|
1158
|
+
currentWorker.stdoutBuffer += chunk;
|
|
1065
1159
|
for (;;) {
|
|
1066
|
-
const newline =
|
|
1160
|
+
const newline = currentWorker.stdoutBuffer.indexOf("\n");
|
|
1067
1161
|
if (newline < 0) {
|
|
1068
1162
|
break;
|
|
1069
1163
|
}
|
|
1070
|
-
const line =
|
|
1071
|
-
|
|
1072
|
-
handleWorkerLine(line);
|
|
1164
|
+
const line = currentWorker.stdoutBuffer.slice(0, newline);
|
|
1165
|
+
currentWorker.stdoutBuffer = currentWorker.stdoutBuffer.slice(newline + 1);
|
|
1166
|
+
handleWorkerLine(currentWorker, line);
|
|
1073
1167
|
}
|
|
1074
1168
|
});
|
|
1075
1169
|
child.stderr.on("data", (chunk) => {
|
|
1076
|
-
if (worker) {
|
|
1077
|
-
|
|
1170
|
+
if (worker === currentWorker) {
|
|
1171
|
+
currentWorker.stderr = `${currentWorker.stderr}${chunk}`.slice(-4096);
|
|
1078
1172
|
}
|
|
1079
1173
|
});
|
|
1080
1174
|
child.once("error", (error) => {
|
|
@@ -1083,17 +1177,17 @@ function getWorker() {
|
|
|
1083
1177
|
: error instanceof Error
|
|
1084
1178
|
? error
|
|
1085
1179
|
: new Error(String(error));
|
|
1086
|
-
rejectPending(mapped);
|
|
1180
|
+
rejectPending(mapped, currentWorker);
|
|
1087
1181
|
});
|
|
1088
1182
|
child.once("close", (code, signal) => {
|
|
1089
|
-
const stderr =
|
|
1090
|
-
rejectPending(new errors_FsSafeError("helper-failed", stderr || `pinned helper exited with code ${code ?? "null"} (${signal ?? "?"})`));
|
|
1183
|
+
const stderr = currentWorker.stderr.trim();
|
|
1184
|
+
rejectPending(new errors_FsSafeError("helper-failed", stderr || `pinned helper exited with code ${code ?? "null"} (${signal ?? "?"})`), currentWorker);
|
|
1091
1185
|
});
|
|
1092
1186
|
process.once("exit", () => {
|
|
1093
1187
|
child.kill("SIGTERM");
|
|
1094
1188
|
});
|
|
1095
|
-
setWorkerRef(
|
|
1096
|
-
return
|
|
1189
|
+
setWorkerRef(currentWorker, false);
|
|
1190
|
+
return currentWorker;
|
|
1097
1191
|
}
|
|
1098
1192
|
function setRefable(value, ref) {
|
|
1099
1193
|
if (!value) {
|
|
@@ -1159,9 +1253,7 @@ function validatePinnedOperationPayload(payload) {
|
|
|
1159
1253
|
}
|
|
1160
1254
|
}
|
|
1161
1255
|
function isPinnedHelperUnavailable(error) {
|
|
1162
|
-
return error instanceof Error &&
|
|
1163
|
-
"code" in error &&
|
|
1164
|
-
error.code === "helper-unavailable";
|
|
1256
|
+
return error instanceof Error && "code" in error && error.code === "helper-unavailable";
|
|
1165
1257
|
}
|
|
1166
1258
|
function validatePinnedRelativePath(relativePath) {
|
|
1167
1259
|
if (relativePath.length === 0 || relativePath === ".") {
|
|
@@ -1259,29 +1351,21 @@ function assertWithinMaxBytes(bytes, maxBytes) {
|
|
|
1259
1351
|
throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
1260
1352
|
}
|
|
1261
1353
|
}
|
|
1262
|
-
function
|
|
1263
|
-
if (maxBytes === undefined) {
|
|
1264
|
-
return undefined;
|
|
1265
|
-
}
|
|
1354
|
+
async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
1266
1355
|
let bytes = 0;
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1356
|
+
for await (const chunk of stream) {
|
|
1357
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1358
|
+
bytes += buffer.byteLength;
|
|
1359
|
+
assertWithinMaxBytes(bytes, maxBytes);
|
|
1360
|
+
let offset = 0;
|
|
1361
|
+
while (offset < buffer.byteLength) {
|
|
1362
|
+
const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
|
|
1363
|
+
if (bytesWritten <= 0) {
|
|
1364
|
+
throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
|
|
1273
1365
|
}
|
|
1274
|
-
|
|
1275
|
-
}
|
|
1276
|
-
});
|
|
1277
|
-
}
|
|
1278
|
-
async function pipelineWithMaxBytes(stream, destination, maxBytes) {
|
|
1279
|
-
const limiter = pinned_write_createMaxBytesTransform(maxBytes);
|
|
1280
|
-
if (limiter) {
|
|
1281
|
-
await (0,external_node_stream_promises_namespaceObject.pipeline)(stream, limiter, destination);
|
|
1282
|
-
return;
|
|
1366
|
+
offset += bytesWritten;
|
|
1367
|
+
}
|
|
1283
1368
|
}
|
|
1284
|
-
await (0,external_node_stream_promises_namespaceObject.pipeline)(stream, destination);
|
|
1285
1369
|
}
|
|
1286
1370
|
async function inputToBase64(input, maxBytes) {
|
|
1287
1371
|
if (input.kind === "buffer") {
|
|
@@ -1306,7 +1390,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
1306
1390
|
relativeParentPath: params.relativeParentPath,
|
|
1307
1391
|
});
|
|
1308
1392
|
if (getFsSafePythonConfig().mode === "off") {
|
|
1309
|
-
return await
|
|
1393
|
+
return await runPinnedWriteFallbackOrThrow(params);
|
|
1310
1394
|
}
|
|
1311
1395
|
if (params.input.kind === "stream") {
|
|
1312
1396
|
try {
|
|
@@ -1314,7 +1398,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
1314
1398
|
}
|
|
1315
1399
|
catch (error) {
|
|
1316
1400
|
if (canFallbackFromPythonError(error)) {
|
|
1317
|
-
return await
|
|
1401
|
+
return await runPinnedWriteFallbackOrThrow(params, error);
|
|
1318
1402
|
}
|
|
1319
1403
|
throw error;
|
|
1320
1404
|
}
|
|
@@ -1327,6 +1411,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
1327
1411
|
mode: params.mode || 0o600,
|
|
1328
1412
|
overwrite: params.overwrite !== false,
|
|
1329
1413
|
relativeParentPath: params.relativeParentPath,
|
|
1414
|
+
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
1330
1415
|
};
|
|
1331
1416
|
try {
|
|
1332
1417
|
return await runPinnedPythonOperation({
|
|
@@ -1337,7 +1422,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
1337
1422
|
}
|
|
1338
1423
|
catch (error) {
|
|
1339
1424
|
if (canFallbackFromPythonError(error)) {
|
|
1340
|
-
return await
|
|
1425
|
+
return await runPinnedWriteFallbackOrThrow(params, error);
|
|
1341
1426
|
}
|
|
1342
1427
|
throw error;
|
|
1343
1428
|
}
|
|
@@ -1357,22 +1442,29 @@ async function runPinnedCopyHelper(params) {
|
|
|
1357
1442
|
mode: params.mode || 0o600,
|
|
1358
1443
|
overwrite: params.overwrite !== false,
|
|
1359
1444
|
relativeParentPath: params.relativeParentPath,
|
|
1445
|
+
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
1360
1446
|
sourceDev: params.sourceIdentity.dev,
|
|
1361
1447
|
sourceIno: params.sourceIdentity.ino,
|
|
1362
1448
|
sourcePath: params.sourcePath,
|
|
1363
1449
|
},
|
|
1364
1450
|
});
|
|
1365
1451
|
}
|
|
1452
|
+
async function runPinnedWriteFallbackOrThrow(params, cause) {
|
|
1453
|
+
if (process.platform !== "win32") {
|
|
1454
|
+
throw new errors_FsSafeError("helper-unavailable", "Python helper is required for pinned writes on this platform", { cause });
|
|
1455
|
+
}
|
|
1456
|
+
return await runPinnedWriteFallback(params);
|
|
1457
|
+
}
|
|
1366
1458
|
async function runPinnedWriteFallback(params) {
|
|
1367
1459
|
const parentPath = params.relativeParentPath
|
|
1368
1460
|
? external_node_path_namespaceObject.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
1369
1461
|
: params.rootPath;
|
|
1370
|
-
const parentGuard = await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
|
|
1371
1462
|
if (params.mkdir) {
|
|
1372
|
-
await
|
|
1373
|
-
await promises_namespaceObject.mkdir(parentPath, { recursive: true });
|
|
1374
|
-
});
|
|
1463
|
+
await mkdirPathComponentsWithGuards({ rootReal: params.rootPath, targetPath: parentPath });
|
|
1375
1464
|
}
|
|
1465
|
+
const parentGuard = params.mkdir
|
|
1466
|
+
? await directory_guard_createAsyncDirectoryGuard(parentPath)
|
|
1467
|
+
: await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
|
|
1376
1468
|
const targetPath = external_node_path_namespaceObject.join(parentPath, params.basename);
|
|
1377
1469
|
if (params.overwrite === false) {
|
|
1378
1470
|
let handle = await withAsyncDirectoryGuards([parentGuard], async () => await promises_namespaceObject.open(targetPath, external_node_fs_namespaceObject.constants.O_WRONLY | external_node_fs_namespaceObject.constants.O_CREAT | external_node_fs_namespaceObject.constants.O_EXCL, params.mode), {
|
|
@@ -1394,7 +1486,7 @@ async function runPinnedWriteFallback(params) {
|
|
|
1394
1486
|
}
|
|
1395
1487
|
}
|
|
1396
1488
|
else {
|
|
1397
|
-
await
|
|
1489
|
+
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
1398
1490
|
}
|
|
1399
1491
|
const stat = await handle.stat();
|
|
1400
1492
|
created = false;
|
|
@@ -1415,7 +1507,9 @@ async function runPinnedWriteFallback(params) {
|
|
|
1415
1507
|
? external_node_fs_namespaceObject.constants.O_NOFOLLOW
|
|
1416
1508
|
: 0);
|
|
1417
1509
|
let handle;
|
|
1418
|
-
let
|
|
1510
|
+
let tempStat;
|
|
1511
|
+
let targetStat;
|
|
1512
|
+
let renamed = false;
|
|
1419
1513
|
try {
|
|
1420
1514
|
handle = await promises_namespaceObject.open(tempPath, tempFlags, params.mode);
|
|
1421
1515
|
if (params.input.kind === "buffer") {
|
|
@@ -1428,29 +1522,36 @@ async function runPinnedWriteFallback(params) {
|
|
|
1428
1522
|
}
|
|
1429
1523
|
}
|
|
1430
1524
|
else {
|
|
1431
|
-
|
|
1432
|
-
writable.once("close", () => {
|
|
1433
|
-
handleClosedByStream = true;
|
|
1434
|
-
});
|
|
1435
|
-
await pipelineWithMaxBytes(params.input.stream, writable, params.maxBytes);
|
|
1525
|
+
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
1436
1526
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1527
|
+
tempStat = await handle.stat();
|
|
1528
|
+
const tempPathStat = await promises_namespaceObject.lstat(tempPath);
|
|
1529
|
+
if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
|
|
1530
|
+
throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
|
|
1440
1531
|
}
|
|
1532
|
+
const expectedTempStat = tempStat;
|
|
1533
|
+
await handle.close().catch(() => undefined);
|
|
1534
|
+
handle = undefined;
|
|
1441
1535
|
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
1442
1536
|
await promises_namespaceObject.rename(tempPath, targetPath);
|
|
1537
|
+
renamed = true;
|
|
1538
|
+
targetStat = await promises_namespaceObject.lstat(targetPath);
|
|
1539
|
+
if (targetStat.isSymbolicLink() || !file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
|
|
1540
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
1541
|
+
}
|
|
1443
1542
|
});
|
|
1444
1543
|
}
|
|
1445
1544
|
catch (error) {
|
|
1446
|
-
|
|
1447
|
-
|
|
1545
|
+
await handle?.close().catch(() => undefined);
|
|
1546
|
+
if (!renamed) {
|
|
1547
|
+
await promises_namespaceObject.rm(tempPath, { force: true }).catch(() => undefined);
|
|
1448
1548
|
}
|
|
1449
|
-
await promises_namespaceObject.rm(tempPath, { force: true }).catch(() => undefined);
|
|
1450
1549
|
throw error;
|
|
1451
1550
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1551
|
+
if (!targetStat) {
|
|
1552
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target was not verified");
|
|
1553
|
+
}
|
|
1554
|
+
return { dev: targetStat.dev, ino: targetStat.ino };
|
|
1454
1555
|
}
|
|
1455
1556
|
|
|
1456
1557
|
;// CONCATENATED MODULE: external "node:os"
|
|
@@ -1989,7 +2090,7 @@ function relativeInsideRoot(rootPath, targetPath) {
|
|
|
1989
2090
|
if (!relative || relative === ".") {
|
|
1990
2091
|
return "";
|
|
1991
2092
|
}
|
|
1992
|
-
if (
|
|
2093
|
+
if (isPathRelativeEscape(relative)) {
|
|
1993
2094
|
return "";
|
|
1994
2095
|
}
|
|
1995
2096
|
return relative;
|
|
@@ -2340,6 +2441,15 @@ function __setFsSafeTestHooksForTest(hooks) {
|
|
|
2340
2441
|
test_hooks_fsSafeTestHooks = hooks;
|
|
2341
2442
|
}
|
|
2342
2443
|
|
|
2444
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/json-stringify.js
|
|
2445
|
+
function stringifyJsonDocument(value, replacer, space) {
|
|
2446
|
+
const text = JSON.stringify(value, replacer, space);
|
|
2447
|
+
if (typeof text !== "string") {
|
|
2448
|
+
throw new TypeError("value is not representable as a JSON document");
|
|
2449
|
+
}
|
|
2450
|
+
return text;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2343
2453
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/temp-cleanup.js
|
|
2344
2454
|
|
|
2345
2455
|
const tempCleanupEntries = new Map();
|
|
@@ -2427,6 +2537,7 @@ async function serializePathWrite(key, run) {
|
|
|
2427
2537
|
|
|
2428
2538
|
|
|
2429
2539
|
|
|
2540
|
+
|
|
2430
2541
|
|
|
2431
2542
|
|
|
2432
2543
|
function logWarn(message) {
|
|
@@ -2667,12 +2778,12 @@ class RootHandle {
|
|
|
2667
2778
|
}
|
|
2668
2779
|
async writeJson(relativePath, data, options = {}) {
|
|
2669
2780
|
const { replacer, space, trailingNewline = true, ...writeOptions } = options;
|
|
2670
|
-
const json =
|
|
2781
|
+
const json = stringifyJsonDocument(data, replacer, space);
|
|
2671
2782
|
await this.write(relativePath, trailingNewline ? `${json}\n` : json, writeOptions);
|
|
2672
2783
|
}
|
|
2673
2784
|
async createJson(relativePath, data, options = {}) {
|
|
2674
2785
|
const { replacer, space, trailingNewline = true, ...writeOptions } = options;
|
|
2675
|
-
const json =
|
|
2786
|
+
const json = stringifyJsonDocument(data, replacer, space);
|
|
2676
2787
|
await this.create(relativePath, trailingNewline ? `${json}\n` : json, writeOptions);
|
|
2677
2788
|
}
|
|
2678
2789
|
async copyIn(relativePath, sourcePath, options = {}) {
|
|
@@ -3457,6 +3568,9 @@ async function movePathFallback(root, params) {
|
|
|
3457
3568
|
if (sourceStat.isFile() && sourceStat.nlink > 1) {
|
|
3458
3569
|
throw new errors_FsSafeError("hardlink", "hardlinked path not allowed");
|
|
3459
3570
|
}
|
|
3571
|
+
if (!params.overwrite && sourceStat.isDirectory()) {
|
|
3572
|
+
throw new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
|
|
3573
|
+
}
|
|
3460
3574
|
if (!params.overwrite) {
|
|
3461
3575
|
try {
|
|
3462
3576
|
await promises_namespaceObject.lstat(target.resolved);
|
|
@@ -14782,4 +14896,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
|
|
|
14782
14896
|
// module factories are used so entry inlining is disabled
|
|
14783
14897
|
// startup
|
|
14784
14898
|
// Load entry module and return exports
|
|
14785
|
-
var __webpack_exports__ = __webpack_require__(
|
|
14899
|
+
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.
|
|
3
|
+
"version": "5.14.10",
|
|
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.
|
|
39
|
+
"@openclaw/fs-safe": "0.2.6",
|
|
40
40
|
"zod": "4.4.3"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|