langchain-agentx-python 2.3.9__py3-none-any.whl → 2.4.0__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__ = "2.3.9"
14
+ __version__ = "2.4.0"
15
15
 
16
16
  from .mcp.call import McpToolCaller
17
17
  from .mcp.client_manager import McpClientManager, McpConnectedClient
@@ -13,6 +13,8 @@ run_context.py — Workflow/Agent 结构化并发上下文(parent → child ca
13
13
  P1:reset 失败时在仍可见 owner 的 derived logical context 内 force-clear;
14
14
  宿主 detached ingest 用 create_task_with_copied_context / run_context_scope。
15
15
  nested Parallelization:child_contexts + 递归 cancel_tree;W4 可用 use_cancel_authority。
16
+ agent loop 登记:供 skill fork 同步桥 ``run_coroutine_threadsafe`` 解析父 loop
17
+ (ContextVar 在 ThreadPoolExecutor 上常丢失,故用进程级 stack)。
16
18
  """
17
19
 
18
20
  from __future__ import annotations
@@ -34,6 +36,81 @@ _current_run_context: ContextVar[Optional["RunContext"]] = ContextVar(
34
36
  "run_ctx", default=None
35
37
  )
36
38
 
39
+ # 进程级父 agent loop / RunContext 栈:ThreadPoolExecutor worker 看不见 ContextVar,
40
+ # 须 threadsafe 回父 loop(bug4)并恢复 owner 供 cancel 登记(skill-fork cancel orphan)。
41
+ _agent_loop_lock = threading.Lock()
42
+ _agent_loop_stack: list[asyncio.AbstractEventLoop] = []
43
+ _agent_run_ctx_stack: list["RunContext"] = []
44
+
45
+
46
+ def register_agent_loop(loop: asyncio.AbstractEventLoop) -> None:
47
+ """登记当前存活的 agent/session event loop(可嵌套;同 loop 可重复 push)。"""
48
+ with _agent_loop_lock:
49
+ _agent_loop_stack.append(loop)
50
+
51
+
52
+ def unregister_agent_loop(loop: asyncio.AbstractEventLoop) -> None:
53
+ """弹出最近一次与 ``loop`` 相同的登记(LIFO;未见则 no-op)。"""
54
+ with _agent_loop_lock:
55
+ for index in range(len(_agent_loop_stack) - 1, -1, -1):
56
+ if _agent_loop_stack[index] is loop:
57
+ del _agent_loop_stack[index]
58
+ return
59
+
60
+
61
+ def resolve_agent_loop(
62
+ *,
63
+ owner: RunContext | None = None,
64
+ ) -> asyncio.AbstractEventLoop | None:
65
+ """解析仍在跑的父 agent loop;优先 ``owner.root_task``,否则栈顶存活项。"""
66
+ if owner is not None:
67
+ try:
68
+ loop = owner.root_task.get_loop()
69
+ if not loop.is_closed() and loop.is_running():
70
+ return loop
71
+ except Exception:
72
+ pass
73
+ with _agent_loop_lock:
74
+ for loop in reversed(_agent_loop_stack):
75
+ try:
76
+ if not loop.is_closed() and loop.is_running():
77
+ return loop
78
+ except Exception:
79
+ continue
80
+ return None
81
+
82
+
83
+ def resolve_owner_run_context(
84
+ owner: RunContext | None = None,
85
+ ) -> RunContext | None:
86
+ """解析取消挂接用的 owner:显式参数 → ContextVar → 进程级 install 栈顶。"""
87
+ if owner is not None:
88
+ return owner
89
+ current = get_run_context()
90
+ if current is not None:
91
+ return current
92
+ with _agent_loop_lock:
93
+ if _agent_run_ctx_stack:
94
+ return _agent_run_ctx_stack[-1]
95
+ return None
96
+
97
+
98
+ async def run_under_owner_run_context(
99
+ coro: Awaitable[T],
100
+ *,
101
+ owner: RunContext | None,
102
+ ) -> T:
103
+ """在指定 owner 的 ContextVar 下 await(threadsafe 桥恢复父 RunContext)。"""
104
+ if owner is None:
105
+ return await coro
106
+ if get_run_context() is owner:
107
+ return await coro
108
+ token = _current_run_context.set(owner)
109
+ try:
110
+ return await coro
111
+ finally:
112
+ _current_run_context.reset(token)
113
+
37
114
 
38
115
  @dataclass
39
116
  class RunContext:
@@ -50,6 +127,12 @@ class RunContext:
50
127
  # D-EVT-6:宿主在 cancel_tree / killAll 时写入;finish 投影读取;缺省 unknown。
51
128
  # 取值:escape | workflow_stop | kill_agents | unknown;禁止从「是否有后台」猜测。
52
129
  cancel_source: str | None = None
130
+ # install 时若成功 register_agent_loop,记下以便 reset 精确 unregister(勿误弹外层)。
131
+ _registered_agent_loop: asyncio.AbstractEventLoop | None = field(
132
+ default=None, repr=False, compare=False
133
+ )
134
+ # install 时是否 push 进进程级 RunContext 栈(供 worker 解析 owner)。
135
+ _registered_on_ctx_stack: bool = field(default=False, repr=False, compare=False)
53
136
 
54
137
  def register_child(self, task: asyncio.Task) -> None:
55
138
  self.child_tasks.add(task)
@@ -167,6 +250,18 @@ def install_run_context(
167
250
  parent.register_child(ctx.root_task)
168
251
  parent.register_child_context(ctx)
169
252
  token = _current_run_context.set(ctx)
253
+ try:
254
+ loop = ctx.root_task.get_loop()
255
+ register_agent_loop(loop)
256
+ ctx._registered_agent_loop = loop
257
+ except Exception:
258
+ logger.debug(
259
+ "register_agent_loop skipped at install_run_context",
260
+ exc_info=True,
261
+ )
262
+ with _agent_loop_lock:
263
+ _agent_run_ctx_stack.append(ctx)
264
+ ctx._registered_on_ctx_stack = True
170
265
  return ctx, token
171
266
 
172
267
 
@@ -177,33 +272,53 @@ def reset_run_context(token: Token, *, owner: RunContext | None = None) -> None:
177
272
  P1:若当前 logical context 仍可见 ``owner``(derived context 继承值但 token 无效),
178
273
  显式 ``set(None)`` 清理本层,避免 workflow finally 后 run_ctx 悬挂。
179
274
  """
275
+ source = owner if owner is not None else get_run_context()
276
+ loop_to_unreg: asyncio.AbstractEventLoop | None = None
277
+ ctx_to_unreg: RunContext | None = None
278
+ if source is not None and source._registered_agent_loop is not None:
279
+ loop_to_unreg = source._registered_agent_loop
280
+ source._registered_agent_loop = None
281
+ if source is not None and source._registered_on_ctx_stack:
282
+ ctx_to_unreg = source
283
+ source._registered_on_ctx_stack = False
284
+
180
285
  try:
181
- _current_run_context.reset(token)
182
- return
183
- except ValueError:
184
- pass
286
+ try:
287
+ _current_run_context.reset(token)
288
+ return
289
+ except ValueError:
290
+ pass
291
+
292
+ current = get_run_context()
293
+ if owner is not None and current is owner:
294
+ _current_run_context.set(None)
295
+ logger.warning(
296
+ "run_ctx force-cleared: token reset failed in derived logical context "
297
+ "(task=%r)",
298
+ owner.root_task.get_name(),
299
+ )
300
+ return
185
301
 
186
- current = get_run_context()
187
- if owner is not None and current is owner:
188
- _current_run_context.set(None)
189
- logger.warning(
190
- "run_ctx force-cleared: token reset failed in derived logical context "
191
- "(task=%r)",
192
- owner.root_task.get_name(),
193
- )
194
- return
302
+ if owner is not None and owner.root_task.done():
303
+ logger.debug(
304
+ "run_ctx reset skipped: owner task %r already done; context discarded",
305
+ owner.root_task.get_name(),
306
+ )
307
+ return
195
308
 
196
- if owner is not None and owner.root_task.done():
197
- logger.debug(
198
- "run_ctx reset skipped: owner task %r already done; context discarded",
199
- owner.root_task.get_name(),
309
+ logger.warning(
310
+ "run_ctx reset skipped: token created in a different context",
311
+ exc_info=True,
200
312
  )
201
- return
202
-
203
- logger.warning(
204
- "run_ctx reset skipped: token created in a different context",
205
- exc_info=True,
206
- )
313
+ finally:
314
+ if loop_to_unreg is not None:
315
+ unregister_agent_loop(loop_to_unreg)
316
+ if ctx_to_unreg is not None:
317
+ with _agent_loop_lock:
318
+ for index in range(len(_agent_run_ctx_stack) - 1, -1, -1):
319
+ if _agent_run_ctx_stack[index] is ctx_to_unreg:
320
+ del _agent_run_ctx_stack[index]
321
+ break
207
322
 
208
323
 
209
324
  @asynccontextmanager
@@ -11,6 +11,7 @@ loop_driver.py — Loop-aware shared stream driver.
11
11
  root closeout 捕获、synthetic error/finish、orphan tool_result repair(stream surface)。
12
12
  正常结束但缺 root on_chain_end(LangGraph) 时打 anomaly WARNING(不伪造 synthetic finish)。
13
13
  R1 反应恢复:单次 drain 内 budgeted continue 重入(S2,对齐 CC queryLoop continue)。
14
+ 进入/退出时登记 agent loop,供 Ask 路径无 Workflow RunContext 时 fork 同步桥解析。
14
15
  """
15
16
 
16
17
  from __future__ import annotations
@@ -22,6 +23,7 @@ from typing import Any, AsyncIterator, Literal
22
23
  from langgraph.errors import GraphRecursionError
23
24
 
24
25
  from langchain_agentx.loop.loop_abort import LoopAbortSignal, is_cancel_requested
26
+ from langchain_agentx.loop.run_context import register_agent_loop, unregister_agent_loop
25
27
  from ..exit.reason_codes import (
26
28
  TERMINAL_REASON_ABORTED_STREAMING,
27
29
  TERMINAL_REASON_ABORTED_TOOLS,
@@ -84,6 +86,8 @@ class LoopStreamDriver:
84
86
  self.result.reactive_continue_drains = 0
85
87
  current_input = dict(graph_input)
86
88
  reactive_continues = 0
89
+ agent_loop = asyncio.get_running_loop()
90
+ register_agent_loop(agent_loop)
87
91
  try:
88
92
  while True:
89
93
  async for event in self._stream_single_drain(
@@ -113,6 +117,7 @@ class LoopStreamDriver:
113
117
  (self.result.graph_output or {}).get("transition", {}).get("reason"),
114
118
  )
115
119
  finally:
120
+ unregister_agent_loop(agent_loop)
116
121
  self._log_stream_closeout_summary()
117
122
 
118
123
  async def _stream_single_drain(
@@ -666,6 +666,7 @@ class SubagentOrchestrator:
666
666
  loader=child_loader,
667
667
  model=runtime_ctx.model,
668
668
  allowed_tool_names=allowed_tool_names,
669
+ owner_run_ctx=get_run_context(),
669
670
  )
670
671
 
671
672
  return AgentToolOutput(
@@ -1015,13 +1016,17 @@ class SubagentOrchestrator:
1015
1016
  model: Any,
1016
1017
  allowed_tool_names: set[str],
1017
1018
  ) -> Any:
1018
- owner_run_ctx = get_run_context()
1019
+ from langchain_agentx.loop.run_context import resolve_owner_run_context
1020
+
1021
+ # worker 上 ContextVar 常空;进程级 install 栈仍可解析父 RunContext(cancel 挂接)。
1022
+ owner_run_ctx = resolve_owner_run_context()
1019
1023
  return _run_coroutine_sync(
1020
1024
  run_subagent(
1021
1025
  ctx=subagent_ctx,
1022
1026
  loader=loader,
1023
1027
  model=model,
1024
1028
  allowed_tool_names=allowed_tool_names,
1029
+ owner_run_ctx=owner_run_ctx,
1025
1030
  ),
1026
1031
  owner_run_ctx=owner_run_ctx,
1027
1032
  )
@@ -119,23 +119,43 @@ def _is_abort_like_terminal_reason(reason: str | None) -> bool:
119
119
 
120
120
 
121
121
  def _run_coroutine_sync(coro: Any, *, owner_run_ctx: Any | None = None) -> Any:
122
- """在无 running loop 的同步上下文用 asyncio.run 跑协程。
123
-
124
- P0(Doc-35):已有 running loop 时禁止 Thread+asyncio.run(C2);须改走
125
- ``await`` / ``ainvoke_subagent``。``owner_run_ctx`` 保留签名兼容,有 loop 时不再使用。
122
+ """同步桥:无当前线程 running loop 时跑协程。
123
+
124
+ - 当前线程已有 running loop:禁止阻塞桥接(会死锁)→ 须 ``await`` /
125
+ ``ainvoke_subagent``(Doc-35 / C2)。
126
+ - 进程内已有存活的父 agent loop(``resolve_agent_loop``):
127
+ ``run_coroutine_threadsafe`` 提交回父 loop(skill fork 对齐 CC 同 loop;
128
+ 避免 worker ``asyncio.run`` 新 loop + 共享 httpx 池毒化,bug4);
129
+ 并 ``run_under_owner_run_context`` 恢复父 RunContext(cancel 登记,bug cancel-orphan)。
130
+ - 否则:``asyncio.run``(纯同步脚本、无父会话)。
126
131
  """
127
- del owner_run_ctx # C2 删除后同步路径不再登记 worker thread
128
132
  try:
129
133
  asyncio.get_running_loop()
130
134
  except RuntimeError:
131
- return asyncio.run(coro)
135
+ pass
136
+ else:
137
+ raise RuntimeError(
138
+ "cannot run subagent coroutine via thread while an asyncio event loop "
139
+ "is running (C2 removed, Doc-35 P0). Use await run_subagent / "
140
+ "ainvoke_subagent on the same loop instead of sync invoke."
141
+ )
132
142
 
133
- raise RuntimeError(
134
- "cannot run subagent coroutine via thread while an asyncio event loop "
135
- "is running (C2 removed, Doc-35 P0). Use await run_subagent / "
136
- "ainvoke_subagent on the same loop instead of sync invoke."
143
+ from langchain_agentx.loop.run_context import (
144
+ resolve_agent_loop,
145
+ resolve_owner_run_context,
146
+ run_under_owner_run_context,
137
147
  )
138
148
 
149
+ owner = resolve_owner_run_context(owner_run_ctx)
150
+ target = resolve_agent_loop(owner=owner)
151
+ if target is not None and target.is_running():
152
+ return asyncio.run_coroutine_threadsafe(
153
+ run_under_owner_run_context(coro, owner=owner),
154
+ target,
155
+ ).result()
156
+
157
+ return asyncio.run(coro)
158
+
139
159
 
140
160
  @dataclass
141
161
  class BackgroundLaunchedResult:
@@ -815,7 +835,12 @@ async def run_subagent(
815
835
  ),
816
836
  name=f"agentx-subagent-fg-{ctx.agent_id or 'anon'}",
817
837
  )
818
- register_spawned_task(task, owner=owner_run_ctx)
838
+ # Skill fork / nested Parallelization:挂 cancel authority(L0),对齐 D-REG-1。
839
+ register_spawned_task(
840
+ task,
841
+ owner=owner_run_ctx,
842
+ use_cancel_authority=True,
843
+ )
819
844
  return await task
820
845
  return await _run_subagent_body(
821
846
  ctx=ctx,
@@ -219,6 +219,18 @@ class BashPermissionSegmenter:
219
219
  def __init__(self, node_helper: BashAstNodeHelper) -> None:
220
220
  self._node_helper = node_helper
221
221
 
222
+ # 控制结构整段保留:避免 for/while 的变量名与 in-list 词被拆成伪 command 段
223
+ # (否则 ``for i in 1 2; do echo; done`` → segments ``i``/``1``/``2``…,
224
+ # 触发 bash_passthrough ask 误阻只读循环)。
225
+ _ATOMIC_CONTROL_TYPES = frozenset({
226
+ "for_statement",
227
+ "while_statement",
228
+ "until_statement",
229
+ "if_statement",
230
+ "case_statement",
231
+ "function_definition",
232
+ })
233
+
222
234
  def collect_segments(self, node: Node) -> list[str]:
223
235
  if node.type == "program":
224
236
  return self._collect_from_container(node)
@@ -226,7 +238,13 @@ class BashPermissionSegmenter:
226
238
  if node.type in {"pipeline", "list"}:
227
239
  return self._collect_from_container(node)
228
240
 
229
- if node.type in {"command", "redirected_statement", "subshell", "compound_statement"}:
241
+ if node.type in {
242
+ "command",
243
+ "redirected_statement",
244
+ "subshell",
245
+ "compound_statement",
246
+ *self._ATOMIC_CONTROL_TYPES,
247
+ }:
230
248
  text = self._node_helper.text(node).strip()
231
249
  return [text] if text else []
232
250
 
@@ -10,6 +10,8 @@ tools/bash/command_allow_rules.py — Bash 内容级 allow 匹配(slash comman
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import re
14
+ import shlex
13
15
  from typing import TYPE_CHECKING
14
16
 
15
17
  from langchain_agentx.tools.bash.shell_rule_matching import match_shell_permission_rule
@@ -29,6 +31,8 @@ from langchain_agentx.tool_runtime.policy import PolicyEngine, resolve_default_p
29
31
  if TYPE_CHECKING:
30
32
  from langchain_agentx.tool_runtime.policy import DefaultPolicyEngine
31
33
 
34
+ _ENV_ASSIGNMENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=.*$")
35
+
32
36
 
33
37
  def bash_command_matches_allow_rule(command: str, rule_content: str) -> bool:
34
38
  """CC shellRuleMatching(prefix / wildcard / exact)。"""
@@ -105,6 +109,27 @@ def registry_has_cliarg_bash_allow(registry: PermissionRuleRegistry) -> bool:
105
109
  return False
106
110
 
107
111
 
112
+ def _is_pure_cd_segment(command: str) -> bool:
113
+ """``cd`` / ``cd <path>``(可带前导 ``VAR=``)不 promote。
114
+
115
+ G4:``cd`` 本身非只读,但段级静默 allow 是 cwd 语义的必要通路;
116
+ 若 promote 为 ask,会抢在 ``compound_cd_write`` / 后续只读段之前,
117
+ 导致 ``cd && touch`` / ``cd && pwd`` / session cwd 合并用例被 headless 误阻。
118
+ """
119
+ try:
120
+ tokens = shlex.split(command.strip())
121
+ except ValueError:
122
+ tokens = command.strip().split()
123
+ index = 0
124
+ while index < len(tokens) and _ENV_ASSIGNMENT_RE.match(tokens[index]):
125
+ index += 1
126
+ rest = tokens[index:]
127
+ if not rest or rest[0].rsplit("/", 1)[-1] != "cd":
128
+ return False
129
+ # 段级调用不应含 list/pipe;若整串被误传,拒绝当作 pure cd
130
+ return not any(tok in {"&&", "||", "|", ";"} for tok in rest)
131
+
132
+
108
133
  def promote_unscoped_bash_passthrough_to_ask(
109
134
  decision: AuthorizationDecision,
110
135
  *,
@@ -126,6 +151,8 @@ def promote_unscoped_bash_passthrough_to_ask(
126
151
  shell_ops_already_resolved=True,
127
152
  ).is_read_only:
128
153
  return decision
154
+ if _is_pure_cd_segment(command):
155
+ return decision
129
156
  # 显式规则 / mode / safety 等已标 policy_id 的 allow 不降级
130
157
  if decision.policy_id not in (None, "", "write_roots"):
131
158
  return decision
@@ -36,6 +36,10 @@ class BashReadOnlyClassifier:
36
36
  def __init__(self, *, host_detector: HostPlatformDetector | None = None) -> None:
37
37
  self._host = host_detector or default_host_platform_detector()
38
38
 
39
+ # 前导 ``VAR=value``(与 bash_hardening.BashCommandCanonicalizer 同形),
40
+ # 避免 ``LANG=C git status`` 被误判为未知二进制 ``LANG=C``。
41
+ _ENV_ASSIGNMENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=.*$")
42
+
39
43
  # ------------------------------------------------------------------
40
44
  # 大 allowlist:常见只读/观测类命令(不含必然写盘的工具如 tee/dd of=)
41
45
  # ------------------------------------------------------------------
@@ -52,7 +56,8 @@ class BashReadOnlyClassifier:
52
56
  "zipinfo",
53
57
  "nl", "cut", "tr", "uniq", "expand", "unexpand", "fold", "fmt", "pr", "rev", "tac",
54
58
  "look", "basename", "dirname", "readlink", "realpath",
55
- "true", "false", "test", "expr", "seq",
59
+ # exit/sleep:进程控制/等待,无写盘;与 true/false 同属只读观测/控制面
60
+ "true", "false", "exit", "sleep", "test", "expr", "seq",
56
61
  "iconv", "locale", "locales", "getconf",
57
62
  "sw_vers", "sysctl", "system_profiler", "pbpaste",
58
63
  "ver",
@@ -137,7 +142,7 @@ class BashReadOnlyClassifier:
137
142
  if not shell_ops_already_resolved and self._shell_compound_or_pipeline(command):
138
143
  return BashReadOnlyClassification(False, "shell_compound_or_pipeline")
139
144
 
140
- tokens = self._split(command)
145
+ tokens = self._strip_leading_assignments(self._split(command))
141
146
  if not tokens:
142
147
  return BashReadOnlyClassification(False, "empty_command")
143
148
 
@@ -146,6 +151,9 @@ class BashReadOnlyClassifier:
146
151
  if base in {"cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh", "pwsh.exe"}:
147
152
  return BashReadOnlyClassification(False, f"{base}_shell_not_classified")
148
153
 
154
+ if base in {"for", "while", "until", "if", "case"}:
155
+ return self._classify_control_structure_body(command, base)
156
+
149
157
  if base == "git":
150
158
  return self._classify_git(tokens)
151
159
 
@@ -218,6 +226,30 @@ class BashReadOnlyClassifier:
218
226
 
219
227
  return BashReadOnlyClassification(False, f"{base}_unknown")
220
228
 
229
+ def _classify_control_structure_body(
230
+ self,
231
+ command: str,
232
+ base: str,
233
+ ) -> BashReadOnlyClassification:
234
+ """``for``/``while``/``if``/``case``:体中 simple command 全只读则整段只读。"""
235
+ from .ast_security import BashAstAnalyzer
236
+
237
+ analysis = BashAstAnalyzer().analyze(command)
238
+ body_cmds = analysis.simple_commands
239
+ if not body_cmds:
240
+ return BashReadOnlyClassification(False, f"{base}_empty_body")
241
+ for sc in body_cmds:
242
+ text = (sc.text or "").strip()
243
+ if not text:
244
+ continue
245
+ sub = self.classify(text, shell_ops_already_resolved=True)
246
+ if not sub.is_read_only:
247
+ return BashReadOnlyClassification(
248
+ False,
249
+ f"{base}_body_{sub.reason or 'not_readonly'}",
250
+ )
251
+ return BashReadOnlyClassification(True, f"{base}_readonly_body")
252
+
221
253
  @staticmethod
222
254
  def _shell_compound_or_pipeline(command: str) -> bool:
223
255
  """单 segment 内的 `|` / `&&` / `||` 保守视为非只读(不含 `;`,避免 awk/sed 脚本误判)。"""
@@ -551,6 +583,12 @@ class BashReadOnlyClassifier:
551
583
  except ValueError:
552
584
  return command.split()
553
585
 
586
+ def _strip_leading_assignments(self, tokens: list[str]) -> list[str]:
587
+ index = 0
588
+ while index < len(tokens) and self._ENV_ASSIGNMENT_RE.match(tokens[index]):
589
+ index += 1
590
+ return tokens[index:]
591
+
554
592
  @staticmethod
555
593
  def _basename(token: str) -> str:
556
594
  return token.rsplit("/", 1)[-1]
@@ -5,8 +5,12 @@ tools/skill/fork.py — Skill fork 执行(对齐 CC executeForkedSkill)
5
5
  frontmatter ``context: fork`` 时在隔离子 loop 中执行 skill 正文,
6
6
  父 loop 仅收到摘要结果(不注入 meta HumanMessage / 父级 contextModifier)。
7
7
 
8
+ 链路位置:
9
+ SkillRuntimeTool.ainvoke(主路径 · C1)→ aexecute → ainvoke_subagent;
10
+ SkillRuntimeTool.invoke(纯 sync)→ execute → invoke_subagent(force_sync)。
11
+
8
12
  CC 对照:
9
- SkillTool.executeForkedSkill + prepareForkedCommandContext + runAgent(isAsync=false)
13
+ SkillTool.executeForkedSkill(async)+ runAgent(isAsync=false) 共享父 Abort。
10
14
  """
11
15
 
12
16
  from __future__ import annotations
@@ -16,7 +20,7 @@ from typing import Any
16
20
  from langchain_agentx.tool_runtime.loop_runtime_context import resolve_skill_model_override
17
21
  from langchain_agentx.tool_runtime.models import ToolExecutionContext
18
22
  from langchain_agentx.tools.agent.builtin_subagent_loader import DefaultSubagentRegistryProvider
19
- from langchain_agentx.tools.agent.models import AgentToolInput
23
+ from langchain_agentx.tools.agent.models import AgentToolInput, AgentToolOutput
20
24
  from langchain_agentx.tools.agent.registry.config import SubagentConfig
21
25
  from langchain_agentx.tools.agent.registry.registry import SubagentRegistry
22
26
 
@@ -31,7 +35,7 @@ def is_fork_execution_context(frontmatter: dict[str, Any]) -> bool:
31
35
 
32
36
 
33
37
  class SkillForkExecutor:
34
- """在子 agent loop 中同步执行 fork skill。"""
38
+ """在子 agent loop 中执行 fork skill(首选 async / C1)。"""
35
39
 
36
40
  def __init__(self, registry: SubagentRegistry | None = None) -> None:
37
41
  self._registry_override = registry
@@ -49,17 +53,14 @@ class SkillForkExecutor:
49
53
  return self._registry_override
50
54
  return DefaultSubagentRegistryProvider.get()
51
55
 
52
- def execute(
56
+ def _build_agent_input(
53
57
  self,
54
58
  *,
55
59
  cmd: SkillCommand,
56
60
  content: str,
57
61
  ctx: ToolExecutionContext,
58
62
  model_override: str | None,
59
- effort_override: str | None,
60
- allowed_tools: list[str],
61
- constraints_applied: dict[str, Any],
62
- ) -> SkillToolOutput:
63
+ ) -> tuple[AgentToolInput, SubagentConfig, str]:
63
64
  subagent_type = self._resolve_subagent_type(cmd.frontmatter)
64
65
  subagent_config = self._resolve_subagent_config(subagent_type)
65
66
  resolved_model = (
@@ -74,17 +75,20 @@ class SkillForkExecutor:
74
75
  model=resolved_model,
75
76
  run_in_background=False,
76
77
  )
77
- agent_out = self._get_orchestrator().invoke_subagent(
78
- inp,
79
- ctx,
80
- subagent_config,
81
- skill_allowed_tools=allowed_tools or None,
82
- skill_effort_override=effort_override,
83
- skill_hook_frontmatter=cmd.frontmatter,
84
- skill_hook_root=cmd.path.parent,
85
- skill_hook_name=cmd.name,
86
- force_sync=True,
87
- )
78
+ return inp, subagent_config, subagent_type
79
+
80
+ def _to_skill_output(
81
+ self,
82
+ *,
83
+ cmd: SkillCommand,
84
+ content: str,
85
+ model_override: str | None,
86
+ effort_override: str | None,
87
+ allowed_tools: list[str],
88
+ constraints_applied: dict[str, Any],
89
+ agent_out: AgentToolOutput,
90
+ subagent_type: str,
91
+ ) -> SkillToolOutput:
88
92
  result_text = (agent_out.content or "").strip() or "Skill execution completed"
89
93
  return SkillToolOutput(
90
94
  skill_name=cmd.name,
@@ -105,6 +109,88 @@ class SkillForkExecutor:
105
109
  },
106
110
  )
107
111
 
112
+ async def aexecute(
113
+ self,
114
+ *,
115
+ cmd: SkillCommand,
116
+ content: str,
117
+ ctx: ToolExecutionContext,
118
+ model_override: str | None,
119
+ effort_override: str | None,
120
+ allowed_tools: list[str],
121
+ constraints_applied: dict[str, Any],
122
+ ) -> SkillToolOutput:
123
+ """异步 fork(对齐 CC executeForkedSkill):同 loop await,可被 cancel_tree 停住。"""
124
+ inp, subagent_config, subagent_type = self._build_agent_input(
125
+ cmd=cmd,
126
+ content=content,
127
+ ctx=ctx,
128
+ model_override=model_override,
129
+ )
130
+ # force_sync=True ≡ CC isAsync:false(禁止后台);ainvoke_subagent 仍同 loop await(C1),
131
+ # 不会走 Thread+_run_coroutine_sync。
132
+ agent_out = await self._get_orchestrator().ainvoke_subagent(
133
+ inp,
134
+ ctx,
135
+ subagent_config,
136
+ skill_allowed_tools=allowed_tools or None,
137
+ skill_effort_override=effort_override,
138
+ skill_hook_frontmatter=cmd.frontmatter,
139
+ skill_hook_root=cmd.path.parent,
140
+ skill_hook_name=cmd.name,
141
+ force_sync=True,
142
+ )
143
+ return self._to_skill_output(
144
+ cmd=cmd,
145
+ content=content,
146
+ model_override=model_override,
147
+ effort_override=effort_override,
148
+ allowed_tools=allowed_tools,
149
+ constraints_applied=constraints_applied,
150
+ agent_out=agent_out,
151
+ subagent_type=subagent_type,
152
+ )
153
+
154
+ def execute(
155
+ self,
156
+ *,
157
+ cmd: SkillCommand,
158
+ content: str,
159
+ ctx: ToolExecutionContext,
160
+ model_override: str | None,
161
+ effort_override: str | None,
162
+ allowed_tools: list[str],
163
+ constraints_applied: dict[str, Any],
164
+ ) -> SkillToolOutput:
165
+ """同步 fork(无父 loop / 纯 sync 调用方);有父会话时应走 ``aexecute``。"""
166
+ inp, subagent_config, subagent_type = self._build_agent_input(
167
+ cmd=cmd,
168
+ content=content,
169
+ ctx=ctx,
170
+ model_override=model_override,
171
+ )
172
+ agent_out = self._get_orchestrator().invoke_subagent(
173
+ inp,
174
+ ctx,
175
+ subagent_config,
176
+ skill_allowed_tools=allowed_tools or None,
177
+ skill_effort_override=effort_override,
178
+ skill_hook_frontmatter=cmd.frontmatter,
179
+ skill_hook_root=cmd.path.parent,
180
+ skill_hook_name=cmd.name,
181
+ force_sync=True,
182
+ )
183
+ return self._to_skill_output(
184
+ cmd=cmd,
185
+ content=content,
186
+ model_override=model_override,
187
+ effort_override=effort_override,
188
+ allowed_tools=allowed_tools,
189
+ constraints_applied=constraints_applied,
190
+ agent_out=agent_out,
191
+ subagent_type=subagent_type,
192
+ )
193
+
108
194
  def _resolve_subagent_type(self, frontmatter: dict[str, Any]) -> str:
109
195
  raw = frontmatter.get("agent")
110
196
  if isinstance(raw, str) and raw.strip():
@@ -19,6 +19,7 @@ CC 对照:
19
19
 
20
20
  from __future__ import annotations
21
21
 
22
+ import asyncio
22
23
  from pathlib import Path
23
24
  from typing import Any
24
25
 
@@ -299,6 +300,57 @@ class SkillRuntimeTool(RuntimeTool):
299
300
  constraints_applied=constraints_applied,
300
301
  )
301
302
 
303
+ async def ainvoke(self, data: dict[str, Any], ctx: ToolExecutionContext) -> SkillToolOutput:
304
+ """异步执行;``context: fork`` 走 C1(同 loop await),对齐 CC executeForkedSkill。
305
+
306
+ 非 fork 仍委托默认同步 ``invoke``(经 executor),避免重复展开逻辑。
307
+ """
308
+ inp = SkillToolInput.model_validate(data)
309
+ cmd = self._resolve_skill_command(inp.skill_name, ctx)
310
+ if cmd is None:
311
+ raise ValueError(f"Unknown skill: {inp.skill_name}")
312
+ if not is_fork_execution_context(cmd.frontmatter):
313
+ loop = asyncio.get_running_loop()
314
+ return await loop.run_in_executor(None, lambda: self.invoke(data, ctx))
315
+
316
+ dynamic = get_dynamic_skill_catalog(
317
+ ctx.session_store,
318
+ agent_home_segment=self._agent_home_segment,
319
+ )
320
+ expanded = self._catalog.expand_for_invoke(
321
+ inp.skill_name,
322
+ inp.args,
323
+ dynamic_catalog=dynamic,
324
+ session_store=ctx.session_store,
325
+ agent_home_segment=self._agent_home_segment,
326
+ )
327
+ content = expanded.content
328
+ frontmatter = cmd.frontmatter
329
+ allowed_tools = frontmatter.get("allowed-tools")
330
+ parsed_model, parsed_effort = self._parse_frontmatter_overrides(frontmatter)
331
+ constraints_applied = {
332
+ "has_allowed_tools": isinstance(allowed_tools, list) and len(allowed_tools) > 0,
333
+ "has_paths": isinstance(frontmatter.get("paths"), list) and len(frontmatter.get("paths")) > 0,
334
+ "has_model_override": parsed_model is not None,
335
+ "has_effort_override": parsed_effort is not None,
336
+ }
337
+ resolved_scope = self._resolve_tool_scope(data, ctx)
338
+ constraints_applied["tool_scope_resolution"] = {
339
+ "auto_approved": sorted(resolved_scope.auto_approved),
340
+ "final_allowed": sorted(resolved_scope.final_allowed),
341
+ "explain": list(resolved_scope.explain),
342
+ }
343
+ allowed_list = [str(x) for x in allowed_tools] if isinstance(allowed_tools, list) else []
344
+ return await self._fork_executor.aexecute(
345
+ cmd=cmd,
346
+ content=content,
347
+ ctx=ctx,
348
+ model_override=parsed_model,
349
+ effort_override=parsed_effort,
350
+ allowed_tools=allowed_list,
351
+ constraints_applied=constraints_applied,
352
+ )
353
+
302
354
  @staticmethod
303
355
  def _parse_frontmatter_overrides(
304
356
  frontmatter: dict[str, Any],
@@ -39,7 +39,8 @@ def _mkdir_p(path: Path) -> None:
39
39
  """跨平台 ``mkdir -p``;Windows 近 MAX_PATH 时用 ``\\\\?\\`` 扩展前缀。"""
40
40
  target = Path(path)
41
41
  if os.name == "nt":
42
- abs_path = os.path.abspath(str(target))
42
+ # Windows MAX_PATH 探测:仅取绝对长度,不参与 workspace 语义解析
43
+ abs_path = os.path.abspath(str(target)) # noqa: workspace-boundary reason="Windows MAX_PATH length probe only; not workspace path SSOT"
43
44
  if len(abs_path) >= _WIN_CREATE_DIR_MAX and not abs_path.startswith("\\\\?\\"):
44
45
  Path("\\\\?\\" + abs_path).mkdir(parents=True, exist_ok=True)
45
46
  return
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-agentx-python
3
- Version: 2.3.9
3
+ Version: 2.4.0
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=P6NRAff0z_tvGxr7Vw8SjC7iBM818Lx6SXrGH9p_dvE,2014
1
+ langchain_agentx/__init__.py,sha256=nekQFWWhUqeV0DS8CHFYpK9w0n3NvKObBPoWWU2WHmo,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
@@ -30,7 +30,7 @@ langchain_agentx/loop/__init__.py,sha256=JoFPFrlllvGCYFEk2RUuqq9AfUIdLVSNbrRdTRy
30
30
  langchain_agentx/loop/benchmark_frozen_config.py,sha256=YI4IaDVMBOEucF36F2oNv5mw1TqMG_rm_LJSpKAnFWM,13942
31
31
  langchain_agentx/loop/loop_abort.py,sha256=wFlvCLjE9Avs83_8hqmUq0DlP58LSjtxOge-g1K4oPQ,3547
32
32
  langchain_agentx/loop/message_contract.py,sha256=Twl83TneU2PRdxgGRBxs6S9_ovRT6cb5WeCUzd6oJnE,2196
33
- langchain_agentx/loop/run_context.py,sha256=CRzbzuQziFmjvTTgZ6ctKLGyvzsuqq_oDr9zhLOhFik,10940
33
+ langchain_agentx/loop/run_context.py,sha256=LQ4T8KrBPcjuNPsepFNcKcoS5ZDE5Ek7eI-_6oEmdWg,15410
34
34
  langchain_agentx/loop/config/__init__.py,sha256=0pf3Mml7LZpQ1C3_AtROFQClrBQegaGCG4YCTfSr9v8,1137
35
35
  langchain_agentx/loop/config/agent_config.py,sha256=tOnAHFor6dkle3FvUOVx0YsmiYVTH11UrWSyTmOLV0c,3100
36
36
  langchain_agentx/loop/config/compaction_config.py,sha256=MmXqY47-9nPDhdXvnU92Xp0XeaZ605ErQwX9d2emEkM,1176
@@ -137,7 +137,7 @@ langchain_agentx/loop/runtime/context_factory.py,sha256=m3ics_dOTqEKNlf6ETIOnVj6
137
137
  langchain_agentx/loop/runtime/subagent_execution_paths.py,sha256=Bgyiom48uta1FZcnIE7fIxyrD-VFCRsw7s8IxIxmLb4,4033
138
138
  langchain_agentx/loop/stream/graph_driver.py,sha256=qMZHEw3wJ19Tfbz31-VPLy8Mt6e90xKougIjAoaEFCE,3935
139
139
  langchain_agentx/loop/stream/helpers.py,sha256=b2sqtwrAa5ic5B5AzNqItU8pmDHw13iGs3hBiShSIZk,1390
140
- langchain_agentx/loop/stream/loop_driver.py,sha256=v9XbmYv4O9ad-SLBXM3FN50V96_77bY8Tlvam13GnzQ,25028
140
+ langchain_agentx/loop/stream/loop_driver.py,sha256=IE8IGcgdiLMqLZr32Ag9Q5vhbo1g7UxY1eUYsy7r98M,25357
141
141
  langchain_agentx/loop/stream/result.py,sha256=xQb9r1cxGCQpoAZ0gNo71CjhSE3eYOiYp5KnoOBzBF8,1131
142
142
  langchain_agentx/loop/stream/synthetic_events.py,sha256=Z3JT3iTuB9UQOoQXUprAYvaFV0vXEAuf5iqXYH9YSyI,2175
143
143
  langchain_agentx/loop/stream/transcript_gate.py,sha256=4AH3O0l8OrY_GQdqf-TOKOVS3selPLUC0jYLWQUTfkg,4690
@@ -157,13 +157,13 @@ langchain_agentx/loop/subagent/hook_attachment_messages.py,sha256=em3VXaUfURou_Y
157
157
  langchain_agentx/loop/subagent/hooks_compose.py,sha256=enPKRsAbqjB5HqACE-YFzM8K3Job7azi4wpqI1kmgl8,2515
158
158
  langchain_agentx/loop/subagent/hooks_pipeline.py,sha256=4OweLS52TRbcaUO56YpE-qX71ZPCjPRS0j9KzIcx5ls,6399
159
159
  langchain_agentx/loop/subagent/mcp_child_servers.py,sha256=UJ9FtIZroYvP6KfNLUnHKbb7bqg2qSmo15MkzHcVKME,4395
160
- langchain_agentx/loop/subagent/orchestrator.py,sha256=35gaY6gzdlCxEogyYiTRos5KtSpsSOADJBZIHazsQbc,41785
160
+ langchain_agentx/loop/subagent/orchestrator.py,sha256=6VR5yxnUD4q9lEhI7JVPdo_BdlEBD_ObfTxAnrIaVAg,42085
161
161
  langchain_agentx/loop/subagent/parent_hook_snapshot.py,sha256=cwBC2eXvx1evc6QPpI42zq-iKThpObyoyWmkDNj9E7Q,2095
162
162
  langchain_agentx/loop/subagent/progress.py,sha256=nef3VVT9QmB2ZWCDAO86cmtVwCJIR8lXMr6jczQKqOY,6650
163
163
  langchain_agentx/loop/subagent/progress_dispatcher.py,sha256=KD5EdfoGplIaT8xvt5vgfXgSLPbvn9KqvY85wLpOX7A,4603
164
164
  langchain_agentx/loop/subagent/prompt.py,sha256=p7RgzibNfEJdfX8nBnZGEwQLa3_EgOn4E56T9MBRIEc,1722
165
165
  langchain_agentx/loop/subagent/reasoning_extractor.py,sha256=KrCQtBdKzsjAEfA7H5COuXzBhuGGonT2Su8IEKYlUpA,1334
166
- langchain_agentx/loop/subagent/runner.py,sha256=GCYWWMZtkJRH8L1sJAwrVwe0yOqO-6kBT-6p5J1w_s0,35142
166
+ langchain_agentx/loop/subagent/runner.py,sha256=iwLvLLHlzNluSWx0xTvPpB4yPRsScKxqngX6LGGxAm0,36089
167
167
  langchain_agentx/loop/subagent/subagent_start_hooks.py,sha256=HIyW7_ta9JNxiyrgL8scNpZzcJrAyld4qaRehYx36GY,1359
168
168
  langchain_agentx/loop/subagent/subagent_termination_classifier.py,sha256=ULArQeAibgy6ETSf4vRR0L6X1rsDM7-ZuLzRWQ_pmeY,3158
169
169
  langchain_agentx/loop/subagent/transcript.py,sha256=-S0VjZcvqlJQGkVGu8VDMTYwTwOePdjDHfjYi3uu7BY,7187
@@ -584,14 +584,14 @@ langchain_agentx/tools/ask_user_question/prompt.py,sha256=0RdVuUeNzQBzPeVrLcbiBq
584
584
  langchain_agentx/tools/ask_user_question/tool.py,sha256=DgU7OBAqrJBb77ewQ76okoUFJxTThAiN6PRMPa8_FvU,13467
585
585
  langchain_agentx/tools/ask_user_question/validators.py,sha256=Uc88cBzqUp0PNG_Jf2yX2WtKP8hzBsnkMb_KUXh9C4M,6088
586
586
  langchain_agentx/tools/bash/__init__.py,sha256=molPHFDylE2wWG_aQx7fb0ciAx3wWpdXzIixTZV5ne4,214
587
- langchain_agentx/tools/bash/ast_security.py,sha256=6DFd_TwT986dbGkKT3UnDIZioBdmi8mbJcYSI-XEtdM,22444
587
+ langchain_agentx/tools/bash/ast_security.py,sha256=vMP1lAi2bHrsHojun64eu1-Ym8hGW00dSVe3PexbVxc,22999
588
588
  langchain_agentx/tools/bash/auto_mode_adapter.py,sha256=7-yb_kGmpytBYkD2Hn5YWPIceo_YN4PatEKj8O7HKsM,4243
589
589
  langchain_agentx/tools/bash/backend.py,sha256=E0DMBAENZLUSLvuEh6RubNJ6iCVnx1d2VGsQzVuLA5k,65361
590
590
  langchain_agentx/tools/bash/bash_hardening.py,sha256=KEYKRC7kS_to4mzXuChvCmOIIb4dSUkOb9K1MBDtWDc,29296
591
591
  langchain_agentx/tools/bash/bash_registry_content.py,sha256=YHE3PwuLBeFrEux4xNaDZjJpYtUSMFPLYr6A7g-fFxk,3569
592
592
  langchain_agentx/tools/bash/bash_runtime_contract.py,sha256=s8vrvMl7405KLGw2hPFbzlzWohm5JX3c9cImMBgqh8I,1511
593
593
  langchain_agentx/tools/bash/boundary_check.py,sha256=k9Bv-baIAUDE3-c_Brw3VSXXYMv_g3lTM0mabIadnwc,8822
594
- langchain_agentx/tools/bash/command_allow_rules.py,sha256=CDdfF--gs1SpLU01L1d2Hho9ovk0-lVw1HnxZISGf5Y,7832
594
+ langchain_agentx/tools/bash/command_allow_rules.py,sha256=5WohbA_UT57oqEtwVPBCH0IIP6hjM-FYCYLt4hWX3mk,8853
595
595
  langchain_agentx/tools/bash/cwd_reporter.py,sha256=mASbKWLb4MiX4zLKY3w1ocIdfhmwj5KRgX4OW_PcAns,2642
596
596
  langchain_agentx/tools/bash/events.py,sha256=xDpPA3zhne2cnlYTmiiPTsV423-h-Nw-VKDR_rlnbGE,6065
597
597
  langchain_agentx/tools/bash/l1_decision_reason.py,sha256=MOL9HBBpJEgypuTiYdNojQBl8eIsTwRge5CtGeYRJKI,5365
@@ -603,7 +603,7 @@ langchain_agentx/tools/bash/output_tracker.py,sha256=JJv_PVKEoFJljUrjlIjzlRx6GVo
603
603
  langchain_agentx/tools/bash/output_utils.py,sha256=WYU2vVOjo9HQKSeQ_z88KxJsrvlFm0wf7USCMvuifZ8,6638
604
604
  langchain_agentx/tools/bash/path_security.py,sha256=wUJ4RED77-urSwsIuaaZ0IcjYTHYUYSLpvIyMac7sn0,98788
605
605
  langchain_agentx/tools/bash/prompt.py,sha256=28WBRLt5G6e-OUildOFbeEyw7eL3iI8T40pEwmWVYxo,12527
606
- langchain_agentx/tools/bash/read_only_validation.py,sha256=q1_Gr7rxE6TFk3hH3APPJE4uRb1nrQjc_NPmxeWHcGo,25180
606
+ langchain_agentx/tools/bash/read_only_validation.py,sha256=Y_9cn01m2hGrohqAg7_4riV3hDfOsyyE4pIFIbXiPe4,26878
607
607
  langchain_agentx/tools/bash/redirect_classifier.py,sha256=Xbmrb1VfZMdvlzvsI1YoOk57MkRLlwhf23GP90UNFqQ,8188
608
608
  langchain_agentx/tools/bash/result_presenter.py,sha256=OkJid40AmrAsRxhY7jSaw_PgRMoIfMcj6BNXaqcryTo,14946
609
609
  langchain_agentx/tools/bash/sandbox_decision.py,sha256=T_JaTfPADDcH2mRtrpKTBbIVo2Q6CHkzg_aUXCuDhLQ,4612
@@ -680,7 +680,7 @@ langchain_agentx/tools/skill/catalog.py,sha256=fhIsxJ5qq5RgFoav6OU_sFw0z6lBMYixv
680
680
  langchain_agentx/tools/skill/dynamic_catalog.py,sha256=kc9tcAJMbvieIaO0SN89b58I0OIjPRsccKv8pLVoyXI,5409
681
681
  langchain_agentx/tools/skill/dynamic_discoverer.py,sha256=1zUcWHJn96UFJeDY9NPsjpZAJ3iQFAq_Y7REyUGXfMo,2978
682
682
  langchain_agentx/tools/skill/file_discovery.py,sha256=mEMSg5eqGbv0bU7ZWY_i7WdYZsL618pMo0wMrbunbSc,2202
683
- langchain_agentx/tools/skill/fork.py,sha256=QQ5I1MNX9CUx_-wafXmj0zl5Q3FO-J5eY5POYl14Mo0,4804
683
+ langchain_agentx/tools/skill/fork.py,sha256=zOHrO-bgfXA4PH5f1dM5hIxpScSmVQeHAt_eV6u8SCI,7846
684
684
  langchain_agentx/tools/skill/guards.py,sha256=tO6hLncKW0eMpWLrdnZLeMzfknL0ufbE8NxqfP91LGc,1484
685
685
  langchain_agentx/tools/skill/hook_registration.py,sha256=M8ZTykK7ubN4j-SSbwg9WAzq5OpFaRI-IrnRwyiknSs,6261
686
686
  langchain_agentx/tools/skill/loader.py,sha256=E0P90vvja5thi-1nPT4OkWtOniBkISvumA5agV150XU,10086
@@ -694,7 +694,7 @@ langchain_agentx/tools/skill/prompt.py,sha256=tvSqvCGroVdloveiWIYxIPQmpESqzPAs5Z
694
694
  langchain_agentx/tools/skill/registry_permission.py,sha256=ns_S6IBOdacrZcgHzCOj9rjlPOIecavF5hW-pHmalcY,3258
695
695
  langchain_agentx/tools/skill/repository_factory.py,sha256=aFYIw65ctz1sLfEmn8E3YfpgTvTc1YI9XkueWpeiXtA,1359
696
696
  langchain_agentx/tools/skill/source_paths.py,sha256=x-WHorUgt2xyI5hcvALT2e6Ra-HDpKgIv7plQezRkws,5324
697
- langchain_agentx/tools/skill/tool.py,sha256=6VHUrNsolANQDiNo0UWnxYLLLkt-yvKoMH0p8wKAD7U,19786
697
+ langchain_agentx/tools/skill/tool.py,sha256=a1-uUtNraWy6stcZ-hourI73hCGawzVIPE_2pFtHvFw,22202
698
698
  langchain_agentx/tools/skill/bundled/__init__.py,sha256=gaCH_mROpv0lIgLjin1VIFu-8hbP2nY1PHoF7vZt2Ns,4516
699
699
  langchain_agentx/tools/skill/bundled/prompts.py,sha256=gcaTnNRlWfHNPgO3EfZxU9vMYbOapMrAduvgaVc-_Fc,4838
700
700
  langchain_agentx/tools/skill/bundled/registry.py,sha256=oxvvFeeUzM-qVPtayVaQp5tfRiMazyln2-rCM86AYa8,6274
@@ -831,14 +831,14 @@ langchain_agentx/workspace/path_canonical.py,sha256=Z_mmDJQMP4WmBhjBpRUGKGg7XCsK
831
831
  langchain_agentx/workspace/path_key_normalizer.py,sha256=ICk9uGiiNFH-zaXJG3_vx91uOpgWmgOPIjJoSYevKOc,1113
832
832
  langchain_agentx/workspace/plans.py,sha256=wo0ogpErnmwBrNwsHHCzARnIfHM7xnhpAMiN39SjCCI,11813
833
833
  langchain_agentx/workspace/project_key.py,sha256=iA_C6oLM8ebWX6C4GXpJp-SO_n6O1heNcyNebC513QA,2808
834
- langchain_agentx/workspace/project_memory_layout.py,sha256=cQDHBTEHwn7nvbVQ6LFlsRGkwZT6csiETej0Cvc7HNA,8660
834
+ langchain_agentx/workspace/project_memory_layout.py,sha256=v7Xva8v8E6zjkCRuUy6gAWMkRaicndPJoM5AvuyIFCA,8847
835
835
  langchain_agentx/workspace/resolver.py,sha256=stWBxa-jkNO2nZKEJr89Qb4805FcEFm76NpybWb-LYs,6444
836
836
  langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXOUBGr1Gjnl8CfCoTIuYc,7558
837
837
  langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
838
838
  langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
839
839
  langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
840
- langchain_agentx_python-2.3.9.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
841
- langchain_agentx_python-2.3.9.dist-info/METADATA,sha256=pwTUN5gZ5zW2jrl7nwOYxD53dZUKhEEkZu19bdqS7Ew,23971
842
- langchain_agentx_python-2.3.9.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
843
- langchain_agentx_python-2.3.9.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
844
- langchain_agentx_python-2.3.9.dist-info/RECORD,,
840
+ langchain_agentx_python-2.4.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
841
+ langchain_agentx_python-2.4.0.dist-info/METADATA,sha256=8umkqCu3uA5-5h8qh8wIatqM0aMpj_UV_b6j1MKgF6A,23971
842
+ langchain_agentx_python-2.4.0.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
843
+ langchain_agentx_python-2.4.0.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
844
+ langchain_agentx_python-2.4.0.dist-info/RECORD,,