feat: refactor API key configuration and enhance application initialization

- Renamed `check_environment` to `check_api_key_configured` for clarity, simplifying the API key validation logic.
- Removed the blocking behavior of the API key check during application startup, allowing the app to run while providing a prompt for configuration.
- Updated `LocalAgentApp` to accept an `api_configured` parameter, enabling conditional messaging for API key setup.
- Enhanced the `SandboxRunner` to support backup management and improved execution result handling with detailed metrics.
- Integrated data governance strategies into the `HistoryManager`, ensuring compliance and improved data management.
- Added privacy settings and metrics tracking across various components to enhance user experience and application safety.
This commit is contained in:
Mimikko-zeus
2026-02-27 14:32:30 +08:00
parent ab5bbff6f7
commit 8a538bb950
58 changed files with 13457 additions and 350 deletions

248
app/privacy_config.py Normal file
View File

@@ -0,0 +1,248 @@
"""
隐私配置管理模块
管理环境信息采集的最小化策略和用户控制开关
"""
import os
import platform
import sys
from pathlib import Path
from typing import Dict, Any, Optional
from dataclasses import dataclass, field
@dataclass
class PrivacySettings:
"""隐私设置"""
# 环境信息采集开关
send_os_info: bool = True # 操作系统信息
send_python_version: bool = True # Python 版本
send_architecture: bool = True # 系统架构
send_home_dir: bool = False # 用户主目录(默认关闭)
send_workspace_path: bool = True # 工作空间路径
send_current_dir: bool = False # 当前工作目录(默认关闭)
# 脱敏策略
anonymize_paths: bool = True # 路径脱敏(默认开启)
anonymize_username: bool = True # 用户名脱敏(默认开启)
# 场景化采集
chat_minimal_info: bool = True # 对话场景最小化信息(默认开启)
guidance_full_info: bool = True # 指导场景提供完整信息(默认开启)
def to_dict(self) -> Dict[str, bool]:
"""转换为字典"""
return {
'send_os_info': self.send_os_info,
'send_python_version': self.send_python_version,
'send_architecture': self.send_architecture,
'send_home_dir': self.send_home_dir,
'send_workspace_path': self.send_workspace_path,
'send_current_dir': self.send_current_dir,
'anonymize_paths': self.anonymize_paths,
'anonymize_username': self.anonymize_username,
'chat_minimal_info': self.chat_minimal_info,
'guidance_full_info': self.guidance_full_info,
}
@classmethod
def from_dict(cls, data: Dict[str, bool]) -> 'PrivacySettings':
"""从字典创建"""
return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
class PrivacyManager:
"""隐私管理器"""
def __init__(self, workspace: Path):
self.workspace = workspace
self.config_file = workspace / ".privacy_config.json"
self.settings = self._load_settings()
# 度量指标
self._metrics = {
'sensitive_fields_sent': 0, # 敏感字段上送次数
'anonymized_fields': 0, # 脱敏字段次数
'user_disabled_fields': 0, # 用户关闭的字段数
'total_requests': 0, # 总请求次数
}
def _load_settings(self) -> PrivacySettings:
"""加载隐私设置"""
if self.config_file.exists():
try:
import json
with open(self.config_file, 'r', encoding='utf-8') as f:
data = json.load(f)
return PrivacySettings.from_dict(data)
except Exception:
pass
return PrivacySettings()
def save_settings(self) -> None:
"""保存隐私设置"""
import json
self.workspace.mkdir(parents=True, exist_ok=True)
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.settings.to_dict(), f, indent=2, ensure_ascii=False)
def update_settings(self, **kwargs) -> None:
"""更新设置"""
for key, value in kwargs.items():
if hasattr(self.settings, key):
setattr(self.settings, key, value)
self.save_settings()
# 更新度量:统计用户关闭的字段数
disabled_count = sum(1 for k, v in self.settings.to_dict().items()
if k.startswith('send_') and not v)
self._metrics['user_disabled_fields'] = disabled_count
def anonymize_path(self, path: Path) -> str:
"""路径脱敏"""
if not self.settings.anonymize_paths:
return str(path)
self._metrics['anonymized_fields'] += 1
# 替换用户名
path_str = str(path)
if self.settings.anonymize_username:
username = os.getenv('USERNAME') or os.getenv('USER')
if username:
path_str = path_str.replace(username, '<USER>')
# 替换主目录
home = str(Path.home())
if home in path_str:
path_str = path_str.replace(home, '<HOME>')
return path_str
def get_environment_info(self, scenario: str = 'chat') -> str:
"""
获取环境信息(按场景和设置过滤)
Args:
scenario: 场景类型 ('chat', 'guidance', 'execution')
"""
self._metrics['total_requests'] += 1
info_parts = []
# 场景化最小化策略
if scenario == 'chat' and self.settings.chat_minimal_info:
# 对话场景:仅提供必要信息
if self.settings.send_os_info:
os_name = platform.system()
info_parts.append(f"操作系统: {os_name}")
if self.settings.send_python_version:
python_version = sys.version.split()[0]
info_parts.append(f"Python版本: {python_version}")
# 对话场景不发送路径信息
return "\n".join(info_parts) if info_parts else "(环境信息已最小化)"
# 指导场景或执行场景:根据用户设置提供信息
if self.settings.send_os_info:
os_name = platform.system()
os_version = platform.version()
os_release = platform.release()
info_parts.append(f"操作系统: {os_name} {os_release} ({os_version})")
if self.settings.send_python_version:
python_version = sys.version.split()[0]
info_parts.append(f"Python版本: {python_version}")
if self.settings.send_architecture:
arch = platform.machine()
info_parts.append(f"系统架构: {arch}")
if self.settings.send_home_dir:
home_dir = Path.home()
info_parts.append(f"用户主目录: {self.anonymize_path(home_dir)}")
self._metrics['sensitive_fields_sent'] += 1
if self.settings.send_workspace_path:
info_parts.append(f"工作空间: {self.anonymize_path(self.workspace)}")
if self.settings.send_current_dir:
cwd = Path(os.getcwd())
info_parts.append(f"当前目录: {self.anonymize_path(cwd)}")
self._metrics['sensitive_fields_sent'] += 1
return "\n".join(info_parts) if info_parts else "(环境信息已禁用)"
def get_metrics(self) -> Dict[str, Any]:
"""获取度量指标"""
total = self._metrics['total_requests']
return {
'sensitive_fields_sent': self._metrics['sensitive_fields_sent'],
'anonymized_fields': self._metrics['anonymized_fields'],
'user_disabled_fields': self._metrics['user_disabled_fields'],
'total_requests': total,
'sensitive_ratio': self._metrics['sensitive_fields_sent'] / total if total > 0 else 0,
'anonymization_ratio': self._metrics['anonymized_fields'] / total if total > 0 else 0,
}
def export_metrics(self) -> str:
"""导出度量指标报告"""
metrics = self.get_metrics()
return f"""隐私保护度量报告
==================
总请求次数: {metrics['total_requests']}
敏感字段上送次数: {metrics['sensitive_fields_sent']}
敏感字段上送比率: {metrics['sensitive_ratio']:.1%}
脱敏处理次数: {metrics['anonymized_fields']}
脱敏处理比率: {metrics['anonymization_ratio']:.1%}
用户关闭字段数: {metrics['user_disabled_fields']}
当前隐私设置:
{self._format_settings()}
"""
def _format_settings(self) -> str:
"""格式化设置"""
lines = []
settings_dict = self.settings.to_dict()
lines.append("环境信息采集:")
for key in ['send_os_info', 'send_python_version', 'send_architecture',
'send_home_dir', 'send_workspace_path', 'send_current_dir']:
status = "" if settings_dict[key] else ""
name = key.replace('send_', '').replace('_', ' ').title()
lines.append(f" {status} {name}")
lines.append("\n脱敏策略:")
for key in ['anonymize_paths', 'anonymize_username']:
status = "" if settings_dict[key] else ""
name = key.replace('anonymize_', '').replace('_', ' ').title()
lines.append(f" {status} {name}")
lines.append("\n场景化策略:")
for key in ['chat_minimal_info', 'guidance_full_info']:
status = "" if settings_dict[key] else ""
name = key.replace('_', ' ').title()
lines.append(f" {status} {name}")
return "\n".join(lines)
# 全局单例
_privacy_manager: Optional[PrivacyManager] = None
def get_privacy_manager(workspace: Path) -> PrivacyManager:
"""获取隐私管理器单例"""
global _privacy_manager
if _privacy_manager is None:
_privacy_manager = PrivacyManager(workspace)
return _privacy_manager
def reset_privacy_manager() -> None:
"""重置隐私管理器(用于测试)"""
global _privacy_manager
_privacy_manager = None