langchain-agentx-python 2.4.5__py3-none-any.whl → 2.4.7__py3-none-any.whl
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.
- langchain_agentx/__init__.py +1 -1
- langchain_agentx/loop/subagent/context.py +10 -5
- langchain_agentx/tool_runtime/session_store.py +11 -3
- langchain_agentx/tools/bash/backend.py +237 -119
- langchain_agentx/tools/bash/bash_exec_assembly.py +146 -0
- langchain_agentx/tools/bash/shell_quoting.py +24 -3
- langchain_agentx/tools/bash/shell_snapshot.py +205 -0
- langchain_agentx/tools/bash/tool.py +21 -1
- {langchain_agentx_python-2.4.5.dist-info → langchain_agentx_python-2.4.7.dist-info}/METADATA +1 -1
- {langchain_agentx_python-2.4.5.dist-info → langchain_agentx_python-2.4.7.dist-info}/RECORD +13 -11
- {langchain_agentx_python-2.4.5.dist-info → langchain_agentx_python-2.4.7.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-2.4.5.dist-info → langchain_agentx_python-2.4.7.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-2.4.5.dist-info → langchain_agentx_python-2.4.7.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -19,7 +19,8 @@ Fork 路径区分(对齐 CC):
|
|
|
19
19
|
``path_prep`` 与遗留 ``cwd``/worktree 自建二选一(编排层须传 ``path_prep``);
|
|
20
20
|
fork + worktree 时可选注入 CC 等价 ``buildWorktreeNotice`` user 消息(见 ``fork_worktree_notice``);
|
|
21
21
|
create_subagent_context 在 fork_from_parent=True 时注入 fork-boilerplate 行为规范;
|
|
22
|
-
P0-1:
|
|
22
|
+
P0-1: 子代理隔离 session_store(clone + inherit_grants);读缓存仅在
|
|
23
|
+
fork_from_parent=True 时继承(对齐 CC forkContextMessages ⟺ cloneFileStateCache)。
|
|
23
24
|
"""
|
|
24
25
|
|
|
25
26
|
import os
|
|
@@ -287,11 +288,15 @@ def create_subagent_context(
|
|
|
287
288
|
|
|
288
289
|
parent_store = getattr(parent_ctx, "session_store", None)
|
|
289
290
|
session_store: Any = parent_store
|
|
290
|
-
# P0-1
|
|
291
|
-
#
|
|
292
|
-
#
|
|
291
|
+
# P0-1:子代理隔离 session_store(cwd / 权限缓存不共享);grant 快照可继承。
|
|
292
|
+
# 读缓存绑定消息历史(对齐 CC runAgent.ts):仅 fork_from_parent=True 时
|
|
293
|
+
# clone _file_read_state;plain / Skill fork 空缓存,避免 file_unchanged 误判。
|
|
293
294
|
if isinstance(parent_store, AgentSessionStore):
|
|
294
|
-
session_store = parent_store.clone(
|
|
295
|
+
session_store = parent_store.clone(
|
|
296
|
+
file_state_only=True,
|
|
297
|
+
inherit_grants=True,
|
|
298
|
+
inherit_file_reads=fork_from_parent,
|
|
299
|
+
)
|
|
295
300
|
|
|
296
301
|
# 写入 cwd 到子会话 session_store.cwd_override(runtime state SSOT)
|
|
297
302
|
# 这确保后续 LangChainAdapter.build_tool_execution_context() 和
|
|
@@ -390,11 +390,19 @@ class AgentSessionStore:
|
|
|
390
390
|
*,
|
|
391
391
|
file_state_only: bool = True,
|
|
392
392
|
inherit_grants: bool = False,
|
|
393
|
+
inherit_file_reads: bool = True,
|
|
393
394
|
) -> AgentSessionStore:
|
|
395
|
+
"""克隆会话存储。
|
|
396
|
+
|
|
397
|
+
``inherit_file_reads`` 对齐 CC ``forkContextMessages !== undefined``
|
|
398
|
+
→ ``cloneFileStateCache``;plain / Skill fork 须传 ``False``(空读缓存),
|
|
399
|
+
避免 Read dedup 返回 ``file_unchanged`` 而子会话无父消息历史。
|
|
400
|
+
"""
|
|
394
401
|
new = AgentSessionStore(session_id=str(uuid.uuid4()))
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
402
|
+
if inherit_file_reads:
|
|
403
|
+
new._file_read_state = {
|
|
404
|
+
k: copy.deepcopy(v) for k, v in self._file_read_state.items()
|
|
405
|
+
}
|
|
398
406
|
if inherit_grants:
|
|
399
407
|
new._granted_roots = copy.deepcopy(self._granted_roots)
|
|
400
408
|
new.enable_dynamic_root_grant = self.enable_dynamic_root_grant
|
|
@@ -42,6 +42,7 @@ from dataclasses import dataclass, field
|
|
|
42
42
|
from pathlib import Path
|
|
43
43
|
from typing import Any, Protocol
|
|
44
44
|
|
|
45
|
+
from .bash_exec_assembly import AssembledShellCommand, BashExecAssembler
|
|
45
46
|
from .cwd_reporter import (
|
|
46
47
|
BashCwdReporter,
|
|
47
48
|
bash_single_quoted,
|
|
@@ -52,8 +53,6 @@ from .cwd_reporter import (
|
|
|
52
53
|
from .sandbox_decision import BashSandboxDecision
|
|
53
54
|
from .shell_locator import resolve_posix_shell
|
|
54
55
|
from .task_runtime import BashTaskSnapshot, BashTaskStateMachine
|
|
55
|
-
from .shell_quoting import append_stdin_redirect
|
|
56
|
-
from .windows_shell_quoting import rewrite_windows_null_redirect
|
|
57
56
|
|
|
58
57
|
# cwd 提取用的特殊 sentinel(不太可能出现在正常输出中)
|
|
59
58
|
_CWD_SENTINEL = "__BASH_CWD_SENTINEL_63f8a2__"
|
|
@@ -159,14 +158,16 @@ def _cwd_invalid_execute_result(cwd: str | None, exc: BaseException) -> ExecuteR
|
|
|
159
158
|
)
|
|
160
159
|
|
|
161
160
|
|
|
162
|
-
def
|
|
163
|
-
result: ExecuteResult,
|
|
161
|
+
def _copy_execute_result(
|
|
162
|
+
result: ExecuteResult,
|
|
163
|
+
*,
|
|
164
|
+
stderr: str | None = None,
|
|
165
|
+
cwd_warning: str | None = None,
|
|
164
166
|
) -> ExecuteResult:
|
|
165
|
-
|
|
166
|
-
return result
|
|
167
|
+
"""复制 ExecuteResult,保留 D2/D4 装配观测字段。"""
|
|
167
168
|
return ExecuteResult(
|
|
168
169
|
stdout=result.stdout,
|
|
169
|
-
stderr=
|
|
170
|
+
stderr=result.stderr if stderr is None else stderr,
|
|
170
171
|
exit_code=result.exit_code,
|
|
171
172
|
interrupted=result.interrupted,
|
|
172
173
|
cwd_after=result.cwd_after,
|
|
@@ -174,7 +175,22 @@ def _merge_reset_into_result(
|
|
|
174
175
|
sandbox_bypass_reason=result.sandbox_bypass_reason,
|
|
175
176
|
sandbox_temp_dir=result.sandbox_temp_dir,
|
|
176
177
|
sandbox_violation_message=result.sandbox_violation_message,
|
|
177
|
-
cwd_warning=result.cwd_warning,
|
|
178
|
+
cwd_warning=result.cwd_warning if cwd_warning is None else cwd_warning,
|
|
179
|
+
command_before=result.command_before,
|
|
180
|
+
command_after=result.command_after,
|
|
181
|
+
shell_snapshot_path=result.shell_snapshot_path,
|
|
182
|
+
shell_login_flag=result.shell_login_flag,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _merge_reset_into_result(
|
|
187
|
+
result: ExecuteResult, reset_stderr_prefix: str
|
|
188
|
+
) -> ExecuteResult:
|
|
189
|
+
if not reset_stderr_prefix:
|
|
190
|
+
return result
|
|
191
|
+
return _copy_execute_result(
|
|
192
|
+
result,
|
|
193
|
+
stderr=reset_stderr_prefix + (result.stderr or ""),
|
|
178
194
|
)
|
|
179
195
|
|
|
180
196
|
|
|
@@ -187,16 +203,9 @@ def _post_process_execute_result(result: ExecuteResult) -> ExecuteResult:
|
|
|
187
203
|
cleaned_stderr, cwd_warning = extract_cwd_warning(result.stderr)
|
|
188
204
|
# 替换 stderr 为清理后的版本(UI 层看到干净的 stderr)
|
|
189
205
|
# 同时设置 cwd_warning 字段(UI 层可直接使用)
|
|
190
|
-
return
|
|
191
|
-
|
|
206
|
+
return _copy_execute_result(
|
|
207
|
+
result,
|
|
192
208
|
stderr=cleaned_stderr,
|
|
193
|
-
exit_code=result.exit_code,
|
|
194
|
-
interrupted=result.interrupted,
|
|
195
|
-
cwd_after=result.cwd_after,
|
|
196
|
-
sandboxed=result.sandboxed,
|
|
197
|
-
sandbox_bypass_reason=result.sandbox_bypass_reason,
|
|
198
|
-
sandbox_temp_dir=result.sandbox_temp_dir,
|
|
199
|
-
sandbox_violation_message=result.sandbox_violation_message,
|
|
200
209
|
cwd_warning=cwd_warning,
|
|
201
210
|
)
|
|
202
211
|
|
|
@@ -235,6 +244,18 @@ class ExecuteResult:
|
|
|
235
244
|
cwd_warning: str | None = None
|
|
236
245
|
"""从 stderr 中提取的 cwd 重置警告信息(结构化)。"""
|
|
237
246
|
|
|
247
|
+
command_before: str | None = None
|
|
248
|
+
"""进入 preprocess 前的原始命令(D4 可观测)。"""
|
|
249
|
+
|
|
250
|
+
command_after: str | None = None
|
|
251
|
+
"""preprocess + snapshot/extglob 装配后的命令串(cwd wrap 前)。"""
|
|
252
|
+
|
|
253
|
+
shell_snapshot_path: str | None = None
|
|
254
|
+
"""本次使用的 shell 快照路径;None 表示回退 ``-l``。"""
|
|
255
|
+
|
|
256
|
+
shell_login_flag: bool = False
|
|
257
|
+
"""spawn 是否带 ``-l``(无可用快照时为 True,对齐 CC getSpawnArgs)。"""
|
|
258
|
+
|
|
238
259
|
|
|
239
260
|
@dataclass
|
|
240
261
|
class BackgroundTask:
|
|
@@ -581,6 +602,10 @@ class BashBackend:
|
|
|
581
602
|
sandbox_backend: BashSandboxBackendProtocol | None = None,
|
|
582
603
|
sandbox_runtime_config: BashSandboxRuntimeConfig | None = None,
|
|
583
604
|
posix_shell_executable: str | None = None,
|
|
605
|
+
*,
|
|
606
|
+
enable_shell_snapshot: bool = True,
|
|
607
|
+
shell_snapshot_path: str | None = None,
|
|
608
|
+
exec_assembler: BashExecAssembler | None = None,
|
|
584
609
|
**_: Any,
|
|
585
610
|
) -> None:
|
|
586
611
|
if sandbox_backend is not None:
|
|
@@ -592,6 +617,10 @@ class BashBackend:
|
|
|
592
617
|
config=sandbox_runtime_config,
|
|
593
618
|
)
|
|
594
619
|
self._posix_shell_override = posix_shell_executable
|
|
620
|
+
self._exec_assembler = exec_assembler or BashExecAssembler(
|
|
621
|
+
enable_snapshot=enable_shell_snapshot,
|
|
622
|
+
injected_snapshot_path=shell_snapshot_path,
|
|
623
|
+
)
|
|
595
624
|
|
|
596
625
|
def _posix_shell_executable(self, env: dict[str, str]) -> str:
|
|
597
626
|
"""解析 argv[0];测试可注入 `posix_shell_executable` 跳过 PATH 探测。"""
|
|
@@ -603,10 +632,27 @@ class BashBackend:
|
|
|
603
632
|
merged.update(env)
|
|
604
633
|
return resolve_posix_shell(environ=merged)
|
|
605
634
|
|
|
635
|
+
def _assemble_shell_command(
|
|
636
|
+
self, command: str, *, shell_exe: str
|
|
637
|
+
) -> AssembledShellCommand:
|
|
638
|
+
"""D2/D4:preprocess + snapshot/extglob 装配(对齐 CC bashProvider)。"""
|
|
639
|
+
return self._exec_assembler.assemble(command, shell_exe=shell_exe)
|
|
640
|
+
|
|
606
641
|
def _preprocess_shell_command(self, command: str) -> str:
|
|
607
|
-
"""
|
|
608
|
-
|
|
609
|
-
|
|
642
|
+
"""兼容旧调用:仅返回 preprocess 后文本(不含 snapshot 链)。"""
|
|
643
|
+
from .bash_exec_assembly import preprocess_shell_command
|
|
644
|
+
|
|
645
|
+
return preprocess_shell_command(command).after
|
|
646
|
+
|
|
647
|
+
@staticmethod
|
|
648
|
+
def _attach_assembly_meta(
|
|
649
|
+
result: ExecuteResult, assembled: AssembledShellCommand
|
|
650
|
+
) -> ExecuteResult:
|
|
651
|
+
result.command_before = assembled.preprocess.before
|
|
652
|
+
result.command_after = assembled.command_string
|
|
653
|
+
result.shell_snapshot_path = assembled.snapshot_path
|
|
654
|
+
result.shell_login_flag = assembled.use_login_shell
|
|
655
|
+
return result
|
|
610
656
|
|
|
611
657
|
@staticmethod
|
|
612
658
|
def _spawn_env_with_shell(env: dict[str, str], shell_exe: str) -> dict[str, str]:
|
|
@@ -727,7 +773,8 @@ class BashBackend:
|
|
|
727
773
|
import subprocess as _sp
|
|
728
774
|
|
|
729
775
|
shell_exe = self._posix_shell_executable(env)
|
|
730
|
-
|
|
776
|
+
assembled = self._assemble_shell_command(command, shell_exe=shell_exe)
|
|
777
|
+
command = assembled.command_string
|
|
731
778
|
effective_cwd, reset_prefix = _prepare_spawn_cwd(cwd, fallback=fallback_cwd)
|
|
732
779
|
if effective_cwd is None:
|
|
733
780
|
msg = reset_prefix.strip() or "Shell cwd invalid and no valid fallback"
|
|
@@ -737,6 +784,10 @@ class BashBackend:
|
|
|
737
784
|
interrupted=False,
|
|
738
785
|
stderr=msg + "\n",
|
|
739
786
|
cwd_warning=msg,
|
|
787
|
+
command_before=assembled.preprocess.before,
|
|
788
|
+
command_after=assembled.command_string,
|
|
789
|
+
shell_snapshot_path=assembled.snapshot_path,
|
|
790
|
+
shell_login_flag=assembled.use_login_shell,
|
|
740
791
|
)
|
|
741
792
|
read_fd = -1
|
|
742
793
|
write_fd = -1
|
|
@@ -748,14 +799,16 @@ class BashBackend:
|
|
|
748
799
|
timeout_sec=timeout_sec,
|
|
749
800
|
env=env,
|
|
750
801
|
shell_exe=shell_exe,
|
|
802
|
+
assembled=assembled,
|
|
751
803
|
)
|
|
752
804
|
return _merge_reset_into_result(result, reset_prefix)
|
|
753
805
|
|
|
754
806
|
read_fd, write_fd = os.pipe()
|
|
755
807
|
wrapped = BashCwdReporter.wrap_with_fd3(command, write_fd)
|
|
808
|
+
argv = assembled.spawn_argv(shell_exe, wrapped)
|
|
756
809
|
|
|
757
810
|
proc = _sp.Popen(
|
|
758
|
-
|
|
811
|
+
argv,
|
|
759
812
|
stdin=_sp.DEVNULL,
|
|
760
813
|
stdout=_sp.PIPE,
|
|
761
814
|
stderr=_sp.PIPE,
|
|
@@ -775,15 +828,18 @@ class BashBackend:
|
|
|
775
828
|
os.close(read_fd)
|
|
776
829
|
partial = stdout_bytes.decode("utf-8", errors="replace")
|
|
777
830
|
partial_stderr = stderr_bytes.decode("utf-8", errors="replace")
|
|
778
|
-
return
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
831
|
+
return self._attach_assembly_meta(
|
|
832
|
+
_merge_reset_into_result(
|
|
833
|
+
ExecuteResult(
|
|
834
|
+
stdout=partial,
|
|
835
|
+
stderr=partial_stderr,
|
|
836
|
+
exit_code=-1,
|
|
837
|
+
interrupted=True,
|
|
838
|
+
cwd_after=cwd_after,
|
|
839
|
+
),
|
|
840
|
+
reset_prefix,
|
|
785
841
|
),
|
|
786
|
-
|
|
842
|
+
assembled,
|
|
787
843
|
)
|
|
788
844
|
|
|
789
845
|
cwd_after = _read_fd_safe(read_fd)
|
|
@@ -792,21 +848,27 @@ class BashBackend:
|
|
|
792
848
|
|
|
793
849
|
stdout_str = stdout_bytes.decode("utf-8", errors="replace")
|
|
794
850
|
stderr_str = stderr_bytes.decode("utf-8", errors="replace")
|
|
795
|
-
return
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
851
|
+
return self._attach_assembly_meta(
|
|
852
|
+
_merge_reset_into_result(
|
|
853
|
+
ExecuteResult(
|
|
854
|
+
stdout=stdout_str,
|
|
855
|
+
stderr=stderr_str,
|
|
856
|
+
exit_code=proc.returncode,
|
|
857
|
+
interrupted=False,
|
|
858
|
+
cwd_after=cwd_after,
|
|
859
|
+
),
|
|
860
|
+
reset_prefix,
|
|
802
861
|
),
|
|
803
|
-
|
|
862
|
+
assembled,
|
|
804
863
|
)
|
|
805
864
|
except FileNotFoundError:
|
|
806
|
-
return
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
865
|
+
return self._attach_assembly_meta(
|
|
866
|
+
ExecuteResult(
|
|
867
|
+
stdout="bash: command not found",
|
|
868
|
+
exit_code=127,
|
|
869
|
+
interrupted=False,
|
|
870
|
+
),
|
|
871
|
+
assembled,
|
|
810
872
|
)
|
|
811
873
|
except OSError as exc:
|
|
812
874
|
if _is_cwd_spawn_oserror(exc):
|
|
@@ -832,6 +894,7 @@ class BashBackend:
|
|
|
832
894
|
timeout_sec: int,
|
|
833
895
|
env: dict[str, str],
|
|
834
896
|
shell_exe: str,
|
|
897
|
+
assembled: AssembledShellCommand | None = None,
|
|
835
898
|
) -> ExecuteResult:
|
|
836
899
|
fd, cwd_file = tempfile.mkstemp(prefix="agentx_bash_cwd_", suffix=".txt")
|
|
837
900
|
os.close(fd)
|
|
@@ -840,11 +903,14 @@ class BashBackend:
|
|
|
840
903
|
command, bash_single_quoted(posix_tgt)
|
|
841
904
|
)
|
|
842
905
|
spawn_env = self._spawn_env_with_shell(env, shell_exe)
|
|
906
|
+
if assembled is None:
|
|
907
|
+
assembled = self._assemble_shell_command(command, shell_exe=shell_exe)
|
|
908
|
+
argv = assembled.spawn_argv(shell_exe, wrapped)
|
|
843
909
|
_cf = self._win32_creationflags()
|
|
844
910
|
try:
|
|
845
911
|
if _cf:
|
|
846
912
|
proc = subprocess.Popen(
|
|
847
|
-
|
|
913
|
+
argv,
|
|
848
914
|
stdin=subprocess.DEVNULL,
|
|
849
915
|
stdout=subprocess.PIPE,
|
|
850
916
|
stderr=subprocess.PIPE,
|
|
@@ -854,7 +920,7 @@ class BashBackend:
|
|
|
854
920
|
)
|
|
855
921
|
else:
|
|
856
922
|
proc = subprocess.Popen(
|
|
857
|
-
|
|
923
|
+
argv,
|
|
858
924
|
stdin=subprocess.DEVNULL,
|
|
859
925
|
stdout=subprocess.PIPE,
|
|
860
926
|
stderr=subprocess.PIPE,
|
|
@@ -869,34 +935,46 @@ class BashBackend:
|
|
|
869
935
|
cwd_after = read_cwd_file(cwd_file)
|
|
870
936
|
partial = stdout_bytes.decode("utf-8", errors="replace")
|
|
871
937
|
partial_stderr = stderr_bytes.decode("utf-8", errors="replace")
|
|
872
|
-
return
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
938
|
+
return self._attach_assembly_meta(
|
|
939
|
+
ExecuteResult(
|
|
940
|
+
stdout=partial,
|
|
941
|
+
stderr=partial_stderr,
|
|
942
|
+
exit_code=-1,
|
|
943
|
+
interrupted=True,
|
|
944
|
+
cwd_after=cwd_after,
|
|
945
|
+
),
|
|
946
|
+
assembled,
|
|
878
947
|
)
|
|
879
948
|
|
|
880
949
|
cwd_after = read_cwd_file(cwd_file)
|
|
881
950
|
stdout_str = stdout_bytes.decode("utf-8", errors="replace")
|
|
882
951
|
stderr_str = stderr_bytes.decode("utf-8", errors="replace")
|
|
883
|
-
return
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
952
|
+
return self._attach_assembly_meta(
|
|
953
|
+
ExecuteResult(
|
|
954
|
+
stdout=stdout_str,
|
|
955
|
+
stderr=stderr_str,
|
|
956
|
+
exit_code=proc.returncode,
|
|
957
|
+
interrupted=False,
|
|
958
|
+
cwd_after=cwd_after,
|
|
959
|
+
),
|
|
960
|
+
assembled,
|
|
889
961
|
)
|
|
890
962
|
except FileNotFoundError:
|
|
891
|
-
return
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
963
|
+
return self._attach_assembly_meta(
|
|
964
|
+
ExecuteResult(
|
|
965
|
+
stdout="bash: command not found",
|
|
966
|
+
stderr="",
|
|
967
|
+
exit_code=127,
|
|
968
|
+
interrupted=False,
|
|
969
|
+
),
|
|
970
|
+
assembled,
|
|
896
971
|
)
|
|
897
972
|
except OSError as exc:
|
|
898
973
|
if _is_cwd_spawn_oserror(exc):
|
|
899
|
-
return
|
|
974
|
+
return self._attach_assembly_meta(
|
|
975
|
+
_cwd_invalid_execute_result(cwd, exc),
|
|
976
|
+
assembled,
|
|
977
|
+
)
|
|
900
978
|
raise
|
|
901
979
|
finally:
|
|
902
980
|
try:
|
|
@@ -973,7 +1051,8 @@ class BashBackend:
|
|
|
973
1051
|
fallback_cwd: str | None = None,
|
|
974
1052
|
) -> ExecuteResult:
|
|
975
1053
|
shell_exe = self._posix_shell_executable(env)
|
|
976
|
-
|
|
1054
|
+
assembled = self._assemble_shell_command(command, shell_exe=shell_exe)
|
|
1055
|
+
command = assembled.command_string
|
|
977
1056
|
effective_cwd, reset_prefix = _prepare_spawn_cwd(cwd, fallback=fallback_cwd)
|
|
978
1057
|
if effective_cwd is None:
|
|
979
1058
|
msg = reset_prefix.strip() or "Shell cwd invalid and no valid fallback"
|
|
@@ -983,6 +1062,10 @@ class BashBackend:
|
|
|
983
1062
|
interrupted=False,
|
|
984
1063
|
stderr=msg + "\n",
|
|
985
1064
|
cwd_warning=msg,
|
|
1065
|
+
command_before=assembled.preprocess.before,
|
|
1066
|
+
command_after=assembled.command_string,
|
|
1067
|
+
shell_snapshot_path=assembled.snapshot_path,
|
|
1068
|
+
shell_login_flag=assembled.use_login_shell,
|
|
986
1069
|
)
|
|
987
1070
|
read_fd = -1
|
|
988
1071
|
write_fd = -1
|
|
@@ -994,16 +1077,16 @@ class BashBackend:
|
|
|
994
1077
|
timeout_sec=timeout_sec,
|
|
995
1078
|
env=env,
|
|
996
1079
|
shell_exe=shell_exe,
|
|
1080
|
+
assembled=assembled,
|
|
997
1081
|
)
|
|
998
1082
|
return _merge_reset_into_result(result, reset_prefix)
|
|
999
1083
|
|
|
1000
1084
|
read_fd, write_fd = os.pipe()
|
|
1001
1085
|
wrapped = BashCwdReporter.wrap_with_fd3(command, write_fd)
|
|
1086
|
+
argv = assembled.spawn_argv(shell_exe, wrapped)
|
|
1002
1087
|
|
|
1003
1088
|
proc = await asyncio.create_subprocess_exec(
|
|
1004
|
-
|
|
1005
|
-
"-c",
|
|
1006
|
-
wrapped,
|
|
1089
|
+
*argv,
|
|
1007
1090
|
stdin=asyncio.subprocess.DEVNULL,
|
|
1008
1091
|
stdout=asyncio.subprocess.PIPE,
|
|
1009
1092
|
stderr=asyncio.subprocess.PIPE,
|
|
@@ -1025,15 +1108,18 @@ class BashBackend:
|
|
|
1025
1108
|
cwd_after = _read_fd_safe(read_fd)
|
|
1026
1109
|
os.close(read_fd)
|
|
1027
1110
|
read_fd = -1
|
|
1028
|
-
return
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1111
|
+
return self._attach_assembly_meta(
|
|
1112
|
+
_merge_reset_into_result(
|
|
1113
|
+
ExecuteResult(
|
|
1114
|
+
stdout="",
|
|
1115
|
+
stderr="",
|
|
1116
|
+
exit_code=-1,
|
|
1117
|
+
interrupted=True,
|
|
1118
|
+
cwd_after=cwd_after,
|
|
1119
|
+
),
|
|
1120
|
+
reset_prefix,
|
|
1035
1121
|
),
|
|
1036
|
-
|
|
1122
|
+
assembled,
|
|
1037
1123
|
)
|
|
1038
1124
|
|
|
1039
1125
|
cwd_after = _read_fd_safe(read_fd)
|
|
@@ -1042,26 +1128,35 @@ class BashBackend:
|
|
|
1042
1128
|
|
|
1043
1129
|
stdout_str = (stdout_bytes or b"").decode("utf-8", errors="replace")
|
|
1044
1130
|
stderr_str = (stderr_bytes or b"").decode("utf-8", errors="replace")
|
|
1045
|
-
return
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1131
|
+
return self._attach_assembly_meta(
|
|
1132
|
+
_merge_reset_into_result(
|
|
1133
|
+
ExecuteResult(
|
|
1134
|
+
stdout=stdout_str,
|
|
1135
|
+
stderr=stderr_str,
|
|
1136
|
+
exit_code=proc.returncode or 0,
|
|
1137
|
+
interrupted=False,
|
|
1138
|
+
cwd_after=cwd_after,
|
|
1139
|
+
),
|
|
1140
|
+
reset_prefix,
|
|
1052
1141
|
),
|
|
1053
|
-
|
|
1142
|
+
assembled,
|
|
1054
1143
|
)
|
|
1055
1144
|
except FileNotFoundError:
|
|
1056
|
-
return
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1145
|
+
return self._attach_assembly_meta(
|
|
1146
|
+
ExecuteResult(
|
|
1147
|
+
stdout="bash: command not found",
|
|
1148
|
+
stderr="",
|
|
1149
|
+
exit_code=127,
|
|
1150
|
+
interrupted=False,
|
|
1151
|
+
),
|
|
1152
|
+
assembled,
|
|
1061
1153
|
)
|
|
1062
1154
|
except OSError as exc:
|
|
1063
1155
|
if _is_cwd_spawn_oserror(exc):
|
|
1064
|
-
return
|
|
1156
|
+
return self._attach_assembly_meta(
|
|
1157
|
+
_cwd_invalid_execute_result(effective_cwd or cwd, exc),
|
|
1158
|
+
assembled,
|
|
1159
|
+
)
|
|
1065
1160
|
raise
|
|
1066
1161
|
finally:
|
|
1067
1162
|
if write_fd != -1:
|
|
@@ -1083,6 +1178,7 @@ class BashBackend:
|
|
|
1083
1178
|
timeout_sec: int,
|
|
1084
1179
|
env: dict[str, str],
|
|
1085
1180
|
shell_exe: str,
|
|
1181
|
+
assembled: AssembledShellCommand | None = None,
|
|
1086
1182
|
) -> ExecuteResult:
|
|
1087
1183
|
fd, cwd_file = tempfile.mkstemp(prefix="agentx_bash_cwd_", suffix=".txt")
|
|
1088
1184
|
os.close(fd)
|
|
@@ -1091,13 +1187,14 @@ class BashBackend:
|
|
|
1091
1187
|
command, bash_single_quoted(posix_tgt)
|
|
1092
1188
|
)
|
|
1093
1189
|
spawn_env = self._spawn_env_with_shell(env, shell_exe)
|
|
1190
|
+
if assembled is None:
|
|
1191
|
+
assembled = self._assemble_shell_command(command, shell_exe=shell_exe)
|
|
1192
|
+
argv = assembled.spawn_argv(shell_exe, wrapped)
|
|
1094
1193
|
_cf = self._win32_creationflags()
|
|
1095
1194
|
try:
|
|
1096
1195
|
if _cf:
|
|
1097
1196
|
proc = await asyncio.create_subprocess_exec(
|
|
1098
|
-
|
|
1099
|
-
"-c",
|
|
1100
|
-
wrapped,
|
|
1197
|
+
*argv,
|
|
1101
1198
|
stdin=asyncio.subprocess.DEVNULL,
|
|
1102
1199
|
stdout=asyncio.subprocess.PIPE,
|
|
1103
1200
|
stderr=asyncio.subprocess.PIPE,
|
|
@@ -1107,9 +1204,7 @@ class BashBackend:
|
|
|
1107
1204
|
)
|
|
1108
1205
|
else:
|
|
1109
1206
|
proc = await asyncio.create_subprocess_exec(
|
|
1110
|
-
|
|
1111
|
-
"-c",
|
|
1112
|
-
wrapped,
|
|
1207
|
+
*argv,
|
|
1113
1208
|
stdin=asyncio.subprocess.DEVNULL,
|
|
1114
1209
|
stdout=asyncio.subprocess.PIPE,
|
|
1115
1210
|
stderr=asyncio.subprocess.PIPE,
|
|
@@ -1125,30 +1220,39 @@ class BashBackend:
|
|
|
1125
1220
|
proc.kill()
|
|
1126
1221
|
await proc.wait()
|
|
1127
1222
|
cwd_after = read_cwd_file(cwd_file)
|
|
1128
|
-
return
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1223
|
+
return self._attach_assembly_meta(
|
|
1224
|
+
ExecuteResult(
|
|
1225
|
+
stdout="",
|
|
1226
|
+
stderr="",
|
|
1227
|
+
exit_code=-1,
|
|
1228
|
+
interrupted=True,
|
|
1229
|
+
cwd_after=cwd_after,
|
|
1230
|
+
),
|
|
1231
|
+
assembled,
|
|
1134
1232
|
)
|
|
1135
1233
|
|
|
1136
1234
|
cwd_after = read_cwd_file(cwd_file)
|
|
1137
1235
|
stdout_str = (stdout_bytes or b"").decode("utf-8", errors="replace")
|
|
1138
1236
|
stderr_str = (stderr_bytes or b"").decode("utf-8", errors="replace")
|
|
1139
|
-
return
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1237
|
+
return self._attach_assembly_meta(
|
|
1238
|
+
ExecuteResult(
|
|
1239
|
+
stdout=stdout_str,
|
|
1240
|
+
stderr=stderr_str,
|
|
1241
|
+
exit_code=proc.returncode or 0,
|
|
1242
|
+
interrupted=False,
|
|
1243
|
+
cwd_after=cwd_after,
|
|
1244
|
+
),
|
|
1245
|
+
assembled,
|
|
1145
1246
|
)
|
|
1146
1247
|
except FileNotFoundError:
|
|
1147
|
-
return
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1248
|
+
return self._attach_assembly_meta(
|
|
1249
|
+
ExecuteResult(
|
|
1250
|
+
stdout="bash: command not found",
|
|
1251
|
+
stderr="",
|
|
1252
|
+
exit_code=127,
|
|
1253
|
+
interrupted=False,
|
|
1254
|
+
),
|
|
1255
|
+
assembled,
|
|
1152
1256
|
)
|
|
1153
1257
|
except OSError as exc:
|
|
1154
1258
|
if _is_cwd_spawn_oserror(exc):
|
|
@@ -1281,7 +1385,9 @@ class BashBackend:
|
|
|
1281
1385
|
)
|
|
1282
1386
|
return
|
|
1283
1387
|
stream_env = self._sandbox_stream_env(request)
|
|
1284
|
-
|
|
1388
|
+
shell_exe = self._posix_shell_executable(stream_env)
|
|
1389
|
+
assembled = self._assemble_shell_command(command, shell_exe=shell_exe)
|
|
1390
|
+
wrapped = self._build_stream_wrapper(assembled.command_string)
|
|
1285
1391
|
yield from self._stream_execute_raw_iter(
|
|
1286
1392
|
wrapped_command=wrapped,
|
|
1287
1393
|
cwd=effective_cwd,
|
|
@@ -1289,10 +1395,13 @@ class BashBackend:
|
|
|
1289
1395
|
env=stream_env,
|
|
1290
1396
|
cancel_event=cancel_event,
|
|
1291
1397
|
fallback_cwd=fallback_cwd,
|
|
1398
|
+
assembled=assembled,
|
|
1292
1399
|
)
|
|
1293
1400
|
return
|
|
1294
1401
|
|
|
1295
|
-
|
|
1402
|
+
shell_exe = self._posix_shell_executable(effective_env)
|
|
1403
|
+
assembled = self._assemble_shell_command(command, shell_exe=shell_exe)
|
|
1404
|
+
wrapped = self._build_stream_wrapper(assembled.command_string)
|
|
1296
1405
|
yield from self._stream_execute_raw_iter(
|
|
1297
1406
|
wrapped_command=wrapped,
|
|
1298
1407
|
cwd=cwd,
|
|
@@ -1300,6 +1409,7 @@ class BashBackend:
|
|
|
1300
1409
|
env=effective_env,
|
|
1301
1410
|
cancel_event=cancel_event,
|
|
1302
1411
|
fallback_cwd=fallback_cwd,
|
|
1412
|
+
assembled=assembled,
|
|
1303
1413
|
)
|
|
1304
1414
|
|
|
1305
1415
|
@staticmethod
|
|
@@ -1325,10 +1435,16 @@ class BashBackend:
|
|
|
1325
1435
|
env: dict[str, str],
|
|
1326
1436
|
cancel_event: threading.Event | None,
|
|
1327
1437
|
fallback_cwd: str | None = None,
|
|
1438
|
+
assembled: AssembledShellCommand | None = None,
|
|
1328
1439
|
) -> Iterator[BashStreamEvent]:
|
|
1329
1440
|
marker = "__AGENTX_STREAM_CWD__"
|
|
1330
1441
|
shell_exe = self._posix_shell_executable(env)
|
|
1331
1442
|
spawn_env = self._spawn_env_with_shell(env, shell_exe)
|
|
1443
|
+
if assembled is None:
|
|
1444
|
+
assembled = self._assemble_shell_command(
|
|
1445
|
+
wrapped_command, shell_exe=shell_exe
|
|
1446
|
+
)
|
|
1447
|
+
argv = assembled.spawn_argv(shell_exe, wrapped_command)
|
|
1332
1448
|
effective_cwd, reset_prefix = _prepare_spawn_cwd(cwd, fallback=fallback_cwd)
|
|
1333
1449
|
# effective_cwd is None 有两种情况:
|
|
1334
1450
|
# 1. reset_prefix 为空:用户未指定 cwd,使用当前进程工作目录(有效)
|
|
@@ -1348,7 +1464,7 @@ class BashBackend:
|
|
|
1348
1464
|
try:
|
|
1349
1465
|
if _cf:
|
|
1350
1466
|
proc = subprocess.Popen(
|
|
1351
|
-
|
|
1467
|
+
argv,
|
|
1352
1468
|
stdin=subprocess.DEVNULL,
|
|
1353
1469
|
stdout=subprocess.PIPE,
|
|
1354
1470
|
stderr=subprocess.STDOUT,
|
|
@@ -1360,7 +1476,7 @@ class BashBackend:
|
|
|
1360
1476
|
)
|
|
1361
1477
|
else:
|
|
1362
1478
|
proc = subprocess.Popen(
|
|
1363
|
-
|
|
1479
|
+
argv,
|
|
1364
1480
|
stdin=subprocess.DEVNULL,
|
|
1365
1481
|
stdout=subprocess.PIPE,
|
|
1366
1482
|
stderr=subprocess.STDOUT,
|
|
@@ -1553,7 +1669,9 @@ class BashBackend:
|
|
|
1553
1669
|
output_file = open(output_path, "wb") # noqa: WPS515
|
|
1554
1670
|
exit_code_path = os.path.join(task_dir, "exit_code")
|
|
1555
1671
|
|
|
1556
|
-
|
|
1672
|
+
shell_exe = self._posix_shell_executable(env)
|
|
1673
|
+
assembled = self._assemble_shell_command(command, shell_exe=shell_exe)
|
|
1674
|
+
command = assembled.command_string
|
|
1557
1675
|
quoted_exit_code_path = shlex.quote(exit_code_path)
|
|
1558
1676
|
wrapped = (
|
|
1559
1677
|
"{ "
|
|
@@ -1563,12 +1681,12 @@ class BashBackend:
|
|
|
1563
1681
|
"exit $__AGENTX_EC; "
|
|
1564
1682
|
"}"
|
|
1565
1683
|
)
|
|
1566
|
-
shell_exe = self._posix_shell_executable(env)
|
|
1567
1684
|
spawn_env = self._spawn_env_with_shell(env, shell_exe)
|
|
1685
|
+
argv = assembled.spawn_argv(shell_exe, wrapped)
|
|
1568
1686
|
_cf = self._win32_creationflags()
|
|
1569
1687
|
if _cf:
|
|
1570
1688
|
proc = subprocess.Popen(
|
|
1571
|
-
|
|
1689
|
+
argv,
|
|
1572
1690
|
stdin=subprocess.DEVNULL,
|
|
1573
1691
|
stdout=output_file,
|
|
1574
1692
|
stderr=subprocess.STDOUT,
|
|
@@ -1579,7 +1697,7 @@ class BashBackend:
|
|
|
1579
1697
|
)
|
|
1580
1698
|
else:
|
|
1581
1699
|
proc = subprocess.Popen(
|
|
1582
|
-
|
|
1700
|
+
argv,
|
|
1583
1701
|
stdin=subprocess.DEVNULL,
|
|
1584
1702
|
stdout=output_file,
|
|
1585
1703
|
stderr=subprocess.STDOUT,
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
bash_exec_assembly.py — Bash 命令装配与 spawn argv(对齐 CC bashProvider)
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
1) preprocess(nul rewrite + stdin/管道)并记录 before/after(D4)
|
|
6
|
+
2) 有快照时 ``source snap || true`` + disable extglob + 用户命令(D2 L1/L4)
|
|
7
|
+
3) ``get_spawn_args``:有可用快照 → ``[-c, cmd]``;否则 ``[-c, -l, cmd]``(对齐 CC)
|
|
8
|
+
|
|
9
|
+
链路位置:
|
|
10
|
+
``BashBackend`` 各 Popen / asyncio 路径在 cwd wrap 之前调用。
|
|
11
|
+
|
|
12
|
+
CC 对照:
|
|
13
|
+
``utils/shell/bashProvider.ts::buildExecCommand`` / ``getSpawnArgs``
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
from .cwd_reporter import bash_single_quoted
|
|
22
|
+
from .shell_quoting import append_stdin_redirect
|
|
23
|
+
from .shell_snapshot import ShellSnapshotService
|
|
24
|
+
from .windows_shell_quoting import rewrite_windows_null_redirect
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class PreprocessObservation:
|
|
29
|
+
"""D4:preprocess 前后快照。"""
|
|
30
|
+
|
|
31
|
+
before: str
|
|
32
|
+
after: str
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def changed(self) -> bool:
|
|
36
|
+
return self.before != self.after
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class AssembledShellCommand:
|
|
41
|
+
"""装配结果:进入 cwd wrap / Popen 前的命令与 spawn 策略。"""
|
|
42
|
+
|
|
43
|
+
original_command: str
|
|
44
|
+
preprocessed_command: str
|
|
45
|
+
command_string: str
|
|
46
|
+
snapshot_path: str | None
|
|
47
|
+
use_login_shell: bool
|
|
48
|
+
preprocess: PreprocessObservation
|
|
49
|
+
|
|
50
|
+
def spawn_argv(self, shell_exe: str, wrapped_command: str) -> list[str]:
|
|
51
|
+
"""对齐 CC ``getSpawnArgs``:``['-c', ...]`` 或 ``['-c', '-l', ...]``。"""
|
|
52
|
+
if self.use_login_shell:
|
|
53
|
+
return [shell_exe, "-c", "-l", wrapped_command]
|
|
54
|
+
return [shell_exe, "-c", wrapped_command]
|
|
55
|
+
|
|
56
|
+
def observability_dict(self) -> dict[str, object]:
|
|
57
|
+
return {
|
|
58
|
+
"command_before": self.preprocess.before,
|
|
59
|
+
"command_after": self.preprocess.after,
|
|
60
|
+
"command_rewritten": self.preprocess.changed,
|
|
61
|
+
"assembled_command": self.command_string,
|
|
62
|
+
"shell_snapshot_path": self.snapshot_path,
|
|
63
|
+
"shell_login_flag": self.use_login_shell,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def disable_extglob_command(shell_path: str) -> str | None:
|
|
68
|
+
"""对齐 CC ``getDisableExtglobCommand``(无 SHELL_PREFIX 双写分支)。"""
|
|
69
|
+
base = os.path.basename(shell_path).lower()
|
|
70
|
+
if "bash" in base:
|
|
71
|
+
return "shopt -u extglob 2>/dev/null || true"
|
|
72
|
+
if "zsh" in base:
|
|
73
|
+
return "setopt NO_EXTENDED_GLOB 2>/dev/null || true"
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def preprocess_shell_command(command: str) -> PreprocessObservation:
|
|
78
|
+
after = append_stdin_redirect(rewrite_windows_null_redirect(command))
|
|
79
|
+
return PreprocessObservation(before=command, after=after)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BashExecAssembler:
|
|
83
|
+
"""将用户命令装配为 CC 风格 source/extglob 链,并决定是否 ``-l``。"""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
*,
|
|
88
|
+
snapshot_service: ShellSnapshotService | None = None,
|
|
89
|
+
enable_snapshot: bool = True,
|
|
90
|
+
injected_snapshot_path: str | None = None,
|
|
91
|
+
) -> None:
|
|
92
|
+
self._snapshot_service = snapshot_service or ShellSnapshotService()
|
|
93
|
+
self._enable_snapshot = enable_snapshot
|
|
94
|
+
self._injected_snapshot_path = injected_snapshot_path
|
|
95
|
+
self._cached_snapshot: str | None | object = _UNSET
|
|
96
|
+
|
|
97
|
+
def resolve_snapshot_path(self, shell_exe: str) -> str | None:
|
|
98
|
+
if self._injected_snapshot_path is not None:
|
|
99
|
+
path = self._injected_snapshot_path
|
|
100
|
+
return path if ShellSnapshotService.is_usable(path) else None
|
|
101
|
+
if not self._enable_snapshot:
|
|
102
|
+
return None
|
|
103
|
+
if self._cached_snapshot is _UNSET:
|
|
104
|
+
self._cached_snapshot = self._snapshot_service.create_and_save(shell_exe)
|
|
105
|
+
path = self._cached_snapshot if isinstance(self._cached_snapshot, str) else None
|
|
106
|
+
if not ShellSnapshotService.is_usable(path):
|
|
107
|
+
self._cached_snapshot = None
|
|
108
|
+
return None
|
|
109
|
+
return path
|
|
110
|
+
|
|
111
|
+
def assemble(self, command: str, *, shell_exe: str) -> AssembledShellCommand:
|
|
112
|
+
obs = preprocess_shell_command(command)
|
|
113
|
+
snapshot_path = self.resolve_snapshot_path(shell_exe)
|
|
114
|
+
use_login = snapshot_path is None
|
|
115
|
+
parts: list[str] = []
|
|
116
|
+
if snapshot_path is not None:
|
|
117
|
+
quoted = bash_single_quoted(snapshot_path)
|
|
118
|
+
parts.append(f"source {quoted} 2>/dev/null || true")
|
|
119
|
+
extglob = disable_extglob_command(shell_exe)
|
|
120
|
+
if extglob:
|
|
121
|
+
parts.append(extglob)
|
|
122
|
+
parts.append(obs.after)
|
|
123
|
+
command_string = " && ".join(parts)
|
|
124
|
+
return AssembledShellCommand(
|
|
125
|
+
original_command=command,
|
|
126
|
+
preprocessed_command=obs.after,
|
|
127
|
+
command_string=command_string,
|
|
128
|
+
snapshot_path=snapshot_path,
|
|
129
|
+
use_login_shell=use_login,
|
|
130
|
+
preprocess=obs,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class _UnsetType:
|
|
135
|
+
__slots__ = ()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
_UNSET = _UnsetType()
|
|
139
|
+
|
|
140
|
+
__all__ = [
|
|
141
|
+
"AssembledShellCommand",
|
|
142
|
+
"BashExecAssembler",
|
|
143
|
+
"PreprocessObservation",
|
|
144
|
+
"disable_extglob_command",
|
|
145
|
+
"preprocess_shell_command",
|
|
146
|
+
]
|
|
@@ -68,35 +68,51 @@ def _should_use_pipe_eval_fallback(command: str) -> bool:
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def _split_top_level_pipes(command: str) -> list[str] | None:
|
|
71
|
-
"""
|
|
71
|
+
"""按顶层单 ``|`` 分段(不在引号内);**不**拆 ``||`` / ``|&``(BUG-20261101-20)。"""
|
|
72
72
|
parts: list[str] = []
|
|
73
73
|
buf: list[str] = []
|
|
74
74
|
in_single = False
|
|
75
75
|
in_double = False
|
|
76
76
|
escape = False
|
|
77
|
+
i = 0
|
|
78
|
+
n = len(command)
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
while i < n:
|
|
81
|
+
ch = command[i]
|
|
79
82
|
if escape:
|
|
80
83
|
buf.append(ch)
|
|
81
84
|
escape = False
|
|
85
|
+
i += 1
|
|
82
86
|
continue
|
|
83
87
|
if ch == "\\" and in_double:
|
|
84
88
|
escape = True
|
|
85
89
|
buf.append(ch)
|
|
90
|
+
i += 1
|
|
86
91
|
continue
|
|
87
92
|
if ch == "'" and not in_double:
|
|
88
93
|
in_single = not in_single
|
|
89
94
|
buf.append(ch)
|
|
95
|
+
i += 1
|
|
90
96
|
continue
|
|
91
97
|
if ch == '"' and not in_single:
|
|
92
98
|
in_double = not in_double
|
|
93
99
|
buf.append(ch)
|
|
100
|
+
i += 1
|
|
94
101
|
continue
|
|
95
102
|
if ch == "|" and not in_single and not in_double:
|
|
103
|
+
nxt = command[i + 1] if i + 1 < n else ""
|
|
104
|
+
# ``||`` 逻辑或、``|&`` pipe+stderr:保留为普通字符,不做管道重排分段
|
|
105
|
+
if nxt in ("|", "&"):
|
|
106
|
+
buf.append(ch)
|
|
107
|
+
buf.append(nxt)
|
|
108
|
+
i += 2
|
|
109
|
+
continue
|
|
96
110
|
parts.append("".join(buf))
|
|
97
111
|
buf = []
|
|
112
|
+
i += 1
|
|
98
113
|
continue
|
|
99
114
|
buf.append(ch)
|
|
115
|
+
i += 1
|
|
100
116
|
|
|
101
117
|
if in_single or in_double:
|
|
102
118
|
return None
|
|
@@ -107,6 +123,11 @@ def _split_top_level_pipes(command: str) -> list[str] | None:
|
|
|
107
123
|
return parts
|
|
108
124
|
|
|
109
125
|
|
|
126
|
+
def _has_rearrangeable_pipe(command: str) -> bool:
|
|
127
|
+
"""是否存在可重排的顶层管道(排除仅含 ``||`` 的命令)。"""
|
|
128
|
+
return _split_top_level_pipes(command) is not None
|
|
129
|
+
|
|
130
|
+
|
|
110
131
|
def _rearrange_pipe_command(command: str) -> str:
|
|
111
132
|
"""
|
|
112
133
|
管道场景:``eval 'cmd1 < /dev/null | cmd2'``(redirect 在头段,不在 eval 尾)。
|
|
@@ -140,6 +161,6 @@ def append_stdin_redirect(command: str) -> str:
|
|
|
140
161
|
or has_process_substitution(stripped)
|
|
141
162
|
):
|
|
142
163
|
return command
|
|
143
|
-
if
|
|
164
|
+
if _has_rearrangeable_pipe(stripped):
|
|
144
165
|
return _rearrange_pipe_command(stripped)
|
|
145
166
|
return _eval_with_stdin_redirect(stripped)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
shell_snapshot.py — Bash/Zsh 环境快照(对齐 CC ShellSnapshot.ts)
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
会话级导出用户 shell 配置中的 alias / shopt / PATH,写入可 source 的快照文件;
|
|
6
|
+
供后续每条 Bash 命令 ``source snapshot || true``,避免反复 ``-l`` 的 login 漂移。
|
|
7
|
+
|
|
8
|
+
链路位置:
|
|
9
|
+
``BashExecAssembler`` / ``BashBackend`` 在 spawn 前解析快照路径;
|
|
10
|
+
创建失败时返回 None → spawn 带 ``-l``(对齐 CC getSpawnArgs 回退)。
|
|
11
|
+
|
|
12
|
+
CC 对照:
|
|
13
|
+
``utils/bash/ShellSnapshot.ts::createAndSaveSnapshot``
|
|
14
|
+
``utils/shell/bashProvider.ts``(有快照跳过 ``-l``)
|
|
15
|
+
|
|
16
|
+
当前裁剪范围:
|
|
17
|
+
不移植 CC 嵌入式 rg/find/grep 影子函数;不读 sessionEnv hooks;
|
|
18
|
+
快照目录默认 ``~/.langchain_agentx/shell-snapshots``(可用环境变量覆盖)。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
import os
|
|
25
|
+
import subprocess
|
|
26
|
+
import time
|
|
27
|
+
import uuid
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
_logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
_SNAPSHOT_TIMEOUT_SEC = 10.0
|
|
33
|
+
_DEFAULT_REL = Path(".langchain_agentx") / "shell-snapshots"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def default_snapshots_dir() -> Path:
|
|
37
|
+
override = os.environ.get("LANGCHAIN_AGENTX_SHELL_SNAPSHOT_DIR", "").strip()
|
|
38
|
+
if override:
|
|
39
|
+
return Path(os.path.expanduser(override))
|
|
40
|
+
return Path.home() / _DEFAULT_REL
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def resolve_user_config_file(shell_path: str) -> Path:
|
|
44
|
+
home = Path.home()
|
|
45
|
+
lower = os.path.basename(shell_path).lower()
|
|
46
|
+
if "zsh" in lower:
|
|
47
|
+
return home / ".zshrc"
|
|
48
|
+
if "bash" in lower:
|
|
49
|
+
return home / ".bashrc"
|
|
50
|
+
return home / ".profile"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ShellSnapshotService:
|
|
54
|
+
"""创建并校验 shell 快照文件(进程内可复用同一路径)。"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
*,
|
|
59
|
+
snapshots_dir: Path | None = None,
|
|
60
|
+
timeout_sec: float = _SNAPSHOT_TIMEOUT_SEC,
|
|
61
|
+
) -> None:
|
|
62
|
+
self._snapshots_dir = snapshots_dir or default_snapshots_dir()
|
|
63
|
+
self._timeout_sec = timeout_sec
|
|
64
|
+
|
|
65
|
+
def create_and_save(self, shell_path: str) -> str | None:
|
|
66
|
+
"""对齐 CC ``createAndSaveSnapshot``;失败返回 None。"""
|
|
67
|
+
if os.environ.get("LANGCHAIN_AGENTX_BASH_SKIP_SNAPSHOT", "").strip() == "1":
|
|
68
|
+
return None
|
|
69
|
+
shell_path = os.path.normpath(os.path.realpath(os.path.expanduser(shell_path)))
|
|
70
|
+
if not shell_path or not os.path.isfile(shell_path):
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
self._snapshots_dir.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
except OSError as exc:
|
|
76
|
+
_logger.debug("shell snapshot dir create failed: %s", exc)
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
shell_type = (
|
|
80
|
+
"zsh"
|
|
81
|
+
if "zsh" in os.path.basename(shell_path).lower()
|
|
82
|
+
else "bash"
|
|
83
|
+
if "bash" in os.path.basename(shell_path).lower()
|
|
84
|
+
else "sh"
|
|
85
|
+
)
|
|
86
|
+
stamp = int(time.time() * 1000)
|
|
87
|
+
random_id = uuid.uuid4().hex[:6]
|
|
88
|
+
snapshot_path = self._snapshots_dir / f"snapshot-{shell_type}-{stamp}-{random_id}.sh"
|
|
89
|
+
config_file = resolve_user_config_file(shell_path)
|
|
90
|
+
config_exists = config_file.is_file()
|
|
91
|
+
script = self._build_snapshot_script(
|
|
92
|
+
snapshot_path=snapshot_path,
|
|
93
|
+
config_file=config_file if config_exists else None,
|
|
94
|
+
is_zsh=shell_type == "zsh",
|
|
95
|
+
)
|
|
96
|
+
env = os.environ.copy()
|
|
97
|
+
env["SHELL"] = shell_path
|
|
98
|
+
env.setdefault("GIT_EDITOR", "true")
|
|
99
|
+
try:
|
|
100
|
+
completed = subprocess.run(
|
|
101
|
+
[shell_path, "-c", "-l", script],
|
|
102
|
+
env=env,
|
|
103
|
+
timeout=self._timeout_sec,
|
|
104
|
+
capture_output=True,
|
|
105
|
+
text=True,
|
|
106
|
+
check=False,
|
|
107
|
+
)
|
|
108
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
109
|
+
_logger.debug("shell snapshot spawn failed: %s", exc)
|
|
110
|
+
self._unlink_quiet(snapshot_path)
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
if completed.returncode != 0 or not snapshot_path.is_file():
|
|
114
|
+
_logger.debug(
|
|
115
|
+
"shell snapshot creation failed rc=%s stderr=%s",
|
|
116
|
+
completed.returncode,
|
|
117
|
+
(completed.stderr or "")[:300],
|
|
118
|
+
)
|
|
119
|
+
self._unlink_quiet(snapshot_path)
|
|
120
|
+
return None
|
|
121
|
+
return str(snapshot_path)
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def is_usable(path: str | None) -> bool:
|
|
125
|
+
return bool(path) and os.path.isfile(path)
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def _unlink_quiet(path: Path) -> None:
|
|
129
|
+
try:
|
|
130
|
+
path.unlink(missing_ok=True)
|
|
131
|
+
except OSError:
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _build_snapshot_script(
|
|
136
|
+
*,
|
|
137
|
+
snapshot_path: Path,
|
|
138
|
+
config_file: Path | None,
|
|
139
|
+
is_zsh: bool,
|
|
140
|
+
) -> str:
|
|
141
|
+
snap = str(snapshot_path).replace("'", "'\"'\"'")
|
|
142
|
+
lines = [
|
|
143
|
+
f"SNAPSHOT_FILE='{snap}'",
|
|
144
|
+
]
|
|
145
|
+
if config_file is not None:
|
|
146
|
+
cfg = str(config_file).replace("'", "'\"'\"'")
|
|
147
|
+
lines.append(f"source '{cfg}' < /dev/null")
|
|
148
|
+
else:
|
|
149
|
+
lines.append("# No user config file to source")
|
|
150
|
+
|
|
151
|
+
lines.extend(
|
|
152
|
+
[
|
|
153
|
+
'echo "# Snapshot file" >| "$SNAPSHOT_FILE"',
|
|
154
|
+
'echo "# Unset all aliases to avoid conflicts with functions" >> "$SNAPSHOT_FILE"',
|
|
155
|
+
'echo "unalias -a 2>/dev/null || true" >> "$SNAPSHOT_FILE"',
|
|
156
|
+
]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if is_zsh:
|
|
160
|
+
lines.extend(
|
|
161
|
+
[
|
|
162
|
+
'echo "# Shell Options" >> "$SNAPSHOT_FILE"',
|
|
163
|
+
'setopt 2>/dev/null | sed "s/^/setopt /" | head -n 1000 >> "$SNAPSHOT_FILE" || true',
|
|
164
|
+
'echo "# Aliases" >> "$SNAPSHOT_FILE"',
|
|
165
|
+
"alias 2>/dev/null | sed 's/^alias //g' | sed 's/^/alias -- /' "
|
|
166
|
+
'| head -n 1000 >> "$SNAPSHOT_FILE" || true',
|
|
167
|
+
]
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
if config_file is None:
|
|
171
|
+
lines.append(
|
|
172
|
+
'echo "shopt -s expand_aliases" >> "$SNAPSHOT_FILE"'
|
|
173
|
+
)
|
|
174
|
+
lines.extend(
|
|
175
|
+
[
|
|
176
|
+
'echo "# Shell Options" >> "$SNAPSHOT_FILE"',
|
|
177
|
+
'shopt -p 2>/dev/null | head -n 1000 >> "$SNAPSHOT_FILE" || true',
|
|
178
|
+
'set -o 2>/dev/null | grep "on" | awk \'{print "set -o " $1}\' '
|
|
179
|
+
'| head -n 1000 >> "$SNAPSHOT_FILE" || true',
|
|
180
|
+
'echo "shopt -s expand_aliases" >> "$SNAPSHOT_FILE"',
|
|
181
|
+
'echo "# Aliases" >> "$SNAPSHOT_FILE"',
|
|
182
|
+
"alias 2>/dev/null | sed 's/^alias //g' | sed 's/^/alias -- /' "
|
|
183
|
+
'| head -n 1000 >> "$SNAPSHOT_FILE" || true',
|
|
184
|
+
]
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
path_value = os.environ.get("PATH", "")
|
|
188
|
+
path_q = path_value.replace("'", "'\"'\"'")
|
|
189
|
+
lines.append(f'echo "export PATH=\'{path_q}\'" >> "$SNAPSHOT_FILE"')
|
|
190
|
+
lines.extend(
|
|
191
|
+
[
|
|
192
|
+
'if [ ! -f "$SNAPSHOT_FILE" ]; then',
|
|
193
|
+
' echo "Error: Snapshot file was not created" >&2',
|
|
194
|
+
" exit 1",
|
|
195
|
+
"fi",
|
|
196
|
+
]
|
|
197
|
+
)
|
|
198
|
+
return "\n".join(lines)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
__all__ = [
|
|
202
|
+
"ShellSnapshotService",
|
|
203
|
+
"default_snapshots_dir",
|
|
204
|
+
"resolve_user_config_file",
|
|
205
|
+
]
|
|
@@ -818,6 +818,26 @@ class BashRuntimeTool(RuntimeTool):
|
|
|
818
818
|
auto_backgrounded=False,
|
|
819
819
|
)
|
|
820
820
|
ev.add("task_state_changed", from_state="running", to_state=snapshot.task_status)
|
|
821
|
+
obs_payload = finalize_observability_payload(ev.to_observability())
|
|
822
|
+
shell_obs = {
|
|
823
|
+
k: v
|
|
824
|
+
for k, v in {
|
|
825
|
+
"command_before": result.command_before,
|
|
826
|
+
"command_after": result.command_after,
|
|
827
|
+
"shell_snapshot_path": result.shell_snapshot_path,
|
|
828
|
+
"shell_login_flag": result.shell_login_flag,
|
|
829
|
+
"command_rewritten": (
|
|
830
|
+
result.command_before is not None
|
|
831
|
+
and result.command_after is not None
|
|
832
|
+
and result.command_before != result.command_after
|
|
833
|
+
),
|
|
834
|
+
}.items()
|
|
835
|
+
if v is not None
|
|
836
|
+
}
|
|
837
|
+
if shell_obs:
|
|
838
|
+
merged_obs = dict(obs_payload or {})
|
|
839
|
+
merged_obs["shell_assembly"] = shell_obs
|
|
840
|
+
obs_payload = merged_obs
|
|
821
841
|
return BashToolOutput(
|
|
822
842
|
stdout=stdout_text,
|
|
823
843
|
stderr=result.stderr,
|
|
@@ -838,7 +858,7 @@ class BashRuntimeTool(RuntimeTool):
|
|
|
838
858
|
cwd_commit_allowed=cwd_commit_allowed,
|
|
839
859
|
overflow_file=overflow_file,
|
|
840
860
|
output_truncated=output_truncated,
|
|
841
|
-
observability=
|
|
861
|
+
observability=obs_payload,
|
|
842
862
|
)
|
|
843
863
|
|
|
844
864
|
def collect_side_effects(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
langchain_agentx/__init__.py,sha256=
|
|
1
|
+
langchain_agentx/__init__.py,sha256=N_Fs7Pkm2H6v6bByXzym_2aQuA4Ef7x0K2Sd2IN-5Ho,2014
|
|
2
2
|
langchain_agentx/command/__init__.py,sha256=Ej260S5uFQcmEtGZQG_NH7BYjHoStrpBLtGUsBTxyZk,631
|
|
3
3
|
langchain_agentx/command/allowed_tools.py,sha256=TVA0VM8rm98H-QbLK_GRiX5RhEAZFxKawliMi4kk96A,3359
|
|
4
4
|
langchain_agentx/command/context.py,sha256=DIGOGPBw5UGDBm0WNNJlKx1h_9rCRmcdROv4DT61Qug,731
|
|
@@ -146,7 +146,7 @@ langchain_agentx/loop/subagent/active_run_registry.py,sha256=KurhAMDTBnRkdxNidaM
|
|
|
146
146
|
langchain_agentx/loop/subagent/async_runner.py,sha256=ZJu6DztRmg0SNEapEuPyQbsk8SprXiMKWDWDHb9L7yQ,14124
|
|
147
147
|
langchain_agentx/loop/subagent/boundary_snapshot.py,sha256=9cQuqN29nJDRnHnx9c8b09KRmODE2zv15Z06QyjVJps,10008
|
|
148
148
|
langchain_agentx/loop/subagent/cache_safe_params.py,sha256=-qcgdazEYRy6s4ILQuttpxraE3gsxq17Ro7LYBnuv3E,4460
|
|
149
|
-
langchain_agentx/loop/subagent/context.py,sha256=
|
|
149
|
+
langchain_agentx/loop/subagent/context.py,sha256=YAAR_g7BA_PXL5LGxxO1xWMn5pvVU3VOYSmxdxNPbbg,25243
|
|
150
150
|
langchain_agentx/loop/subagent/definition_permission.py,sha256=_Y30FA0EDTgqpqjw5luIiwbpABLo17myC3aY4NWbGIw,2188
|
|
151
151
|
langchain_agentx/loop/subagent/display_projector.py,sha256=AKsGWS1c9reH0kuD5u4oQ24KsBgyt7X10iZM3ere6Fs,3504
|
|
152
152
|
langchain_agentx/loop/subagent/fork_boilerplate.py,sha256=2Uh3cwegL7V-DvtLIxjX7ik5uW5mXfY2NBzP-biREso,5005
|
|
@@ -489,7 +489,7 @@ langchain_agentx/tool_runtime/read_ignore_patterns.py,sha256=l4kNulVDaOdQxcs4FeC
|
|
|
489
489
|
langchain_agentx/tool_runtime/read_permission.py,sha256=rwH7NEtL57aUKXVjSf6hMOOvEWOayNFyvI_SNoNCHPA,9284
|
|
490
490
|
langchain_agentx/tool_runtime/registry.py,sha256=cvdMRz5cyAqLjM0MWWp6wu1DgqtZeMoiR2vgyAVHlb8,3358
|
|
491
491
|
langchain_agentx/tool_runtime/session_hints.py,sha256=TyoNdSw5GsE5AvYDPHJKsrto9YXZtbAdihI8YKHdRxA,3004
|
|
492
|
-
langchain_agentx/tool_runtime/session_store.py,sha256=
|
|
492
|
+
langchain_agentx/tool_runtime/session_store.py,sha256=47asao4mHf75BijGg2OrJhexCUk2k8w3GpMUGYirNxM,17625
|
|
493
493
|
langchain_agentx/tool_runtime/shell_error.py,sha256=gi2i0sc4qHcUiLD5B2yk-wIvyVaOpLFKyf4ZnnKmAuE,2198
|
|
494
494
|
langchain_agentx/tool_runtime/smoke_test_runtime.py,sha256=vOdoYdFwsWjq8k7rHD7hkgtgM5noGr9a6Wn1hHZ7354,11846
|
|
495
495
|
langchain_agentx/tool_runtime/state_bridge.py,sha256=8F7jG8Uk4EVJaAB8rwKpNmbWrNZtIHYJO5RFBiwSRhs,6011
|
|
@@ -588,7 +588,8 @@ langchain_agentx/tools/ask_user_question/validators.py,sha256=Uc88cBzqUp0PNG_Jf2
|
|
|
588
588
|
langchain_agentx/tools/bash/__init__.py,sha256=molPHFDylE2wWG_aQx7fb0ciAx3wWpdXzIixTZV5ne4,214
|
|
589
589
|
langchain_agentx/tools/bash/ast_security.py,sha256=vMP1lAi2bHrsHojun64eu1-Ym8hGW00dSVe3PexbVxc,22999
|
|
590
590
|
langchain_agentx/tools/bash/auto_mode_adapter.py,sha256=7-yb_kGmpytBYkD2Hn5YWPIceo_YN4PatEKj8O7HKsM,4243
|
|
591
|
-
langchain_agentx/tools/bash/backend.py,sha256=
|
|
591
|
+
langchain_agentx/tools/bash/backend.py,sha256=aqLNdaBkD66c7jMPgguBLr9JYkCb5fsxsOBLGFbdjlk,70285
|
|
592
|
+
langchain_agentx/tools/bash/bash_exec_assembly.py,sha256=Rb1spjxVZ_-H4D5zOdTiPY8d7cwTRYAs2nTi-zW8zuA,5023
|
|
592
593
|
langchain_agentx/tools/bash/bash_hardening.py,sha256=KEYKRC7kS_to4mzXuChvCmOIIb4dSUkOb9K1MBDtWDc,29296
|
|
593
594
|
langchain_agentx/tools/bash/bash_registry_content.py,sha256=YHE3PwuLBeFrEux4xNaDZjJpYtUSMFPLYr6A7g-fFxk,3569
|
|
594
595
|
langchain_agentx/tools/bash/bash_runtime_contract.py,sha256=s8vrvMl7405KLGw2hPFbzlzWohm5JX3c9cImMBgqh8I,1511
|
|
@@ -615,10 +616,11 @@ langchain_agentx/tools/bash/sed_validation.py,sha256=HA-zWe8Wo4LbgC9x8ZMQLzkd73i
|
|
|
615
616
|
langchain_agentx/tools/bash/semantic_matrix.py,sha256=stvsudu8mGjS1JJCawMAUKabt-7co90Q8qZgi063QSw,3285
|
|
616
617
|
langchain_agentx/tools/bash/semantics.py,sha256=pkf4URTulAOVL7m04Rq9JJF7z0J2mTEsGWrjyoY9r6o,5769
|
|
617
618
|
langchain_agentx/tools/bash/shell_locator.py,sha256=AUnCZS8gmZurCs8rL4y0MMOS9UZimfg5rQ5At38mZKU,6296
|
|
618
|
-
langchain_agentx/tools/bash/shell_quoting.py,sha256=
|
|
619
|
+
langchain_agentx/tools/bash/shell_quoting.py,sha256=qh5hl9rgjdhOijkM_MHdodqkj5VuCy68DzIA8zS2Qxw,5223
|
|
619
620
|
langchain_agentx/tools/bash/shell_rule_matching.py,sha256=uYEKg2C5M1r0eh3L4b5NCML1Gj2UBdIXeL_u2A6_RyM,3981
|
|
621
|
+
langchain_agentx/tools/bash/shell_snapshot.py,sha256=Fxj8natFh0vuOapl3Gy3XIL2UdDKK-8G3CCvn0RKGFg,7181
|
|
620
622
|
langchain_agentx/tools/bash/task_runtime.py,sha256=hikVC9YugtvpLf4nDaPlX_GFem1YQvDRwN0ShRflUYU,3568
|
|
621
|
-
langchain_agentx/tools/bash/tool.py,sha256=
|
|
623
|
+
langchain_agentx/tools/bash/tool.py,sha256=XNxdyG0ayxrIdvUZdV_dv46UC_48uOnlB1QN2-wdb4w,47499
|
|
622
624
|
langchain_agentx/tools/bash/windows_shell_quoting.py,sha256=0RzLnTKyFrJ536QzqPZvSyUcQWN794XRlAq2RdHeKHo,1615
|
|
623
625
|
langchain_agentx/tools/edit/__init__.py,sha256=PiiOHUODUzTRQxxPdE7ZMTjd3vfdzm9LEcn1lpVtg8o,203
|
|
624
626
|
langchain_agentx/tools/edit/backend.py,sha256=JrikYafZDGMQ2JjXRxbnv1VSHVGRUdLmZN__nnqkM9o,3159
|
|
@@ -839,8 +841,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
|
|
|
839
841
|
langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
|
|
840
842
|
langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
|
|
841
843
|
langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
|
|
842
|
-
langchain_agentx_python-2.4.
|
|
843
|
-
langchain_agentx_python-2.4.
|
|
844
|
-
langchain_agentx_python-2.4.
|
|
845
|
-
langchain_agentx_python-2.4.
|
|
846
|
-
langchain_agentx_python-2.4.
|
|
844
|
+
langchain_agentx_python-2.4.7.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
845
|
+
langchain_agentx_python-2.4.7.dist-info/METADATA,sha256=rvN_Qn80V-J-HbrRDQBQHw3GdjhEvDO6szODBFZXHhY,23971
|
|
846
|
+
langchain_agentx_python-2.4.7.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
|
|
847
|
+
langchain_agentx_python-2.4.7.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
|
|
848
|
+
langchain_agentx_python-2.4.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{langchain_agentx_python-2.4.5.dist-info → langchain_agentx_python-2.4.7.dist-info}/top_level.txt
RENAMED
|
File without changes
|