langchain-agentx-python 2.2.8__py3-none-any.whl → 2.3.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.2.8"
14
+ __version__ = "2.3.0"
15
15
 
16
16
  from .loop import ( # noqa: F401
17
17
  create_loop_agent,
@@ -43,6 +43,7 @@ from .permission_snapshot import (
43
43
  collect_denied_agent_types_from_policy as collect_denied_agent_types,
44
44
  )
45
45
  from .path_safety import PathSafetyConfig, PathSafetyEvaluator
46
+ from .permission_cli_rules import apply_cli_permission_rules, parse_tool_list_from_cli
46
47
  from .policy import DefaultPolicyEngine, PolicyEngine, ToolPolicyConfig
47
48
  from .read_ignore_patterns import (
48
49
  ReadDenyPatternCollector,
@@ -122,6 +123,8 @@ __all__ = [
122
123
  "validation_error_envelope",
123
124
  # policy
124
125
  "ToolPolicyConfig",
126
+ "apply_cli_permission_rules",
127
+ "parse_tool_list_from_cli",
125
128
  "PolicyEngine",
126
129
  "DefaultPolicyEngine",
127
130
  "PolicyEngineDecorator",
@@ -24,6 +24,7 @@ from .models import AuthorizationDecision
24
24
  from .permission_decision import (
25
25
  POLICY_ID_PREFIX_ALWAYS_ASK_TOOL,
26
26
  attach_decision_reason,
27
+ get_decision_reason,
27
28
  make_other_decision_reason,
28
29
  make_rule_decision_reason,
29
30
  )
@@ -40,6 +41,14 @@ if TYPE_CHECKING:
40
41
  from .policy import DefaultPolicyEngine
41
42
 
42
43
 
44
+ def _l1_ask_blocks_tool_level_allow(decision: AuthorizationDecision) -> bool:
45
+ """CC step 1g:safetyCheck 产生的 ask 不得被段 4 tool-level allow 覆盖。"""
46
+ reason = get_decision_reason(decision)
47
+ if reason is None:
48
+ return False
49
+ return reason.get("type") == "safetyCheck"
50
+
51
+
43
52
  @dataclass
44
53
  class PathPolicyResult:
45
54
  decision: AuthorizationDecision | None = None
@@ -80,6 +89,9 @@ class InnerPermissionChain:
80
89
  raise ValueError(f"L1 outcome={l1.outcome!r} requires decision")
81
90
  if tool.requires_user_interaction_for(data, ctx):
82
91
  return l1.decision
92
+ # CC step 1g:safetyCheck ask 不被 tool-level allow(2b)覆盖
93
+ if _l1_ask_blocks_tool_level_allow(l1.decision):
94
+ return l1.decision
83
95
  seg4 = self._segment_4_mode_shortcircuit(tool, data, ctx, tool_flags)
84
96
  if seg4 is not None:
85
97
  return seg4
@@ -197,12 +209,13 @@ class InnerPermissionChain:
197
209
  command = data.get("command")
198
210
  if not isinstance(command, str) or not command.strip():
199
211
  return None
200
- deny_result = matcher.match_deny(tool_name, command)
201
- if deny_result is not None:
202
- return deny_result
203
- ask_result = matcher.match_ask(tool_name, command)
204
- if ask_result is not None:
205
- 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
206
219
  return None
207
220
 
208
221
  path = self._engine._extract_path(data)
@@ -0,0 +1,143 @@
1
+ """
2
+ permission_cli_rules.py — CC 对齐 CLI allow/disallow 解析与 ToolPolicyConfig 注入
3
+
4
+ 职责:
5
+ parse_tool_list_from_cli(对齐 CC parseToolListFromCLI);
6
+ apply_cli_permission_rules 写入 allow_rules_by_source / deny_rules_by_source["cliArg"]。
7
+
8
+ 链路位置:
9
+ CLI / new_agent / stream_ui → ToolRuntimeLoader(policy_config=...) → build_permission_rule_registry。
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import Sequence
15
+
16
+ from .permission_rules import (
17
+ PermissionRule,
18
+ PermissionRuleBehavior,
19
+ PermissionRuleSource,
20
+ PermissionRuleValue,
21
+ permission_rule_to_string,
22
+ permission_rule_value_from_string,
23
+ )
24
+ from .policy import ToolPolicyConfig
25
+
26
+ _LEGACY_TASK_TOOL = "Task"
27
+ _CANONICAL_AGENT_TOOL = "Agent"
28
+
29
+
30
+ def parse_tool_list_from_cli(tools: Sequence[str]) -> list[str]:
31
+ """对齐 CC permissionSetup.ts parseToolListFromCLI(括号内不切分)。"""
32
+ if not tools:
33
+ return []
34
+ result: list[str] = []
35
+ for tool_string in tools:
36
+ if not tool_string:
37
+ continue
38
+ current = ""
39
+ is_in_parens = False
40
+ for char in tool_string:
41
+ if char == "(":
42
+ is_in_parens = True
43
+ current += char
44
+ elif char == ")":
45
+ is_in_parens = False
46
+ current += char
47
+ elif char == ",":
48
+ if is_in_parens:
49
+ current += char
50
+ else:
51
+ if current.strip():
52
+ result.append(current.strip())
53
+ current = ""
54
+ elif char == " ":
55
+ if is_in_parens:
56
+ current += char
57
+ elif current.strip():
58
+ result.append(current.strip())
59
+ current = ""
60
+ else:
61
+ current += char
62
+ if current.strip():
63
+ result.append(current.strip())
64
+ return result
65
+
66
+
67
+ def canonical_cli_rule_string(rule: str) -> str:
68
+ """对齐 CC allow 列表规范化(permissionRuleValueFromString + ToString;Task→Agent)。"""
69
+ value = permission_rule_value_from_string(rule.strip())
70
+ tool_name = value.tool_name
71
+ if tool_name == _LEGACY_TASK_TOOL:
72
+ tool_name = _CANONICAL_AGENT_TOOL
73
+ normalized = PermissionRule(
74
+ source=PermissionRuleSource.CLI_ARG,
75
+ rule_behavior=PermissionRuleBehavior.ALLOW,
76
+ rule_value=PermissionRuleValue(
77
+ tool_name=tool_name,
78
+ rule_content=value.rule_content,
79
+ ),
80
+ )
81
+ return permission_rule_to_string(normalized)
82
+
83
+
84
+ def apply_cli_permission_rules(
85
+ config: ToolPolicyConfig,
86
+ *,
87
+ allowed: Sequence[str] | None = None,
88
+ disallowed: Sequence[str] | None = None,
89
+ ) -> ToolPolicyConfig:
90
+ """
91
+ 合并 cliArg 规则;不修改 userSettings / projectSettings 等其它 source。
92
+
93
+ ``allowed is None`` / ``disallowed is None`` 表示不触碰对应 cliArg 键;
94
+ 传空序列表示清除该 cliArg 键。
95
+ """
96
+ allow_map = dict(config.allow_rules_by_source)
97
+ deny_map = dict(config.deny_rules_by_source)
98
+
99
+ if allowed is not None:
100
+ if allowed:
101
+ parsed = parse_tool_list_from_cli(allowed)
102
+ allow_map[PermissionRuleSource.CLI_ARG.value] = tuple(
103
+ canonical_cli_rule_string(r) for r in parsed
104
+ )
105
+ else:
106
+ allow_map.pop(PermissionRuleSource.CLI_ARG.value, None)
107
+
108
+ if disallowed is not None:
109
+ if disallowed:
110
+ parsed = parse_tool_list_from_cli(disallowed)
111
+ deny_map[PermissionRuleSource.CLI_ARG.value] = tuple(
112
+ canonical_cli_rule_string(r) for r in parsed
113
+ )
114
+ else:
115
+ deny_map.pop(PermissionRuleSource.CLI_ARG.value, None)
116
+
117
+ return ToolPolicyConfig(
118
+ read_roots=list(config.read_roots),
119
+ write_roots=list(config.write_roots),
120
+ deny_globs=list(config.deny_globs),
121
+ read_deny_globs=list(config.read_deny_globs),
122
+ ask_globs=list(config.ask_globs),
123
+ always_ask_tools=config.always_ask_tools,
124
+ allow_rules_by_source=allow_map,
125
+ deny_rules_by_source=deny_map,
126
+ ask_rules_by_source=dict(config.ask_rules_by_source),
127
+ cli_allow_tools=config.cli_allow_tools,
128
+ flag_settings_path=config.flag_settings_path,
129
+ read_only_mode=config.read_only_mode,
130
+ enable_builtin_deny=config.enable_builtin_deny,
131
+ enable_path_safety=config.enable_path_safety,
132
+ path_safety=config.path_safety,
133
+ auto_mode=config.auto_mode,
134
+ project_memory_layout=config.project_memory_layout,
135
+ denial_tracking=config.denial_tracking,
136
+ )
137
+
138
+
139
+ __all__ = [
140
+ "apply_cli_permission_rules",
141
+ "canonical_cli_rule_string",
142
+ "parse_tool_list_from_cli",
143
+ ]
@@ -54,6 +54,13 @@ class RegistryPermissionMatcher:
54
54
  ) -> AuthorizationDecision | None:
55
55
  return self._match(tool_name, target, PermissionRuleBehavior.ASK)
56
56
 
57
+ def match_allow(
58
+ self,
59
+ tool_name: str,
60
+ target: str,
61
+ ) -> AuthorizationDecision | None:
62
+ return self._match(tool_name, target, PermissionRuleBehavior.ALLOW)
63
+
57
64
  def _match(
58
65
  self,
59
66
  tool_name: str,
@@ -91,7 +98,12 @@ def collect_denied_agent_types(registry: PermissionRuleRegistry) -> frozenset[st
91
98
  def _decision_from_rule(rule: PermissionRule, *, behavior: str) -> AuthorizationDecision:
92
99
  rule_str = permission_rule_to_string(rule)
93
100
  policy_id = f"permission_rule:{rule.source_name}:{rule_str}"
94
- if behavior == "ask":
101
+ if behavior == "allow":
102
+ decision = AuthorizationDecision(
103
+ behavior="allow",
104
+ policy_id=policy_id,
105
+ )
106
+ elif behavior == "ask":
95
107
  decision = AuthorizationDecision(
96
108
  behavior="ask",
97
109
  ask_prompt=(
@@ -106,6 +106,39 @@ class PermissionRule:
106
106
  return s.value if isinstance(s, PermissionRuleSource) else str(s)
107
107
 
108
108
 
109
+ def _content_rule_matches_target(
110
+ *,
111
+ tool_name: str,
112
+ target: str,
113
+ rule_content: str,
114
+ ) -> bool:
115
+ if tool_name == "Bash":
116
+ from langchain_agentx.tools.bash.command_allow_rules import (
117
+ bash_command_matches_allow_rule,
118
+ )
119
+
120
+ return bash_command_matches_allow_rule(target, rule_content)
121
+ if tool_name == "WebFetch":
122
+ from langchain_agentx.tools.webfetch.registry_permission import (
123
+ webfetch_rule_content_matches,
124
+ )
125
+
126
+ return webfetch_rule_content_matches(
127
+ target=target,
128
+ rule_content=rule_content,
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
+ )
139
+ return fnmatch.fnmatch(target, rule_content)
140
+
141
+
109
142
  def permission_rule_value_from_string(rule_string: str) -> PermissionRuleValue:
110
143
  """解析 CC 规则串,如 ``Bash(python:*)`` 或工具级 ``Read``。"""
111
144
  text = rule_string.strip()
@@ -254,7 +287,7 @@ class PermissionRuleRegistry:
254
287
  target: str,
255
288
  behavior: PermissionRuleBehavior,
256
289
  ) -> PermissionRule | None:
257
- """CC Tool(content) 规则:工具名精确匹配 + fnmatch(content, target)。"""
290
+ """CC Tool(content) 规则:工具名精确匹配 + fnmatch / Bash prefix:*。"""
258
291
  tool_key = tool_name.strip()
259
292
  if not tool_key:
260
293
  return None
@@ -264,7 +297,11 @@ class PermissionRuleRegistry:
264
297
  continue
265
298
  if rule.tool_name != tool_key:
266
299
  continue
267
- if fnmatch.fnmatch(target, content):
300
+ if _content_rule_matches_target(
301
+ tool_name=tool_key,
302
+ target=target,
303
+ rule_content=content,
304
+ ):
268
305
  return rule
269
306
  return None
270
307
 
@@ -274,6 +311,8 @@ class PermissionRuleRegistry:
274
311
  behavior: PermissionRuleBehavior,
275
312
  ) -> str | None:
276
313
  """v1 deny_globs/ask_globs 减法:裸 glob / 命令模式 fnmatch。"""
314
+ if behavior is PermissionRuleBehavior.ALLOW:
315
+ return None
277
316
  patterns = (
278
317
  self.legacy_deny_globs
279
318
  if behavior is PermissionRuleBehavior.DENY
@@ -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"]
@@ -1,39 +1,45 @@
1
1
  """
2
- tools/bash/command_allow_rules.py — /commit 等 command 级 Bash 白名单匹配。
2
+ tools/bash/command_allow_rules.py — Bash 内容级 allow 匹配(slash command + cliArg registry)。
3
3
 
4
4
  职责:
5
- 将 CC 风格 Bash(git add:*) 规则用于 check_permissions 早放行。
5
+ CC 风格 Bash(git:*) / Bash(python:*) 前缀规则;slash 白名单与 registry cliArg 内容 allow。
6
6
 
7
7
  链路位置:
8
- BashRuntimeTool.check_permissions → try_command_allow_decision。
8
+ BashRuntimeTool._evaluate_bash_l1_chain / _authorize_command_segment。
9
9
  """
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
- import fnmatch
13
+ from typing import TYPE_CHECKING
14
+
15
+ from langchain_agentx.tools.bash.shell_rule_matching import match_shell_permission_rule
14
16
 
15
17
  from langchain_agentx.tool_runtime.models import AuthorizationDecision, ToolExecutionContext
16
18
  from langchain_agentx.tool_runtime.permission_context import get_command_bash_allow_rules
19
+ from langchain_agentx.tool_runtime.permission_rule_matcher import RegistryPermissionMatcher
20
+ from langchain_agentx.tool_runtime.permission_rules import (
21
+ PermissionRuleBehavior,
22
+ PermissionRuleRegistry,
23
+ build_permission_rule_registry,
24
+ )
17
25
  from langchain_agentx.tools.bash.read_only_validation import BashReadOnlyClassifier
18
26
 
27
+ if TYPE_CHECKING:
28
+ from langchain_agentx.tool_runtime.policy import DefaultPolicyEngine
29
+
19
30
 
20
31
  def bash_command_matches_allow_rule(command: str, rule_content: str) -> bool:
21
- """CC prefix:* 与简单 fnmatch 子集。"""
32
+ """CC shellRuleMatching(prefix / wildcard / exact)。"""
22
33
  cmd = command.strip()
23
34
  pattern = rule_content.strip()
24
35
  if not cmd or not pattern:
25
36
  return False
26
-
27
- if pattern.endswith(":*"):
28
- prefix = pattern[:-2].rstrip()
29
- if not prefix:
30
- return False
31
- return cmd == prefix or cmd.startswith(f"{prefix} ")
32
-
33
- if "*" in pattern or "?" in pattern or "[" in pattern:
34
- return fnmatch.fnmatch(cmd, pattern)
35
-
36
- return cmd == pattern or cmd.startswith(f"{pattern} ")
37
+ return match_shell_permission_rule(
38
+ pattern,
39
+ cmd,
40
+ match_mode="prefix",
41
+ is_compound=False,
42
+ )
37
43
 
38
44
 
39
45
  def try_command_allow_decision(
@@ -55,6 +61,114 @@ def try_command_allow_decision(
55
61
  return None
56
62
 
57
63
 
64
+ def cliarg_bash_has_content_allow_without_bare_tool(
65
+ registry: PermissionRuleRegistry,
66
+ ) -> bool:
67
+ """cliArg 仅配置了 Bash(pattern) 而无裸 Bash(CC-CLI-B-05 / B-08 边界)。"""
68
+ has_content = False
69
+ has_bare = False
70
+ for rule in registry.iter_rules(PermissionRuleBehavior.ALLOW):
71
+ if rule.source_name != "cliArg" or rule.tool_name != "Bash":
72
+ continue
73
+ if rule.rule_content is None:
74
+ has_bare = True
75
+ else:
76
+ has_content = True
77
+ return has_content and not has_bare
78
+
79
+
80
+ def try_registry_bash_content_allow_decision(
81
+ command: str,
82
+ ctx: ToolExecutionContext,
83
+ policy_engine: DefaultPolicyEngine | None,
84
+ ) -> AuthorizationDecision | None:
85
+ """cliArg/settings 等内容级 Bash allow(CC bashPermissions prefix allow,path 通过后)。"""
86
+ if policy_engine is None:
87
+ return None
88
+ registry = build_permission_rule_registry(
89
+ policy_config=policy_engine._config,
90
+ session_store=ctx.session_store,
91
+ workspace_root=ctx.workspace_root,
92
+ agent_home_segment=ctx.agent_home_segment,
93
+ )
94
+ matcher = RegistryPermissionMatcher(registry)
95
+ return matcher.match_allow("Bash", command.strip())
96
+
97
+
98
+ def registry_has_cliarg_bash_allow(registry: PermissionRuleRegistry) -> bool:
99
+ for rule in registry.iter_rules(PermissionRuleBehavior.ALLOW):
100
+ if rule.source_name == "cliArg" and rule.tool_name == "Bash":
101
+ return True
102
+ return False
103
+
104
+
105
+ def promote_unscoped_bash_passthrough_to_ask(
106
+ decision: AuthorizationDecision,
107
+ *,
108
+ command: str,
109
+ ctx: ToolExecutionContext,
110
+ policy_engine: DefaultPolicyEngine | None,
111
+ ) -> AuthorizationDecision:
112
+ """无 cliArg Bash allow 时,write_roots 静默 allow → ask(对齐 CC passthrough)。"""
113
+ if policy_engine is None or decision.behavior != "allow":
114
+ return decision
115
+ if _READ_ONLY_CLASSIFIER.classify(
116
+ command.strip(),
117
+ shell_ops_already_resolved=True,
118
+ ).is_read_only:
119
+ return decision
120
+ if decision.policy_id != "write_roots":
121
+ return decision
122
+ registry = build_permission_rule_registry(
123
+ policy_config=policy_engine._config,
124
+ session_store=ctx.session_store,
125
+ workspace_root=ctx.workspace_root,
126
+ agent_home_segment=ctx.agent_home_segment,
127
+ )
128
+ if registry_has_cliarg_bash_allow(registry):
129
+ return decision
130
+ if cliarg_bash_has_content_allow_without_bare_tool(registry):
131
+ return decision
132
+ return AuthorizationDecision(
133
+ behavior="ask",
134
+ message=decision.message,
135
+ policy_id="bash_passthrough",
136
+ ask_prompt=(
137
+ decision.ask_prompt
138
+ or f"Allow Bash command?\n{command.strip()}"
139
+ ),
140
+ )
141
+
142
+
143
+ def promote_unmatched_cliarg_content_only_allow_to_ask(
144
+ decision: AuthorizationDecision,
145
+ *,
146
+ command: str,
147
+ ctx: ToolExecutionContext,
148
+ policy_engine: DefaultPolicyEngine | None,
149
+ ) -> AuthorizationDecision:
150
+ """roots 内 silent allow → ask(仅 cliArg 有 Bash(pattern) 且无裸 Bash 时)。"""
151
+ if policy_engine is None or decision.behavior != "allow" or decision.policy_id:
152
+ return decision
153
+ registry = build_permission_rule_registry(
154
+ policy_config=policy_engine._config,
155
+ session_store=ctx.session_store,
156
+ workspace_root=ctx.workspace_root,
157
+ agent_home_segment=ctx.agent_home_segment,
158
+ )
159
+ if not cliarg_bash_has_content_allow_without_bare_tool(registry):
160
+ return decision
161
+ return AuthorizationDecision(
162
+ behavior="ask",
163
+ message=decision.message,
164
+ policy_id="write_roots",
165
+ ask_prompt=(
166
+ decision.ask_prompt
167
+ or f"Allow Bash command in workspace?\n{command.strip()}"
168
+ ),
169
+ )
170
+
171
+
58
172
  _READ_ONLY_CLASSIFIER = BashReadOnlyClassifier()
59
173
 
60
174
 
@@ -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
+ ]
@@ -42,7 +42,12 @@ from .limits import get_bash_limits
42
42
  from .mode_validation import BashPermissionModeValidator
43
43
  from .models import BashToolInput, BashToolOutput
44
44
  from .boundary_check import authorize_bash_boundary
45
- from .command_allow_rules import try_command_allow_decision, try_command_restrict_decision
45
+ from .command_allow_rules import (
46
+ promote_unmatched_cliarg_content_only_allow_to_ask,
47
+ try_command_allow_decision,
48
+ try_command_restrict_decision,
49
+ try_registry_bash_content_allow_decision,
50
+ )
46
51
  from .events import (
47
52
  BashProgressAccumulator,
48
53
  bash_progress_emit_data,
@@ -552,8 +557,15 @@ class BashRuntimeTool(RuntimeTool):
552
557
  (item[1].updated_input for item in decisions if item[1].updated_input),
553
558
  None,
554
559
  )
560
+ _, primary_allow = decisions[0]
555
561
  return self._with_observability(
556
- AuthorizationDecision(behavior="allow", updated_input=updated_input),
562
+ AuthorizationDecision(
563
+ behavior="allow",
564
+ message=primary_allow.message,
565
+ policy_id=primary_allow.policy_id,
566
+ updated_input=updated_input or primary_allow.updated_input,
567
+ metadata=dict(primary_allow.metadata or {}),
568
+ ),
557
569
  trace=trace,
558
570
  trace_context=TraceContext.from_ctx(ctx),
559
571
  )
@@ -977,6 +989,12 @@ class BashRuntimeTool(RuntimeTool):
977
989
  if path_decision.behavior != "allow":
978
990
  return path_decision
979
991
 
992
+ registry_allow = try_registry_bash_content_allow_decision(
993
+ command, ctx, self._policy
994
+ )
995
+ if registry_allow is not None:
996
+ return registry_allow
997
+
980
998
  hardening_decision = self._security_hardener.check_segment(
981
999
  command=command,
982
1000
  analysis=analysis,
@@ -1015,7 +1033,23 @@ class BashRuntimeTool(RuntimeTool):
1015
1033
  if decision.behavior != "allow":
1016
1034
  return decision
1017
1035
 
1018
- return self._authorize_sed_segment(command=command, ctx=ctx, cwd=cwd) or decision
1036
+ final = self._authorize_sed_segment(command=command, ctx=ctx, cwd=cwd) or decision
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(
1048
+ final,
1049
+ command=command,
1050
+ ctx=ctx,
1051
+ policy_engine=self._policy,
1052
+ )
1019
1053
 
1020
1054
  def _authorize_sed_segment(
1021
1055
  self,
@@ -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
+ ]
@@ -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)
@@ -0,0 +1,109 @@
1
+ """
2
+ tools/webfetch/registry_permission.py — WebFetch registry 内容规则(CC domain:hostname)
3
+
4
+ 职责:
5
+ 将 URL 转为 CC ``webFetchToolInputToPermissionRuleContent`` 键;在 L1 按 deny→ask→allow 评估。
6
+
7
+ 链路位置:
8
+ WebFetchRuntimeTool.run_l1_permission_check(预批准 host 之后)。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import TYPE_CHECKING, Any
14
+ from urllib.parse import urlparse
15
+
16
+ from langchain_agentx.tool_runtime.models import AuthorizationDecision
17
+ from langchain_agentx.tool_runtime.permission_decision import (
18
+ attach_decision_reason,
19
+ make_other_decision_reason,
20
+ )
21
+ from langchain_agentx.tool_runtime.permission_rule_matcher import RegistryPermissionMatcher
22
+ from langchain_agentx.tool_runtime.permission_rules import build_permission_rule_registry
23
+
24
+ if TYPE_CHECKING:
25
+ from langchain_agentx.tool_runtime.models import ToolExecutionContext
26
+
27
+
28
+ def webfetch_permission_rule_content(url: str) -> str:
29
+ """对齐 CC WebFetchTool.ts ``domain:${hostname}``。"""
30
+ text = url.strip()
31
+ if not text:
32
+ return "input:"
33
+ try:
34
+ host = (urlparse(text).hostname or "").lower()
35
+ if host:
36
+ return f"domain:{host}"
37
+ except Exception:
38
+ pass
39
+ return f"input:{text}"
40
+
41
+
42
+ def webfetch_rule_content_matches(*, target: str, rule_content: str) -> bool:
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
+ )
69
+
70
+
71
+ def evaluate_webfetch_registry_permission(
72
+ url: str,
73
+ ctx: ToolExecutionContext,
74
+ policy_engine: Any,
75
+ ) -> AuthorizationDecision:
76
+ registry = build_permission_rule_registry(
77
+ policy_config=policy_engine._config,
78
+ session_store=ctx.session_store,
79
+ workspace_root=ctx.workspace_root,
80
+ agent_home_segment=ctx.agent_home_segment,
81
+ )
82
+ target = webfetch_permission_rule_content(url)
83
+ matcher = RegistryPermissionMatcher(registry)
84
+ for match_fn in (matcher.match_deny, matcher.match_ask, matcher.match_allow):
85
+ matched = match_fn("WebFetch", target)
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
+ )
97
+ return matched
98
+ decision = AuthorizationDecision(
99
+ behavior="ask",
100
+ ask_prompt=(
101
+ "Claude requested permissions to use WebFetch, but you haven't granted it yet."
102
+ ),
103
+ policy_id="webfetch_default",
104
+ metadata={"suggestions": webfetch_allow_suggestions(target)},
105
+ )
106
+ return attach_decision_reason(
107
+ decision,
108
+ make_other_decision_reason("WebFetch default permission prompt"),
109
+ )
@@ -123,7 +123,18 @@ class WebFetchRuntimeTool(RuntimeTool):
123
123
  outcome="allow",
124
124
  decision=AuthorizationDecision(behavior="allow"),
125
125
  )
