langchain-agentx-python 2.2.9__py3-none-any.whl → 2.3.1__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.2.9"
14
+ __version__ = "2.3.1"
15
15
 
16
16
  from .loop import ( # noqa: F401
17
17
  create_loop_agent,
@@ -756,8 +756,17 @@ class LoopGraphBuilder:
756
756
  include_agent=not user_has_agent_tool,
757
757
  )
758
758
  for tool in self._services.loader.registry.list():
759
- if tool.name not in self._loop_loader.registry._tools:
760
- self._loop_loader.register(tool)
759
+ if tool.name in self._loop_loader.registry._tools:
760
+ existing = self._loop_loader.registry.get(tool.name)
761
+ if existing is not tool:
762
+ logger.warning(
763
+ "Host loader tool %r not merged into loop; "
764
+ "register_default_tools already registered %r (S-4)",
765
+ tool.name,
766
+ tool.name,
767
+ )
768
+ continue
769
+ self._loop_loader.register(tool)
761
770
  self._runtime_tools = self._loop_loader.registry.list()
762
771
  from ...loop.prompt.tool_aware_sections import collect_enabled_tool_names
763
772
 
@@ -209,12 +209,13 @@ class InnerPermissionChain:
209
209
  command = data.get("command")
210
210
  if not isinstance(command, str) or not command.strip():
211
211
  return None
212
- deny_result = matcher.match_deny(tool_name, command)
213
- if deny_result is not None:
214
- return deny_result
215
- ask_result = matcher.match_ask(tool_name, command)
216
- if ask_result is not None:
217
- return ask_result
212
+ from langchain_agentx.tools.bash.bash_registry_content import (
213
+ evaluate_bash_inner_content_rules,
214
+ )
215
+
216
+ content_decision = evaluate_bash_inner_content_rules(command, registry)
217
+ if content_decision is not None:
218
+ return content_decision
218
219
  return None
219
220
 
220
221
  path = self._engine._extract_path(data)
@@ -155,6 +155,7 @@ class ToolRuntimeLoader:
155
155
  目的:为 subagent 组装“过滤后的工具池”而不丢失权限/管控语义。
156
156
  """
157
157
  child = ToolRuntimeLoader(
158
+ policy_config=self._process.policy_config,
158
159
  policy_engine=self._process.policy,
159
160
  headless=self._process.headless if headless is None else headless,
160
161
  prompt_handler=self._process.prompt_handler,
@@ -107,6 +107,7 @@ class LoopLoader:
107
107
  agent_home_segment=self._agent_home,
108
108
  session_cwd=effective_cwd,
109
109
  )
110
+ self._merge_skill_read_roots_policy(cfg)
110
111
  self._inject_project_memory_policy(cfg)
111
112
  plugin_ex = PluginCacheGlobExclusions(
112
113
  cache_root=str(cfg.plugin_cache_dir.resolve()),
@@ -210,11 +211,21 @@ class LoopLoader:
210
211
  setter(patterns)
211
212
  return patterns
212
213
 
214
+ def _merge_skill_read_roots_policy(self, workspace_cfg: Any) -> None:
215
+ """G-2:skill 发现目录并入 read_roots(在 project_memory 注入之前)。"""
216
+ from langchain_agentx.tools.skill.source_paths import resolve_skill_read_roots
217
+
218
+ roots = resolve_skill_read_roots(workspace_cfg)
219
+ if not roots:
220
+ return
221
+ base = self._process.effective_policy_config()
222
+ self._process.apply_policy_config(base.with_merged_read_roots(roots))
223
+
213
224
  def _inject_project_memory_policy(self, workspace_cfg: Any) -> None:
214
225
  """Per-loop 注入 CC projects 树读写根与 path_safety layout。"""
215
226
  layout = workspace_cfg.project_memory_layout
216
227
  workspace_root = str(Path(self._workspace_root).resolve())
217
- base = self._process.policy_config or ToolPolicyConfig()
228
+ base = self._process.effective_policy_config()
218
229
  if not base.read_roots:
219
230
  base = replace(base, read_roots=[workspace_root])
220
231
  if not base.write_roots:
@@ -127,6 +127,15 @@ def _content_rule_matches_target(
127
127
  target=target,
128
128
  rule_content=rule_content,
129
129
  )
130
+ if tool_name == "Skill":
131
+ from langchain_agentx.tools.skill.registry_permission import (
132
+ skill_rule_content_matches,
133
+ )
134
+
135
+ return skill_rule_content_matches(
136
+ skill_name=target,
137
+ rule_content=rule_content,
138
+ )
130
139
  return fnmatch.fnmatch(target, rule_content)
131
140
 
132
141
 
@@ -45,9 +45,11 @@ from __future__ import annotations
45
45
  import fnmatch
46
46
  import os
47
47
  import sys
48
+ from pathlib import Path
48
49
  import warnings
49
50
  from abc import ABC, abstractmethod
50
51
  from dataclasses import dataclass, field, replace
52
+ from collections.abc import Iterable
51
53
  from typing import TYPE_CHECKING, Any
52
54
 
53
55
  from .models import AuthorizationDecision, ToolExecutionContext
@@ -123,6 +125,20 @@ class ToolPolicyConfig:
123
125
  normalize_tool_name_set(self.cli_allow_tools),
124
126
  )
125
127
 
128
+ def with_merged_read_roots(
129
+ self,
130
+ extra: Iterable[str | os.PathLike[str]],
131
+ ) -> "ToolPolicyConfig":
132
+ """Union ``read_roots``;保留 cliArg / rules 等字段(Phase 2 skill 发现根)。"""
133
+ merged: list[str] = []
134
+ seen: set[str] = set()
135
+ for raw in (*self.read_roots, *(str(Path(p).expanduser().resolve()) for p in extra)):
136
+ if not raw or raw in seen:
137
+ continue
138
+ seen.add(raw)
139
+ merged.append(raw)
140
+ return replace(self, read_roots=merged)
141
+
126
142
  def with_project_memory_layout(
127
143
  self,
128
144
  layout: "ProjectMemoryLayout",
@@ -313,11 +329,16 @@ class DefaultPolicyEngine(PolicyEngine):
313
329
  config: 策略配置
314
330
  auto_mode_hook: Auto Mode 授权 Hook(可选,Phase 1 集成)
315
331
  """
332
+ self._auto_mode_hook = auto_mode_hook
333
+ self._internal_paths = InternalPathEvaluator()
334
+ self._agent_home_bypass = AgentHomeSessionBypassEvaluator()
335
+ self.replace_policy_config(config)
336
+
337
+ def replace_policy_config(self, config: ToolPolicyConfig) -> None:
338
+ """原地更新 config 与派生 roots/authorizer(loop project memory 注入)。"""
316
339
  self._config = config
