langchain-agentx-python 1.0.2__py3-none-any.whl → 1.0.3__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.
@@ -11,7 +11,7 @@ from langchain_agentx import create_loop_agent
11
11
  ```
12
12
  """
13
13
 
14
- __version__ = "1.0.2"
14
+ __version__ = "1.0.3"
15
15
 
16
16
  from .loop import ( # noqa: F401
17
17
  create_loop_agent,
@@ -154,6 +154,8 @@ class LangChainAdapter:
154
154
  # 如果 loop_config 存在但 workspace_root 仍为 None,默认使用当前工作目录
155
155
  if loop_config is not None and workspace_root is None:
156
156
  workspace_root = str(Path.cwd())
157
+ if workspace_root is not None:
158
+ workspace_root = str(workspace_root)
157
159
 
158
160
  # agent_home_segment:优先从新嵌套结构获取,再回退到直接字段,再回退到旧字段
159
161
  agent_home_segment = None
@@ -606,11 +606,15 @@ class RuntimeToolExecutionEngine:
606
606
  override = getattr(store, "cwd_override", None)
607
607
  if not isinstance(override, str) or not override.strip():
608
608
  return
609
- candidate = Path(override)
610
- if candidate.is_dir():
609
+ from langchain_agentx.tools.bash.cwd_reporter import normalize_subprocess_cwd
610
+
611
+ normalized = normalize_subprocess_cwd(override)
612
+ if normalized and Path(normalized).is_dir():
613
+ if normalized != override and hasattr(store, "set_cwd"):
614
+ store.set_cwd(normalized)
611
615
  return
612
616
  if hasattr(store, "set_cwd"):
613
- store.set_cwd(workspace_root)
617
+ store.set_cwd(str(workspace_root))
614
618
 
615
619
  def _unknown_tool_envelope(self, tool_name: str) -> ToolResultEnvelope:
616
620
  return ToolResultEnvelope(
@@ -46,6 +46,7 @@ from .cwd_reporter import (
46
46
  BashCwdReporter,
47
47
  bash_single_quoted,
48
48
  native_path_for_bash_redirect,
49
+ normalize_subprocess_cwd,
49
50
  read_cwd_file,
50
51
  )
51
52
  from .sandbox_decision import BashSandboxDecision
@@ -90,30 +91,32 @@ def extract_cwd_warning(stderr: str) -> tuple[str, str | None]:
90
91
  return cleaned_stderr, cwd_warning
91
92
 
92
93
 
93
- def _cwd_invalid_reason(cwd: str | None) -> str | None:
94
+ def _cwd_invalid_reason(cwd: str | os.PathLike[str] | None) -> str | None:
94
95
  """返回 cwd 不可用于 subprocess 的原因;None 表示可用(含 cwd is None)。"""
95
96
  if cwd is None:
96
97
  return None
97
- if not cwd.strip():
98
+ normalized = normalize_subprocess_cwd(cwd)
99
+ if normalized is None:
98
100
  return "empty path"
99
- if not os.path.exists(cwd):
101
+ if not os.path.exists(normalized):
100
102
  return "does not exist"
101
- if not os.path.isdir(cwd):
103
+ if not os.path.isdir(normalized):
102
104
  return "not a directory"
103
105
  return None
104
106
 
105
107
 
106
- def _pick_cwd_fallback(fallback: str | None) -> str | None:
108
+ def _pick_cwd_fallback(fallback: str | os.PathLike[str] | None) -> str | None:
107
109
  """选择可 spawn 的回退目录(fail-closed:仅 fallback,不用进程 cwd / ~)。"""
108
- if fallback and _cwd_invalid_reason(fallback) is None:
109
- return os.path.realpath(fallback)
110
+ normalized = normalize_subprocess_cwd(fallback)
111
+ if normalized and _cwd_invalid_reason(normalized) is None:
112
+ return os.path.realpath(normalized)
110
113
  return None
111
114
 
112
115
 
113
116
  def _prepare_spawn_cwd(
114
- cwd: str | None,
117
+ cwd: str | os.PathLike[str] | None,
115
118
  *,
116
- fallback: str | None,
119
+ fallback: str | os.PathLike[str] | None,
117
120
  ) -> tuple[str | None, str]:
118
121
  """
119
122
  spawn 前解析 cwd:无效时 fail-closed 回退到 fallback,并生成 CC 风格 stderr 前缀。
@@ -121,9 +124,10 @@ def _prepare_spawn_cwd(
121
124
  Returns:
122
125
  (effective_cwd, stderr_prefix) — stderr_prefix 形如 ``Shell cwd was reset to ...\\n``
123
126
  """
124
- reason = _cwd_invalid_reason(cwd)
127
+ normalized_cwd = normalize_subprocess_cwd(cwd) if cwd is not None else None
128
+ reason = _cwd_invalid_reason(normalized_cwd)
125
129
  if reason is None:
126
- return cwd, ""
130
+ return normalized_cwd, ""
127
131
  reset_to = _pick_cwd_fallback(fallback)
128
132
  if reset_to is None:
129
133
  detail = f"previous cwd invalid: {cwd}: {reason}" if cwd else f"cwd invalid: {reason}"
@@ -1274,10 +1278,14 @@ class BashBackend:
1274
1278
  @staticmethod
1275
1279
  def _build_stream_wrapper(command: str) -> str:
1276
1280
  marker = "__AGENTX_STREAM_CWD__"
1281
+ if sys.platform == "win32":
1282
+ pwd_expr = '$(cygpath -m "$PWD" 2>/dev/null || printf %s "$PWD")'
1283
+ else:
1284
+ pwd_expr = '"$PWD"'
1277
1285
  return (
1278
1286
  f"{command}\n"
1279
1287
  "__EC=$?\n"
1280
- f'printf "\\n{marker}%s\\n" "$PWD"\n'
1288
+ f'printf "\\n{marker}%s\\n" {pwd_expr}\n'
1281
1289
  "exit $__EC"
1282
1290
  )
1283
1291
 
@@ -1437,7 +1445,8 @@ class BashBackend:
1437
1445
  before, _, after = merged.rpartition(marker)
1438
1446
  # 末尾的 cwd 标记不再作为 chunk 内容处理
1439
1447
  cleaned = before
1440
- cwd_after = after.strip().splitlines()[0] if after.strip() else None
1448
+ raw_cwd = after.strip().splitlines()[0] if after.strip() else None
1449
+ cwd_after = normalize_subprocess_cwd(raw_cwd)
1441
1450
  if cleaned != merged:
1442
1451
  # 让上游获得去 marker 后的稳定输出
1443
1452
  pass
@@ -1696,8 +1705,7 @@ def _read_fd_safe(fd: int) -> str | None:
1696
1705
  if not chunk:
1697
1706
  break
1698
1707
  data += chunk
1699
- result = data.decode("utf-8", errors="replace").strip()
1700
- return result if result else None
1708
+ return normalize_subprocess_cwd(data.decode("utf-8", errors="replace"))
1701
1709
  except OSError:
1702
1710
  return None
1703
1711
 
@@ -1723,7 +1731,7 @@ def _extract_cwd_from_output(raw_output: str) -> tuple[str, str | None]:
1723
1731
  for line in lines:
1724
1732
  stripped = line.rstrip("\n").rstrip("\r")
1725
1733
  if stripped.startswith(_CWD_SENTINEL):
1726
- cwd_after = stripped[len(_CWD_SENTINEL):]
1734
+ cwd_after = normalize_subprocess_cwd(stripped[len(_CWD_SENTINEL) :])
1727
1735
  else:
1728
1736
  clean_lines.append(line)
1729
1737
 
@@ -19,7 +19,85 @@ cwd_reporter.py — Bash 执行后 cwd 回传(Unix fd3 / Windows 临时文件
19
19
  from __future__ import annotations
20
20
 
21
21
  import os
22
+ import re
23
+ import subprocess
24
+ import sys
22
25
  from pathlib import Path
26
+ from typing import TYPE_CHECKING
27
+
28
+ if TYPE_CHECKING:
29
+ from os import PathLike
30
+
31
+
32
+ def posix_path_to_windows_path(posix_path: str) -> str:
33
+ """
34
+ POSIX → Windows 本机路径(对齐 CC ``posixPathToWindowsPath``)。
35
+
36
+ 覆盖 Git Bash ``/c/...``、Cygwin ``/cygdrive/c/...``、UNC ``//server/share``。
37
+ """
38
+ p = posix_path.strip()
39
+ if not p:
40
+ return p
41
+ if p.startswith("//"):
42
+ return p.replace("/", "\\")
43
+ cyg = re.match(r"^/cygdrive/([A-Za-z])(/|$)", p)
44
+ if cyg:
45
+ letter = cyg.group(1).upper()
46
+ rest = p[len(f"/cygdrive/{cyg.group(1)}") :]
47
+ return letter + ":" + (rest or "\\").replace("/", "\\")
48
+ drive = re.match(r"^/([A-Za-z])(/|$)", p)
49
+ if drive:
50
+ letter = drive.group(1).upper()
51
+ rest = p[2:]
52
+ return letter + ":" + (rest or "\\").replace("/", "\\")
53
+ if p.startswith("/"):
54
+ # /tmp、/usr 等非 MSYS 盘符路径:禁止盲转 ``\tmp``(对齐 CC 仅处理盘符/UNC)
55
+ return p
56
+ return p.replace("/", "\\")
57
+
58
+
59
+ def _cygpath_m(posix_path: str) -> str | None:
60
+ """MSYS ``/tmp`` 等挂载点 → 本机路径(Git Bash ``cygpath -m``)。"""
61
+ try:
62
+ proc = subprocess.run(
63
+ ["cygpath", "-m", posix_path],
64
+ capture_output=True,
65
+ text=True,
66
+ timeout=5,
67
+ check=False,
68
+ )
69
+ except (OSError, subprocess.TimeoutExpired):
70
+ return None
71
+ if proc.returncode != 0:
72
+ return None
73
+ out = (proc.stdout or "").strip()
74
+ return out or None
75
+
76
+
77
+ def normalize_subprocess_cwd(path: str | PathLike[str] | None) -> str | None:
78
+ """
79
+ 将 cwd 规范为当前平台可用于 ``subprocess`` spawn 的本机路径字符串。
80
+
81
+ Windows:把 bash ``pwd -P`` 输出的 POSIX 形态转回 ``C:\\...``;
82
+ Linux/macOS:仅 ``os.fspath`` + strip,不做 MSYS 转换。
83
+ """
84
+ if path is None:
85
+ return None
86
+ s = os.fspath(path).strip()
87
+ if not s:
88
+ return None
89
+ if sys.platform == "win32":
90
+ if len(s) >= 2 and s[1] == ":":
91
+ return os.path.normpath(s)
92
+ converted = posix_path_to_windows_path(s)
93
+ if os.path.isdir(converted):
94
+ return os.path.normpath(converted)
95
+ if s.startswith("/"):
96
+ via_cyg = _cygpath_m(s)
97
+ if via_cyg:
98
+ return os.path.normpath(via_cyg)
99
+ return os.path.normpath(converted)
100
+ return s
23
101
 
24
102
 
25
103
  def bash_single_quoted(s: str) -> str:
@@ -55,7 +133,7 @@ def native_path_for_bash_redirect(win_native: str) -> str:
55
133
 
56
134
 
57
135
  def read_cwd_file(native_path: str) -> str | None:
58
- """读取 `pwd -P` 写入的临时文件,返回最后一行非空 cwd,失败为 None。"""
136
+ """读取 `pwd -P` 写入的临时文件,返回最后一行非空 cwd(本机路径),失败为 None。"""
59
137
  try:
60
138
  raw = Path(native_path).read_text(encoding="utf-8", errors="replace").strip()
61
139
  except OSError:
@@ -63,7 +141,9 @@ def read_cwd_file(native_path: str) -> str | None:
63
141
  if not raw:
64
142
  return None
65
143
  lines = [ln.strip() for ln in raw.splitlines() if ln.strip()]
66
- return lines[-1] if lines else None
144
+ if not lines:
145
+ return None
146
+ return normalize_subprocess_cwd(lines[-1])
67
147
 
68
148
 
69
149
  class BashCwdReporter:
@@ -523,7 +523,9 @@ class BashRuntimeTool(RuntimeTool):
523
523
 
524
524
  # 从 state_bridge / boundary 取 cwd(跨调用持久化;含 session 中可能已失效的路径)
525
525
  cwd = self._get_current_cwd(ctx)
526
- fallback_cwd = ctx.workspace_root or self._initial_cwd or cwd
526
+ fallback_cwd = str(
527
+ ctx.workspace_root or self._initial_cwd or cwd
528
+ )
527
529
 
528
530
  sandbox_decision = self._sandbox_decider.decide(
529
531
  command=command,
@@ -1076,10 +1078,12 @@ def _is_silent_command(command: str) -> bool:
1076
1078
 
1077
1079
 
1078
1080
  def _strip_stream_cwd_marker(stdout: str) -> tuple[str, str | None]:
1081
+ from .cwd_reporter import normalize_subprocess_cwd
1082
+
1079
1083
  marker = "__AGENTX_STREAM_CWD__"
1080
1084
  if marker not in stdout:
1081
1085
  return stdout, None
1082
1086
  before, _, after = stdout.rpartition(marker)
1083
- cwd = after.strip().splitlines()[0] if after.strip() else None
1087
+ raw_cwd = after.strip().splitlines()[0] if after.strip() else None
1084
1088
  cleaned = before.rstrip("\n")
1085
- return cleaned, cwd
1089
+ return cleaned, normalize_subprocess_cwd(raw_cwd)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-agentx-python
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: LangChain/LangGraph-based agent utilities for CodeBaseX.
5
5
  Author-email: GoodMood2008 <GoodMood2008@users.noreply.github.com>
6
6
  License: Apache License
@@ -1,4 +1,4 @@
1
- langchain_agentx/__init__.py,sha256=isKM6mKwr5got1W8U8rGTuYE8gdbN-ezro4rqzuxaAQ,1678
1
+ langchain_agentx/__init__.py,sha256=J0Q1hPGPRMm2f0CenLKa_PzWvhHnACpX3K_s0R4LTH0,1678
2
2
  langchain_agentx/command/__init__.py,sha256=Ej260S5uFQcmEtGZQG_NH7BYjHoStrpBLtGUsBTxyZk,631
3
3
  langchain_agentx/command/context.py,sha256=DIGOGPBw5UGDBm0WNNJlKx1h_9rCRmcdROv4DT61Qug,731
4
4
  langchain_agentx/command/dispatcher.py,sha256=72gRi20Gqv3b96MZygKfJX635AnPcFNuZOOh01n3Wm0,7309
@@ -300,7 +300,7 @@ langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py,sha256=xYVabnY0Qe
300
300
  langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py,sha256=qez3WnzBTagYZMc0qA1mk-jvM2dMy7uCrwZ7aWhEJzw,2094
301
301
  langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py,sha256=lqWlCqucs26fhr4M9LfgY8I0xuNDxwSLkre6XjAJDhA,6877
302
302
  langchain_agentx/tool_runtime/__init__.py,sha256=_Otkq0iN2cAUwWUxwZLwixcgSmpLWqyl0fqul9r56AY,4306
303
- langchain_agentx/tool_runtime/adapter.py,sha256=JI9w-IE4y_xXRwO3kdqM15HstGa709VNN_r2lpy3puQ,29631
303
+ langchain_agentx/tool_runtime/adapter.py,sha256=U1vf3bvbTl7naKmnE1NnVColfoZH5osa29hYD-VjJAE,29719
304
304
  langchain_agentx/tool_runtime/base.py,sha256=AXzRxRQ2sAp5KmYcd6rstVUjfX4HfpUUllSmIAMszGc,19669
305
305
  langchain_agentx/tool_runtime/capabilities.py,sha256=KHwu5KLqrpPKLOTwHhnvIIs9MTmQYeuS2hAFmI_Nsy8,3837
306
306
  langchain_agentx/tool_runtime/errors.py,sha256=Ed1FMjBdPfr0WG-YwLIm_7jokl9k_DjHFNiewZTeQfg,8883
@@ -348,7 +348,7 @@ langchain_agentx/tool_runtime/auto_mode/classifier/schema.py,sha256=hkSIXBtOa60k
348
348
  langchain_agentx/tool_runtime/auto_mode/classifier/transcript.py,sha256=c0pDnHfdUEgtpAAIknCLdPkMYJ9zLdTfHxhuUAmzU8Y,7131
349
349
  langchain_agentx/tool_runtime/execution/__init__.py,sha256=57fsqsMcdOzJCBQdmOx0HXGkcoT5jupt_OFat90G630,1571
350
350
  langchain_agentx/tool_runtime/execution/callback_bridge.py,sha256=puHOfJGSeC62rOJJV2_kuy1EUW0S3LRiikhOxNgH-WQ,5065
351
- langchain_agentx/tool_runtime/execution/engine.py,sha256=w3hZ1-m38D5DibfUTmthoSODnAe8K0Qhwey09yPKX1Q,24166
351
+ langchain_agentx/tool_runtime/execution/engine.py,sha256=muUrbmXsaq3G0pTyV5S95xKd6MvOK8AzOH7teyCb3W4,24412
352
352
  langchain_agentx/tool_runtime/execution/message_adapter.py,sha256=S3Ou4Xzef91rSvSPTAoa_0XeP2U3IF9z50xlNYNKoXk,2368
353
353
  langchain_agentx/tool_runtime/execution/models.py,sha256=5qRiwAmchBh5JBul1PMLC1e3ti2xHxhx6-0blgiEZBM,2252
354
354
  langchain_agentx/tool_runtime/execution/parity.py,sha256=M-mc2tYrMlxSt_MMgz5a5glMBQcx6pCkKUojT_qfasY,6394
@@ -414,11 +414,11 @@ langchain_agentx/tools/ask_user_question/validators.py,sha256=gUYuuKJNrExWGvhFlm
414
414
  langchain_agentx/tools/bash/__init__.py,sha256=molPHFDylE2wWG_aQx7fb0ciAx3wWpdXzIixTZV5ne4,214
415
415
  langchain_agentx/tools/bash/ast_security.py,sha256=-MlvQ559dR3vCactzAEnNAFEJB-QhZDXmDIqpPPpqR0,20874
416
416
  langchain_agentx/tools/bash/auto_mode_adapter.py,sha256=bKnIDeUrCLQPaWD-lz0d7Lle-Knyj3Sv72DFgktGuAo,4243
417
- langchain_agentx/tools/bash/backend.py,sha256=Eyn_pN3mP7grjIQ11Q0xDj2gGa0Y67Q4i9y6zYfqQBg,61738
417
+ langchain_agentx/tools/bash/backend.py,sha256=S3bp6nGrWrgKn4g9W6nAHaKRlz8amXKGN7fhajKZmg8,62289
418
418
  langchain_agentx/tools/bash/bash_hardening.py,sha256=xtOxaALegk0VQYjpzKNP-UG77vpjno6aLJnUOw256Yw,29342
419
419
  langchain_agentx/tools/bash/bash_runtime_contract.py,sha256=s8vrvMl7405KLGw2hPFbzlzWohm5JX3c9cImMBgqh8I,1511
420
420
  langchain_agentx/tools/bash/boundary_check.py,sha256=RjO445ksF_WbUNjBs65wTvCVu98GUIKcUvUOP-bvN9k,6216
421
- langchain_agentx/tools/bash/cwd_reporter.py,sha256=-Y8kGT9S9igP6If2EK2O8RL_-DRwGAc0EvAc-J6Oox0,3244
421
+ langchain_agentx/tools/bash/cwd_reporter.py,sha256=dcpHOz2Q69LERf7ZKsauZ6eRTcHmmoyCPYt7PRbgFfk,5769
422
422
  langchain_agentx/tools/bash/events.py,sha256=xDpPA3zhne2cnlYTmiiPTsV423-h-Nw-VKDR_rlnbGE,6065
423
423
  langchain_agentx/tools/bash/limits.py,sha256=pXLD1xf-ygIbUsbsUjCTGFCdPTc3KdhP1qvnzpw6Yho,2701
424
424
  langchain_agentx/tools/bash/mode_validation.py,sha256=wmd7WWdZPoPn7WzX4QO3NdbNxhyQu9vJTk1cc8UsNyk,11969
@@ -438,7 +438,7 @@ langchain_agentx/tools/bash/semantics.py,sha256=gLmyO9UFN2UhPuS5-DfRYspgsNNtDy8j
438
438
  langchain_agentx/tools/bash/shell_locator.py,sha256=AUnCZS8gmZurCs8rL4y0MMOS9UZimfg5rQ5At38mZKU,6296
439
439
  langchain_agentx/tools/bash/shell_quoting.py,sha256=K4JLVGwU8VpdQzfQB1o3xsq4HoCKHew1pry8hHChcoA,4498
440
440
  langchain_agentx/tools/bash/task_runtime.py,sha256=hikVC9YugtvpLf4nDaPlX_GFem1YQvDRwN0ShRflUYU,3568
441
- langchain_agentx/tools/bash/tool.py,sha256=wY6iUPCvqbq7qsItteumr6KQ9RMwOiKrVUzjGPVzwH4,40973
441
+ langchain_agentx/tools/bash/tool.py,sha256=ueJrTIkxuHWsraV_CkVeIoJoxSUjTQrAfAlK1zuyCyE,41090
442
442
  langchain_agentx/tools/bash/windows_shell_quoting.py,sha256=0RzLnTKyFrJ536QzqPZvSyUcQWN794XRlAq2RdHeKHo,1615
443
443
  langchain_agentx/tools/edit/__init__.py,sha256=PiiOHUODUzTRQxxPdE7ZMTjd3vfdzm9LEcn1lpVtg8o,203
444
444
  langchain_agentx/tools/edit/backend.py,sha256=JrikYafZDGMQ2JjXRxbnv1VSHVGRUdLmZN__nnqkM9o,3159
@@ -574,8 +574,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
574
574
  langchain_agentx/workspace/tool_boundary.py,sha256=3b4KwMrGhsS_HZaoCjytJJk39pN0lN3nvYQIj1vLD1k,3327
575
575
  langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
576
576
  langchain_agentx/workspace/view.py,sha256=Ip02CZNI-ZjF5k0OYT3q7RUR_auMEw83VJ6rQsaBcYU,4588
577
- langchain_agentx_python-1.0.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
578
- langchain_agentx_python-1.0.2.dist-info/METADATA,sha256=_QHAcdzO9t0ai3qjEo28Q5MmglUJrrM4pX_6Ajz978c,24846
579
- langchain_agentx_python-1.0.2.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
580
- langchain_agentx_python-1.0.2.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
581
- langchain_agentx_python-1.0.2.dist-info/RECORD,,
577
+ langchain_agentx_python-1.0.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
578
+ langchain_agentx_python-1.0.3.dist-info/METADATA,sha256=J9hc1apzuQ1_FCFH-H0_L7QSPavIfz4oesfV6bemikI,24846
579
+ langchain_agentx_python-1.0.3.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
580
+ langchain_agentx_python-1.0.3.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
581
+ langchain_agentx_python-1.0.3.dist-info/RECORD,,