126
- return L1PermissionResult(outcome="continue")
126
+ if self._policy is None:
127
+ from langchain_agentx.tool_runtime.policy import DefaultPolicyEngine, ToolPolicyConfig
128
+
129
+ engine = DefaultPolicyEngine(ToolPolicyConfig())
130
+ else:
131
+ engine = self._policy
132
+ from langchain_agentx.tools.webfetch.registry_permission import (
133
+ evaluate_webfetch_registry_permission,
134
+ )
135
+
136
+ decision = evaluate_webfetch_registry_permission(url, ctx, engine)
137
+ return L1PermissionResult(outcome=decision.behavior, decision=decision)
127
138
 
128
139
  def invoke(self, data: dict[str, Any], ctx: ToolExecutionContext) -> WebFetchToolOutput:
129
140
  parsed = _as_input(data)
@@ -155,7 +155,23 @@ class WebSearchRuntimeTool(RuntimeTool):
155
155
  outcome="deny",
156
156
  decision=AuthorizationDecision(behavior="deny", message=deny_reason),
157
157
  )
158
- return L1PermissionResult(outcome="continue")
158
+ from langchain_agentx.tool_runtime.permission_decision import (
159
+ attach_decision_reason,
160
+ make_other_decision_reason,
161
+ )
162
+
163
+ decision = AuthorizationDecision(
164
+ behavior="ask",
165
+ ask_prompt="WebSearch requires permission.",
166
+ policy_id="websearch_default",
167
+ )
168
+ return L1PermissionResult(
169
+ outcome="ask",
170
+ decision=attach_decision_reason(
171
+ decision,
172
+ make_other_decision_reason("WebSearch passthrough promoted to ask"),
173
+ ),
174
+ )
159
175
 