317
340
  self._read_roots = [os.path.realpath(r) for r in config.read_roots]
318
341
  self._write_roots = [os.path.realpath(r) for r in config.write_roots]
319
- self._auto_mode_hook = auto_mode_hook
320
- self._internal_paths = InternalPathEvaluator()
321
342
  self._path_safety: PathSafetyEvaluator | None = None
322
343
  if config.enable_path_safety and config.path_safety is not None:
323
344
  self._path_safety = PathSafetyEvaluator(
@@ -341,7 +362,6 @@ class DefaultPolicyEngine(PolicyEngine):
341
362
  self._within_root(real_path, r) for r in self._read_roots
342
363
  ),
343
364
  )
344
- self._agent_home_bypass = AgentHomeSessionBypassEvaluator()
345
365
 
346
366
  def authorize(
347
367
  self,
@@ -50,14 +50,21 @@ class ProcessLoader:
50
50
  permission_resolver=permission_resolver,
51
51
  )
52
52
  self._adapter = LangChainAdapter()
53
- self._policy_config = policy_config
54
53
  if policy_engine is not None:
55
54
  self._policy: PolicyEngine | None = policy_engine
55
+ if policy_config is not None:
56
+ self._policy_config = policy_config
57
+ elif isinstance(policy_engine, DefaultPolicyEngine):
58
+ self._policy_config = policy_engine._config
59
+ else:
60
+ self._policy_config = None
56
61
  elif policy_config is not None:
62
+ self._policy_config = policy_config
57
63
  self._policy = DefaultPolicyEngine(policy_config)
58
64
  else:
59
- self._policy = DefaultPolicyEngine(ToolPolicyConfig())
60
- self._policy_config = ToolPolicyConfig()
65
+ empty = ToolPolicyConfig()
66
+ self._policy_config = empty
67
+ self._policy = DefaultPolicyEngine(empty)
61
68
 
