"""工具策略例外与参数级策略解析。""" from typing import Any, Mapping from app.agent.policy.contracts import ( ActionEffect, ActionPolicy, ConfirmationMode, MigrationState, PrincipalRole, RecoveryMode, ResultSensitivity, ) # 这些非管理员读取在运行时解析为强制 SAFE_READ;管理员门禁仍沿用原有授权事实源。 SAFE_READ_TOOL_NAMES = frozenset( { "list_slash_commands", "query_installed_plugins", "query_personas", "query_schedulers", "query_workflows", } ) # 该清单只校验固定工具 inventory;未命中的固定或动态工具同样默认 LEGACY_SHADOW。 BUILTIN_LEGACY_SHADOW_INVENTORY = frozenset( { "add_custom_filter_rule", "add_download_tasks", "add_rule_group", "add_subscribe", "apply_patch", "ask_user_choice", "browse_webpage", "create_agent_task", "delete_agent_task", "delete_custom_filter_rule", "delete_download_history", "delete_download_tasks", "delete_rule_group", "delete_subscribe", "delete_transfer_history", "edit_file", "execute_command", "get_recommendations", "get_search_results", "install_plugin", "list_directory", "query_agent_tasks", "query_builtin_filter_rules", "query_custom_filter_rules", "query_custom_identifiers", "query_directory_settings", "query_doctor_report", "query_download_tasks", "query_downloaders", "query_episode_schedule", "query_library_exists", "query_library_latest", "query_market_plugins", "query_media_detail", "query_plugin_capabilities", "query_plugin_config", "query_plugin_data", "query_popular_subscribes", "query_rule_groups", "query_site_userdata", "query_sites", "query_subscribe_history", "query_subscribe_shares", "query_subscribes", "query_system_settings", "query_transfer_history", "read_file", "recognize_captcha", "recognize_media", "reload_plugin", "run_agent_task", "run_scheduler", "run_slash_command", "run_workflow", "scrape_metadata", "search_media", "search_person", "search_person_credits", "search_subscribe", "search_torrents", "search_web", "send_local_file", "send_message", "send_voice_message", "switch_persona", "test_site", "transfer_file", "uninstall_plugin", "update_agent_task", "update_custom_filter_rule", "update_custom_identifiers", "update_download_tasks", "update_persona_definition", "update_plugin_config", "update_rule_group", "update_site", "update_site_cookie", "update_subscribe", "update_system_settings", "write_file", } ) class ToolPolicyRegistry: """解析固定和动态工具的宿主策略。""" def __init__( self, *, safe_read_tool_names: frozenset[str] = SAFE_READ_TOOL_NAMES, builtin_legacy_shadow_inventory: frozenset[str] = ( BUILTIN_LEGACY_SHADOW_INVENTORY ), ) -> None: """建立 SAFE_READ 例外与固定工具 inventory。""" overlap = safe_read_tool_names & builtin_legacy_shadow_inventory if overlap: raise ValueError(f"工具策略 inventory 存在重复项: {sorted(overlap)}") self._safe_read_tool_names = safe_read_tool_names self._builtin_legacy_shadow_inventory = builtin_legacy_shadow_inventory @property def builtin_tool_inventory(self) -> set[str]: """返回用于测试校验的固定工具 inventory。""" return set( self._safe_read_tool_names | self._builtin_legacy_shadow_inventory ) def resolve( self, *, tool_name: str, arguments: Mapping[str, Any], requires_admin: bool, ) -> ActionPolicy: """根据工具名和宿主权限声明解析当前参数级策略。""" required_role = ( PrincipalRole.SYSTEM_ADMIN if requires_admin else PrincipalRole.USER ) if ( tool_name == "query_system_settings" and arguments.get("show_secrets") is True ): return ActionPolicy( effect=ActionEffect.SENSITIVE_READ, required_role=PrincipalRole.SYSTEM_ADMIN, confirmation=ConfirmationMode.REQUIRED, recovery=RecoveryMode.NONE, result_sensitivity=ResultSensitivity.SECRET, migration_state=MigrationState.ENFORCED, policy_version="p1-g2a2-v1", machine_allowed=True, background_allowed=False, subagent_allowed=False, ) if tool_name in self._safe_read_tool_names: return ActionPolicy( effect=ActionEffect.SAFE_READ, required_role=required_role, confirmation=ConfirmationMode.NONE, recovery=RecoveryMode.NONE, result_sensitivity=ResultSensitivity.NORMAL, # 角色门禁仍由既有授权事实源判断,管理员读取保持兼容观测。 migration_state=( MigrationState.LEGACY_SHADOW if requires_admin else MigrationState.ENFORCED ), ) # 除明确例外外,固定和动态工具都保持现有能力,但不得被视为安全读取。 return ActionPolicy( effect=ActionEffect.UNKNOWN, required_role=required_role, confirmation=ConfirmationMode.REQUIRED, recovery=RecoveryMode.MANUAL_ONLY, result_sensitivity=ResultSensitivity.UNKNOWN, migration_state=MigrationState.LEGACY_SHADOW, ) DEFAULT_TOOL_POLICY_REGISTRY = ToolPolicyRegistry() __all__ = [ "BUILTIN_LEGACY_SHADOW_INVENTORY", "DEFAULT_TOOL_POLICY_REGISTRY", "SAFE_READ_TOOL_NAMES", "ToolPolicyRegistry", ]