160
176
  def invoke(self, data: dict[str, Any], ctx: ToolExecutionContext) -> WebSearchToolOutput:
161
177
  parsed = WebSearchToolInput.model_validate(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-agentx-python
3
- Version: 2.2.8
3
+ Version: 2.3.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=LJcHRX1vByixzGF-W9TusSlpy5m55fezzOqb0utYbrU,1614
1
+ langchain_agentx/__init__.py,sha256=8oCd_P48WoQZB9jQROU6rc2POQ3qA6K1LHWG1xtaSPo,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
@@ -389,7 +389,7 @@ langchain_agentx/task_runtime/tasks/trace_cleanup/__init__.py,sha256=riWlkXNpJb3
389
389
  langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py,sha256=xYVabnY0Qe4AnKKXhLIL10h22ZOwtaxRGdQ9ew0T9zo,3399
390
390
  langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py,sha256=qez3WnzBTagYZMc0qA1mk-jvM2dMy7uCrwZ7aWhEJzw,2094
391
391
  langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py,sha256=lqWlCqucs26fhr4M9LfgY8I0xuNDxwSLkre6XjAJDhA,6877
392
- langchain_agentx/tool_runtime/__init__.py,sha256=C4ZrS6UWSL65ErfNWMvBuZBnht6ZuGb0PBXwA0Xke74,5144
392
+ langchain_agentx/tool_runtime/__init__.py,sha256=bl_PIAbVXuaPgM-qH4z0mh7vgKBSttoxb-XMxsshles,5297
393
393
  langchain_agentx/tool_runtime/accept_edits_fast_path.py,sha256=UMCOF9Ws7hx3pcYvL-Kq7vzIAGvOGdEfx69ccUDXT-k,2281
394
394
  langchain_agentx/tool_runtime/adapter.py,sha256=rM-Y2zSZ--N-TIvx1HtI1J7ralPU0w4jJuX3bBvIVxM,33847
395
395
  langchain_agentx/tool_runtime/agent_home_bypass.py,sha256=na9xQPgJ02qA3YiJ1F8jN0Uv4uBDPzqRk3lfPPE089g,8010
@@ -404,7 +404,7 @@ 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=k0uf-Tvr4NitX9xsSvGki2iuwBn0IZZ0t_rQf49CbnE,17075
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
409
  langchain_agentx/tool_runtime/loader.py,sha256=ZeT-xIB6TDkehqyYWx1gkvI0aKx9_xKDKJhqyIvlAvw,9397
410
410
  langchain_agentx/tool_runtime/loop_loader.py,sha256=C_4T9XxlDmKaTzGv4MO6mJuR-rd6uueb1ZWwEKvvFaE,9087
@@ -414,11 +414,12 @@ langchain_agentx/tool_runtime/models.py,sha256=cO9EgLKPXiktHl_xXtBwLQrCo6mj4tbT4
414
414
  langchain_agentx/tool_runtime/outer_permission_resolver.py,sha256=8uKS148DXxt5SfDIHmbRwee4FchKdlquUZbfWeyNRXw,4184
415
415
  langchain_agentx/tool_runtime/override.py,sha256=D8TRftoeWzqlTuWCGUxNkFY7XzWx_d5oxI_ltNibqPA,10258
416
416
  langchain_agentx/tool_runtime/path_safety.py,sha256=dUrl2SObKmRDwdm9k8veO_Eh0iXw0LPLAMZJGxeKnTE,8548
417
+ langchain_agentx/tool_runtime/permission_cli_rules.py,sha256=fz8cW-jXH3f92EYfO0ShIaBSC0k2Lb6GtLJwCpLiaPk,4799
417
418
  langchain_agentx/tool_runtime/permission_context.py,sha256=ReVV2kjVWAPTsPaYXxRB-dICqualEvi2zzwF8NX_Wkk,6229
418
419
  langchain_agentx/tool_runtime/permission_decision.py,sha256=-dWuC60MV6PYgHHOsSxr6Qad8Mm3qgMuh6nkzM87LO4,10280
419
420
  langchain_agentx/tool_runtime/permission_orchestrator.py,sha256=nwx76k74aAFgo63tfkoi6zUJTj9-10tnQzj03VYqFuw,4637
420
- langchain_agentx/tool_runtime/permission_rule_matcher.py,sha256=n3k3EKC4cd_2RMxprKP4NR0DR5iAig4YtsdRwt7wHws,4384
421
- langchain_agentx/tool_runtime/permission_rules.py,sha256=ev6-Hm3N8YtbJO3SLGpoGMUlKGNE6ocvz6lQBhP0waY,18575
421
+ langchain_agentx/tool_runtime/permission_rule_matcher.py,sha256=z4VesjyvvEry-L2U1VsrJB3SR6wtaG8OOuhvftsvj9U,4725
422
+ langchain_agentx/tool_runtime/permission_rules.py,sha256=2N66xdR9khrzRY_1OCKsv05oAwWyf45e546NUwC_7hg,19709
422
423
  langchain_agentx/tool_runtime/permission_settings_loader.py,sha256=Xu6dso0ghh4pb7Puf8nj20nXxjGeQvL4kIGycavwG_o,5624
423
424
  langchain_agentx/tool_runtime/permission_settings_sync.py,sha256=g6TvjWwa7D0kw5rDznPsoMv7xylESPOyWA2vZW4HDm0,4590
424
425
  langchain_agentx/tool_runtime/permission_snapshot.py,sha256=xFoi9fIcZjSw_zrIoyglneM-vJsDOLCEjiVoazr5Ack,1947
@@ -531,9 +532,10 @@ langchain_agentx/tools/bash/ast_security.py,sha256=6DFd_TwT986dbGkKT3UnDIZioBdmi
531
532
  langchain_agentx/tools/bash/auto_mode_adapter.py,sha256=7-yb_kGmpytBYkD2Hn5YWPIceo_YN4PatEKj8O7HKsM,4243
532
533
  langchain_agentx/tools/bash/backend.py,sha256=E0DMBAENZLUSLvuEh6RubNJ6iCVnx1d2VGsQzVuLA5k,65361
533
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
534
536
  langchain_agentx/tools/bash/bash_runtime_contract.py,sha256=s8vrvMl7405KLGw2hPFbzlzWohm5JX3c9cImMBgqh8I,1511
535
537
  langchain_agentx/tools/bash/boundary_check.py,sha256=k9Bv-baIAUDE3-c_Brw3VSXXYMv_g3lTM0mabIadnwc,8822
536
- langchain_agentx/tools/bash/command_allow_rules.py,sha256=T8TZeGjb6f7C1KAdkzRG01gjyNpNiRlm65bx0y0Asag,3275
538
+ langchain_agentx/tools/bash/command_allow_rules.py,sha256=RYW1M9QpJa2HuvOeU9dnmoiNhmExKA1Sgv5jPWDd_Ag,7357
537
539
  langchain_agentx/tools/bash/cwd_reporter.py,sha256=mASbKWLb4MiX4zLKY3w1ocIdfhmwj5KRgX4OW_PcAns,2642
538
540
  langchain_agentx/tools/bash/events.py,sha256=xDpPA3zhne2cnlYTmiiPTsV423-h-Nw-VKDR_rlnbGE,6065
539
541
  langchain_agentx/tools/bash/l1_decision_reason.py,sha256=MOL9HBBpJEgypuTiYdNojQBl8eIsTwRge5CtGeYRJKI,5365
@@ -556,8 +558,9 @@ langchain_agentx/tools/bash/semantic_matrix.py,sha256=stvsudu8mGjS1JJCawMAUKabt-
556
558
  langchain_agentx/tools/bash/semantics.py,sha256=pkf4URTulAOVL7m04Rq9JJF7z0J2mTEsGWrjyoY9r6o,5769
557
559
  langchain_agentx/tools/bash/shell_locator.py,sha256=AUnCZS8gmZurCs8rL4y0MMOS9UZimfg5rQ5At38mZKU,6296
558
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
559
562
  langchain_agentx/tools/bash/task_runtime.py,sha256=hikVC9YugtvpLf4nDaPlX_GFem1YQvDRwN0ShRflUYU,3568
560
- langchain_agentx/tools/bash/tool.py,sha256=SsLLQdWNUAvXAQD6p_QwtVbwpAx3eGrzG6d6hUcCHZU,45618
563
+ langchain_agentx/tools/bash/tool.py,sha256=O8wDLMsPM9ryx_BfKo_SaGtlyRb7UyUxDAmOEgUgIek,46699
561
564
  langchain_agentx/tools/bash/windows_shell_quoting.py,sha256=0RzLnTKyFrJ536QzqPZvSyUcQWN794XRlAq2RdHeKHo,1615
562
565
  langchain_agentx/tools/edit/__init__.py,sha256=PiiOHUODUzTRQxxPdE7ZMTjd3vfdzm9LEcn1lpVtg8o,203
563
566
  langchain_agentx/tools/edit/backend.py,sha256=JrikYafZDGMQ2JjXRxbnv1VSHVGRUdLmZN__nnqkM9o,3159
@@ -617,9 +620,10 @@ langchain_agentx/tools/skill/paths_matcher.py,sha256=J4SOBThray_72mrYiQUfJ5Icu6T
617
620
  langchain_agentx/tools/skill/plugin_loader.py,sha256=qT0VcIWkKFmRGGH8-e_f4y1XIpjY71TVbnZwnu2M9_M,1191
618
621
  langchain_agentx/tools/skill/policy.py,sha256=SFlW627gyo_-wyIppAqoytBmrLPlSGw-mbR00gjOJQY,1541
619
622
  langchain_agentx/tools/skill/prompt.py,sha256=tvSqvCGroVdloveiWIYxIPQmpESqzPAs5ZJztvHBdOI,1782
623
+ langchain_agentx/tools/skill/registry_permission.py,sha256=ns_S6IBOdacrZcgHzCOj9rjlPOIecavF5hW-pHmalcY,3258
620
624
  langchain_agentx/tools/skill/repository_factory.py,sha256=7zDoPDZgxoR03CkmKdx9b0aUXwaH8E5LtBYQPGF26rA,1222
621
625
  langchain_agentx/tools/skill/source_paths.py,sha256=7qhErxCyDTBiGZc41i_EqMZi6aueCdY4kvMJ03g6z4s,2996
622
- langchain_agentx/tools/skill/tool.py,sha256=LJvXAjnEONIF0-Z2hBksEuj6qV5coId8AP99ov01pWM,19135
626
+ langchain_agentx/tools/skill/tool.py,sha256=vhiwjeyST9BhsMZmKk8-Vej3JId0AxTPrQ5ebFvguG4,19711
623
627
  langchain_agentx/tools/skill/bundled/__init__.py,sha256=gaCH_mROpv0lIgLjin1VIFu-8hbP2nY1PHoF7vZt2Ns,4516
624
628
  langchain_agentx/tools/skill/bundled/prompts.py,sha256=gcaTnNRlWfHNPgO3EfZxU9vMYbOapMrAduvgaVc-_Fc,4838
625
629
  langchain_agentx/tools/skill/bundled/registry.py,sha256=oxvvFeeUzM-qVPtayVaQp5tfRiMazyln2-rCM86AYa8,6274
@@ -649,8 +653,9 @@ langchain_agentx/tools/webfetch/loader.py,sha256=QCATTqBvQHXiMVuAbGQGQeymVevDfWw
649
653
  langchain_agentx/tools/webfetch/models.py,sha256=e_k3FS_9JLekKsq1auRioLDhUFz4YNUlX3MrvnW5BE0,2561
650
654
  langchain_agentx/tools/webfetch/preapproved.py,sha256=8SjbOZdZla208oml8pzixkA-vvZ9c0kofCQ-3XQCoeQ,4312
651
655
  langchain_agentx/tools/webfetch/prompt.py,sha256=lpiAV9ftjs-M1BS6R5_L_uNAcOPJ5bJ9H-ZE2eq_5OY,3771
656
+ langchain_agentx/tools/webfetch/registry_permission.py,sha256=lkJx0XhY6XOeYgM3uAsFiFEtWawE5mZMl8IyuQchYSo,3924
652
657
  langchain_agentx/tools/webfetch/summary.py,sha256=wk-5C39VoEIj9OyZtw6DOs6GYtdvPVECb80uGgVwI7w,1038
653
- langchain_agentx/tools/webfetch/tool.py,sha256=DYHuXuf1ebpEjh6Vd-8mAbWeBLjpGDXgM3lGk1ES78U,8720
658
+ langchain_agentx/tools/webfetch/tool.py,sha256=4qT0nH2DwC8iPEWzmnZjns8lnynXS7muc-2xCgVlbZY,9199
654
659
  langchain_agentx/tools/websearch/__init__.py,sha256=gOVwLv3X9iLUpnN5lhyLeEiovw-JDmzdqxGg7ufkIMY,950
655
660
  langchain_agentx/tools/websearch/backend.py,sha256=AIv9vh1rwFLlYmSEl-NRTM_LiMHRu5sMiMpE5CksWrg,8871
656
661
  langchain_agentx/tools/websearch/events.py,sha256=iQY_vEdCS1mCYfiB7jFMrGNUbpnVI9gJoA6jaXvNxeA,810
@@ -658,7 +663,7 @@ langchain_agentx/tools/websearch/limits.py,sha256=trZxxFAXHUv2JstYxMIJb7_hIWYkXm
658
663
  langchain_agentx/tools/websearch/loader.py,sha256=Y9FX3BPGe_oBSpqfzDke7XN4QqRjjVSVprnsSd0K_SI,1489
659
664
  langchain_agentx/tools/websearch/models.py,sha256=YkVhxM2CXj2RAn4KtOO_dgxZuvo4viJ0AUdAy06-4hM,4167
660
665
  langchain_agentx/tools/websearch/prompt.py,sha256=Sy2zlAO233WvmpOkJuCrkTCpS6oZIkCS_i2CmhBb_js,3188
661
- langchain_agentx/tools/websearch/tool.py,sha256=ZrVdsAvkle8OuqSEeY1QkIac1yDBolZ6wuVEL1WWFhQ,8878
666
+ langchain_agentx/tools/websearch/tool.py,sha256=KQW_awFJqcSVV-oltjb4QTQGjLqC8RnTCZtgbVXCIZ0,9406
662
667
  langchain_agentx/tools/write/__init__.py,sha256=lsmRlcXRgiN3boyfp3QjqRdhHarl2xJkC_XjN5AvEFk,171
663
668
  langchain_agentx/tools/write/backend.py,sha256=qZ86LKIunfIYLsfVZrwvBiJsjmvJCbNA_PB4ZLUuHFM,2298
664
669
  langchain_agentx/tools/write/display_builder.py,sha256=3pRUuLyOoHzY9iqHMeg8yTVCby5AAFz2Zk3NMOttEbM,3932
@@ -761,8 +766,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
761
766
  langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
762
767
  langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
763
768
  langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
764
- langchain_agentx_python-2.2.8.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
765
- langchain_agentx_python-2.2.8.dist-info/METADATA,sha256=zGJT9dLY-3VueNxw8g-rgeUuaWvpKMIb1nnc6ka5zFY,24250
766
- langchain_agentx_python-2.2.8.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
767
- langchain_agentx_python-2.2.8.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
768
- langchain_agentx_python-2.2.8.dist-info/RECORD,,
769
+ langchain_agentx_python-2.3.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
770
+ langchain_agentx_python-2.3.0.dist-info/METADATA,sha256=PGoShL7qCuKn4cToEl22oCzy1TD3bu1o7uYqnN9rpOk,24250
771
+ langchain_agentx_python-2.3.0.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
772
+ langchain_agentx_python-2.3.0.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
773
+ langchain_agentx_python-2.3.0.dist-info/RECORD,,