62
69
  def set_permission_resolver(
63
70
  self,
@@ -141,12 +148,22 @@ class ProcessLoader:
141
148
  def permission_resolver(self) -> PermissionResolver | None:
142
149
  return self._permission_resolver
143
150
 
151
+ def effective_policy_config(self) -> ToolPolicyConfig:
152
+ """policy_config → DefaultPolicyEngine._config → 空配置(loop 注入 SSOT)。"""
153
+ if self._policy_config is not None:
154
+ return self._policy_config
155
+ policy = self._policy
156
+ if isinstance(policy, DefaultPolicyEngine):
157
+ return policy._config
158
+ return ToolPolicyConfig()
159
+
144
160
  def apply_policy_config(self, policy_config: ToolPolicyConfig) -> None:
145
- """替换策略配置并重建 DefaultPolicyEngine(保留 auto_mode hook)。"""
146
- auto_hook = None
147
- if isinstance(self._policy, DefaultPolicyEngine):
148
- auto_hook = self._policy._auto_mode_hook
161
+ """替换策略配置;DefaultPolicyEngine 原地 rebind,保留 engine 实例 id。"""
149
162
  self._policy_config = policy_config
163
+ if isinstance(self._policy, DefaultPolicyEngine):
164
+ self._policy.replace_policy_config(policy_config)
165
+ return
166
+ auto_hook = None
150
167
  self._policy = DefaultPolicyEngine(policy_config, auto_mode_hook=auto_hook)
151
168
 
152
169
  def sync_permission_rules_from_disk(
@@ -0,0 +1,106 @@
1
+ """
2
+ tools/bash/bash_registry_content.py — Bash cliArg 内容 deny/ask(compound 段级 · Phase D)
3
+
4
+ 职责:
5
+ Inner 段 1 对 Bash 按 AST permission_segments 评估内容级 deny/ask(对齐 CC compound 子命令优先 deny)。
6
+
7
+ 链路位置:
8
+ InnerPermissionChain._segment_1_2_global(Bash 分支)。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from langchain_agentx.tool_runtime.models import AuthorizationDecision
14
+ from langchain_agentx.tool_runtime.permission_rule_matcher import (
15
+ RegistryPermissionMatcher,
16
+ _decision_from_legacy_glob,
17
+ _decision_from_rule,
18
+ )
19
+ from langchain_agentx.tool_runtime.permission_rules import (
20
+ PermissionRuleBehavior,
21
+ PermissionRuleRegistry,
22
+ )
23
+ from langchain_agentx.tools.bash.shell_rule_matching import match_shell_permission_rule
24
+
25
+
26
+ def bash_permission_segments(command: str) -> list[str]:
27
+ from langchain_agentx.tools.bash.ast_security import BashAstAnalyzer
28
+
29
+ analysis = BashAstAnalyzer().analyze(command)
30
+ segments = [s.strip() for s in analysis.permission_segments if s and s.strip()]
31
+ return segments or [command.strip()]
32
+
33
+
34
+ def _segment_content_decision(
35
+ segment: str,
36
+ registry: PermissionRuleRegistry,
37
+ behavior: PermissionRuleBehavior,
38
+ ) -> AuthorizationDecision | None:
39
+ matcher = RegistryPermissionMatcher(registry)
40
+ if behavior is PermissionRuleBehavior.DENY:
41
+ direct = matcher.match_deny("Bash", segment)
42
+ else:
43
+ direct = matcher.match_ask("Bash", segment)
44
+ if direct is not None:
45
+ return direct
46
+
47
+ legacy = registry.find_legacy_glob_match(segment, behavior)
48
+ if legacy is not None:
49
+ return _decision_from_legacy_glob(legacy, behavior=behavior.value)
50
+
51
+ for rule in registry.iter_rules(behavior):
52
+ if rule.tool_name != "Bash" or not rule.rule_content:
53
+ continue
54
+ if match_shell_permission_rule(rule.rule_content, segment, match_mode="prefix"):
55
+ return _decision_from_rule(rule, behavior=behavior.value)
56
+ return None
57
+
58
+
59
+ def evaluate_bash_inner_content_rules(
60
+ command: str,
61
+ registry: PermissionRuleRegistry,
62
+ ) -> AuthorizationDecision | None:
63
+ """CC compound:段级 deny 优先 → full deny/ask → 段级 ask。"""
64
+ cmd = command.strip()
65
+ if not cmd:
66
+ return None
67
+
68
+ matcher = RegistryPermissionMatcher(registry)
69
+ full_deny = matcher.match_deny("Bash", cmd)
70
+ if full_deny is not None:
71
+ return full_deny
72
+
73
+ segments = bash_permission_segments(cmd)
74
+ if len(segments) > 1:
75
+ for segment in segments:
76
+ seg_deny = _segment_content_decision(
77
+ segment, registry, PermissionRuleBehavior.DENY
78
+ )
79
+ if seg_deny is not None:
80
+ return seg_deny
81
+
82
+ full_ask = matcher.match_ask("Bash", cmd)
83
+ if full_ask is not None:
84
+ return full_ask
85
+
86
+ if len(segments) > 1:
87
+ first_ask: AuthorizationDecision | None = None
88
+ for segment in segments:
89
+ seg_ask = _segment_content_decision(
90
+ segment, registry, PermissionRuleBehavior.ASK
91
+ )
92
+ if seg_ask is not None and first_ask is None:
93
+ first_ask = seg_ask
94
+ if first_ask is not None:
95
+ return first_ask
96
+
97
+ if len(segments) == 1:
98
+ return _segment_content_decision(
99
+ segments[0], registry, PermissionRuleBehavior.DENY
100
+ ) or _segment_content_decision(
101
+ segments[0], registry, PermissionRuleBehavior.ASK
102
+ )
103
+ return None
104
+
105
+
106
+ __all__ = ["bash_permission_segments", "evaluate_bash_inner_content_rules"]
@@ -10,9 +10,10 @@ tools/bash/command_allow_rules.py — Bash 内容级 allow 匹配(slash comman
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
- import fnmatch
14
13
  from typing import TYPE_CHECKING
15
14
 
15
+ from langchain_agentx.tools.bash.shell_rule_matching import match_shell_permission_rule
16
+
16
17
  from langchain_agentx.tool_runtime.models import AuthorizationDecision, ToolExecutionContext
17
18
  from langchain_agentx.tool_runtime.permission_context import get_command_bash_allow_rules
18
19
  from langchain_agentx.tool_runtime.permission_rule_matcher import RegistryPermissionMatcher
@@ -23,27 +24,24 @@ from langchain_agentx.tool_runtime.permission_rules import (
23
24
  )
24
25
  from langchain_agentx.tools.bash.read_only_validation import BashReadOnlyClassifier
25
26
 
27
+ from langchain_agentx.tool_runtime.policy import PolicyEngine, resolve_default_policy_engine
28
+
26
29
  if TYPE_CHECKING:
27
30
  from langchain_agentx.tool_runtime.policy import DefaultPolicyEngine
28
31
 
29
32
 
30
33
  def bash_command_matches_allow_rule(command: str, rule_content: str) -> bool:
31
- """CC prefix:* 与简单 fnmatch 子集。"""
34
+ """CC shellRuleMatching(prefix / wildcard / exact)。"""
32
35
  cmd = command.strip()
33
36
  pattern = rule_content.strip()
34
37
  if not cmd or not pattern:
35
38
  return False
36
-
37
- if pattern.endswith(":*"):
38
- prefix = pattern[:-2].rstrip()
39
- if not prefix:
40
- return False
41
- return cmd == prefix or cmd.startswith(f"{prefix} ")
42
-
43
- if "*" in pattern or "?" in pattern or "[" in pattern:
44
- return fnmatch.fnmatch(cmd, pattern)
45
-
46
- return cmd == pattern or cmd.startswith(f"{pattern} ")
39
+ return match_shell_permission_rule(
40
+ pattern,
41
+ cmd,
42
+ match_mode="prefix",
43
+ is_compound=False,
44
+ )
47
45
 
48
46
 
49
47
  def try_command_allow_decision(
@@ -84,13 +82,14 @@ def cliarg_bash_has_content_allow_without_bare_tool(
84
82
  def try_registry_bash_content_allow_decision(
85
83
  command: str,
86
84
  ctx: ToolExecutionContext,
87
- policy_engine: DefaultPolicyEngine | None,
85
+ policy_engine: PolicyEngine | None,
88
86
  ) -> AuthorizationDecision | None:
89
87
  """cliArg/settings 等内容级 Bash allow(CC bashPermissions prefix allow,path 通过后)。"""
90
- if policy_engine is None:
88
+ engine = resolve_default_policy_engine(policy_engine)
89
+ if engine is None:
91
90
  return None
92
91
  registry = build_permission_rule_registry(
93
- policy_config=policy_engine._config,
92
+ policy_config=engine._config,
94
93
  session_store=ctx.session_store,
95
94
  workspace_root=ctx.workspace_root,
96
95
  agent_home_segment=ctx.agent_home_segment,
@@ -99,18 +98,65 @@ def try_registry_bash_content_allow_decision(
99
98
  return matcher.match_allow("Bash", command.strip())
100
99
 
101
100
 
101
+ def registry_has_cliarg_bash_allow(registry: PermissionRuleRegistry) -> bool:
102
+ for rule in registry.iter_rules(PermissionRuleBehavior.ALLOW):
103
+ if rule.source_name == "cliArg" and rule.tool_name == "Bash":
104
+ return True
105
+ return False
106
+
107
+
108
+ def promote_unscoped_bash_passthrough_to_ask(
109
+ decision: AuthorizationDecision,
110
+ *,
111
+ command: str,
112
+ ctx: ToolExecutionContext,
113
+ policy_engine: PolicyEngine | None,
114
+ ) -> AuthorizationDecision:
115
+ """无 cliArg Bash allow 时,write_roots 静默 allow → ask(对齐 CC passthrough)。"""
116
+ engine = resolve_default_policy_engine(policy_engine)
117
+ if engine is None or decision.behavior != "allow":
118
+ return decision
119
+ if _READ_ONLY_CLASSIFIER.classify(
120
+ command.strip(),
121
+ shell_ops_already_resolved=True,
122
+ ).is_read_only:
123
+ return decision
124
+ if decision.policy_id != "write_roots":
125
+ return decision
126
+ registry = build_permission_rule_registry(
127
+ policy_config=engine._config,
128
+ session_store=ctx.session_store,
129
+ workspace_root=ctx.workspace_root,
130
+ agent_home_segment=ctx.agent_home_segment,
131
+ )
132
+ if registry_has_cliarg_bash_allow(registry):
133
+ return decision
134
+ if cliarg_bash_has_content_allow_without_bare_tool(registry):
135
+ return decision
136
+ return AuthorizationDecision(
137
+ behavior="ask",
138
+ message=decision.message,
139
+ policy_id="bash_passthrough",
140
+ ask_prompt=(
141
+ decision.ask_prompt
142
+ or f"Allow Bash command?\n{command.strip()}"
143
+ ),
144
+ )
145
+
146
+
102
147
  def promote_unmatched_cliarg_content_only_allow_to_ask(
103
148
  decision: AuthorizationDecision,
104
149
  *,
105
150
  command: str,
106
151
  ctx: ToolExecutionContext,
107
- policy_engine: DefaultPolicyEngine | None,
152
+ policy_engine: PolicyEngine | None,
108
153
  ) -> AuthorizationDecision:
109
154
  """roots 内 silent allow → ask(仅 cliArg 有 Bash(pattern) 且无裸 Bash 时)。"""
110
- if policy_engine is None or decision.behavior != "allow" or decision.policy_id:
155
+ engine = resolve_default_policy_engine(policy_engine)
156
+ if engine is None or decision.behavior != "allow" or decision.policy_id:
111
157
  return decision
112
158
  registry = build_permission_rule_registry(
113
- policy_config=policy_engine._config,
159
+ policy_config=engine._config,
114
160
  session_store=ctx.session_store,
115
161
  workspace_root=ctx.workspace_root,
116
162
  agent_home_segment=ctx.agent_home_segment,
@@ -0,0 +1,143 @@
1
+ """
2
+ tools/bash/shell_rule_matching.py — CC shellRuleMatching.ts 对齐(Phase D)
3
+
4
+ 职责:
5
+ prefix :*、wildcard(含 \\* / \\\\ 转义)、``git *`` 尾 wildcard 可选参数语义。
6
+
7
+ 链路位置:
8
+ command_allow_rules.bash_command_matches_permission_rule。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from dataclasses import dataclass
15
+ from typing import Literal
16
+
17
+ _ESCAPED_STAR = "\x00ESCAPED_STAR\x00"
18
+ _ESCAPED_BACKSLASH = "\x00ESCAPED_BACKSLASH\x00"
19
+
20
+
21
+ def permission_rule_extract_prefix(permission_rule: str) -> str | None:
22
+ m = re.match(r"^(.+):\*$", permission_rule.strip())
23
+ return m.group(1) if m else None
24
+
25
+
26
+ def has_wildcards(pattern: str) -> bool:
27
+ text = pattern.strip()
28
+ if text.endswith(":*"):
29
+ return False
30
+ i = 0
31
+ while i < len(text):
32
+ if text[i] == "*":
33
+ backslashes = 0
34
+ j = i - 1
35
+ while j >= 0 and text[j] == "\\":
36
+ backslashes += 1
37
+ j -= 1
38
+ if backslashes % 2 == 0:
39
+ return True
40
+ i += 1
41
+ return False
42
+
43
+
44
+ def match_wildcard_pattern(
45
+ pattern: str,
46
+ command: str,
47
+ *,
48
+ case_insensitive: bool = False,
49
+ ) -> bool:
50
+ """对齐 CC ``matchWildcardPattern``(含 trailing `` *`` → optional args)。"""
51
+ trimmed = pattern.strip()
52
+ processed = ""
53
+ i = 0
54
+ while i < len(trimmed):
55
+ ch = trimmed[i]
56
+ if ch == "\\" and i + 1 < len(trimmed):
57
+ nxt = trimmed[i + 1]
58
+ if nxt == "*":
59
+ processed += _ESCAPED_STAR
60
+ i += 2
61
+ continue
62
+ if nxt == "\\":
63
+ processed += _ESCAPED_BACKSLASH
64
+ i += 2
65
+ continue
66
+ processed += ch
67
+ i += 1
68
+
69
+ escaped = re.sub(r"[.+?^${}()|\[\]\\'\"]", r"\\\g<0>", processed)
70
+ with_wildcards = escaped.replace("*", ".*")
71
+ regex_body = with_wildcards.replace(_ESCAPED_STAR, r"\*").replace(
72
+ _ESCAPED_BACKSLASH, r"\\"
73
+ )
74
+
75
+ unescaped_stars = processed.count("*")
76
+ if regex_body.endswith(" .*") and unescaped_stars == 1:
77
+ regex_body = regex_body[:-3] + "( .*)?"
78
+
79
+ flags = re.DOTALL
80
+ if case_insensitive:
81
+ flags |= re.IGNORECASE
82
+ return re.fullmatch(f"^{regex_body}$", command, flags=flags) is not None
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class ShellPermissionRule:
87
+ kind: Literal["exact", "prefix", "wildcard"]
88
+ value: str
89
+
90
+
91
+ def parse_permission_rule(rule_content: str) -> ShellPermissionRule:
92
+ text = rule_content.strip()
93
+ prefix = permission_rule_extract_prefix(text)
94
+ if prefix is not None:
95
+ return ShellPermissionRule(kind="prefix", value=prefix)
96
+ if has_wildcards(text):
97
+ return ShellPermissionRule(kind="wildcard", value=text)
98
+ return ShellPermissionRule(kind="exact", value=text)
99
+
100
+
101
+ def match_shell_permission_rule(
102
+ rule_content: str,
103
+ command: str,
104
+ *,
105
+ match_mode: Literal["exact", "prefix"] = "prefix",
106
+ is_compound: bool = False,
107
+ ) -> bool:
108
+ """单条 Bash 内容规则对单段 command 的匹配(CC ``bashPermissionRule`` 分支)。"""
109
+ cmd = command.strip()
110
+ if not cmd:
111
+ return False
112
+ parsed = parse_permission_rule(rule_content)
113
+
114
+ if parsed.kind == "exact":
115
+ return parsed.value == cmd
116
+
117
+ if parsed.kind == "prefix":
118
+ if match_mode == "exact":
119
+ return parsed.value == cmd
120
+ if is_compound:
121
+ return False
122
+ prefix = parsed.value
123
+ if cmd == prefix:
124
+ return True
125
+ if cmd.startswith(prefix + " "):
126
+ return True
127
+ xargs_prefix = "xargs " + prefix
128
+ if cmd == xargs_prefix or cmd.startswith(xargs_prefix + " "):
129
+ return True
130
+ return False
131
+
132
+ if match_mode == "exact":
133
+ return False
134
+ if is_compound:
135
+ return False
136
+ return match_wildcard_pattern(parsed.value, cmd)
137
+
138
+
139
+ __all__ = [
140
+ "match_shell_permission_rule",
141
+ "match_wildcard_pattern",
142
+ "parse_permission_rule",
143
+ ]
@@ -1034,7 +1034,17 @@ class BashRuntimeTool(RuntimeTool):
1034
1034
  return decision
1035
1035
 
1036
1036
  final = self._authorize_sed_segment(command=command, ctx=ctx, cwd=cwd) or decision
1037
- return promote_unmatched_cliarg_content_only_allow_to_ask(
1037
+ final = promote_unmatched_cliarg_content_only_allow_to_ask(
1038
+ final,
1039
+ command=command,
1040
+ ctx=ctx,
1041
+ policy_engine=self._policy,
1042
+ )
1043
+ from langchain_agentx.tools.bash.command_allow_rules import (
1044
+ promote_unscoped_bash_passthrough_to_ask,
1045
+ )
1046
+
1047
+ return promote_unscoped_bash_passthrough_to_ask(
1038
1048
  final,
1039
1049
  command=command,
1040
1050
  ctx=ctx,
@@ -0,0 +1,99 @@
1
+ """
2
+ tools/skill/registry_permission.py — Skill 内容级 deny/allow(CC SkillTool.checkPermissions · Phase D)
3
+
4
+ 职责:
5
+ Skill(name) / Skill(prefix:*) 与 registry 规则匹配。
6
+
7
+ 链路位置:
8
+ SkillRuntimeTool.run_l1_permission_check(frontmatter 约束之前)。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ from langchain_agentx.tool_runtime.models import AuthorizationDecision
16
+ from langchain_agentx.tool_runtime.permission_decision import (
17
+ attach_decision_reason,
18
+ make_other_decision_reason,
19
+ )
20
+ from langchain_agentx.tool_runtime.permission_rule_matcher import RegistryPermissionMatcher
21
+ from langchain_agentx.tool_runtime.permission_rules import build_permission_rule_registry
22
+ from langchain_agentx.tools.bash.shell_rule_matching import match_shell_permission_rule
23
+
24
+ if TYPE_CHECKING:
25
+ from langchain_agentx.tool_runtime.models import ToolExecutionContext
26
+
27
+
28
+ def skill_permission_rule_content(skill_name: str) -> str:
29
+ text = skill_name.strip()
30
+ if text.startswith("/"):
31
+ text = text[1:]
32
+ return text
33
+
34
+
35
+ def skill_rule_content_matches(*, skill_name: str, rule_content: str) -> bool:
36
+ """对齐 CC ``ruleMatches``:精确或 ``prefix:*``。"""
37
+ command_name = skill_permission_rule_content(skill_name)
38
+ normalized_rule = rule_content.strip()
39
+ if normalized_rule.startswith("/"):
40
+ normalized_rule = normalized_rule[1:]
41
+ if normalized_rule == command_name:
42
+ return True
43
+ if normalized_rule.endswith(":*"):
44
+ prefix = normalized_rule[:-2]
45
+ return command_name.startswith(prefix)
46
+ return match_shell_permission_rule(
47
+ normalized_rule, command_name, match_mode="prefix"
48
+ )
49
+
50
+
51
+ def evaluate_skill_registry_permission(
52
+ skill_name: str,
53
+ ctx: ToolExecutionContext,
54
+ policy_engine: Any,
55
+ ) -> AuthorizationDecision | None:
56
+ if policy_engine is None:
57
+ return None
58
+ registry = build_permission_rule_registry(
59
+ policy_config=policy_engine._config,
60
+ session_store=ctx.session_store,
61
+ workspace_root=ctx.workspace_root,
62
+ agent_home_segment=ctx.agent_home_segment,
63
+ )
64
+ target = skill_permission_rule_content(skill_name)
65
+ matcher = RegistryPermissionMatcher(registry)
66
+ for match_fn in (matcher.match_deny, matcher.match_ask, matcher.match_allow):
67
+ matched = match_fn("Skill", target)
68
+ if matched is not None:
69
+ return matched
70
+ return None
71
+
72
+
73
+ def skill_default_ask_decision(skill_name: str) -> AuthorizationDecision:
74
+ target = skill_permission_rule_content(skill_name)
75
+ suggestions = (
76
+ {
77
+ "type": "addRules",
78
+ "destination": "localSettings",
79
+ "behavior": "allow",
80
+ "rules": [{"toolName": "Skill", "ruleContent": target}],
81
+ },
82
+ )
83
+ decision = AuthorizationDecision(
84
+ behavior="ask",
85
+ ask_prompt=f"Allow skill {target}?",
86
+ policy_id="skill_default",
87
+ metadata={"suggestions": suggestions},
88
+ )
89
+ return attach_decision_reason(
90
+ decision,
91
+ make_other_decision_reason("Skill default permission prompt"),
92
+ )
93
+
94
+
95
+ __all__ = [
96
+ "evaluate_skill_registry_permission",
97
+ "skill_default_ask_decision",
98
+ "skill_rule_content_matches",
99
+ ]
@@ -61,6 +61,26 @@ def resolve_managed_skills_dir(agent_home_segment: str) -> Path:
61
61
  return resolve_managed_settings_root() / segment / "skills"
62
62
 
63
63
 
64
+ def resolve_skill_read_roots(
65
+ workspace_cfg: ResolvedAgentStateConfig,
66
+ *,
67
+ project_skills_dir: Path | None = None,
68
+ ) -> tuple[Path, ...]:
69
+ """Skill 发现目录绝对路径(与 ``resolve_skill_source_specs`` 同源,去重保序)。"""
70
+ seen: set[Path] = set()
71
+ roots: list[Path] = []
72
+ for spec in resolve_skill_source_specs(
73
+ workspace_cfg,
74
+ project_skills_dir=project_skills_dir,
75
+ ):
76
+ resolved = spec.skills_dir.expanduser().resolve()
77
+ if resolved in seen:
78
+ continue
79
+ seen.add(resolved)
80
+ roots.append(resolved)
81
+ return tuple(roots)
82
+
83
+
64
84
  def resolve_skill_source_specs(
65
85
  workspace_cfg: ResolvedAgentStateConfig,
66
86
  *,
@@ -83,6 +103,7 @@ __all__ = [
83
103
  "SourcedSkillEntry",
84
104
  "policy_skills_disabled",
85
105
  "resolve_managed_skills_dir",
106
+ "resolve_skill_read_roots",
86
107
  "resolve_skill_source_specs",
87
108
  "resolve_user_skills_dir",
88
109
  ]
@@ -187,6 +187,24 @@ class SkillRuntimeTool(RuntimeTool):
187
187
  ),
188
188
  )
189
189
 
190
+ from langchain_agentx.tools.skill.registry_permission import (
191
+ evaluate_skill_registry_permission,
192
+ )
193
+
194
+ registry_decision = evaluate_skill_registry_permission(
195
+ inp.skill_name,
196
+ ctx,
197
+ self._policy,
198
+ )
199
+ if registry_decision is not None:
200
+ if registry_decision.behavior == "allow":
201
+ pass
202
+ else:
203
+ return L1PermissionResult(
204
+ outcome=registry_decision.behavior,
205
+ decision=registry_decision,
206
+ )
207
+
190
208
  local = self._constraint_evaluator.evaluate(cmd.frontmatter)
191
209
  if local.behavior != "allow":
192
210
  return L1PermissionResult(outcome=local.behavior, decision=local)
@@ -40,8 +40,32 @@ def webfetch_permission_rule_content(url: str) -> str:
40
40
 
41
41
 
42
42
  def webfetch_rule_content_matches(*, target: str, rule_content: str) -> bool:
43
- """WebFetch 内容规则:target 与 rule 均为 ``domain:host`` 或 ``input:…``。"""
44
- return target.strip().lower() == rule_content.strip().lower()
43
+ """WebFetch 内容规则:``domain:host`` 精确或 ``domain:*.example.com`` wildcard(Phase D)。"""
44
+ t = target.strip().lower()
45
+ r = rule_content.strip().lower()
46
+ if t == r:
47
+ return True
48
+ if not r.startswith("domain:") or not t.startswith("domain:"):
49
+ return False
50
+ host_pattern = r[len("domain:") :]
51
+ host_target = t[len("domain:") :]
52
+ if "*" not in host_pattern and "?" not in host_pattern:
53
+ return False
54
+ from langchain_agentx.tools.bash.shell_rule_matching import match_wildcard_pattern
55
+
56
+ return match_wildcard_pattern(host_pattern, host_target)
57
+
58
+
59
+ def webfetch_allow_suggestions(rule_content: str) -> tuple[dict[str, object], ...]:
60
+ """对齐 CC WebFetchTool ``buildSuggestions``(L3 ``metadata.suggestions``)。"""
61
+ return (
62
+ {
63
+ "type": "addRules",
64
+ "destination": "localSettings",
65
+ "behavior": "allow",
66
+ "rules": [{"toolName": "WebFetch", "ruleContent": rule_content}],
67
+ },
68
+ )
45
69
 
46
70
 
47
71
  def evaluate_webfetch_registry_permission(
@@ -60,6 +84,16 @@ def evaluate_webfetch_registry_permission(
60
84
  for match_fn in (matcher.match_deny, matcher.match_ask, matcher.match_allow):
61
85
  matched = match_fn("WebFetch", target)
62
86
  if matched is not None:
87
+ if matched.behavior == "ask":
88
+ meta = dict(matched.metadata or {})
89
+ meta.setdefault("suggestions", webfetch_allow_suggestions(target))
90
+ return AuthorizationDecision(
91
+ behavior=matched.behavior,
92
+ message=matched.message,
93
+ policy_id=matched.policy_id,
94
+ ask_prompt=matched.ask_prompt,
95
+ metadata=meta,
96
+ )
63
97
  return matched
64
98
  decision = AuthorizationDecision(
65
99
  behavior="ask",
@@ -67,6 +101,7 @@ def evaluate_webfetch_registry_permission(
67
101
  "Claude requested permissions to use WebFetch, but you haven't granted it yet."
68
102
  ),
69
103
  policy_id="webfetch_default",
104
+ metadata={"suggestions": webfetch_allow_suggestions(target)},
70
105
  )
71
106
  return attach_decision_reason(
72
107
  decision,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-agentx-python
3
- Version: 2.2.9
3
+ Version: 2.3.1
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=sLs2vKbNQCEmhW-uC3NdfDAK1zHUjcNzPxy1y2UPlqg,1614
1
+ langchain_agentx/__init__.py,sha256=89oyf7kdDyMvgQvOwz2Ud0jqlfoa9LoiieytUKz2w0w,1614
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
@@ -68,7 +68,7 @@ langchain_agentx/loop/exit/terminal.py,sha256=_DAZk-kS4Xd0nxMlayLo60-uj1Y5ozgBjL
68
68
  langchain_agentx/loop/exit/withheld_error.py,sha256=LfX3j7Rgd9VoMzlz7GOs9TPuRe7RXr_Gxweyotx3tqQ,1846
69
69
  langchain_agentx/loop/graph/__init__.py,sha256=9pUUqn7G87n3lV45iYIU24j0wcTHyCD--SSdtv3urh4,136
70
70
  langchain_agentx/loop/graph/builtin_loop_control.py,sha256=31o4H6SDGC_vECSliXecuVEnzlD1ahlVZ5JzEpxYY04,9248
71
- langchain_agentx/loop/graph/factory.py,sha256=TthwQtNiOeT5K-bgBhuW89yrkdP6iyYXCcL4tn-puJk,103958
71
+ langchain_agentx/loop/graph/factory.py,sha256=mw2nJUddW5uV2oEi0-tt59eJ65JNQVZLM4pCwINyAY4,104396
72
72
  langchain_agentx/loop/graph/graph_edges.py,sha256=kW6Npx-nMIPzcaisCxVPkTwf5Y9GOKy1NWCNkJWeJBQ,47370
73
73
  langchain_agentx/loop/graph/reactive_compact_node.py,sha256=633b5hTnR3BK8GBEKWhzOwGB0BQrVRKozNxDomWFjLo,8762
74
74
  langchain_agentx/loop/graph/runtime_tools_node.py,sha256=wQkbc0ophyefpPZEc0q1vKoJOIdS4S6d6hSsrkdRLDk,7387
@@ -404,10 +404,10 @@ langchain_agentx/tool_runtime/denial_tracking_bridge.py,sha256=qssnZ_tzCIrDeJvtx
404
404
  langchain_agentx/tool_runtime/errors.py,sha256=Ed1FMjBdPfr0WG-YwLIm_7jokl9k_DjHFNiewZTeQfg,8883
405
405
  langchain_agentx/tool_runtime/failure_event_emitter.py,sha256=C7c4Gc7Vo_R6_ah2Xxa2eso7l9Iuz_KTgRV0j0B08rA,10467
406
406
  langchain_agentx/tool_runtime/identical_call_cache.py,sha256=v8WWuTHWR5ThujcMO4Ty28mZS_w-JN24267fZ4PHWK8,3865
407
- langchain_agentx/tool_runtime/inner_permission_chain.py,sha256=SSr45DuwiuKxoyb3r7DdrRu7YUFzKIWBCmTYhBKFO68,17594
407
+ langchain_agentx/tool_runtime/inner_permission_chain.py,sha256=50wYXRu7Hi-rvDiI2QxSLC4uBAsJwG3Vp-v3AutLY4E,17629
408
408
  langchain_agentx/tool_runtime/limits.py,sha256=imE1IetmyKQM82INR0VkP3qZNM4bjUJuf1CG5BkY0uw,356
409
- langchain_agentx/tool_runtime/loader.py,sha256=ZeT-xIB6TDkehqyYWx1gkvI0aKx9_xKDKJhqyIvlAvw,9397
410
- langchain_agentx/tool_runtime/loop_loader.py,sha256=C_4T9XxlDmKaTzGv4MO6mJuR-rd6uueb1ZWwEKvvFaE,9087
409
+ langchain_agentx/tool_runtime/loader.py,sha256=vT1_1QoBCpDGhe9sWRqgDZBcuErF35rAcOAENDrUcCI,9452
410
+ langchain_agentx/tool_runtime/loop_loader.py,sha256=H8QKRmoi3ni-8vkyx90W2zfC4quDOfnNTc3d4ote4hM,9615
411
411
  langchain_agentx/tool_runtime/loop_runtime_context.py,sha256=HBZMdtKD7MpU8o7JmURFZGPTfxr2aFFlFF2nBZR_n8s,3613
412
412
  langchain_agentx/tool_runtime/messages.py,sha256=Fp-ZiLAbOQAQa8uIv-owE8PXtWyhpYSXxtzy3PVbKSw,3631
413
413
  langchain_agentx/tool_runtime/models.py,sha256=cO9EgLKPXiktHl_xXtBwLQrCo6mj4tbT4WoPmuFLE0w,20345
@@ -419,14 +419,14 @@ langchain_agentx/tool_runtime/permission_context.py,sha256=ReVV2kjVWAPTsPaYXxRB-
419
419
  langchain_agentx/tool_runtime/permission_decision.py,sha256=-dWuC60MV6PYgHHOsSxr6Qad8Mm3qgMuh6nkzM87LO4,10280
420
420
  langchain_agentx/tool_runtime/permission_orchestrator.py,sha256=nwx76k74aAFgo63tfkoi6zUJTj9-10tnQzj03VYqFuw,4637
421
421
  langchain_agentx/tool_runtime/permission_rule_matcher.py,sha256=z4VesjyvvEry-L2U1VsrJB3SR6wtaG8OOuhvftsvj9U,4725
422
- langchain_agentx/tool_runtime/permission_rules.py,sha256=Aw4qTckMCpQDZpGeAHkRS4fON3ixWU9B5B9W6D2OlMg,19435
422
+ langchain_agentx/tool_runtime/permission_rules.py,sha256=2N66xdR9khrzRY_1OCKsv05oAwWyf45e546NUwC_7hg,19709
423
423
  langchain_agentx/tool_runtime/permission_settings_loader.py,sha256=Xu6dso0ghh4pb7Puf8nj20nXxjGeQvL4kIGycavwG_o,5624
424
424
  langchain_agentx/tool_runtime/permission_settings_sync.py,sha256=g6TvjWwa7D0kw5rDznPsoMv7xylESPOyWA2vZW4HDm0,4590
425
425
  langchain_agentx/tool_runtime/permission_snapshot.py,sha256=xFoi9fIcZjSw_zrIoyglneM-vJsDOLCEjiVoazr5Ack,1947
426
426
  langchain_agentx/tool_runtime/pipeline.py,sha256=cNC8FzeBtfNo8I02wXsaBbbpwxbVXu67eRnr_OfEU3Y,53104
427
- langchain_agentx/tool_runtime/policy.py,sha256=PLk_-5wu69_Gjqig0pj71brcu6VfLvGSIHsAUHOiYIQ,30068
427
+ langchain_agentx/tool_runtime/policy.py,sha256=CZvkFS9m28q1txTz5MTltKGouMQZJnRVyz8DEfRewYA,30892
428
428
  langchain_agentx/tool_runtime/policy_decorator.py,sha256=XWbgh_TRlPJ-J-RFo1FKjIN_n_Z9rtLTfSwBQoyLTTg,2264
429
- langchain_agentx/tool_runtime/process_loader.py,sha256=1YMn6ntnpdbIHJbO7wF1xuz7gNKYxK0UJAOxq96dZX4,6166
429
+ langchain_agentx/tool_runtime/process_loader.py,sha256=qpgfRIla7Tyvbf9FcwGo5Z8x_ymNdMBxrx7OrZAQpzA,6889
430
430
  langchain_agentx/tool_runtime/prompt_context.py,sha256=Dw3Qs8yWwT7dv-ZVHKnQxtPPKQXGySOQpwH4sstfsjE,3233
431
431
  langchain_agentx/tool_runtime/read_ignore_patterns.py,sha256=l4kNulVDaOdQxcs4FeC2DT6k3172D4hgeafEbqGGvTw,3034
432
432
  langchain_agentx/tool_runtime/read_permission.py,sha256=rwH7NEtL57aUKXVjSf6hMOOvEWOayNFyvI_SNoNCHPA,9284
@@ -532,9 +532,10 @@ langchain_agentx/tools/bash/ast_security.py,sha256=6DFd_TwT986dbGkKT3UnDIZioBdmi
532
532
  langchain_agentx/tools/bash/auto_mode_adapter.py,sha256=7-yb_kGmpytBYkD2Hn5YWPIceo_YN4PatEKj8O7HKsM,4243
533
533
  langchain_agentx/tools/bash/backend.py,sha256=E0DMBAENZLUSLvuEh6RubNJ6iCVnx1d2VGsQzVuLA5k,65361
534
534
  langchain_agentx/tools/bash/bash_hardening.py,sha256=KEYKRC7kS_to4mzXuChvCmOIIb4dSUkOb9K1MBDtWDc,29296
535
+ langchain_agentx/tools/bash/bash_registry_content.py,sha256=YHE3PwuLBeFrEux4xNaDZjJpYtUSMFPLYr6A7g-fFxk,3569
535
536
  langchain_agentx/tools/bash/bash_runtime_contract.py,sha256=s8vrvMl7405KLGw2hPFbzlzWohm5JX3c9cImMBgqh8I,1511
536
537
  langchain_agentx/tools/bash/boundary_check.py,sha256=k9Bv-baIAUDE3-c_Brw3VSXXYMv_g3lTM0mabIadnwc,8822
537
- langchain_agentx/tools/bash/command_allow_rules.py,sha256=-1m-yzTCEBHnomeliejUs-CR9yBr9SqtczKoLZIZKsc,5950
538
+ langchain_agentx/tools/bash/command_allow_rules.py,sha256=aIXxnb4tD3fDWqADTKAgNaN5DP8TqbMeFw0NxfFtFQQ,7562
538
539
  langchain_agentx/tools/bash/cwd_reporter.py,sha256=mASbKWLb4MiX4zLKY3w1ocIdfhmwj5KRgX4OW_PcAns,2642
539
540
  langchain_agentx/tools/bash/events.py,sha256=xDpPA3zhne2cnlYTmiiPTsV423-h-Nw-VKDR_rlnbGE,6065
540
541
  langchain_agentx/tools/bash/l1_decision_reason.py,sha256=MOL9HBBpJEgypuTiYdNojQBl8eIsTwRge5CtGeYRJKI,5365
@@ -557,8 +558,9 @@ langchain_agentx/tools/bash/semantic_matrix.py,sha256=stvsudu8mGjS1JJCawMAUKabt-
557
558
  langchain_agentx/tools/bash/semantics.py,sha256=pkf4URTulAOVL7m04Rq9JJF7z0J2mTEsGWrjyoY9r6o,5769
558
559
  langchain_agentx/tools/bash/shell_locator.py,sha256=AUnCZS8gmZurCs8rL4y0MMOS9UZimfg5rQ5At38mZKU,6296
559
560
  langchain_agentx/tools/bash/shell_quoting.py,sha256=K4JLVGwU8VpdQzfQB1o3xsq4HoCKHew1pry8hHChcoA,4498
561
+ langchain_agentx/tools/bash/shell_rule_matching.py,sha256=uYEKg2C5M1r0eh3L4b5NCML1Gj2UBdIXeL_u2A6_RyM,3981
560
562
  langchain_agentx/tools/bash/task_runtime.py,sha256=hikVC9YugtvpLf4nDaPlX_GFem1YQvDRwN0ShRflUYU,3568
561
- langchain_agentx/tools/bash/tool.py,sha256=xhuFynhBg4Vcar3WjLoJptlYPoqRnkFiV_lQRi0l6T0,46387
563
+ langchain_agentx/tools/bash/tool.py,sha256=O8wDLMsPM9ryx_BfKo_SaGtlyRb7UyUxDAmOEgUgIek,46699
562
564
  langchain_agentx/tools/bash/windows_shell_quoting.py,sha256=0RzLnTKyFrJ536QzqPZvSyUcQWN794XRlAq2RdHeKHo,1615
563
565
  langchain_agentx/tools/edit/__init__.py,sha256=PiiOHUODUzTRQxxPdE7ZMTjd3vfdzm9LEcn1lpVtg8o,203
564
566
  langchain_agentx/tools/edit/backend.py,sha256=JrikYafZDGMQ2JjXRxbnv1VSHVGRUdLmZN__nnqkM9o,3159
@@ -618,9 +620,10 @@ langchain_agentx/tools/skill/paths_matcher.py,sha256=J4SOBThray_72mrYiQUfJ5Icu6T
618
620
  langchain_agentx/tools/skill/plugin_loader.py,sha256=qT0VcIWkKFmRGGH8-e_f4y1XIpjY71TVbnZwnu2M9_M,1191
619
621
  langchain_agentx/tools/skill/policy.py,sha256=SFlW627gyo_-wyIppAqoytBmrLPlSGw-mbR00gjOJQY,1541
620
622
  langchain_agentx/tools/skill/prompt.py,sha256=tvSqvCGroVdloveiWIYxIPQmpESqzPAs5ZJztvHBdOI,1782
623
+ langchain_agentx/tools/skill/registry_permission.py,sha256=ns_S6IBOdacrZcgHzCOj9rjlPOIecavF5hW-pHmalcY,3258
621
624
  langchain_agentx/tools/skill/repository_factory.py,sha256=7zDoPDZgxoR03CkmKdx9b0aUXwaH8E5LtBYQPGF26rA,1222
622
- langchain_agentx/tools/skill/source_paths.py,sha256=7qhErxCyDTBiGZc41i_EqMZi6aueCdY4kvMJ03g6z4s,2996
623
- langchain_agentx/tools/skill/tool.py,sha256=LJvXAjnEONIF0-Z2hBksEuj6qV5coId8AP99ov01pWM,19135
625
+ langchain_agentx/tools/skill/source_paths.py,sha256=2CeVYF37JONiXrKQcogdBs0Y31p9AAMIOgGXZKnBQfE,3651
626
+ langchain_agentx/tools/skill/tool.py,sha256=vhiwjeyST9BhsMZmKk8-Vej3JId0AxTPrQ5ebFvguG4,19711
624
627
  langchain_agentx/tools/skill/bundled/__init__.py,sha256=gaCH_mROpv0lIgLjin1VIFu-8hbP2nY1PHoF7vZt2Ns,4516
625
628
  langchain_agentx/tools/skill/bundled/prompts.py,sha256=gcaTnNRlWfHNPgO3EfZxU9vMYbOapMrAduvgaVc-_Fc,4838
626
629
  langchain_agentx/tools/skill/bundled/registry.py,sha256=oxvvFeeUzM-qVPtayVaQp5tfRiMazyln2-rCM86AYa8,6274
@@ -650,7 +653,7 @@ langchain_agentx/tools/webfetch/loader.py,sha256=QCATTqBvQHXiMVuAbGQGQeymVevDfWw
650
653
  langchain_agentx/tools/webfetch/models.py,sha256=e_k3FS_9JLekKsq1auRioLDhUFz4YNUlX3MrvnW5BE0,2561
651
654
  langchain_agentx/tools/webfetch/preapproved.py,sha256=8SjbOZdZla208oml8pzixkA-vvZ9c0kofCQ-3XQCoeQ,4312
652
655
  langchain_agentx/tools/webfetch/prompt.py,sha256=lpiAV9ftjs-M1BS6R5_L_uNAcOPJ5bJ9H-ZE2eq_5OY,3771
653
- langchain_agentx/tools/webfetch/registry_permission.py,sha256=5jSg_c2TuAqNGMA1_C_pSNRufN2fuOaqTOEcitjaQf8,2535
656
+ langchain_agentx/tools/webfetch/registry_permission.py,sha256=lkJx0XhY6XOeYgM3uAsFiFEtWawE5mZMl8IyuQchYSo,3924
654
657
  langchain_agentx/tools/webfetch/summary.py,sha256=wk-5C39VoEIj9OyZtw6DOs6GYtdvPVECb80uGgVwI7w,1038
655
658
  langchain_agentx/tools/webfetch/tool.py,sha256=4qT0nH2DwC8iPEWzmnZjns8lnynXS7muc-2xCgVlbZY,9199
656
659
  langchain_agentx/tools/websearch/__init__.py,sha256=gOVwLv3X9iLUpnN5lhyLeEiovw-JDmzdqxGg7ufkIMY,950
@@ -763,8 +766,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
763
766
  langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
764
767
  langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
765
768
  langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
766
- langchain_agentx_python-2.2.9.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
767
- langchain_agentx_python-2.2.9.dist-info/METADATA,sha256=MWx8nrJ8lXDQorwBf7tximEwOMScMgBTbNOqshUd2v0,24250
768
- langchain_agentx_python-2.2.9.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
769
- langchain_agentx_python-2.2.9.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
770
- langchain_agentx_python-2.2.9.dist-info/RECORD,,
769
+ langchain_agentx_python-2.3.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
770
+ langchain_agentx_python-2.3.1.dist-info/METADATA,sha256=Ww83WPubbcBJqsMh8jSNRJhu7Wcx9XxRyz0AvqS1c-E,24250
771
+ langchain_agentx_python-2.3.1.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
772
+ langchain_agentx_python-2.3.1.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
773
+ langchain_agentx_python-2.3.1.dist-info/RECORD,,