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.
- langchain_agentx/__init__.py +1 -1
- langchain_agentx/tool_runtime/__init__.py +3 -0
- langchain_agentx/tool_runtime/inner_permission_chain.py +19 -6
- langchain_agentx/tool_runtime/permission_cli_rules.py +143 -0
- langchain_agentx/tool_runtime/permission_rule_matcher.py +13 -1
- langchain_agentx/tool_runtime/permission_rules.py +41 -2
- langchain_agentx/tools/bash/bash_registry_content.py +106 -0
- langchain_agentx/tools/bash/command_allow_rules.py +130 -16
- langchain_agentx/tools/bash/shell_rule_matching.py +143 -0
- langchain_agentx/tools/bash/tool.py +37 -3
- langchain_agentx/tools/skill/registry_permission.py +99 -0
- langchain_agentx/tools/skill/tool.py +18 -0
- langchain_agentx/tools/webfetch/registry_permission.py +109 -0
- langchain_agentx/tools/webfetch/tool.py +12 -1
- langchain_agentx/tools/websearch/tool.py +17 -1
- {langchain_agentx_python-2.2.8.dist-info → langchain_agentx_python-2.3.0.dist-info}/METADATA +1 -1
- {langchain_agentx_python-2.2.8.dist-info → langchain_agentx_python-2.3.0.dist-info}/RECORD +20 -15
- {langchain_agentx_python-2.2.8.dist-info → langchain_agentx_python-2.3.0.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-2.2.8.dist-info → langchain_agentx_python-2.3.0.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-2.2.8.dist-info → langchain_agentx_python-2.3.0.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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 == "
|
|
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
|
|
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
|
|
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 —
|
|
2
|
+
tools/bash/command_allow_rules.py — Bash 内容级 allow 匹配(slash command + cliArg registry)。
|
|
3
3
|
|
|
4
4
|
职责:
|
|
5
|
-
|
|
5
|
+
CC 风格 Bash(git:*) / Bash(python:*) 前缀规则;slash 白名单与 registry cliArg 内容 allow。
|
|
6
6
|
|
|
7
7
|
链路位置:
|
|
8
|
-
BashRuntimeTool.
|
|
8
|
+
BashRuntimeTool._evaluate_bash_l1_chain / _authorize_command_segment。
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
-
import
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,4 +1,4 @@
|
|
|
1
|
-
langchain_agentx/__init__.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
421
|
-
langchain_agentx/tool_runtime/permission_rules.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
765
|
-
langchain_agentx_python-2.
|
|
766
|
-
langchain_agentx_python-2.
|
|
767
|
-
langchain_agentx_python-2.
|
|
768
|
-
langchain_agentx_python-2.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
{langchain_agentx_python-2.2.8.dist-info → langchain_agentx_python-2.3.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|